@everystack/auth 0.1.0 → 0.2.0

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/src/postgres.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Password hashing: crypt() / gen_salt('bf') via auth.hash_password()
5
5
  * JWT signing: hmac() via auth.sign_jwt()
6
- * Refresh tokens: auth.generate_refresh_token() / auth.validate_refresh_token()
6
+ * Refresh tokens: auth.issue_refresh_token() / auth.validate_refresh_token()
7
7
  *
8
8
  * Uses postgres.js tagged templates directly (not drizzle's sql) because
9
9
  * drizzle expands arrays into individual params instead of PostgreSQL arrays.
@@ -15,16 +15,52 @@
15
15
  import { and, eq } from 'drizzle-orm';
16
16
  import { createJwt } from './jwt';
17
17
  import { hmacNode } from './jwt/hmac-node';
18
+ import type { RefreshTokenOptions } from './refresh';
18
19
  import type { AuthHandlers, AuthOptions, TokenPayload } from './index';
20
+ import { getPasswordHash, withPassword } from './index';
19
21
  import type { OAuthProvider } from './oauth';
20
22
  import { getRefreshFromCookies } from './cookies';
21
23
  import { authResponse, signoutResponse } from './response';
24
+ import type { CookieConfig } from './response';
25
+ import { validateCsrfOrigin } from './csrf';
22
26
 
23
27
  const TOKEN_EXPIRY_SECONDS = 3600; // 1 hour
24
28
 
29
+ /**
30
+ * Dummy bcrypt hash for timing-safe user enumeration protection.
31
+ * When a user is not found, we still run bcrypt against this hash
32
+ * so response time is constant regardless of whether the email exists.
33
+ */
34
+ const DUMMY_BCRYPT_HASH = '$2a$10$abcdefghijklmnopqrstuuABCDEFGHIJKLMNOPQRSTUVWXYZ012345';
35
+
36
+ /**
37
+ * Extract client IP and user agent from a request for refresh token metadata.
38
+ * When repositoryId is provided, it's included for multi-tenant scoping.
39
+ */
40
+ function extractRequestMeta(request: Request, repositoryId?: string): RefreshTokenOptions {
41
+ const forwarded = request.headers.get('x-forwarded-for');
42
+ const ipAddress = forwarded ? forwarded.split(',')[0].trim() : undefined;
43
+ const userAgent = request.headers.get('user-agent') ?? undefined;
44
+ return { repositoryId, ipAddress, userAgent };
45
+ }
46
+
47
+ /** Run CSRF origin check if cookies are configured with allowedOrigins. */
48
+ function csrfCheck(request: Request, cookies?: CookieConfig): Response | null {
49
+ if (!cookies || cookies === true) return null;
50
+ return validateCsrfOrigin(request, cookies.allowedOrigins);
51
+ }
52
+
25
53
  export function createPostgresAuthHandlers(
26
54
  db: any,
27
- schema: { users: any; profiles: any; sessions?: any; credentials?: any; identities?: any },
55
+ schema: {
56
+ users: any;
57
+ profiles: any;
58
+ refreshTokens?: any;
59
+ /** Legacy OAuth tables. Use `oauth.callbacks` instead for new apps. */
60
+ credentials?: any;
61
+ /** Legacy OAuth tables. Use `oauth.callbacks` instead for new apps. */
62
+ identities?: any;
63
+ },
28
64
  jwtSecret: Uint8Array,
29
65
  options?: AuthOptions,
30
66
  ): AuthHandlers {
@@ -34,16 +70,25 @@ export function createPostgresAuthHandlers(
34
70
  }
35
71
 
36
72
  const { users, profiles } = schema;
73
+ const refreshTable = schema.refreshTokens;
37
74
  const jwtSecretString = new TextDecoder().decode(jwtSecret);
38
75
 
39
76
  /**
40
77
  * Sign JWT via PostgreSQL pgcrypto hmac().
41
78
  * Requires GUC app.jwt_secret — uses pgSql.begin() for transaction-local setting.
79
+ * When enrichClaims is configured and a request is provided, merges extra claims.
42
80
  */
43
- async function signJwtPg(payload: Record<string, unknown>): Promise<string> {
81
+ async function signJwtPg(payload: Record<string, unknown>, request?: Request): Promise<string> {
82
+ let claims = payload;
83
+ if (options?.enrichClaims && request) {
84
+ const extra = await options.enrichClaims(claims, request);
85
+ if (extra && Object.keys(extra).length > 0) {
86
+ claims = { ...claims, ...extra };
87
+ }
88
+ }
44
89
  const now = Math.floor(Date.now() / 1000);
45
90
  const fullPayload = {
46
- ...payload,
91
+ ...claims,
47
92
  iat: now,
48
93
  exp: now + TOKEN_EXPIRY_SECONDS,
49
94
  };
@@ -72,29 +117,62 @@ export function createPostgresAuthHandlers(
72
117
  }
73
118
 
74
119
  /**
75
- * Generate refresh token via PostgreSQL.
120
+ * Issue refresh token via PostgreSQL SECURITY DEFINER function.
121
+ * Handles family ceiling, SHA-256 hashing, and token generation atomically.
76
122
  */
77
- async function generateRefreshTokenPg(userId: string): Promise<string> {
78
- const rows = await pgSql`SELECT auth.generate_refresh_token(${userId}::uuid) as generate_refresh_token`;
79
- return rows[0].generate_refresh_token;
123
+ async function issueRefreshTokenPg(
124
+ userId: string,
125
+ repositoryId?: string,
126
+ ipAddress?: string,
127
+ userAgent?: string,
128
+ deviceId?: string,
129
+ ): Promise<{ token: string; familyId: string }> {
130
+ const repoId = repositoryId ?? null;
131
+ const rows = await pgSql`SELECT auth.issue_refresh_token(
132
+ ${userId}::uuid,
133
+ NULL::uuid,
134
+ ${ipAddress ?? null},
135
+ ${userAgent ?? null},
136
+ ${deviceId ?? null}::uuid,
137
+ ${repoId}::uuid
138
+ ) as result`;
139
+ const result = typeof rows[0].result === 'string' ? JSON.parse(rows[0].result) : rows[0].result;
140
+ return { token: result.token, familyId: result.familyId };
80
141
  }
81
142
 
82
143
  /**
83
144
  * Validate and rotate refresh token via PostgreSQL.
84
- * The SQL function handles its own transactional locking (FOR UPDATE).
85
- * Returns { userId, token, familyId }.
145
+ * The SQL function handles SELECT FOR UPDATE, reuse detection, user state check.
86
146
  */
87
- async function validateRefreshTokenPg(token: string): Promise<{ userId: string; token: string; familyId: string }> {
88
- const rows = await pgSql`SELECT auth.validate_refresh_token(${token}) as validate_refresh_token`;
89
- const result = rows[0].validate_refresh_token;
90
- return typeof result === 'string' ? JSON.parse(result) : result;
147
+ async function validateRefreshTokenPg(
148
+ token: string,
149
+ deviceId?: string,
150
+ ipAddress?: string,
151
+ userAgent?: string,
152
+ ): Promise<{ userId: string; token: string; familyId: string }> {
153
+ const rows = await pgSql`SELECT auth.validate_refresh_token(
154
+ ${token},
155
+ ${deviceId ?? null}::uuid,
156
+ ${ipAddress ?? null},
157
+ ${userAgent ?? null}
158
+ ) as result`;
159
+ const result = typeof rows[0].result === 'string' ? JSON.parse(rows[0].result) : rows[0].result;
160
+ // token_reused returns as JSONB { error: 'token_reused' } instead of RAISE
161
+ // because PostgreSQL rolls back DELETEs when exceptions propagate.
162
+ if (result.error === 'token_reused') {
163
+ const err: any = new Error('token_reused');
164
+ err.code = 'P0002';
165
+ throw err;
166
+ }
167
+ return { userId: result.userId, token: result.token, familyId: result.familyId };
91
168
  }
92
169
 
93
170
  /**
94
- * Revoke all refresh tokens for a user.
171
+ * Revoke all refresh tokens for a user, optionally scoped by repository.
95
172
  */
96
- async function revokeRefreshTokensPg(userId: string): Promise<void> {
97
- await pgSql`SELECT auth.revoke_refresh_tokens(${userId}::uuid)`;
173
+ async function revokeRefreshTokensPg(userId: string, repositoryId?: string): Promise<void> {
174
+ const repoId = repositoryId ?? null;
175
+ await pgSql`SELECT auth.revoke_refresh_tokens(${userId}::uuid, NULL::uuid, ${repoId}::uuid)`;
98
176
  }
99
177
 
100
178
  // JS-side verifier (compatible with HS256 tokens minted by SQL or JS).
@@ -111,6 +189,10 @@ export function createPostgresAuthHandlers(
111
189
  },
112
190
 
113
191
  signup: async (request: Request): Promise<Response> => {
192
+ const csrfError = csrfCheck(request, options?.cookies);
193
+ if (csrfError) return csrfError;
194
+
195
+ const enrichRequest = options?.enrichClaims ? request.clone() : undefined;
114
196
  const { email, password, username, displayName } = await request.json();
115
197
 
116
198
  if (!email || !password || !username || !displayName) {
@@ -126,15 +208,21 @@ export function createPostgresAuthHandlers(
126
208
  .where(eq(users.email, email))
127
209
  .limit(1);
128
210
  if (existing.length > 0) {
211
+ if (options?.uniformSignupResponse) {
212
+ return Response.json(
213
+ { message: 'Check your email to continue.' },
214
+ { status: 200 },
215
+ );
216
+ }
129
217
  return Response.json(
130
218
  { message: 'Email already registered' },
131
219
  { status: 409 },
132
220
  );
133
221
  }
134
222
 
135
- const passwordHash = await hashPasswordPg(password);
223
+ const hash = await hashPasswordPg(password);
136
224
  const { user, profile } = await db.transaction(async (tx: any) => {
137
- const [u] = await tx.insert(users).values({ email, passwordHash }).returning();
225
+ const [u] = await tx.insert(users).values(withPassword(users, { email }, hash)).returning();
138
226
  const [p] = await tx.insert(profiles).values({ id: u.id, username, displayName }).returning();
139
227
  return { user: u, profile: p };
140
228
  });
@@ -143,11 +231,19 @@ export function createPostgresAuthHandlers(
143
231
  sub: user.id,
144
232
  email: user.email,
145
233
  role: user.role ?? 'authenticated',
146
- });
147
-
148
- const refreshToken = schema.sessions
149
- ? await generateRefreshTokenPg(user.id)
150
- : await signJwtPg({ sub: user.id });
234
+ }, enrichRequest);
235
+
236
+ const meta = extractRequestMeta(request, options?.repositoryId);
237
+ let refreshToken: string;
238
+ if (refreshTable) {
239
+ // Prefer PG function path when sql client is available
240
+ const result = await issueRefreshTokenPg(
241
+ user.id, meta.repositoryId, meta.ipAddress, meta.userAgent,
242
+ );
243
+ refreshToken = result.token;
244
+ } else {
245
+ refreshToken = await signJwtPg({ sub: user.id });
246
+ }
151
247
 
152
248
  return authResponse(
153
249
  {
@@ -167,6 +263,10 @@ export function createPostgresAuthHandlers(
167
263
  },
168
264
 
169
265
  signin: async (request: Request): Promise<Response> => {
266
+ const csrfError = csrfCheck(request, options?.cookies);
267
+ if (csrfError) return csrfError;
268
+
269
+ const enrichRequest = options?.enrichClaims ? request.clone() : undefined;
170
270
  const { email, password } = await request.json();
171
271
 
172
272
  if (!email || !password) {
@@ -181,14 +281,18 @@ export function createPostgresAuthHandlers(
181
281
  .from(users)
182
282
  .where(eq(users.email, email))
183
283
  .limit(1);
284
+
184
285
  if (!user) {
286
+ // Timing-safe: run bcrypt even when user doesn't exist
287
+ // so response time is constant regardless of email existence
288
+ await verifyPasswordPg(password, DUMMY_BCRYPT_HASH);
185
289
  return Response.json(
186
290
  { message: 'Invalid email or password' },
187
291
  { status: 401 },
188
292
  );
189
293
  }
190
294
 
191
- const valid = await verifyPasswordPg(password, user.passwordHash);
295
+ const valid = await verifyPasswordPg(password, getPasswordHash(user)!);
192
296
  if (!valid) {
193
297
  return Response.json(
194
298
  { message: 'Invalid email or password' },
@@ -206,11 +310,18 @@ export function createPostgresAuthHandlers(
206
310
  sub: user.id,
207
311
  email: user.email,
208
312
  role: user.role ?? 'authenticated',
209
- });
313
+ }, enrichRequest);
210
314
 
211
- const refreshToken = schema.sessions
212
- ? await generateRefreshTokenPg(user.id)
213
- : await signJwtPg({ sub: user.id });
315
+ const meta = extractRequestMeta(request, options?.repositoryId);
316
+ let refreshToken: string;
317
+ if (refreshTable) {
318
+ const result = await issueRefreshTokenPg(
319
+ user.id, meta.repositoryId, meta.ipAddress, meta.userAgent,
320
+ );
321
+ refreshToken = result.token;
322
+ } else {
323
+ refreshToken = await signJwtPg({ sub: user.id });
324
+ }
214
325
 
215
326
  return authResponse(
216
327
  {
@@ -230,6 +341,9 @@ export function createPostgresAuthHandlers(
230
341
  },
231
342
 
232
343
  refresh: async (request: Request): Promise<Response> => {
344
+ const csrfError = csrfCheck(request, options?.cookies);
345
+ if (csrfError) return csrfError;
346
+
233
347
  // Read refresh token from JSON body (mobile) or cookie (web SSR)
234
348
  let refreshToken: string | undefined;
235
349
  try {
@@ -248,10 +362,13 @@ export function createPostgresAuthHandlers(
248
362
  );
249
363
  }
250
364
 
251
- if (schema.sessions) {
252
- // DB-backed rotation via SQL function
365
+ if (refreshTable) {
366
+ // DB-backed rotation via PG SECURITY DEFINER functions (preferred)
253
367
  try {
254
- const result = await validateRefreshTokenPg(refreshToken);
368
+ const meta = extractRequestMeta(request, options?.repositoryId);
369
+ const result = await validateRefreshTokenPg(
370
+ refreshToken, undefined, meta.ipAddress, meta.userAgent,
371
+ );
255
372
 
256
373
  const [user] = await db
257
374
  .select()
@@ -259,6 +376,7 @@ export function createPostgresAuthHandlers(
259
376
  .where(eq(users.id, result.userId))
260
377
  .limit(1);
261
378
  if (!user) {
379
+ await revokeRefreshTokensPg(result.userId, options?.repositoryId);
262
380
  return Response.json({ message: 'User not found' }, { status: 401 });
263
381
  }
264
382
 
@@ -266,18 +384,24 @@ export function createPostgresAuthHandlers(
266
384
  sub: user.id,
267
385
  email: user.email,
268
386
  role: user.role ?? 'authenticated',
269
- });
387
+ }, request);
270
388
 
271
389
  return authResponse(
272
390
  { token, refreshToken: result.token },
273
391
  200,
274
392
  options?.cookies,
275
393
  );
276
- } catch {
277
- return Response.json(
278
- { message: 'Invalid or expired refresh token' },
279
- { status: 401 },
280
- );
394
+ } catch (err: any) {
395
+ // Map PG error codes to HTTP responses
396
+ const pgCode = err?.code;
397
+ if (pgCode === 'P0001' || pgCode === 'P0002' || pgCode === 'P0003'
398
+ || pgCode === 'P0004' || pgCode === 'P0005') {
399
+ return Response.json(
400
+ { message: 'Invalid or expired refresh token' },
401
+ { status: 401 },
402
+ );
403
+ }
404
+ throw err;
281
405
  }
282
406
  } else {
283
407
  // Stateless JWT-based refresh (fallback)
@@ -301,7 +425,7 @@ export function createPostgresAuthHandlers(
301
425
  sub: user.id,
302
426
  email: user.email,
303
427
  role: user.role ?? 'authenticated',
304
- });
428
+ }, request);
305
429
  const newRefreshToken = await signJwtPg({ sub: user.id });
306
430
 
307
431
  return authResponse(
@@ -320,14 +444,21 @@ export function createPostgresAuthHandlers(
320
444
 
321
445
  oauth: options?.oauth?.providers.length
322
446
  ? async (request: Request): Promise<Response> => {
323
- if (!schema.credentials || !schema.identities) {
447
+ const csrfError = csrfCheck(request, options?.cookies);
448
+ if (csrfError) return csrfError;
449
+
450
+ const enrichRequest = options?.enrichClaims ? request.clone() : undefined;
451
+ const oauthCallbacks = options?.oauth?.callbacks;
452
+
453
+ // Require either callbacks or legacy tables
454
+ if (!oauthCallbacks && (!schema.credentials || !schema.identities)) {
324
455
  return Response.json(
325
- { message: 'OAuth not configured: credentials and identities tables required in schema' },
456
+ { message: 'OAuth not configured: provide oauth.callbacks or credentials/identities tables' },
326
457
  { status: 500 },
327
458
  );
328
459
  }
329
460
 
330
- let body: { provider?: string; token?: string };
461
+ let body: { provider?: string; token?: string; nonce?: string };
331
462
  try {
332
463
  body = await request.json();
333
464
  } catch {
@@ -337,7 +468,7 @@ export function createPostgresAuthHandlers(
337
468
  );
338
469
  }
339
470
 
340
- const { provider: providerName, token } = body;
471
+ const { provider: providerName, token, nonce } = body;
341
472
  if (!providerName || !token) {
342
473
  return Response.json(
343
474
  { message: 'provider and token are required' },
@@ -363,7 +494,112 @@ export function createPostgresAuthHandlers(
363
494
  );
364
495
  }
365
496
 
366
- // Look up existing OAuth credential by (type='oauth', credentialId=providerUid)
497
+ // Optional nonce validation for web OAuth flows
498
+ if (options?.oauth?.validateNonce) {
499
+ if (!nonce) {
500
+ return Response.json(
501
+ { message: 'Nonce is required when nonce validation is configured' },
502
+ { status: 400 },
503
+ );
504
+ }
505
+ const nonceValid = await options.oauth.validateNonce(nonce, userInfo);
506
+ if (!nonceValid) {
507
+ return Response.json(
508
+ { message: 'Nonce validation failed' },
509
+ { status: 401 },
510
+ );
511
+ }
512
+ }
513
+
514
+ const meta = extractRequestMeta(request, options?.repositoryId);
515
+
516
+ if (oauthCallbacks) {
517
+ // Callback-based path (recommended) — no identities/credentials tables needed
518
+ const existing = await oauthCallbacks.findByProvider(providerName, userInfo.uid);
519
+
520
+ if (existing) {
521
+ const [existingUser] = await db
522
+ .select()
523
+ .from(users)
524
+ .where(eq(users.id, existing.userId))
525
+ .limit(1);
526
+
527
+ const [profile] = await db
528
+ .select()
529
+ .from(profiles)
530
+ .where(eq(profiles.id, existing.userId))
531
+ .limit(1);
532
+
533
+ const accessToken = await signJwtPg({
534
+ sub: existingUser.id,
535
+ email: existingUser.email,
536
+ role: existingUser.role ?? 'authenticated',
537
+ }, enrichRequest);
538
+ let refreshTokenStr: string;
539
+ if (refreshTable) {
540
+ const result = await issueRefreshTokenPg(
541
+ existingUser.id, meta.repositoryId, meta.ipAddress, meta.userAgent,
542
+ );
543
+ refreshTokenStr = result.token;
544
+ } else {
545
+ refreshTokenStr = await signJwtPg({ sub: existingUser.id });
546
+ }
547
+
548
+ return authResponse(
549
+ {
550
+ token: accessToken,
551
+ refreshToken: refreshTokenStr,
552
+ user: {
553
+ id: existingUser.id,
554
+ email: existingUser.email,
555
+ username: profile?.username,
556
+ displayName: profile?.displayName,
557
+ avatarUrl: profile?.avatarUrl,
558
+ role: existingUser.role ?? 'authenticated',
559
+ },
560
+ },
561
+ 200,
562
+ options?.cookies,
563
+ );
564
+ }
565
+
566
+ // New user via callback
567
+ const newUser = await oauthCallbacks.createFromOAuth(userInfo, providerName);
568
+
569
+ const accessToken = await signJwtPg({
570
+ sub: newUser.id,
571
+ email: newUser.email,
572
+ role: newUser.role ?? 'authenticated',
573
+ }, enrichRequest);
574
+ let refreshTokenStr: string;
575
+ if (refreshTable) {
576
+ const result = await issueRefreshTokenPg(
577
+ newUser.id, meta.repositoryId, meta.ipAddress, meta.userAgent,
578
+ );
579
+ refreshTokenStr = result.token;
580
+ } else {
581
+ refreshTokenStr = await signJwtPg({ sub: newUser.id });
582
+ }
583
+
584
+ return authResponse(
585
+ {
586
+ token: accessToken,
587
+ refreshToken: refreshTokenStr,
588
+ user: {
589
+ id: newUser.id,
590
+ email: newUser.email,
591
+ username: newUser.username,
592
+ displayName: newUser.displayName,
593
+ avatarUrl: newUser.avatarUrl,
594
+ role: newUser.role ?? 'authenticated',
595
+ },
596
+ },
597
+ 200,
598
+ options?.cookies,
599
+ );
600
+ }
601
+
602
+ // Legacy path: identities + credentials tables
367
603
  const existingResults = await db
368
604
  .select({
369
605
  credentialId: schema.credentials.id,
@@ -396,15 +632,21 @@ export function createPostgresAuthHandlers(
396
632
  sub: existingUser.id,
397
633
  email: existingUser.email,
398
634
  role: existingUser.role ?? 'authenticated',
399
- });
400
- const refreshToken = schema.sessions
401
- ? await generateRefreshTokenPg(existingUser.id)
402
- : await signJwtPg({ sub: existingUser.id });
635
+ }, enrichRequest);
636
+ let refreshTokenStr: string;
637
+ if (refreshTable) {
638
+ const result = await issueRefreshTokenPg(
639
+ existingUser.id, meta.repositoryId, meta.ipAddress, meta.userAgent,
640
+ );
641
+ refreshTokenStr = result.token;
642
+ } else {
643
+ refreshTokenStr = await signJwtPg({ sub: existingUser.id });
644
+ }
403
645
 
404
646
  return authResponse(
405
647
  {
406
648
  token: accessToken,
407
- refreshToken,
649
+ refreshToken: refreshTokenStr,
408
650
  user: {
409
651
  id: existingUser.id,
410
652
  email: existingUser.email,
@@ -419,7 +661,7 @@ export function createPostgresAuthHandlers(
419
661
  );
420
662
  }
421
663
 
422
- // New user — create account + identity + OAuth credential
664
+ // New user — create account + identity + OAuth credential (legacy path)
423
665
  const displayName = [userInfo.firstName, userInfo.lastName]
424
666
  .filter(Boolean)
425
667
  .join(' ') || userInfo.email.split('@')[0];
@@ -428,7 +670,7 @@ export function createPostgresAuthHandlers(
428
670
  const { user, profile } = await db.transaction(async (tx: any) => {
429
671
  const [u] = await tx
430
672
  .insert(users)
431
- .values({ email: userInfo.email, passwordHash: null })
673
+ .values(withPassword(users, { email: userInfo.email }, null))
432
674
  .returning();
433
675
 
434
676
  const [ident] = await tx
@@ -467,15 +709,21 @@ export function createPostgresAuthHandlers(
467
709
  sub: user.id,
468
710
  email: user.email,
469
711
  role: user.role ?? 'authenticated',
470
- });
471
- const refreshToken = schema.sessions
472
- ? await generateRefreshTokenPg(user.id)
473
- : await signJwtPg({ sub: user.id });
712
+ }, enrichRequest);
713
+ let refreshTokenStr: string;
714
+ if (refreshTable) {
715
+ const result = await issueRefreshTokenPg(
716
+ user.id, meta.repositoryId, meta.ipAddress, meta.userAgent,
717
+ );
718
+ refreshTokenStr = result.token;
719
+ } else {
720
+ refreshTokenStr = await signJwtPg({ sub: user.id });
721
+ }
474
722
 
475
723
  return authResponse(
476
724
  {
477
725
  token: accessToken,
478
- refreshToken,
726
+ refreshToken: refreshTokenStr,
479
727
  user: {
480
728
  id: user.id,
481
729
  email: user.email,
@@ -491,13 +739,16 @@ export function createPostgresAuthHandlers(
491
739
  }
492
740
  : undefined,
493
741
 
494
- signout: async (_request: Request): Promise<Response> => {
742
+ signout: async (request: Request): Promise<Response> => {
743
+ const csrfError = csrfCheck(request, options?.cookies);
744
+ if (csrfError) return csrfError;
745
+
495
746
  return signoutResponse(options?.cookies);
496
747
  },
497
748
 
498
749
  revokeRefreshTokens: async (userId: string): Promise<void> => {
499
- if (schema.sessions) {
500
- await revokeRefreshTokensPg(userId);
750
+ if (refreshTable) {
751
+ await revokeRefreshTokensPg(userId, options?.repositoryId);
501
752
  }
502
753
  },
503
754
  };