@getstrata/bootstrap 0.2.68 → 0.4.3

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
  }
@@ -26,25 +34,39 @@ function defaultSessionSql() {
26
34
  function defaultMapSessionUser(user) {
27
35
  return {
28
36
  id: user.id,
29
- role: user.is_admin ? "admin" : "member"
37
+ role: user.is_admin ? "admin" : "member",
38
+ ...user.email_verified_at !== undefined ? { emailVerifiedAt: user.email_verified_at } : {}
39
+ };
40
+ }
41
+ function sessionDisplayName(row, email) {
42
+ if (typeof row.name === "string" && row.name.trim() !== "") {
43
+ return row.name;
44
+ }
45
+ const first = typeof row.first_name === "string" ? row.first_name.trim() : "";
46
+ const last = typeof row.last_name === "string" ? row.last_name.trim() : "";
47
+ const composed = `${first} ${last}`.trim();
48
+ return composed || email;
49
+ }
50
+ function mapSessionUserRow(row) {
51
+ const email = typeof row.email === "string" ? row.email : "";
52
+ return {
53
+ id: Number(row.user_id ?? row.id),
54
+ name: sessionDisplayName(row, email),
55
+ email,
56
+ learn_subscriber: Boolean(row.learn_subscriber),
57
+ is_admin: Boolean(row.is_admin),
58
+ ...row.email_verified_at !== undefined ? { email_verified_at: row.email_verified_at } : {}
30
59
  };
31
60
  }
32
61
  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
62
+ const rows = await sql.unsafe(`SELECT s.user_id, s.expires_at, u.*
35
63
  FROM sessions s
36
64
  INNER JOIN users u ON u.id = s.user_id
37
- WHERE s.id = $1 AND s.expires_at > NOW()`, [sessionId]);
65
+ WHERE s.id = ${sqlPlaceholder(1)} AND s.expires_at > ${sqlNow()}`, [sessionId]);
38
66
  const row = rows[0];
39
67
  if (!row)
40
68
  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
- };
69
+ return mapSessionUserRow(row);
48
70
  }
49
71
  function redirectWithCookie(location, setCookie, status) {
50
72
  return new Response(null, {
@@ -82,7 +104,7 @@ class CookieSessionStore {
82
104
  if (!raw)
83
105
  return null;
84
106
  const [sessionId, signature] = raw.split(".");
85
- if (!sessionId || !signature || signature !== this.sign(sessionId)) {
107
+ if (!sessionId || !signature || !timingSafeCompareString(signature, this.sign(sessionId))) {
86
108
  return null;
87
109
  }
88
110
  return sessionId;
@@ -100,28 +122,24 @@ class CookieSessionStore {
100
122
  const id = randomBytes(32).toString("hex");
101
123
  const expires = new Date(Date.now() + this.maxAgeSeconds * 1000);
102
124
  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]);
125
+ VALUES (${sqlPlaceholder(1)}, ${sqlPlaceholder(2)}, ${sqlPlaceholder(3)}, ${sqlPlaceholder(4)}, ${sqlPlaceholder(5)}, ${sqlNow()})`, [id, user.id, expires.toISOString(), meta.userAgent ?? null, meta.ipAddress ?? null]);
104
126
  return id;
105
127
  }
106
128
  async destroy(sessionId) {
107
- await this.sql().unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
129
+ await this.sql().unsafe(`DELETE FROM sessions WHERE id = ${sqlPlaceholder(1)}`, [sessionId]);
108
130
  }
109
131
  async destroyOtherSessions(userId, keepSessionId) {
110
- await this.sql().unsafe(`DELETE FROM sessions WHERE user_id = $1 AND id <> $2`, [
111
- userId,
112
- keepSessionId
113
- ]);
132
+ await this.sql().unsafe(`DELETE FROM sessions WHERE user_id = ${sqlPlaceholder(1)} AND id <> ${sqlPlaceholder(2)}`, [userId, keepSessionId]);
114
133
  }
115
134
  async listForUser(userId) {
135
+ const dialect = currentSqlDialect();
116
136
  return this.sql().unsafe(`SELECT id, user_id, user_agent, ip_address, last_active_at, expires_at
117
137
  FROM sessions
118
- WHERE user_id = $1 AND expires_at > NOW()
119
- ORDER BY last_active_at DESC NULLS LAST, expires_at DESC`, [userId]);
138
+ WHERE user_id = ${dialect.placeholder(1)} AND expires_at > ${dialect.nowExpression()}
139
+ ORDER BY last_active_at DESC${dialect.nullsLastSuffix()}, expires_at DESC`, [userId]);
120
140
  }
121
141
  async touch(sessionId) {
122
- await this.sql().unsafe(`UPDATE sessions SET last_active_at = NOW() WHERE id = $1`, [
123
- sessionId
124
- ]);
142
+ await this.sql().unsafe(`UPDATE sessions SET last_active_at = ${sqlNow()} WHERE id = ${sqlPlaceholder(1)}`, [sessionId]);
125
143
  }
126
144
  async read(request) {
127
145
  const sessionId = this.sessionIdFromRequest(request);
@@ -130,7 +148,7 @@ class CookieSessionStore {
130
148
  return this.loadSessionUser(this.sql(), sessionId);
131
149
  }
132
150
  sign(value) {
133
- return createHash("sha256").update(`${value}.${this.secret}`).digest("hex").slice(0, 32);
151
+ return createHmac("sha256", this.secret).update(value).digest("hex").slice(0, 32);
134
152
  }
135
153
  }
136
154
 
@@ -153,8 +171,11 @@ class CookieSessionGuard {
153
171
  class CookieSessionAuthManager extends AuthManager {
154
172
  store;
155
173
  constructor(store, mapUser = defaultMapSessionUser) {
156
- super(new CookieSessionGuard(store, mapUser));
174
+ const guard = new CookieSessionGuard(store, mapUser);
175
+ super(guard);
157
176
  this.store = store;
177
+ this.registerGuard("web", guard);
178
+ this.registerGuard("session", guard);
158
179
  }
159
180
  async signIn(user, meta = {}) {
160
181
  const sessionId = await this.store.create(user, meta);