@getstrata/bootstrap 0.2.68 → 0.4.2

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.
@@ -4,37 +4,61 @@ var __jsonParse = (a) => JSON.parse(a);
4
4
  // ../../src/bootstrap/createSpaRoutes.ts
5
5
  import { join } from "path";
6
6
  import { jsonResponse } from "@getstrata/core/http/response";
7
- import { isSpaEnabled } from "@getstrata/core/runtime/frontendMode";
7
+ import { isSpaEnabled, isViewsEnabled, readSpaPrefix } from "@getstrata/core/runtime/frontendMode";
8
8
  var SPA_DIST_DIRECTORY = join(process.cwd(), "frontend/dist");
9
- var SPA_INDEX_FILE = join(SPA_DIST_DIRECTORY, "index.html");
10
- function createSpaRoutes(_dependencies) {
11
- return {
12
- "/app/*": async (request) => {
13
- const pathname = new URL(request.url).pathname;
14
- const relativePath = pathname.replace(/^\/app\//, "");
15
- const assetFile = Bun.file(join(SPA_DIST_DIRECTORY, relativePath));
16
- if (relativePath.length > 0 && await assetFile.exists()) {
17
- return new Response(assetFile);
18
- }
19
- const indexFile = Bun.file(SPA_INDEX_FILE);
20
- if (await indexFile.exists()) {
21
- return new Response(indexFile, {
22
- headers: { "Content-Type": "text/html; charset=utf-8" }
23
- });
24
- }
25
- return jsonResponse({
26
- error: "SPA build not found. Run `bun run build:frontend`."
27
- }, { status: 503 });
28
- },
29
- "/": async () => Response.redirect("/app/", 302)
9
+ function relativeSpaPath(pathname, prefix) {
10
+ if (pathname === prefix || pathname === `${prefix}/`) {
11
+ return "";
12
+ }
13
+ if (pathname.startsWith(`${prefix}/`)) {
14
+ return pathname.slice(prefix.length + 1);
15
+ }
16
+ return "";
17
+ }
18
+ function createSpaDocumentHandler(prefix, distDirectory) {
19
+ const indexFilePath = join(distDirectory, "index.html");
20
+ return async (request) => {
21
+ const pathname = new URL(request.url).pathname;
22
+ if (pathname.startsWith("/api/")) {
23
+ return new Response("Not found", { status: 404 });
24
+ }
25
+ const relativePath = relativeSpaPath(pathname, prefix);
26
+ const assetFile = Bun.file(join(distDirectory, relativePath));
27
+ if (relativePath.length > 0 && await assetFile.exists()) {
28
+ return new Response(assetFile);
29
+ }
30
+ const indexFile = Bun.file(indexFilePath);
31
+ if (await indexFile.exists()) {
32
+ return new Response(indexFile, {
33
+ headers: { "Content-Type": "text/html; charset=utf-8" }
34
+ });
35
+ }
36
+ return jsonResponse({
37
+ error: "SPA build not found. Run `bun run frontend:build` in your app."
38
+ }, { status: 503 });
30
39
  };
31
40
  }
32
- function mergeSpaRoutes(dependencies, routes) {
41
+ function createSpaRoutes(_dependencies, options = {}) {
42
+ const prefix = options.prefix ?? readSpaPrefix();
43
+ const distDirectory = options.distDirectory ?? SPA_DIST_DIRECTORY;
44
+ const wrap = options.wrap ?? ((handler2) => handler2);
45
+ const handler = wrap(createSpaDocumentHandler(prefix, distDirectory));
46
+ const routes = {
47
+ [prefix]: handler,
48
+ [`${prefix}/`]: handler,
49
+ [`${prefix}/*`]: handler
50
+ };
51
+ if (!isViewsEnabled()) {
52
+ routes["/"] = async () => Response.redirect(`${prefix}/`, 302);
53
+ }
54
+ return routes;
55
+ }
56
+ function mergeSpaRoutes(dependencies, routes, options) {
33
57
  if (!isSpaEnabled()) {
34
58
  return routes;
35
59
  }
36
60
  return {
37
- ...createSpaRoutes(dependencies),
61
+ ...createSpaRoutes(dependencies, options),
38
62
  ...routes
39
63
  };
40
64
  }
@@ -36,7 +36,7 @@ import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/require
36
36
  import { createRequirePasswordConfirmMiddleware } from "@getstrata/core/http/requirePasswordConfirmMiddleware";
37
37
  import { createRequireVerifiedMiddleware } from "@getstrata/core/http/requireVerifiedMiddleware";
38
38
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
39
- import { withErrorHandling } from "@getstrata/core/http/response";
39
+ import { withErrorHandling, withJsonErrorHandling } from "@getstrata/core/http/response";
40
40
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
41
41
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
42
42
  import { createValidateSignatureMiddleware } from "@getstrata/core/http/signedUrl";
@@ -184,7 +184,7 @@ class HttpKernel {
184
184
  return withMiddleware(...middleware)(handler);
185
185
  }
186
186
  wrapApi(handler) {
187
- return this.wrap(["api", "authenticated"], handler);
187
+ return withJsonErrorHandling(this.wrap(["api", "authenticated"], handler));
188
188
  }
189
189
  wrapWeb(handler) {
190
190
  return withErrorHandling(this.wrap("web", handler));
@@ -96,17 +96,19 @@ function resetDiscoverModulesForTests() {
96
96
  state.modulesReady = undefined;
97
97
  }
98
98
  // ../../src/bootstrap/providers/auth.ts
99
+ import { BasicAuthGuard } from "@getstrata/core/auth/basicAuthGuard";
99
100
  import {
100
101
  AuthManager,
101
102
  CompositeGuard,
102
103
  DatabaseTokenGuard,
103
104
  GuestGuard
104
105
  } from "@getstrata/core/auth/guard";
106
+ import { JwtGuard } from "@getstrata/core/auth/jwtGuard";
105
107
  import { SessionGuard } from "@getstrata/core/auth/sessionGuard";
106
108
 
107
109
  // ../../src/config/auth.ts
108
110
  var authConfig = {
109
- allowDevHeaders: (process.env.AUTH_DEV_HEADERS ?? "true") !== "false",
111
+ allowDevHeaders: process.env.AUTH_DEV_HEADERS === "true",
110
112
  tokenDefaultAbilities: ["*"]
111
113
  };
112
114
 
@@ -141,11 +143,22 @@ var authProvider = {
141
143
  name: "core.auth",
142
144
  register({ container, config }) {
143
145
  config.set("auth.allowDevHeaders", authConfig.allowDevHeaders);
144
- const guards = [new DatabaseTokenGuard(container), new SessionGuard(container)];
146
+ const apiGuard = new DatabaseTokenGuard(container);
147
+ const sessionGuard = new SessionGuard(container);
148
+ const jwtGuard = new JwtGuard;
149
+ const basicGuard = new BasicAuthGuard(container);
150
+ const guards = [apiGuard, jwtGuard, basicGuard, sessionGuard];
145
151
  if (authConfig.allowDevHeaders) {
146
152
  guards.push(new GuestGuard);
147
153
  }
148
- container.set(CORE_AUTH_TOKEN, new AuthManager(new CompositeGuard(guards)));
154
+ const auth = new AuthManager(new CompositeGuard(guards));
155
+ auth.registerGuard("api", apiGuard);
156
+ auth.registerGuard("access_token", apiGuard);
157
+ auth.registerGuard("jwt", jwtGuard);
158
+ auth.registerGuard("basic", basicGuard);
159
+ auth.registerGuard("web", sessionGuard);
160
+ auth.registerGuard("session", sessionGuard);
161
+ container.set(CORE_AUTH_TOKEN, auth);
149
162
  }
150
163
  };
151
164
  var auth_default = authProvider;
@@ -206,8 +219,12 @@ var queueConfig = {
206
219
  };
207
220
  // ../../src/bootstrap/env.ts
208
221
  import { defineEnvSchema } from "@getstrata/core/config/envSchema";
222
+ import { DEFAULT_SPA_PREFIX, FRONTEND_MODE_PATTERN } from "@getstrata/core/runtime/frontendMode";
209
223
  var appEnvSchema = defineEnvSchema({
210
- DATABASE_URL: { required: true, pattern: /^postgres(ql)?:\/\// },
224
+ DATABASE_URL: {
225
+ required: true,
226
+ pattern: /^(postgres(ql)?|mysql|sqlite):\/\//i
227
+ },
211
228
  PORT: {
212
229
  integer: true,
213
230
  minimum: 1,
@@ -235,9 +252,21 @@ var appEnvSchema = defineEnvSchema({
235
252
  pattern: /^(sync|async|redis)$/
236
253
  },
237
254
  AUTH_DEV_HEADERS: {
238
- default: "true",
255
+ default: "false",
239
256
  pattern: /^(true|false|0|1)$/
240
257
  },
258
+ DB_CONNECTION: {
259
+ default: "",
260
+ pattern: /^(pgsql|postgres|postgresql|mysql|mariadb|sqlite)?$/i
261
+ },
262
+ AUTH_DEFAULT_GUARD: {
263
+ default: "web"
264
+ },
265
+ JWT_TTL_SECONDS: {
266
+ integer: true,
267
+ minimum: 60,
268
+ default: "3600"
269
+ },
241
270
  APP_ENV: {
242
271
  default: "local"
243
272
  },
@@ -247,6 +276,13 @@ var appEnvSchema = defineEnvSchema({
247
276
  APP_URL: {
248
277
  default: "http://localhost:3000"
249
278
  },
279
+ FRONTEND_MODE: {
280
+ default: "api",
281
+ pattern: FRONTEND_MODE_PATTERN
282
+ },
283
+ SPA_PREFIX: {
284
+ default: DEFAULT_SPA_PREFIX
285
+ },
250
286
  API_PREFIX: {
251
287
  default: "/api/v1"
252
288
  },
@@ -24,7 +24,7 @@ import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/require
24
24
  import { createRequirePasswordConfirmMiddleware } from "@getstrata/core/http/requirePasswordConfirmMiddleware";
25
25
  import { createRequireVerifiedMiddleware } from "@getstrata/core/http/requireVerifiedMiddleware";
26
26
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
27
- import { withErrorHandling } from "@getstrata/core/http/response";
27
+ import { withErrorHandling, withJsonErrorHandling } from "@getstrata/core/http/response";
28
28
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
29
29
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
30
30
  import { createValidateSignatureMiddleware } from "@getstrata/core/http/signedUrl";
@@ -172,7 +172,7 @@ class HttpKernel {
172
172
  return withMiddleware(...middleware)(handler);
173
173
  }
174
174
  wrapApi(handler) {
175
- return this.wrap(["api", "authenticated"], handler);
175
+ return withJsonErrorHandling(this.wrap(["api", "authenticated"], handler));
176
176
  }
177
177
  wrapWeb(handler) {
178
178
  return withErrorHandling(this.wrap("web", handler));
@@ -2,17 +2,19 @@
2
2
  var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/providers/auth.ts
5
+ import { BasicAuthGuard } from "@getstrata/core/auth/basicAuthGuard";
5
6
  import {
6
7
  AuthManager,
7
8
  CompositeGuard,
8
9
  DatabaseTokenGuard,
9
10
  GuestGuard
10
11
  } from "@getstrata/core/auth/guard";
12
+ import { JwtGuard } from "@getstrata/core/auth/jwtGuard";
11
13
  import { SessionGuard } from "@getstrata/core/auth/sessionGuard";
12
14
 
13
15
  // ../../src/config/auth.ts
14
16
  var authConfig = {
15
- allowDevHeaders: (process.env.AUTH_DEV_HEADERS ?? "true") !== "false",
17
+ allowDevHeaders: process.env.AUTH_DEV_HEADERS === "true",
16
18
  tokenDefaultAbilities: ["*"]
17
19
  };
18
20
 
@@ -47,11 +49,22 @@ var authProvider = {
47
49
  name: "core.auth",
48
50
  register({ container, config }) {
49
51
  config.set("auth.allowDevHeaders", authConfig.allowDevHeaders);
50
- const guards = [new DatabaseTokenGuard(container), new SessionGuard(container)];
52
+ const apiGuard = new DatabaseTokenGuard(container);
53
+ const sessionGuard = new SessionGuard(container);
54
+ const jwtGuard = new JwtGuard;
55
+ const basicGuard = new BasicAuthGuard(container);
56
+ const guards = [apiGuard, jwtGuard, basicGuard, sessionGuard];
51
57
  if (authConfig.allowDevHeaders) {
52
58
  guards.push(new GuestGuard);
53
59
  }
54
- container.set(CORE_AUTH_TOKEN, new AuthManager(new CompositeGuard(guards)));
60
+ const auth = new AuthManager(new CompositeGuard(guards));
61
+ auth.registerGuard("api", apiGuard);
62
+ auth.registerGuard("access_token", apiGuard);
63
+ auth.registerGuard("jwt", jwtGuard);
64
+ auth.registerGuard("basic", basicGuard);
65
+ auth.registerGuard("web", sessionGuard);
66
+ auth.registerGuard("session", sessionGuard);
67
+ container.set(CORE_AUTH_TOKEN, auth);
55
68
  }
56
69
  };
57
70
  var auth_default = authProvider;
@@ -112,8 +125,12 @@ var queueConfig = {
112
125
  };
113
126
  // ../../src/bootstrap/env.ts
114
127
  import { defineEnvSchema } from "@getstrata/core/config/envSchema";
128
+ import { DEFAULT_SPA_PREFIX, FRONTEND_MODE_PATTERN } from "@getstrata/core/runtime/frontendMode";
115
129
  var appEnvSchema = defineEnvSchema({
116
- DATABASE_URL: { required: true, pattern: /^postgres(ql)?:\/\// },
130
+ DATABASE_URL: {
131
+ required: true,
132
+ pattern: /^(postgres(ql)?|mysql|sqlite):\/\//i
133
+ },
117
134
  PORT: {
118
135
  integer: true,
119
136
  minimum: 1,
@@ -141,9 +158,21 @@ var appEnvSchema = defineEnvSchema({
141
158
  pattern: /^(sync|async|redis)$/
142
159
  },
143
160
  AUTH_DEV_HEADERS: {
144
- default: "true",
161
+ default: "false",
145
162
  pattern: /^(true|false|0|1)$/
146
163
  },
164
+ DB_CONNECTION: {
165
+ default: "",
166
+ pattern: /^(pgsql|postgres|postgresql|mysql|mariadb|sqlite)?$/i
167
+ },
168
+ AUTH_DEFAULT_GUARD: {
169
+ default: "web"
170
+ },
171
+ JWT_TTL_SECONDS: {
172
+ integer: true,
173
+ minimum: 60,
174
+ default: "3600"
175
+ },
147
176
  APP_ENV: {
148
177
  default: "local"
149
178
  },
@@ -153,6 +182,13 @@ var appEnvSchema = defineEnvSchema({
153
182
  APP_URL: {
154
183
  default: "http://localhost:3000"
155
184
  },
185
+ FRONTEND_MODE: {
186
+ default: "api",
187
+ pattern: FRONTEND_MODE_PATTERN
188
+ },
189
+ SPA_PREFIX: {
190
+ default: DEFAULT_SPA_PREFIX
191
+ },
156
192
  API_PREFIX: {
157
193
  default: "/api/v1"
158
194
  },
@@ -2,15 +2,21 @@
2
2
  var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/secretsGuard.ts
5
- var PUBLISHED_TEST_ADMIN_API_TOKEN = "workhub-admin-test-token";
6
- var PUBLISHED_TEST_MEMBER_API_TOKEN = "workhub-member-test-token";
7
- var PUBLISHED_TEST_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
5
+ import { isViewsMode, parseFrontendMode } from "@getstrata/core/runtime/frontendMode";
6
+ var PUBLISHED_TEST_ADMIN_API_TOKEN = "strata-admin-test-token";
7
+ var PUBLISHED_TEST_MEMBER_API_TOKEN = "strata-member-test-token";
8
+ var PUBLISHED_TEST_SCIM_BEARER_TOKEN = "strata-scim-test-token";
8
9
  var MIN_SESSION_SECRET_LENGTH = 32;
9
10
  var PUBLISHED_TEST_TOKENS = new Set([
10
11
  PUBLISHED_TEST_ADMIN_API_TOKEN,
11
- PUBLISHED_TEST_MEMBER_API_TOKEN
12
+ PUBLISHED_TEST_MEMBER_API_TOKEN,
13
+ "workhub-admin-test-token",
14
+ "workhub-member-test-token"
15
+ ]);
16
+ var PUBLISHED_TEST_SCIM_TOKENS = new Set([
17
+ PUBLISHED_TEST_SCIM_BEARER_TOKEN,
18
+ "workhub-scim-test-token"
12
19
  ]);
13
- var PUBLISHED_TEST_SCIM_TOKENS = new Set([PUBLISHED_TEST_SCIM_BEARER_TOKEN]);
14
20
  function isEnabled(value, defaultEnabled) {
15
21
  if (value === undefined) {
16
22
  return defaultEnabled;
@@ -34,7 +40,7 @@ function assertAuthDevHeadersDisabled(env) {
34
40
  function assertSessionSecret(env) {
35
41
  const secret = env.SESSION_SECRET?.trim() ?? "";
36
42
  if (secret.length < MIN_SESSION_SECRET_LENGTH) {
37
- throw new Error("Production startup blocked: set SESSION_SECRET when FRONTEND_MODE=server-htmx (32+ characters).");
43
+ throw new Error("Production startup blocked: set SESSION_SECRET when FRONTEND_MODE=server-htmx or hybrid (32+ characters).");
38
44
  }
39
45
  }
40
46
  function assertPublishedTestTokensRotated(env) {
@@ -96,8 +102,7 @@ function assertProductionSecrets(env = process.env) {
96
102
  assertTokenAuthProductionSecrets(env);
97
103
  }
98
104
  assertFeatureProductionSecrets(env);
99
- const frontendMode = (env.FRONTEND_MODE ?? "api").trim();
100
- if (frontendMode === "server-htmx") {
105
+ if (isViewsMode(parseFrontendMode(env.FRONTEND_MODE))) {
101
106
  assertSessionSecret(env);
102
107
  }
103
108
  }
@@ -2,6 +2,7 @@
2
2
  var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/web/routing.ts
5
+ import { requestPrefersJson } from "@getstrata/core/http/contentNegotiation";
5
6
  import { withErrorHandling as withErrorHandling2 } from "@getstrata/core/http/response";
6
7
 
7
8
  // ../../src/bootstrap/http/securedRouteModelBinding.ts
@@ -33,7 +34,7 @@ import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/require
33
34
  import { createRequirePasswordConfirmMiddleware } from "@getstrata/core/http/requirePasswordConfirmMiddleware";
34
35
  import { createRequireVerifiedMiddleware } from "@getstrata/core/http/requireVerifiedMiddleware";
35
36
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
36
- import { withErrorHandling } from "@getstrata/core/http/response";
37
+ import { withErrorHandling, withJsonErrorHandling } from "@getstrata/core/http/response";
37
38
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
38
39
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
39
40
  import { createValidateSignatureMiddleware } from "@getstrata/core/http/signedUrl";
@@ -181,7 +182,7 @@ class HttpKernel {
181
182
  return withMiddleware(...middleware)(handler);
182
183
  }
183
184
  wrapApi(handler) {
184
- return this.wrap(["api", "authenticated"], handler);
185
+ return withJsonErrorHandling(this.wrap(["api", "authenticated"], handler));
185
186
  }
186
187
  wrapWeb(handler) {
187
188
  return withErrorHandling(this.wrap("web", handler));
@@ -366,7 +367,7 @@ function wrapWebThrottle(kernel, scope, handler, onThrottled) {
366
367
  const throttled = scope === "login" ? kernel.wrapLogin(handler) : kernel.wrapRegister(handler);
367
368
  return async (request) => {
368
369
  const response = await throttled(request);
369
- if (response.status === 429) {
370
+ if (response.status === 429 && !requestPrefersJson(request)) {
370
371
  return onThrottled(request);
371
372
  }
372
373
  return response;
@@ -2,11 +2,19 @@
2
2
  var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/web/session.ts
5
- import { createHash, randomBytes } from "crypto";
5
+ import { createHmac, randomBytes } from "crypto";
6
6
  import { AuthManager } from "@getstrata/core/auth/guard";
7
7
  import { getBoundDatabaseConnection } from "@getstrata/core/database/boundConnection";
8
8
  import { getDefaultDatabasePool } from "@getstrata/core/database/defaultConnection";
9
+ import { currentSqlDialect } from "@getstrata/core/database/dialect";
9
10
  import { readRequestCookie } from "@getstrata/core/http/cookies";
11
+ import { timingSafeCompareString } from "@getstrata/core/security/timingSafeCompare";
12
+ function sqlPlaceholder(index) {
13
+ return currentSqlDialect().placeholder(index);
14
+ }
15
+ function sqlNow() {
16
+ return currentSqlDialect().nowExpression();
17
+ }
10
18
  function isSqlClient(value) {
11
19
  return typeof value.unsafe === "function";
12
20
  }
@@ -29,22 +37,34 @@ function defaultMapSessionUser(user) {
29
37
  role: user.is_admin ? "admin" : "member"
30
38
  };
31
39
  }
40
+ function sessionDisplayName(row, email) {
41
+ if (typeof row.name === "string" && row.name.trim() !== "") {
42
+ return row.name;
43
+ }
44
+ const first = typeof row.first_name === "string" ? row.first_name.trim() : "";
45
+ const last = typeof row.last_name === "string" ? row.last_name.trim() : "";
46
+ const composed = `${first} ${last}`.trim();
47
+ return composed || email;
48
+ }
49
+ function mapSessionUserRow(row) {
50
+ const email = typeof row.email === "string" ? row.email : "";
51
+ return {
52
+ id: Number(row.user_id ?? row.id),
53
+ name: sessionDisplayName(row, email),
54
+ email,
55
+ learn_subscriber: Boolean(row.learn_subscriber),
56
+ is_admin: Boolean(row.is_admin)
57
+ };
58
+ }
32
59
  async function defaultLoadSessionUser(sql, sessionId) {
33
- const rows = await sql.unsafe(`SELECT s.id, s.user_id, s.expires_at, u.name, u.email, u.learn_subscriber,
34
- COALESCE(u.is_admin, false) AS is_admin
60
+ const rows = await sql.unsafe(`SELECT s.user_id, s.expires_at, u.*
35
61
  FROM sessions s
36
62
  INNER JOIN users u ON u.id = s.user_id
37
- WHERE s.id = $1 AND s.expires_at > NOW()`, [sessionId]);
63
+ WHERE s.id = ${sqlPlaceholder(1)} AND s.expires_at > ${sqlNow()}`, [sessionId]);
38
64
  const row = rows[0];
39
65
  if (!row)
40
66
  return null;
41
- return {
42
- id: row.user_id,
43
- name: row.name,
44
- email: row.email,
45
- learn_subscriber: row.learn_subscriber,
46
- is_admin: row.is_admin
47
- };
67
+ return mapSessionUserRow(row);
48
68
  }
49
69
  function redirectWithCookie(location, setCookie, status) {
50
70
  return new Response(null, {
@@ -82,7 +102,7 @@ class CookieSessionStore {
82
102
  if (!raw)
83
103
  return null;
84
104
  const [sessionId, signature] = raw.split(".");
85
- if (!sessionId || !signature || signature !== this.sign(sessionId)) {
105
+ if (!sessionId || !signature || !timingSafeCompareString(signature, this.sign(sessionId))) {
86
106
  return null;
87
107
  }
88
108
  return sessionId;
@@ -100,28 +120,24 @@ class CookieSessionStore {
100
120
  const id = randomBytes(32).toString("hex");
101
121
  const expires = new Date(Date.now() + this.maxAgeSeconds * 1000);
102
122
  await this.sql().unsafe(`INSERT INTO sessions (id, user_id, expires_at, user_agent, ip_address, last_active_at)
103
- VALUES ($1, $2, $3, $4, $5, NOW())`, [id, user.id, expires, meta.userAgent ?? null, meta.ipAddress ?? null]);
123
+ VALUES (${sqlPlaceholder(1)}, ${sqlPlaceholder(2)}, ${sqlPlaceholder(3)}, ${sqlPlaceholder(4)}, ${sqlPlaceholder(5)}, ${sqlNow()})`, [id, user.id, expires, meta.userAgent ?? null, meta.ipAddress ?? null]);
104
124
  return id;
105
125
  }
106
126
  async destroy(sessionId) {
107
- await this.sql().unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
127
+ await this.sql().unsafe(`DELETE FROM sessions WHERE id = ${sqlPlaceholder(1)}`, [sessionId]);
108
128
  }
109
129
  async destroyOtherSessions(userId, keepSessionId) {
110
- await this.sql().unsafe(`DELETE FROM sessions WHERE user_id = $1 AND id <> $2`, [
111
- userId,
112
- keepSessionId
113
- ]);
130
+ await this.sql().unsafe(`DELETE FROM sessions WHERE user_id = ${sqlPlaceholder(1)} AND id <> ${sqlPlaceholder(2)}`, [userId, keepSessionId]);
114
131
  }
115
132
  async listForUser(userId) {
133
+ const dialect = currentSqlDialect();
116
134
  return this.sql().unsafe(`SELECT id, user_id, user_agent, ip_address, last_active_at, expires_at
117
135
  FROM sessions
118
- WHERE user_id = $1 AND expires_at > NOW()
119
- ORDER BY last_active_at DESC NULLS LAST, expires_at DESC`, [userId]);
136
+ WHERE user_id = ${dialect.placeholder(1)} AND expires_at > ${dialect.nowExpression()}
137
+ ORDER BY last_active_at DESC${dialect.nullsLastSuffix()}, expires_at DESC`, [userId]);
120
138
  }
121
139
  async touch(sessionId) {
122
- await this.sql().unsafe(`UPDATE sessions SET last_active_at = NOW() WHERE id = $1`, [
123
- sessionId
124
- ]);
140
+ await this.sql().unsafe(`UPDATE sessions SET last_active_at = ${sqlNow()} WHERE id = ${sqlPlaceholder(1)}`, [sessionId]);
125
141
  }
126
142
  async read(request) {
127
143
  const sessionId = this.sessionIdFromRequest(request);
@@ -130,7 +146,7 @@ class CookieSessionStore {
130
146
  return this.loadSessionUser(this.sql(), sessionId);
131
147
  }
132
148
  sign(value) {
133
- return createHash("sha256").update(`${value}.${this.secret}`).digest("hex").slice(0, 32);
149
+ return createHmac("sha256", this.secret).update(value).digest("hex").slice(0, 32);
134
150
  }
135
151
  }
136
152
 
@@ -153,8 +169,11 @@ class CookieSessionGuard {
153
169
  class CookieSessionAuthManager extends AuthManager {
154
170
  store;
155
171
  constructor(store, mapUser = defaultMapSessionUser) {
156
- super(new CookieSessionGuard(store, mapUser));
172
+ const guard = new CookieSessionGuard(store, mapUser);
173
+ super(guard);
157
174
  this.store = store;
175
+ this.registerGuard("web", guard);
176
+ this.registerGuard("session", guard);
158
177
  }
159
178
  async signIn(user, meta = {}) {
160
179
  const sessionId = await this.store.create(user, meta);