@rebasepro/server-postgres 0.9.1-canary.ff338b5 → 0.10.1-canary.18115ba

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.
@@ -14,6 +14,7 @@ import {
14
14
  RoleData,
15
15
  CreateRoleData,
16
16
  RefreshTokenInfo,
17
+ RefreshTokenSession,
17
18
  PasswordResetTokenInfo,
18
19
  MagicLinkTokenInfo,
19
20
  UserIdentityData,
@@ -86,7 +87,7 @@ export class UserService implements UserRepository {
86
87
  * Run a privileged auth write with an explicitly cleared RLS context.
87
88
  *
88
89
  * The auth services run on the base/owner connection, which by design
89
- * carries a NULL `app.user_id` so the `auth.uid() IS NULL` server-escape
90
+ * carries a NULL `app.uid` so the `auth.uid() IS NULL` server-escape
90
91
  * in the default policies applies. That NULL is normally guaranteed by
91
92
  * `set_config(..., is_local = true)` resetting at transaction end — but a
92
93
  * GUC that survives on a pooled connection (or a connection role that
@@ -100,7 +101,8 @@ export class UserService implements UserRepository {
100
101
  private async withServerContext<T>(fn: (db: NodePgDatabase) => Promise<T>): Promise<T> {
101
102
  return await this.db.transaction(async (tx) => {
102
103
  await tx.execute(sql`
103
- SELECT set_config('app.user_id', '', true),
104
+ SELECT set_config('app.uid', '', true),
105
+ set_config('app.user_id', '', true),
104
106
  set_config('app.user_roles', '', true),
105
107
  set_config('app.jwt', '', true)
106
108
  `);
@@ -253,7 +255,7 @@ export class UserService implements UserRepository {
253
255
  const result = await this.db
254
256
  .select({ user: this.usersTable })
255
257
  .from(this.usersTable)
256
- .innerJoin(this.userIdentitiesTable, eq(userIdCol, this.userIdentitiesTable.userId))
258
+ .innerJoin(this.userIdentitiesTable, eq(userIdCol, this.userIdentitiesTable.uid))
257
259
  .where(
258
260
  sql`${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`
259
261
  )
@@ -263,17 +265,17 @@ export class UserService implements UserRepository {
263
265
  return this.mapRowToUser(result[0].user as Record<string, unknown>);
264
266
  }
265
267
 
266
- async getUserIdentities(userId: string): Promise<UserIdentityData[]> {
268
+ async getUserIdentities(uid: string): Promise<UserIdentityData[]> {
267
269
  const schema = getTableConfig(this.userIdentitiesTable).schema || "public";
268
270
  const result = await this.db.execute(sql`
269
- SELECT id, user_id, provider, provider_id, profile_data, created_at, updated_at
271
+ SELECT id, uid, provider, provider_id, profile_data, created_at, updated_at
270
272
  FROM ${sql.raw(`"${schema}"."user_identities"`)}
271
- WHERE user_id = ${userId}
273
+ WHERE uid = ${uid}
272
274
  `);
273
275
 
274
276
  return result.rows.map((row: Record<string, unknown>) => ({
275
277
  id: row.id as string,
276
- userId: row.user_id as string,
278
+ uid: row.uid as string,
277
279
  provider: row.provider as string,
278
280
  providerId: row.provider_id as string,
279
281
  profileData: (row.profile_data as Record<string, unknown> | null) ?? null,
@@ -282,9 +284,9 @@ export class UserService implements UserRepository {
282
284
  }));
283
285
  }
284
286
 
285
- async linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
287
+ async linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
286
288
  await this.withServerContext(async (db) => db.insert(this.userIdentitiesTable).values({
287
- userId,
289
+ uid,
288
290
  provider,
289
291
  providerId,
290
292
  profileData: profileData || null
@@ -452,10 +454,10 @@ export class UserService implements UserRepository {
452
454
  /**
453
455
  * Get roles for a user from database (inline TEXT[] column)
454
456
  */
455
- async getUserRoles(userId: string): Promise<Role[]> {
457
+ async getUserRoles(uid: string): Promise<Role[]> {
456
458
  const usersTableName = this.getQualifiedUsersTableName();
457
459
  const result = await this.db.execute(sql`
458
- SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${userId}
460
+ SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${uid}
459
461
  `);
460
462
 
461
463
  if (result.rows.length === 0) return [];
@@ -475,10 +477,10 @@ export class UserService implements UserRepository {
475
477
  /**
476
478
  * Get role IDs for a user
477
479
  */
478
- async getUserRoleIds(userId: string): Promise<string[]> {
480
+ async getUserRoleIds(uid: string): Promise<string[]> {
479
481
  const usersTableName = this.getQualifiedUsersTableName();
480
482
  const result = await this.db.execute(sql`
481
- SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${userId}
483
+ SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${uid}
482
484
  `);
483
485
 
484
486
  if (result.rows.length === 0) return [];
@@ -490,36 +492,36 @@ export class UserService implements UserRepository {
490
492
  /**
491
493
  * Set roles for a user (replaces existing roles)
492
494
  */
493
- async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
495
+ async setUserRoles(uid: string, roleIds: string[]): Promise<void> {
494
496
  const usersTableName = this.getQualifiedUsersTableName();
495
497
  const rolesArray = `{${roleIds.join(",")}}`;
496
498
  await this.withServerContext(async (db) => db.execute(sql`
497
499
  UPDATE ${sql.raw(usersTableName)}
498
500
  SET roles = ${rolesArray}::text[], updated_at = NOW()
499
- WHERE id = ${userId}
501
+ WHERE id = ${uid}
500
502
  `));
501
503
  }
502
504
 
503
505
  /**
504
506
  * Assign a specific role to new user (appends if not present)
505
507
  */
506
- async assignDefaultRole(userId: string, roleId: string): Promise<void> {
508
+ async assignDefaultRole(uid: string, roleId: string): Promise<void> {
507
509
  const usersTableName = this.getQualifiedUsersTableName();
508
510
  await this.withServerContext(async (db) => db.execute(sql`
509
511
  UPDATE ${sql.raw(usersTableName)}
510
512
  SET roles = array_append(roles, ${roleId}), updated_at = NOW()
511
- WHERE id = ${userId} AND NOT (${roleId} = ANY(roles))
513
+ WHERE id = ${uid} AND NOT (${roleId} = ANY(roles))
512
514
  `));
513
515
  }
514
516
 
515
517
  /**
516
518
  * Get user with their roles
517
519
  */
518
- async getUserWithRoles(userId: string): Promise<{ user: UserData; roles: Role[] } | null> {
519
- const user = await this.getUserById(userId);
520
+ async getUserWithRoles(uid: string): Promise<{ user: UserData; roles: Role[] } | null> {
521
+ const user = await this.getUserById(uid);
520
522
  if (!user) return null;
521
523
 
522
- const roles = await this.getUserRoles(userId);
524
+ const roles = await this.getUserRoles(uid);
523
525
  return { user,
524
526
  roles };
525
527
  }
@@ -528,6 +530,7 @@ export class UserService implements UserRepository {
528
530
 
529
531
  export class RefreshTokenService {
530
532
  private refreshTokensTable: RebasePgTable;
533
+ private usersTable: RebasePgTable | null;
531
534
 
532
535
  constructor(
533
536
  private db: NodePgDatabase,
@@ -535,86 +538,182 @@ export class RefreshTokenService {
535
538
  ) {
536
539
  if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).refreshTokens || (tableOrTables as Partial<AuthSchemaTables>).users)) {
537
540
  this.refreshTokensTable = ((tableOrTables as Partial<AuthSchemaTables>).refreshTokens || refreshTokens) as RebasePgTable;
541
+ this.usersTable = ((tableOrTables as Partial<AuthSchemaTables>).users || users) as RebasePgTable;
538
542
  } else {
539
543
  this.refreshTokensTable = (tableOrTables as RebasePgTable) || (refreshTokens as unknown as RebasePgTable);
544
+ this.usersTable = users as unknown as RebasePgTable;
540
545
  }
541
546
  }
542
547
 
543
- async createToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
544
- // Fallback to empty string because UNIQUE constraints treat NULLs as strictly distinct in standard Postgres.
545
- // We want (userId, NULL, NULL) to collide and overwrite, so we map undefined/null to empty strings.
548
+ /**
549
+ * Whether the table actually carries a column, so a host application that
550
+ * supplied its own `refresh_tokens` table — one that predates session
551
+ * grouping — degrades instead of throwing on every sign-in.
552
+ */
553
+ private has(column: string): boolean {
554
+ return Boolean((this.refreshTokensTable as unknown as Record<string, unknown>)[column]);
555
+ }
556
+
557
+ private col(column: string) {
558
+ return (this.refreshTokensTable as unknown as Record<string, never>)[column];
559
+ }
560
+
561
+ /** The columns to read back, narrowed to the ones this table has. */
562
+ private selection() {
563
+ const selection: Record<string, never> = {
564
+ id: this.refreshTokensTable.id,
565
+ uid: this.refreshTokensTable.uid,
566
+ tokenHash: this.refreshTokensTable.tokenHash,
567
+ expiresAt: this.refreshTokensTable.expiresAt,
568
+ createdAt: this.refreshTokensTable.createdAt,
569
+ userAgent: this.refreshTokensTable.userAgent,
570
+ ipAddress: this.refreshTokensTable.ipAddress
571
+ } as unknown as Record<string, never>;
572
+ for (const optional of ["sessionId", "rotatedAt", "revoked", "sessionStartedAt"]) {
573
+ if (this.has(optional)) selection[optional] = this.col(optional);
574
+ }
575
+ return selection;
576
+ }
577
+
578
+ async createToken(
579
+ uid: string,
580
+ tokenHash: string,
581
+ expiresAt: Date,
582
+ userAgent?: string,
583
+ ipAddress?: string,
584
+ session?: RefreshTokenSession
585
+ ): Promise<void> {
586
+ // Empty strings rather than NULLs: the device-session UNIQUE constraint
587
+ // that needed them is gone, but sessions-list UIs already render "" as
588
+ // "unknown device" and would start showing blanks otherwise.
546
589
  const safeUserAgent = userAgent || "";
547
590
  const safeIpAddress = ipAddress || "";
548
591
 
549
- // Atomic upsert keyed on the device session (user_id, user_agent, ip_address).
550
- // A DELETE-then-INSERT races under concurrent refreshes — cookie-mode boot can
551
- // fire two /refresh calls at once (both carrying the same refresh cookie): both
552
- // DELETE, then the second INSERT violates `unique_device_session` and 500s.
553
- // A single INSERT ... ON CONFLICT DO UPDATE rotates the token atomically.
554
- await this.db.insert(this.refreshTokensTable)
555
- .values({
556
- userId,
557
- tokenHash,
558
- expiresAt,
559
- userAgent: safeUserAgent,
560
- ipAddress: safeIpAddress
561
- })
562
- .onConflictDoUpdate({
563
- target: [
564
- this.refreshTokensTable.userId,
565
- this.refreshTokensTable.userAgent,
566
- this.refreshTokensTable.ipAddress
567
- ],
568
- set: { tokenHash, expiresAt }
569
- });
592
+ // A plain INSERT. Rotation ADDS a token; it does not replace a device's
593
+ // row. Two refreshes racing on the same session therefore both succeed
594
+ // and both end holding a usable token, where the previous upsert had
595
+ // them overwrite each other and logged one of the two tabs out.
596
+ const values: Record<string, unknown> = {
597
+ uid,
598
+ tokenHash,
599
+ expiresAt,
600
+ userAgent: safeUserAgent,
601
+ ipAddress: safeIpAddress
602
+ };
603
+ if (session && this.has("sessionId")) values.sessionId = session.id;
604
+ if (session && this.has("sessionStartedAt")) values.sessionStartedAt = session.startedAt;
605
+
606
+ await this.db.insert(this.refreshTokensTable).values(values);
570
607
  }
571
608
 
572
609
  async findByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
573
610
  const [token] = await this.db
574
- .select({
575
- id: this.refreshTokensTable.id,
576
- userId: this.refreshTokensTable.userId,
577
- tokenHash: this.refreshTokensTable.tokenHash,
578
- expiresAt: this.refreshTokensTable.expiresAt,
579
- createdAt: this.refreshTokensTable.createdAt,
580
- userAgent: this.refreshTokensTable.userAgent,
581
- ipAddress: this.refreshTokensTable.ipAddress
582
- })
611
+ .select(this.selection())
583
612
  .from(this.refreshTokensTable)
584
613
  .where(eq(this.refreshTokensTable.tokenHash, tokenHash));
585
614
 
586
- return (token as RefreshTokenInfo) || null;
615
+ return (token as unknown as RefreshTokenInfo) || null;
616
+ }
617
+
618
+ /**
619
+ * Record that a token was rotated away, keeping the row.
620
+ *
621
+ * The row is what lets `/auth/refresh` distinguish "you already used this,
622
+ * here is a fresh one" from "no idea what this is". Deleting it — which is
623
+ * what this used to do — collapsed both into a 401 and signed the user out
624
+ * for the crime of losing a response.
625
+ */
626
+ async markRotated(tokenHash: string): Promise<void> {
627
+ if (!this.has("rotatedAt")) {
628
+ await this.deleteByHash(tokenHash);
629
+ return;
630
+ }
631
+ await this.db
632
+ .update(this.refreshTokensTable)
633
+ .set({ rotatedAt: new Date() })
634
+ .where(eq(this.refreshTokensTable.tokenHash, tokenHash));
635
+ }
636
+
637
+ /** Final kill of one sign-in: logout, or revoking a device remotely. */
638
+ async revokeSession(sessionId: string): Promise<void> {
639
+ if (!this.has("sessionId")) return;
640
+ if (this.has("revoked")) {
641
+ await this.db
642
+ .update(this.refreshTokensTable)
643
+ .set({ revoked: true, ...(this.has("rotatedAt") ? { rotatedAt: new Date() } : {}) })
644
+ .where(eq(this.col("sessionId"), sessionId));
645
+ return;
646
+ }
647
+ await this.db.delete(this.refreshTokensTable).where(eq(this.col("sessionId"), sessionId));
648
+ }
649
+
650
+ /**
651
+ * Housekeeping: rotation would otherwise leave a row per refresh forever.
652
+ * Superseded rows are only needed for as long as a straggler might still
653
+ * present them, and expired ones are dead weight everywhere.
654
+ */
655
+ async prune(uid: string, sessionId: string, supersededBefore: Date): Promise<void> {
656
+ const uidCol = this.refreshTokensTable.uid;
657
+ const expiresCol = this.refreshTokensTable.expiresAt;
658
+ if (!this.has("rotatedAt") || !this.has("sessionId")) {
659
+ await this.db.delete(this.refreshTokensTable)
660
+ .where(sql`${uidCol} = ${uid} AND ${expiresCol} < NOW()`);
661
+ return;
662
+ }
663
+ const rotatedCol = this.col("rotatedAt");
664
+ const sessionCol = this.col("sessionId");
665
+ await this.db.delete(this.refreshTokensTable).where(sql`
666
+ ${uidCol} = ${uid}
667
+ AND (
668
+ ${expiresCol} < NOW()
669
+ OR (
670
+ ${sessionCol} = ${sessionId}
671
+ AND ${rotatedCol} IS NOT NULL
672
+ AND ${rotatedCol} < ${supersededBefore}
673
+ )
674
+ )
675
+ `);
676
+ }
677
+
678
+ async getTokensValidAfter(uid: string): Promise<Date | null> {
679
+ if (!this.usersTable || !(this.usersTable as unknown as Record<string, unknown>).tokensValidAfter) return null;
680
+ const [row] = await this.db
681
+ .select({ tokensValidAfter: (this.usersTable as unknown as Record<string, never>).tokensValidAfter })
682
+ .from(this.usersTable)
683
+ .where(eq(this.usersTable.id, uid));
684
+ const value = (row as { tokensValidAfter?: Date | string | null } | undefined)?.tokensValidAfter;
685
+ return value ? new Date(value) : null;
686
+ }
687
+
688
+ async setTokensValidAfter(uid: string, at: Date): Promise<void> {
689
+ if (!this.usersTable || !(this.usersTable as unknown as Record<string, unknown>).tokensValidAfter) return;
690
+ await this.db
691
+ .update(this.usersTable)
692
+ .set({ tokensValidAfter: at })
693
+ .where(eq(this.usersTable.id, uid));
587
694
  }
588
695
 
589
696
  async deleteByHash(tokenHash: string): Promise<void> {
590
697
  await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
591
698
  }
592
699
 
593
- async deleteAllForUser(userId: string): Promise<void> {
594
- await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.userId, userId));
700
+ async deleteAllForUser(uid: string): Promise<void> {
701
+ await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.uid, uid));
595
702
  }
596
703
 
597
- async listForUser(userId: string): Promise<RefreshTokenInfo[]> {
704
+ async listForUser(uid: string): Promise<RefreshTokenInfo[]> {
598
705
  const tokens = await this.db
599
- .select({
600
- id: this.refreshTokensTable.id,
601
- userId: this.refreshTokensTable.userId,
602
- tokenHash: this.refreshTokensTable.tokenHash,
603
- expiresAt: this.refreshTokensTable.expiresAt,
604
- createdAt: this.refreshTokensTable.createdAt,
605
- userAgent: this.refreshTokensTable.userAgent,
606
- ipAddress: this.refreshTokensTable.ipAddress
607
- })
706
+ .select(this.selection())
608
707
  .from(this.refreshTokensTable)
609
- .where(eq(this.refreshTokensTable.userId, userId))
708
+ .where(eq(this.refreshTokensTable.uid, uid))
610
709
  .orderBy(this.refreshTokensTable.createdAt);
611
710
 
612
- return tokens as RefreshTokenInfo[];
711
+ return tokens as unknown as RefreshTokenInfo[];
613
712
  }
614
713
 
615
- async deleteById(id: string, userId: string): Promise<void> {
714
+ async deleteById(id: string, uid: string): Promise<void> {
616
715
  await this.db.delete(this.refreshTokensTable)
617
- .where(sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.userId} = ${userId}`);
716
+ .where(sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.uid} = ${uid}`);
618
717
  }
619
718
  }
620
719
 
@@ -644,16 +743,16 @@ export class PasswordResetTokenService {
644
743
  /**
645
744
  * Create a password reset token
646
745
  */
647
- async createToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
746
+ async createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
648
747
  // Delete any existing unused tokens for this user
649
748
  const tableName = this.getQualifiedPasswordResetTokensTableName();
650
749
  await this.db.execute(sql`
651
750
  DELETE FROM ${sql.raw(tableName)}
652
- WHERE user_id = ${userId} AND used_at IS NULL
751
+ WHERE uid = ${uid} AND used_at IS NULL
653
752
  `);
654
753
 
655
754
  await this.db.insert(this.passwordResetTokensTable).values({
656
- userId,
755
+ uid,
657
756
  tokenHash,
658
757
  expiresAt
659
758
  });
@@ -662,21 +761,21 @@ export class PasswordResetTokenService {
662
761
  /**
663
762
  * Find a valid (not expired, not used) token by hash
664
763
  */
665
- async findValidByHash(tokenHash: string): Promise<{ userId: string; expiresAt: Date } | null> {
764
+ async findValidByHash(tokenHash: string): Promise<{ uid: string; expiresAt: Date } | null> {
666
765
  const [token] = await this.db
667
766
  .select({
668
- userId: this.passwordResetTokensTable.userId,
767
+ uid: this.passwordResetTokensTable.uid,
669
768
  expiresAt: this.passwordResetTokensTable.expiresAt
670
769
  })
671
770
  .from(this.passwordResetTokensTable)
672
- .where(eq(this.passwordResetTokensTable.tokenHash, tokenHash)) as unknown as Array<{ userId: string; expiresAt: Date }>;
771
+ .where(eq(this.passwordResetTokensTable.tokenHash, tokenHash)) as unknown as Array<{ uid: string; expiresAt: Date }>;
673
772
 
674
773
  if (!token) return null;
675
774
 
676
775
  // Check if expired or used
677
776
  const tableName = this.getQualifiedPasswordResetTokensTableName();
678
777
  const result = await this.db.execute(sql`
679
- SELECT user_id, expires_at
778
+ SELECT uid, expires_at
680
779
  FROM ${sql.raw(tableName)}
681
780
  WHERE token_hash = ${tokenHash}
682
781
  AND used_at IS NULL
@@ -685,9 +784,9 @@ export class PasswordResetTokenService {
685
784
 
686
785
  if (result.rows.length === 0) return null;
687
786
 
688
- const row = result.rows[0] as { user_id: string; expires_at: string | number | Date };
787
+ const row = result.rows[0] as { uid: string; expires_at: string | number | Date };
689
788
  return {
690
- userId: row.user_id,
789
+ uid: row.uid,
691
790
  expiresAt: new Date(row.expires_at)
692
791
  };
693
792
  }
@@ -705,8 +804,8 @@ export class PasswordResetTokenService {
705
804
  /**
706
805
  * Delete all tokens for a user
707
806
  */
708
- async deleteAllForUser(userId: string): Promise<void> {
709
- await this.db.delete(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.userId, userId));
807
+ async deleteAllForUser(uid: string): Promise<void> {
808
+ await this.db.delete(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.uid, uid));
710
809
  }
711
810
 
712
811
  /**
@@ -741,16 +840,16 @@ export class MagicLinkTokenService {
741
840
  return `"${schema}"."${name}"`;
742
841
  }
743
842
 
744
- async createToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
843
+ async createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
745
844
  // Delete any existing unused tokens for this user
746
845
  const tableName = this.getQualifiedTableName();
747
846
  await this.db.execute(sql`
748
847
  DELETE FROM ${sql.raw(tableName)}
749
- WHERE user_id = ${userId} AND used_at IS NULL
848
+ WHERE uid = ${uid} AND used_at IS NULL
750
849
  `);
751
850
 
752
851
  await this.db.insert(this.magicLinkTokensTable).values({
753
- userId,
852
+ uid,
754
853
  tokenHash,
755
854
  expiresAt
756
855
  });
@@ -759,7 +858,7 @@ export class MagicLinkTokenService {
759
858
  async findValidByHash(tokenHash: string): Promise<MagicLinkTokenInfo | null> {
760
859
  const tableName = this.getQualifiedTableName();
761
860
  const result = await this.db.execute(sql`
762
- SELECT user_id, expires_at
861
+ SELECT uid, expires_at
763
862
  FROM ${sql.raw(tableName)}
764
863
  WHERE token_hash = ${tokenHash}
765
864
  AND used_at IS NULL
@@ -768,9 +867,9 @@ export class MagicLinkTokenService {
768
867
 
769
868
  if (result.rows.length === 0) return null;
770
869
 
771
- const row = result.rows[0] as { user_id: string; expires_at: string | number | Date };
870
+ const row = result.rows[0] as { uid: string; expires_at: string | number | Date };
772
871
  return {
773
- userId: row.user_id,
872
+ uid: row.uid,
774
873
  expiresAt: new Date(row.expires_at)
775
874
  };
776
875
  }
@@ -803,8 +902,28 @@ export class PostgresTokenRepository implements TokenRepository {
803
902
 
804
903
  // Refresh token operations
805
904
 
806
- async createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
807
- await this.refreshTokenService.createToken(userId, tokenHash, expiresAt, userAgent, ipAddress);
905
+ async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string, session?: RefreshTokenSession): Promise<void> {
906
+ await this.refreshTokenService.createToken(uid, tokenHash, expiresAt, userAgent, ipAddress, session);
907
+ }
908
+
909
+ async markRefreshTokenRotated(tokenHash: string): Promise<void> {
910
+ await this.refreshTokenService.markRotated(tokenHash);
911
+ }
912
+
913
+ async revokeRefreshTokenSession(sessionId: string): Promise<void> {
914
+ await this.refreshTokenService.revokeSession(sessionId);
915
+ }
916
+
917
+ async pruneRefreshTokens(uid: string, sessionId: string, supersededBefore: Date): Promise<void> {
918
+ await this.refreshTokenService.prune(uid, sessionId, supersededBefore);
919
+ }
920
+
921
+ async getTokensValidAfter(uid: string): Promise<Date | null> {
922
+ return this.refreshTokenService.getTokensValidAfter(uid);
923
+ }
924
+
925
+ async setTokensValidAfter(uid: string, at: Date): Promise<void> {
926
+ await this.refreshTokenService.setTokensValidAfter(uid, at);
808
927
  }
809
928
 
810
929
  async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
@@ -815,22 +934,22 @@ export class PostgresTokenRepository implements TokenRepository {
815
934
  await this.refreshTokenService.deleteByHash(tokenHash);
816
935
  }
817
936
 
818
- async deleteAllRefreshTokensForUser(userId: string): Promise<void> {
819
- await this.refreshTokenService.deleteAllForUser(userId);
937
+ async deleteAllRefreshTokensForUser(uid: string): Promise<void> {
938
+ await this.refreshTokenService.deleteAllForUser(uid);
820
939
  }
821
940
 
822
- async listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]> {
823
- return this.refreshTokenService.listForUser(userId);
941
+ async listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]> {
942
+ return this.refreshTokenService.listForUser(uid);
824
943
  }
825
944
 
826
- async deleteRefreshTokenById(id: string, userId: string): Promise<void> {
827
- await this.refreshTokenService.deleteById(id, userId);
945
+ async deleteRefreshTokenById(id: string, uid: string): Promise<void> {
946
+ await this.refreshTokenService.deleteById(id, uid);
828
947
  }
829
948
 
830
949
  // Password reset token operations
831
950
 
832
- async createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
833
- await this.passwordResetTokenService.createToken(userId, tokenHash, expiresAt);
951
+ async createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
952
+ await this.passwordResetTokenService.createToken(uid, tokenHash, expiresAt);
834
953
  }
835
954
 
836
955
  async findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null> {
@@ -841,8 +960,8 @@ export class PostgresTokenRepository implements TokenRepository {
841
960
  await this.passwordResetTokenService.markAsUsed(tokenHash);
842
961
  }
843
962
 
844
- async deleteAllPasswordResetTokensForUser(userId: string): Promise<void> {
845
- await this.passwordResetTokenService.deleteAllForUser(userId);
963
+ async deleteAllPasswordResetTokensForUser(uid: string): Promise<void> {
964
+ await this.passwordResetTokenService.deleteAllForUser(uid);
846
965
  }
847
966
 
848
967
  async deleteExpiredTokens(): Promise<void> {
@@ -851,8 +970,8 @@ export class PostgresTokenRepository implements TokenRepository {
851
970
 
852
971
  // Magic link token operations
853
972
 
854
- async createMagicLinkToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
855
- await this.magicLinkTokenService.createToken(userId, tokenHash, expiresAt);
973
+ async createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
974
+ await this.magicLinkTokenService.createToken(uid, tokenHash, expiresAt);
856
975
  }
857
976
 
858
977
  async findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null> {
@@ -899,12 +1018,12 @@ export class PostgresAuthRepository implements AuthRepository {
899
1018
  return this.userService.getUserByIdentity(provider, providerId);
900
1019
  }
901
1020
 
902
- async getUserIdentities(userId: string): Promise<UserIdentityData[]> {
903
- return this.userService.getUserIdentities(userId);
1021
+ async getUserIdentities(uid: string): Promise<UserIdentityData[]> {
1022
+ return this.userService.getUserIdentities(uid);
904
1023
  }
905
1024
 
906
- async linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
907
- return this.userService.linkUserIdentity(userId, provider, providerId, profileData);
1025
+ async linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
1026
+ return this.userService.linkUserIdentity(uid, provider, providerId, profileData);
908
1027
  }
909
1028
 
910
1029
  async updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null> {
@@ -939,24 +1058,24 @@ export class PostgresAuthRepository implements AuthRepository {
939
1058
  return this.userService.getUserByVerificationToken(token);
940
1059
  }
941
1060
 
942
- async getUserRoles(userId: string): Promise<RoleData[]> {
943
- return this.userService.getUserRoles(userId);
1061
+ async getUserRoles(uid: string): Promise<RoleData[]> {
1062
+ return this.userService.getUserRoles(uid);
944
1063
  }
945
1064
 
946
- async getUserRoleIds(userId: string): Promise<string[]> {
947
- return this.userService.getUserRoleIds(userId);
1065
+ async getUserRoleIds(uid: string): Promise<string[]> {
1066
+ return this.userService.getUserRoleIds(uid);
948
1067
  }
949
1068
 
950
- async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
951
- await this.userService.setUserRoles(userId, roleIds);
1069
+ async setUserRoles(uid: string, roleIds: string[]): Promise<void> {
1070
+ await this.userService.setUserRoles(uid, roleIds);
952
1071
  }
953
1072
 
954
- async assignDefaultRole(userId: string, roleId: string): Promise<void> {
955
- await this.userService.assignDefaultRole(userId, roleId);
1073
+ async assignDefaultRole(uid: string, roleId: string): Promise<void> {
1074
+ await this.userService.assignDefaultRole(uid, roleId);
956
1075
  }
957
1076
 
958
- async getUserWithRoles(userId: string): Promise<{ user: UserData; roles: RoleData[] } | null> {
959
- return this.userService.getUserWithRoles(userId);
1077
+ async getUserWithRoles(uid: string): Promise<{ user: UserData; roles: RoleData[] } | null> {
1078
+ return this.userService.getUserWithRoles(uid);
960
1079
  }
961
1080
 
962
1081
  // Role operations (roles are inline on users, synthesized from string IDs)
@@ -1017,8 +1136,28 @@ collectionPermissions: null }
1017
1136
 
1018
1137
  // Token operations (delegate to PostgresTokenRepository)
1019
1138
 
1020
- async createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
1021
- await this.tokenRepository.createRefreshToken(userId, tokenHash, expiresAt, userAgent, ipAddress);
1139
+ async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string, session?: RefreshTokenSession): Promise<void> {
1140
+ await this.tokenRepository.createRefreshToken(uid, tokenHash, expiresAt, userAgent, ipAddress, session);
1141
+ }
1142
+
1143
+ async markRefreshTokenRotated(tokenHash: string): Promise<void> {
1144
+ await this.tokenRepository.markRefreshTokenRotated(tokenHash);
1145
+ }
1146
+
1147
+ async revokeRefreshTokenSession(sessionId: string): Promise<void> {
1148
+ await this.tokenRepository.revokeRefreshTokenSession(sessionId);
1149
+ }
1150
+
1151
+ async pruneRefreshTokens(uid: string, sessionId: string, supersededBefore: Date): Promise<void> {
1152
+ await this.tokenRepository.pruneRefreshTokens(uid, sessionId, supersededBefore);
1153
+ }
1154
+
1155
+ async getTokensValidAfter(uid: string): Promise<Date | null> {
1156
+ return this.tokenRepository.getTokensValidAfter(uid);
1157
+ }
1158
+
1159
+ async setTokensValidAfter(uid: string, at: Date): Promise<void> {
1160
+ await this.tokenRepository.setTokensValidAfter(uid, at);
1022
1161
  }
1023
1162
 
1024
1163
  async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
@@ -1029,20 +1168,20 @@ collectionPermissions: null }
1029
1168
  await this.tokenRepository.deleteRefreshToken(tokenHash);
1030
1169
  }
1031
1170
 
1032
- async deleteAllRefreshTokensForUser(userId: string): Promise<void> {
1033
- await this.tokenRepository.deleteAllRefreshTokensForUser(userId);
1171
+ async deleteAllRefreshTokensForUser(uid: string): Promise<void> {
1172
+ await this.tokenRepository.deleteAllRefreshTokensForUser(uid);
1034
1173
  }
1035
1174
 
1036
- async listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]> {
1037
- return this.tokenRepository.listRefreshTokensForUser(userId);
1175
+ async listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]> {
1176
+ return this.tokenRepository.listRefreshTokensForUser(uid);
1038
1177
  }
1039
1178
 
1040
- async deleteRefreshTokenById(id: string, userId: string): Promise<void> {
1041
- await this.tokenRepository.deleteRefreshTokenById(id, userId);
1179
+ async deleteRefreshTokenById(id: string, uid: string): Promise<void> {
1180
+ await this.tokenRepository.deleteRefreshTokenById(id, uid);
1042
1181
  }
1043
1182
 
1044
- async createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
1045
- await this.tokenRepository.createPasswordResetToken(userId, tokenHash, expiresAt);
1183
+ async createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
1184
+ await this.tokenRepository.createPasswordResetToken(uid, tokenHash, expiresAt);
1046
1185
  }
1047
1186
 
1048
1187
  async findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null> {
@@ -1053,8 +1192,8 @@ collectionPermissions: null }
1053
1192
  await this.tokenRepository.markPasswordResetTokenUsed(tokenHash);
1054
1193
  }
1055
1194
 
1056
- async deleteAllPasswordResetTokensForUser(userId: string): Promise<void> {
1057
- await this.tokenRepository.deleteAllPasswordResetTokensForUser(userId);
1195
+ async deleteAllPasswordResetTokensForUser(uid: string): Promise<void> {
1196
+ await this.tokenRepository.deleteAllPasswordResetTokensForUser(uid);
1058
1197
  }
1059
1198
 
1060
1199
  async deleteExpiredTokens(): Promise<void> {
@@ -1063,8 +1202,8 @@ collectionPermissions: null }
1063
1202
 
1064
1203
  // Magic link token operations
1065
1204
 
1066
- async createMagicLinkToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
1067
- await this.tokenRepository.createMagicLinkToken(userId, tokenHash, expiresAt);
1205
+ async createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
1206
+ await this.tokenRepository.createMagicLinkToken(uid, tokenHash, expiresAt);
1068
1207
  }
1069
1208
 
1070
1209
  async findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null> {
@@ -1085,12 +1224,12 @@ collectionPermissions: null }
1085
1224
  return this._mfaService;
1086
1225
  }
1087
1226
 
1088
- async createMfaFactor(userId: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor> {
1089
- return this.getMfaService().createMfaFactor(userId, factorType, secretEncrypted, friendlyName);
1227
+ async createMfaFactor(uid: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor> {
1228
+ return this.getMfaService().createMfaFactor(uid, factorType, secretEncrypted, friendlyName);
1090
1229
  }
1091
1230
 
1092
- async getMfaFactors(userId: string): Promise<MfaFactor[]> {
1093
- return this.getMfaService().getMfaFactors(userId);
1231
+ async getMfaFactors(uid: string): Promise<MfaFactor[]> {
1232
+ return this.getMfaService().getMfaFactors(uid);
1094
1233
  }
1095
1234
 
1096
1235
  async getMfaFactorById(factorId: string): Promise<(MfaFactor & { secretEncrypted: string }) | null> {
@@ -1101,8 +1240,8 @@ collectionPermissions: null }
1101
1240
  return this.getMfaService().verifyMfaFactor(factorId);
1102
1241
  }
1103
1242
 
1104
- async deleteMfaFactor(factorId: string, userId: string): Promise<void> {
1105
- return this.getMfaService().deleteMfaFactor(factorId, userId);
1243
+ async deleteMfaFactor(factorId: string, uid: string): Promise<void> {
1244
+ return this.getMfaService().deleteMfaFactor(factorId, uid);
1106
1245
  }
1107
1246
 
1108
1247
  async createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo> {
@@ -1117,24 +1256,24 @@ collectionPermissions: null }
1117
1256
  return this.getMfaService().verifyMfaChallenge(challengeId);
1118
1257
  }
1119
1258
 
1120
- async createRecoveryCodes(userId: string, codeHashes: string[]): Promise<void> {
1121
- return this.getMfaService().createRecoveryCodes(userId, codeHashes);
1259
+ async createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void> {
1260
+ return this.getMfaService().createRecoveryCodes(uid, codeHashes);
1122
1261
  }
1123
1262
 
1124
- async useRecoveryCode(userId: string, codeHash: string): Promise<boolean> {
1125
- return this.getMfaService().useRecoveryCode(userId, codeHash);
1263
+ async useRecoveryCode(uid: string, codeHash: string): Promise<boolean> {
1264
+ return this.getMfaService().useRecoveryCode(uid, codeHash);
1126
1265
  }
1127
1266
 
1128
- async getUnusedRecoveryCodeCount(userId: string): Promise<number> {
1129
- return this.getMfaService().getUnusedRecoveryCodeCount(userId);
1267
+ async getUnusedRecoveryCodeCount(uid: string): Promise<number> {
1268
+ return this.getMfaService().getUnusedRecoveryCodeCount(uid);
1130
1269
  }
1131
1270
 
1132
- async deleteAllRecoveryCodes(userId: string): Promise<void> {
1133
- return this.getMfaService().deleteAllRecoveryCodes(userId);
1271
+ async deleteAllRecoveryCodes(uid: string): Promise<void> {
1272
+ return this.getMfaService().deleteAllRecoveryCodes(uid);
1134
1273
  }
1135
1274
 
1136
- async hasVerifiedMfaFactors(userId: string): Promise<boolean> {
1137
- return this.getMfaService().hasVerifiedMfaFactors(userId);
1275
+ async hasVerifiedMfaFactors(uid: string): Promise<boolean> {
1276
+ return this.getMfaService().hasVerifiedMfaFactors(uid);
1138
1277
  }
1139
1278
  }
1140
1279
 
@@ -1154,22 +1293,22 @@ export class MfaService implements MfaRepository {
1154
1293
  }
1155
1294
 
1156
1295
  async createMfaFactor(
1157
- userId: string,
1296
+ uid: string,
1158
1297
  factorType: "totp",
1159
1298
  secretEncrypted: string,
1160
1299
  friendlyName?: string
1161
1300
  ): Promise<MfaFactor> {
1162
1301
  const tableName = this.qualify("mfa_factors");
1163
1302
  const result = await this.db.execute(sql`
1164
- INSERT INTO ${sql.raw(tableName)} (user_id, factor_type, secret_encrypted, friendly_name)
1165
- VALUES (${userId}, ${factorType}, ${secretEncrypted}, ${friendlyName ?? null})
1166
- RETURNING id, user_id, factor_type, friendly_name, verified, created_at, updated_at
1303
+ INSERT INTO ${sql.raw(tableName)} (uid, factor_type, secret_encrypted, friendly_name)
1304
+ VALUES (${uid}, ${factorType}, ${secretEncrypted}, ${friendlyName ?? null})
1305
+ RETURNING id, uid, factor_type, friendly_name, verified, created_at, updated_at
1167
1306
  `);
1168
1307
 
1169
1308
  const row = result.rows[0] as Record<string, unknown>;
1170
1309
  return {
1171
1310
  id: row.id as string,
1172
- userId: row.user_id as string,
1311
+ uid: row.uid as string,
1173
1312
  factorType: row.factor_type as "totp",
1174
1313
  friendlyName: (row.friendly_name as string | null) ?? undefined,
1175
1314
  verified: row.verified as boolean,
@@ -1178,18 +1317,18 @@ export class MfaService implements MfaRepository {
1178
1317
  };
1179
1318
  }
1180
1319
 
1181
- async getMfaFactors(userId: string): Promise<MfaFactor[]> {
1320
+ async getMfaFactors(uid: string): Promise<MfaFactor[]> {
1182
1321
  const tableName = this.qualify("mfa_factors");
1183
1322
  const result = await this.db.execute(sql`
1184
- SELECT id, user_id, factor_type, friendly_name, verified, created_at, updated_at
1323
+ SELECT id, uid, factor_type, friendly_name, verified, created_at, updated_at
1185
1324
  FROM ${sql.raw(tableName)}
1186
- WHERE user_id = ${userId}
1325
+ WHERE uid = ${uid}
1187
1326
  ORDER BY created_at
1188
1327
  `);
1189
1328
 
1190
1329
  return (result.rows as Array<Record<string, unknown>>).map(row => ({
1191
1330
  id: row.id as string,
1192
- userId: row.user_id as string,
1331
+ uid: row.uid as string,
1193
1332
  factorType: row.factor_type as "totp",
1194
1333
  friendlyName: (row.friendly_name as string | null) ?? undefined,
1195
1334
  verified: row.verified as boolean,
@@ -1201,7 +1340,7 @@ export class MfaService implements MfaRepository {
1201
1340
  async getMfaFactorById(factorId: string): Promise<(MfaFactor & { secretEncrypted: string }) | null> {
1202
1341
  const tableName = this.qualify("mfa_factors");
1203
1342
  const result = await this.db.execute(sql`
1204
- SELECT id, user_id, factor_type, secret_encrypted, friendly_name, verified, created_at, updated_at
1343
+ SELECT id, uid, factor_type, secret_encrypted, friendly_name, verified, created_at, updated_at
1205
1344
  FROM ${sql.raw(tableName)}
1206
1345
  WHERE id = ${factorId}
1207
1346
  `);
@@ -1211,7 +1350,7 @@ export class MfaService implements MfaRepository {
1211
1350
  const row = result.rows[0] as Record<string, unknown>;
1212
1351
  return {
1213
1352
  id: row.id as string,
1214
- userId: row.user_id as string,
1353
+ uid: row.uid as string,
1215
1354
  factorType: row.factor_type as "totp",
1216
1355
  secretEncrypted: row.secret_encrypted as string,
1217
1356
  friendlyName: (row.friendly_name as string | null) ?? undefined,
@@ -1230,11 +1369,11 @@ export class MfaService implements MfaRepository {
1230
1369
  `);
1231
1370
  }
1232
1371
 
1233
- async deleteMfaFactor(factorId: string, userId: string): Promise<void> {
1372
+ async deleteMfaFactor(factorId: string, uid: string): Promise<void> {
1234
1373
  const tableName = this.qualify("mfa_factors");
1235
1374
  await this.db.execute(sql`
1236
1375
  DELETE FROM ${sql.raw(tableName)}
1237
- WHERE id = ${factorId} AND user_id = ${userId}
1376
+ WHERE id = ${factorId} AND uid = ${uid}
1238
1377
  `);
1239
1378
  }
1240
1379
 
@@ -1287,56 +1426,56 @@ export class MfaService implements MfaRepository {
1287
1426
  `);
1288
1427
  }
1289
1428
 
1290
- async createRecoveryCodes(userId: string, codeHashes: string[]): Promise<void> {
1429
+ async createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void> {
1291
1430
  const tableName = this.qualify("recovery_codes");
1292
1431
  // Delete existing codes first
1293
1432
  await this.db.execute(sql`
1294
- DELETE FROM ${sql.raw(tableName)} WHERE user_id = ${userId}
1433
+ DELETE FROM ${sql.raw(tableName)} WHERE uid = ${uid}
1295
1434
  `);
1296
1435
 
1297
1436
  // Insert new codes
1298
1437
  for (const hash of codeHashes) {
1299
1438
  await this.db.execute(sql`
1300
- INSERT INTO ${sql.raw(tableName)} (user_id, code_hash)
1301
- VALUES (${userId}, ${hash})
1439
+ INSERT INTO ${sql.raw(tableName)} (uid, code_hash)
1440
+ VALUES (${uid}, ${hash})
1302
1441
  `);
1303
1442
  }
1304
1443
  }
1305
1444
 
1306
- async useRecoveryCode(userId: string, codeHash: string): Promise<boolean> {
1445
+ async useRecoveryCode(uid: string, codeHash: string): Promise<boolean> {
1307
1446
  const tableName = this.qualify("recovery_codes");
1308
1447
  const result = await this.db.execute(sql`
1309
1448
  UPDATE ${sql.raw(tableName)}
1310
1449
  SET used_at = NOW()
1311
- WHERE user_id = ${userId} AND code_hash = ${codeHash} AND used_at IS NULL
1450
+ WHERE uid = ${uid} AND code_hash = ${codeHash} AND used_at IS NULL
1312
1451
  RETURNING id
1313
1452
  `);
1314
1453
 
1315
1454
  return result.rows.length > 0;
1316
1455
  }
1317
1456
 
1318
- async getUnusedRecoveryCodeCount(userId: string): Promise<number> {
1457
+ async getUnusedRecoveryCodeCount(uid: string): Promise<number> {
1319
1458
  const tableName = this.qualify("recovery_codes");
1320
1459
  const result = await this.db.execute(sql`
1321
1460
  SELECT COUNT(*)::int as count FROM ${sql.raw(tableName)}
1322
- WHERE user_id = ${userId} AND used_at IS NULL
1461
+ WHERE uid = ${uid} AND used_at IS NULL
1323
1462
  `);
1324
1463
 
1325
1464
  return (result.rows[0] as { count: number }).count;
1326
1465
  }
1327
1466
 
1328
- async deleteAllRecoveryCodes(userId: string): Promise<void> {
1467
+ async deleteAllRecoveryCodes(uid: string): Promise<void> {
1329
1468
  const tableName = this.qualify("recovery_codes");
1330
1469
  await this.db.execute(sql`
1331
- DELETE FROM ${sql.raw(tableName)} WHERE user_id = ${userId}
1470
+ DELETE FROM ${sql.raw(tableName)} WHERE uid = ${uid}
1332
1471
  `);
1333
1472
  }
1334
1473
 
1335
- async hasVerifiedMfaFactors(userId: string): Promise<boolean> {
1474
+ async hasVerifiedMfaFactors(uid: string): Promise<boolean> {
1336
1475
  const tableName = this.qualify("mfa_factors");
1337
1476
  const result = await this.db.execute(sql`
1338
1477
  SELECT COUNT(*)::int as count FROM ${sql.raw(tableName)}
1339
- WHERE user_id = ${userId} AND verified = TRUE
1478
+ WHERE uid = ${uid} AND verified = TRUE
1340
1479
  `);
1341
1480
 
1342
1481
  return (result.rows[0] as { count: number }).count > 0;