@getstrata/bootstrap 0.2.49 → 0.2.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +3 -1
- package/dist/bootstrap/config.d.ts +1 -1
- package/dist/bootstrap/health.d.ts +8 -2
- package/dist/bootstrap/public-api.d.ts +4 -2
- package/dist/bootstrap/web/index.d.ts +1 -1
- package/dist/bootstrap/web/session.d.ts +25 -3
- package/dist/entries/buildModuleRoutes.js +19 -4
- package/dist/entries/buildWebModuleRoutes.js +19 -4
- package/dist/entries/config.js +4 -0
- package/dist/entries/context.js +2 -0
- package/dist/entries/createRoutes.js +203 -155
- package/dist/entries/createSpaRoutes.js +1 -1
- package/dist/entries/createWebRoutes.js +19 -4
- package/dist/entries/dependencies.js +2 -0
- package/dist/entries/health.js +69 -152
- package/dist/entries/httpKernel.js +19 -4
- package/dist/entries/providers.js +2 -0
- package/dist/entries/secretsGuard.js +64 -10
- package/dist/entries/web/routing.js +19 -4
- package/dist/entries/web/session.js +59 -7
- package/dist/index.js +240 -20
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { applyMiddlewareToRoutes } from "@getstrata/core/http/middleware";
|
|
|
20
20
|
|
|
21
21
|
// ../../src/bootstrap/httpKernel.ts
|
|
22
22
|
import { createMembershipMiddleware } from "@getstrata/core/auth/membershipMiddleware";
|
|
23
|
+
import { resolveAbilityChecker } from "@getstrata/core/contracts/serviceTokens";
|
|
23
24
|
import { createAuthMiddleware } from "@getstrata/core/http/authMiddleware";
|
|
24
25
|
import { createAuthorizeMiddleware } from "@getstrata/core/http/authorizeMiddleware";
|
|
25
26
|
import { createBodySizeLimitMiddleware } from "@getstrata/core/http/bodySizeLimitMiddleware";
|
|
@@ -77,24 +78,38 @@ function parsePositiveInt(value, fallback) {
|
|
|
77
78
|
}
|
|
78
79
|
return Math.trunc(parsed);
|
|
79
80
|
}
|
|
81
|
+
function parseWindowSeconds(secondsValue, msValue, fallback) {
|
|
82
|
+
if (secondsValue !== undefined && secondsValue.trim() !== "") {
|
|
83
|
+
return parsePositiveInt(secondsValue, fallback);
|
|
84
|
+
}
|
|
85
|
+
if (msValue !== undefined && msValue.trim() !== "") {
|
|
86
|
+
const parsedMs = Number(msValue);
|
|
87
|
+
if (Number.isFinite(parsedMs) && parsedMs > 0) {
|
|
88
|
+
return Math.max(1, Math.trunc(parsedMs / 1000));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return fallback;
|
|
92
|
+
}
|
|
80
93
|
function resolveLoginRateLimit() {
|
|
81
94
|
const defaults = isLocalAppEnv() ? LOCAL_LOGIN_RATE_LIMIT : PRODUCTION_LOGIN_RATE_LIMIT;
|
|
82
95
|
return {
|
|
83
96
|
maxAttempts: parsePositiveInt(process.env.LOGIN_RATE_LIMIT_PER_WINDOW, defaults.maxAttempts),
|
|
84
|
-
decaySeconds:
|
|
97
|
+
decaySeconds: parseWindowSeconds(process.env.LOGIN_RATE_LIMIT_WINDOW_SECONDS, process.env.LOGIN_RATE_LIMIT_WINDOW_MS, defaults.decaySeconds)
|
|
85
98
|
};
|
|
86
99
|
}
|
|
87
100
|
function resolveRegisterRateLimit() {
|
|
88
101
|
const defaults = isLocalAppEnv() ? { maxAttempts: 100, decaySeconds: 60 } : { maxAttempts: 10, decaySeconds: 60 };
|
|
89
102
|
return {
|
|
90
103
|
maxAttempts: parsePositiveInt(process.env.REGISTER_RATE_LIMIT_PER_WINDOW, defaults.maxAttempts),
|
|
91
|
-
decaySeconds:
|
|
104
|
+
decaySeconds: parseWindowSeconds(process.env.REGISTER_RATE_LIMIT_WINDOW_SECONDS, process.env.REGISTER_RATE_LIMIT_WINDOW_MS, defaults.decaySeconds)
|
|
92
105
|
};
|
|
93
106
|
}
|
|
94
107
|
|
|
95
108
|
// ../../src/bootstrap/config.ts
|
|
96
109
|
import {
|
|
110
|
+
CORE_ABILITY_CHECKER_TOKEN,
|
|
97
111
|
CORE_AUTH_TOKEN,
|
|
112
|
+
CORE_AUTH_USER_DIRECTORY_TOKEN,
|
|
98
113
|
CORE_CACHE_TOKEN,
|
|
99
114
|
CORE_CONFIG_TOKEN,
|
|
100
115
|
CORE_EVENT_BUS_TOKEN,
|
|
@@ -197,7 +212,7 @@ class HttpKernel {
|
|
|
197
212
|
}
|
|
198
213
|
wrapWebAbility(ability, handler) {
|
|
199
214
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
200
|
-
const abilityChecker = this.dependencies.container
|
|
215
|
+
const abilityChecker = resolveAbilityChecker(this.dependencies.container);
|
|
201
216
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
202
217
|
const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
|
|
203
218
|
return this.wrapWeb(withMiddleware(...middleware)(handler));
|
|
@@ -221,7 +236,7 @@ class HttpKernel {
|
|
|
221
236
|
return withMiddleware(...middleware)(handler);
|
|
222
237
|
}
|
|
223
238
|
wrapAbility(ability, handler) {
|
|
224
|
-
const abilityChecker = this.dependencies.container
|
|
239
|
+
const abilityChecker = resolveAbilityChecker(this.dependencies.container);
|
|
225
240
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
226
241
|
const middleware = [...this.group("authenticated"), requireAbility(ability)];
|
|
227
242
|
return withMiddleware(...middleware)(handler);
|
|
@@ -891,6 +906,101 @@ function mergeWebRoutes(dependencies, routes) {
|
|
|
891
906
|
function createAppDependencies() {
|
|
892
907
|
return createAppContext().dependencies;
|
|
893
908
|
}
|
|
909
|
+
// ../../src/bootstrap/health.ts
|
|
910
|
+
import { getBoundDatabaseConnection } from "@getstrata/core/database/boundConnection";
|
|
911
|
+
import { getDefaultDatabasePool } from "@getstrata/core/database/defaultConnection";
|
|
912
|
+
import { jsonResponse } from "@getstrata/core/http/response";
|
|
913
|
+
var {RedisClient } = globalThis.Bun;
|
|
914
|
+
function resolveRedisUrl(dependencies) {
|
|
915
|
+
if (!dependencies.container.has(CORE_CONFIG_TOKEN)) {
|
|
916
|
+
return process.env.REDIS_URL?.trim() || undefined;
|
|
917
|
+
}
|
|
918
|
+
const config = dependencies.container.resolve(CORE_CONFIG_TOKEN);
|
|
919
|
+
const redisUrl = config.get(REDIS_URL_CONFIG_KEY)?.trim();
|
|
920
|
+
return redisUrl || undefined;
|
|
921
|
+
}
|
|
922
|
+
function resolveHealthDatabase() {
|
|
923
|
+
const bound = getBoundDatabaseConnection();
|
|
924
|
+
if (bound) {
|
|
925
|
+
return bound;
|
|
926
|
+
}
|
|
927
|
+
try {
|
|
928
|
+
return getDefaultDatabasePool();
|
|
929
|
+
} catch {
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
async function pingDatabaseClient(connection) {
|
|
934
|
+
try {
|
|
935
|
+
await connection.unsafe("SELECT 1");
|
|
936
|
+
return true;
|
|
937
|
+
} catch {
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
async function checkDatabase() {
|
|
942
|
+
const connection = resolveHealthDatabase();
|
|
943
|
+
if (!connection) {
|
|
944
|
+
return false;
|
|
945
|
+
}
|
|
946
|
+
return await pingDatabaseClient(connection);
|
|
947
|
+
}
|
|
948
|
+
async function pingDatabase() {
|
|
949
|
+
return await checkDatabase();
|
|
950
|
+
}
|
|
951
|
+
async function checkRedis(redisUrl) {
|
|
952
|
+
try {
|
|
953
|
+
const client = new RedisClient(redisUrl);
|
|
954
|
+
const response = await client.ping();
|
|
955
|
+
return response === "PONG";
|
|
956
|
+
} catch {
|
|
957
|
+
return false;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
async function resolveExtraFields(extra) {
|
|
961
|
+
if (!extra) {
|
|
962
|
+
return {};
|
|
963
|
+
}
|
|
964
|
+
return typeof extra === "function" ? await extra() : extra;
|
|
965
|
+
}
|
|
966
|
+
async function collectDependencyChecks(dependencies) {
|
|
967
|
+
const checks = {
|
|
968
|
+
database: await checkDatabase() ? "ok" : "error"
|
|
969
|
+
};
|
|
970
|
+
const redisUrl = resolveRedisUrl(dependencies);
|
|
971
|
+
if (redisUrl) {
|
|
972
|
+
checks.redis = await checkRedis(redisUrl) ? "ok" : "error";
|
|
973
|
+
} else {
|
|
974
|
+
checks.redis = "skipped";
|
|
975
|
+
}
|
|
976
|
+
const ready = checks.database === "ok" && (checks.redis === "ok" || checks.redis === "skipped");
|
|
977
|
+
return { checks, ready };
|
|
978
|
+
}
|
|
979
|
+
function createHealthRoutes(dependencies, options = {}) {
|
|
980
|
+
return {
|
|
981
|
+
"/health": async () => {
|
|
982
|
+
const extra = await resolveExtraFields(options.extra);
|
|
983
|
+
if (!options.pingOnHealth) {
|
|
984
|
+
return jsonResponse({ status: "ok", ...extra });
|
|
985
|
+
}
|
|
986
|
+
const { checks, ready } = await collectDependencyChecks(dependencies);
|
|
987
|
+
return jsonResponse({
|
|
988
|
+
status: ready ? "ok" : "error",
|
|
989
|
+
checks,
|
|
990
|
+
...extra
|
|
991
|
+
}, { status: ready ? 200 : 503 });
|
|
992
|
+
},
|
|
993
|
+
"/ready": async () => {
|
|
994
|
+
const extra = await resolveExtraFields(options.extra);
|
|
995
|
+
const { checks, ready } = await collectDependencyChecks(dependencies);
|
|
996
|
+
return jsonResponse({
|
|
997
|
+
status: ready ? "ready" : "not_ready",
|
|
998
|
+
checks,
|
|
999
|
+
...extra
|
|
1000
|
+
}, { status: ready ? 200 : 503 });
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
894
1004
|
// ../../src/bootstrap/http/securedRouteModelBinding.ts
|
|
895
1005
|
import {
|
|
896
1006
|
securedBindRouteModel,
|
|
@@ -902,6 +1012,7 @@ import { resolveMembershipService } from "@getstrata/core/auth/membershipService
|
|
|
902
1012
|
var DEFAULT_ADMIN_API_TOKEN = "workhub-admin-test-token";
|
|
903
1013
|
var DEFAULT_MEMBER_API_TOKEN = "workhub-member-test-token";
|
|
904
1014
|
var DEFAULT_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
|
|
1015
|
+
var MIN_SESSION_SECRET_LENGTH = 32;
|
|
905
1016
|
var DEFAULT_TOKENS = new Set([DEFAULT_ADMIN_API_TOKEN, DEFAULT_MEMBER_API_TOKEN]);
|
|
906
1017
|
var DEFAULT_SCIM_TOKENS = new Set([DEFAULT_SCIM_BEARER_TOKEN]);
|
|
907
1018
|
function isEnabled(value, defaultEnabled) {
|
|
@@ -910,19 +1021,31 @@ function isEnabled(value, defaultEnabled) {
|
|
|
910
1021
|
}
|
|
911
1022
|
return defaultEnabled ? value !== "false" : value === "true";
|
|
912
1023
|
}
|
|
913
|
-
function
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1024
|
+
function isTokenAuthEnabled(env) {
|
|
1025
|
+
return Boolean(env.ADMIN_API_TOKEN?.trim()) || Boolean(env.MEMBER_API_TOKEN?.trim()) || env.FEATURE_API_TOKENS === "true";
|
|
1026
|
+
}
|
|
1027
|
+
function isOAuthEnabled(env) {
|
|
1028
|
+
return isEnabled(env.FEATURE_OAUTH, false) || isEnabled(env.FEATURE_SAML, false) || Boolean(env.GITHUB_CLIENT_ID?.trim()) || Boolean(env.OIDC_ISSUER?.trim()) || Boolean(env.SAML_LOGIN_URL?.trim());
|
|
1029
|
+
}
|
|
1030
|
+
function isCorsConfigured(env) {
|
|
1031
|
+
return env.CORS_ALLOWED_ORIGINS !== undefined;
|
|
1032
|
+
}
|
|
1033
|
+
function assertAuthDevHeadersDisabled(env) {
|
|
1034
|
+
if (isEnabled(env.AUTH_DEV_HEADERS, true)) {
|
|
1035
|
+
throw new Error("Production startup blocked: set AUTH_DEV_HEADERS=false to disable development auth headers.");
|
|
917
1036
|
}
|
|
1037
|
+
}
|
|
1038
|
+
function assertSessionSecret(env) {
|
|
1039
|
+
const secret = env.SESSION_SECRET?.trim() ?? "";
|
|
1040
|
+
if (secret.length < MIN_SESSION_SECRET_LENGTH) {
|
|
1041
|
+
throw new Error("Production startup blocked: set SESSION_SECRET when FRONTEND_MODE=server-htmx (32+ characters).");
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
function assertWorkHubProductionSecrets(env) {
|
|
918
1045
|
const adminToken = env.ADMIN_API_TOKEN ?? DEFAULT_ADMIN_API_TOKEN;
|
|
919
1046
|
const memberToken = env.MEMBER_API_TOKEN ?? DEFAULT_MEMBER_API_TOKEN;
|
|
920
1047
|
const scimToken = env.SCIM_BEARER_TOKEN ?? DEFAULT_SCIM_BEARER_TOKEN;
|
|
921
1048
|
const encryptionEnabled = isEnabled(env.FEATURE_FIELD_ENCRYPTION, true);
|
|
922
|
-
const devHeadersEnabled = isEnabled(env.AUTH_DEV_HEADERS, true);
|
|
923
|
-
if (devHeadersEnabled) {
|
|
924
|
-
throw new Error("Production startup blocked: set AUTH_DEV_HEADERS=false to disable development auth headers.");
|
|
925
|
-
}
|
|
926
1049
|
if (DEFAULT_TOKENS.has(adminToken) || DEFAULT_TOKENS.has(memberToken)) {
|
|
927
1050
|
throw new Error("Production startup blocked: rotate ADMIN_API_TOKEN and MEMBER_API_TOKEN away from default WorkHub test values.");
|
|
928
1051
|
}
|
|
@@ -954,9 +1077,50 @@ function assertProductionSecrets(env = process.env) {
|
|
|
954
1077
|
if (!env.API_TOKEN_DEFAULT_EXPIRY_DAYS?.trim()) {
|
|
955
1078
|
throw new Error("Production startup blocked: set API_TOKEN_DEFAULT_EXPIRY_DAYS to enforce token rotation.");
|
|
956
1079
|
}
|
|
1080
|
+
}
|
|
1081
|
+
function assertSiblingProductionSecrets(env) {
|
|
1082
|
+
if (isEnabled(env.FEATURE_SCIM, false)) {
|
|
1083
|
+
const scimToken = env.SCIM_BEARER_TOKEN ?? DEFAULT_SCIM_BEARER_TOKEN;
|
|
1084
|
+
if (DEFAULT_SCIM_TOKENS.has(scimToken) || !env.SCIM_BEARER_TOKEN?.trim()) {
|
|
1085
|
+
throw new Error("Production startup blocked: rotate SCIM_BEARER_TOKEN away from default WorkHub test values.");
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
if (isEnabled(env.FEATURE_FIELD_ENCRYPTION, false) && !env.KMS_ENCRYPTION_KEY?.trim()) {
|
|
1089
|
+
throw new Error("Production startup blocked: set KMS_ENCRYPTION_KEY when field encryption is enabled.");
|
|
1090
|
+
}
|
|
1091
|
+
if (isEnabled(env.FEATURE_BILLING, false) && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
|
|
1092
|
+
throw new Error("Production startup blocked: set STRIPE_WEBHOOK_SECRET when billing webhooks are enabled.");
|
|
1093
|
+
}
|
|
1094
|
+
if (isOAuthEnabled(env) && !env.OAUTH_STATE_SECRET?.trim()) {
|
|
1095
|
+
throw new Error("Production startup blocked: set OAUTH_STATE_SECRET for OAuth CSRF protection.");
|
|
1096
|
+
}
|
|
1097
|
+
if (isCorsConfigured(env)) {
|
|
1098
|
+
const corsOrigins = (env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim());
|
|
1099
|
+
if (corsOrigins.includes("*")) {
|
|
1100
|
+
throw new Error("Production startup blocked: set explicit CORS_ALLOWED_ORIGINS instead of wildcard.");
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
if (isEnabled(env.FEATURE_PUBLIC_READS, false)) {
|
|
1104
|
+
throw new Error("Production startup blocked: set FEATURE_PUBLIC_READS=false for authenticated-only reads.");
|
|
1105
|
+
}
|
|
1106
|
+
if (!env.SIEM_EXPORT_URL?.trim() && isEnabled(env.FEATURE_SIEM_EXPORT, false)) {
|
|
1107
|
+
console.warn("[secrets] SIEM_EXPORT_URL is not configured; audit logs remain database-only.");
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function assertProductionSecrets(env = process.env) {
|
|
1111
|
+
const appEnv = env.APP_ENV ?? "local";
|
|
1112
|
+
if (appEnv !== "production") {
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
assertAuthDevHeadersDisabled(env);
|
|
1116
|
+
if (isTokenAuthEnabled(env)) {
|
|
1117
|
+
assertWorkHubProductionSecrets(env);
|
|
1118
|
+
} else {
|
|
1119
|
+
assertSiblingProductionSecrets(env);
|
|
1120
|
+
}
|
|
957
1121
|
const frontendMode = (env.FRONTEND_MODE ?? "api").trim();
|
|
958
|
-
if (frontendMode === "server-htmx"
|
|
959
|
-
|
|
1122
|
+
if (frontendMode === "server-htmx") {
|
|
1123
|
+
assertSessionSecret(env);
|
|
960
1124
|
}
|
|
961
1125
|
}
|
|
962
1126
|
// ../../src/bootstrap/web/forms.ts
|
|
@@ -1090,15 +1254,40 @@ function createWebServer(options) {
|
|
|
1090
1254
|
}
|
|
1091
1255
|
// ../../src/bootstrap/web/session.ts
|
|
1092
1256
|
import { createHash, randomBytes } from "crypto";
|
|
1257
|
+
import { AuthManager as AuthManager2 } from "@getstrata/core/auth/guard";
|
|
1258
|
+
import { getBoundDatabaseConnection as getBoundDatabaseConnection2 } from "@getstrata/core/database/boundConnection";
|
|
1259
|
+
import { getDefaultDatabasePool as getDefaultDatabasePool2 } from "@getstrata/core/database/defaultConnection";
|
|
1093
1260
|
import { readRequestCookie } from "@getstrata/core/http/cookies";
|
|
1261
|
+
function isSqlClient(value) {
|
|
1262
|
+
return typeof value.unsafe === "function";
|
|
1263
|
+
}
|
|
1264
|
+
function resolveSql(source) {
|
|
1265
|
+
if (isSqlClient(source)) {
|
|
1266
|
+
return source;
|
|
1267
|
+
}
|
|
1268
|
+
return source();
|
|
1269
|
+
}
|
|
1270
|
+
function defaultSessionSql() {
|
|
1271
|
+
const bound = getBoundDatabaseConnection2();
|
|
1272
|
+
if (bound) {
|
|
1273
|
+
return bound;
|
|
1274
|
+
}
|
|
1275
|
+
return getDefaultDatabasePool2();
|
|
1276
|
+
}
|
|
1277
|
+
function defaultMapSessionUser(user) {
|
|
1278
|
+
return {
|
|
1279
|
+
id: user.id,
|
|
1280
|
+
role: user.is_admin ? "admin" : "member"
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1094
1283
|
|
|
1095
1284
|
class CookieSessionStore {
|
|
1096
|
-
|
|
1285
|
+
sqlSource;
|
|
1097
1286
|
secret;
|
|
1098
1287
|
cookieName;
|
|
1099
1288
|
maxAgeSeconds;
|
|
1100
|
-
constructor(
|
|
1101
|
-
this.
|
|
1289
|
+
constructor(sqlSource, secret, cookieName = "strata_session", maxAgeSeconds = 60 * 60 * 24 * 14) {
|
|
1290
|
+
this.sqlSource = sqlSource;
|
|
1102
1291
|
this.secret = secret;
|
|
1103
1292
|
this.cookieName = cookieName;
|
|
1104
1293
|
this.maxAgeSeconds = maxAgeSeconds;
|
|
@@ -1116,10 +1305,13 @@ class CookieSessionStore {
|
|
|
1116
1305
|
}
|
|
1117
1306
|
return header.includes("Secure") ? header : `${header}; Secure`;
|
|
1118
1307
|
}
|
|
1308
|
+
sql() {
|
|
1309
|
+
return resolveSql(this.sqlSource);
|
|
1310
|
+
}
|
|
1119
1311
|
async create(user) {
|
|
1120
1312
|
const id = randomBytes(32).toString("hex");
|
|
1121
1313
|
const expires = new Date(Date.now() + this.maxAgeSeconds * 1000);
|
|
1122
|
-
await this.sql.unsafe(`INSERT INTO sessions (id, user_id, expires_at) VALUES ($1, $2, $3)`, [
|
|
1314
|
+
await this.sql().unsafe(`INSERT INTO sessions (id, user_id, expires_at) VALUES ($1, $2, $3)`, [
|
|
1123
1315
|
id,
|
|
1124
1316
|
user.id,
|
|
1125
1317
|
expires
|
|
@@ -1127,7 +1319,7 @@ class CookieSessionStore {
|
|
|
1127
1319
|
return id;
|
|
1128
1320
|
}
|
|
1129
1321
|
async destroy(sessionId) {
|
|
1130
|
-
await this.sql.unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
|
|
1322
|
+
await this.sql().unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
|
|
1131
1323
|
}
|
|
1132
1324
|
async read(request) {
|
|
1133
1325
|
const cookie = readRequestCookie(request, this.cookieName);
|
|
@@ -1138,7 +1330,7 @@ class CookieSessionStore {
|
|
|
1138
1330
|
if (!sessionId || !signature || signature !== this.sign(sessionId)) {
|
|
1139
1331
|
return null;
|
|
1140
1332
|
}
|
|
1141
|
-
const rows = await this.sql.unsafe(`SELECT s.id, s.user_id, s.expires_at, u.name, u.email, u.learn_subscriber,
|
|
1333
|
+
const rows = await this.sql().unsafe(`SELECT s.id, s.user_id, s.expires_at, u.name, u.email, u.learn_subscriber,
|
|
1142
1334
|
COALESCE(u.is_admin, false) AS is_admin
|
|
1143
1335
|
FROM sessions s
|
|
1144
1336
|
INNER JOIN users u ON u.id = s.user_id
|
|
@@ -1158,19 +1350,42 @@ class CookieSessionStore {
|
|
|
1158
1350
|
return createHash("sha256").update(`${value}.${this.secret}`).digest("hex").slice(0, 32);
|
|
1159
1351
|
}
|
|
1160
1352
|
}
|
|
1353
|
+
|
|
1354
|
+
class CookieSessionGuard {
|
|
1355
|
+
store;
|
|
1356
|
+
mapUser;
|
|
1357
|
+
constructor(store, mapUser = defaultMapSessionUser) {
|
|
1358
|
+
this.store = store;
|
|
1359
|
+
this.mapUser = mapUser;
|
|
1360
|
+
}
|
|
1361
|
+
async resolve(request) {
|
|
1362
|
+
const user = await this.store.read(request);
|
|
1363
|
+
if (!user) {
|
|
1364
|
+
return null;
|
|
1365
|
+
}
|
|
1366
|
+
return this.mapUser(user);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
function createCookieSessionAuthManager(options = {}) {
|
|
1370
|
+
const store = options.store ?? new CookieSessionStore(options.sql ?? defaultSessionSql, options.secret ?? process.env.SESSION_SECRET?.trim() ?? "", options.cookieName, options.maxAgeSeconds);
|
|
1371
|
+
return new AuthManager2(new CookieSessionGuard(store, options.mapUser ?? defaultMapSessionUser));
|
|
1372
|
+
}
|
|
1161
1373
|
// ../../src/bootstrap/web/slug.ts
|
|
1162
1374
|
function slugify(value) {
|
|
1163
1375
|
return value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
1164
1376
|
}
|
|
1165
1377
|
export {
|
|
1166
1378
|
APP_PORT_CONFIG_KEY,
|
|
1379
|
+
CORE_ABILITY_CHECKER_TOKEN,
|
|
1167
1380
|
CORE_AUTH_TOKEN,
|
|
1381
|
+
CORE_AUTH_USER_DIRECTORY_TOKEN,
|
|
1168
1382
|
CORE_CACHE_TOKEN,
|
|
1169
1383
|
CORE_CONFIG_TOKEN,
|
|
1170
1384
|
CORE_EVENT_BUS_TOKEN,
|
|
1171
1385
|
CORE_POLICY_GATE_TOKEN,
|
|
1172
1386
|
CORE_QUEUE_TOKEN,
|
|
1173
1387
|
CORE_TOKEN_SERVICE_TOKEN,
|
|
1388
|
+
CookieSessionGuard,
|
|
1174
1389
|
CookieSessionStore,
|
|
1175
1390
|
DATABASE_URL_CONFIG_KEY,
|
|
1176
1391
|
DEFAULT_APP_PORT,
|
|
@@ -1184,12 +1399,16 @@ export {
|
|
|
1184
1399
|
buildModuleRoutes,
|
|
1185
1400
|
buildWebModuleRoutes,
|
|
1186
1401
|
cacheTagsForModelWrite,
|
|
1402
|
+
checkDatabase,
|
|
1403
|
+
checkRedis,
|
|
1187
1404
|
collectProviders,
|
|
1188
1405
|
configureModulesDirectory,
|
|
1189
1406
|
coreProviders,
|
|
1190
1407
|
createAppContext,
|
|
1191
1408
|
createAppDependencies,
|
|
1409
|
+
createCookieSessionAuthManager,
|
|
1192
1410
|
createCsrfProtection,
|
|
1411
|
+
createHealthRoutes,
|
|
1193
1412
|
createHttpKernel,
|
|
1194
1413
|
createRouteKernel,
|
|
1195
1414
|
createWebRoutes,
|
|
@@ -1200,6 +1419,7 @@ export {
|
|
|
1200
1419
|
getRequiredDependency,
|
|
1201
1420
|
mergeWebRoutes,
|
|
1202
1421
|
parseFormBody,
|
|
1422
|
+
pingDatabase,
|
|
1203
1423
|
prefixRouteMap,
|
|
1204
1424
|
registerDefaultJobs,
|
|
1205
1425
|
resolveApplicationAuth,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getstrata/bootstrap",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.50",
|
|
4
4
|
"description": "Strata application bootstrap — HttpKernel, DI, web session helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"default": "./dist/entries/providers.js"
|
|
117
117
|
},
|
|
118
118
|
"./providers/view": {
|
|
119
|
-
"types": "./dist/bootstrap/providers/view
|
|
119
|
+
"types": "./dist/bootstrap/providers/view.d.ts",
|
|
120
120
|
"import": "./dist/entries/providers/view.js",
|
|
121
121
|
"default": "./dist/entries/providers/view.js"
|
|
122
122
|
},
|
|
@@ -165,7 +165,8 @@
|
|
|
165
165
|
"types": "./dist/bootstrap/public-api.d.ts",
|
|
166
166
|
"files": [
|
|
167
167
|
"dist",
|
|
168
|
-
"README.md"
|
|
168
|
+
"README.md",
|
|
169
|
+
"CHANGELOG.md"
|
|
169
170
|
],
|
|
170
171
|
"scripts": {
|
|
171
172
|
"build": "bun run build:bundle && bun run build:shims && bun run build:subpaths && bun run build:types",
|
|
@@ -179,7 +180,7 @@
|
|
|
179
180
|
"access": "public"
|
|
180
181
|
},
|
|
181
182
|
"peerDependencies": {
|
|
182
|
-
"@getstrata/core": "^0.5.
|
|
183
|
+
"@getstrata/core": "^0.5.59",
|
|
183
184
|
"typescript": "^5.9.0"
|
|
184
185
|
}
|
|
185
186
|
}
|