@rebasepro/server-postgres 0.9.1-canary.16c42e9 → 0.9.1-canary.26fe4b2

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.
Files changed (35) hide show
  1. package/README.md +21 -0
  2. package/dist/PostgresBackendDriver.d.ts +18 -0
  3. package/dist/PostgresBootstrapper.d.ts +7 -1
  4. package/dist/auth/services.d.ts +53 -53
  5. package/dist/index.es.js +730 -194
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/schema/auth-bootstrap-sql.d.ts +1 -1
  8. package/dist/schema/auth-schema.d.ts +24 -24
  9. package/dist/schema/doctor.d.ts +1 -1
  10. package/dist/schema/introspect-db-logic.d.ts +0 -5
  11. package/dist/schema/introspect-db-naming.d.ts +10 -0
  12. package/dist/security/policy-drift.d.ts +30 -0
  13. package/dist/security/rls-enforcement.d.ts +2 -2
  14. package/dist/services/channel-history.d.ts +118 -0
  15. package/dist/services/realtimeService.d.ts +69 -2
  16. package/package.json +8 -31
  17. package/src/PostgresBackendDriver.ts +56 -5
  18. package/src/PostgresBootstrapper.ts +18 -1
  19. package/src/auth/ensure-tables.ts +97 -17
  20. package/src/auth/services.ts +134 -133
  21. package/src/cli.ts +60 -0
  22. package/src/schema/auth-bootstrap-sql.ts +7 -1
  23. package/src/schema/auth-schema.ts +13 -13
  24. package/src/schema/doctor.ts +45 -20
  25. package/src/schema/introspect-db-inference.ts +1 -1
  26. package/src/schema/introspect-db-logic.ts +1 -10
  27. package/src/schema/introspect-db-naming.ts +15 -0
  28. package/src/schema/introspect-runtime.ts +1 -1
  29. package/src/security/policy-drift.test.ts +106 -1
  30. package/src/security/policy-drift.ts +56 -0
  31. package/src/security/rls-enforcement.ts +11 -5
  32. package/src/services/channel-history.ts +343 -0
  33. package/src/services/realtimeService.ts +198 -10
  34. package/src/services/row-pipeline.ts +27 -3
  35. package/src/websocket.ts +30 -11
@@ -86,7 +86,7 @@ export class UserService implements UserRepository {
86
86
  * Run a privileged auth write with an explicitly cleared RLS context.
87
87
  *
88
88
  * 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
89
+ * carries a NULL `app.uid` so the `auth.uid() IS NULL` server-escape
90
90
  * in the default policies applies. That NULL is normally guaranteed by
91
91
  * `set_config(..., is_local = true)` resetting at transaction end — but a
92
92
  * GUC that survives on a pooled connection (or a connection role that
@@ -100,7 +100,8 @@ export class UserService implements UserRepository {
100
100
  private async withServerContext<T>(fn: (db: NodePgDatabase) => Promise<T>): Promise<T> {
101
101
  return await this.db.transaction(async (tx) => {
102
102
  await tx.execute(sql`
103
- SELECT set_config('app.user_id', '', true),
103
+ SELECT set_config('app.uid', '', true),
104
+ set_config('app.user_id', '', true),
104
105
  set_config('app.user_roles', '', true),
105
106
  set_config('app.jwt', '', true)
106
107
  `);
@@ -253,7 +254,7 @@ export class UserService implements UserRepository {
253
254
  const result = await this.db
254
255
  .select({ user: this.usersTable })
255
256
  .from(this.usersTable)
256
- .innerJoin(this.userIdentitiesTable, eq(userIdCol, this.userIdentitiesTable.userId))
257
+ .innerJoin(this.userIdentitiesTable, eq(userIdCol, this.userIdentitiesTable.uid))
257
258
  .where(
258
259
  sql`${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`
259
260
  )
@@ -263,17 +264,17 @@ export class UserService implements UserRepository {
263
264
  return this.mapRowToUser(result[0].user as Record<string, unknown>);
264
265
  }
265
266
 
266
- async getUserIdentities(userId: string): Promise<UserIdentityData[]> {
267
+ async getUserIdentities(uid: string): Promise<UserIdentityData[]> {
267
268
  const schema = getTableConfig(this.userIdentitiesTable).schema || "public";
268
269
  const result = await this.db.execute(sql`
269
- SELECT id, user_id, provider, provider_id, profile_data, created_at, updated_at
270
+ SELECT id, uid, provider, provider_id, profile_data, created_at, updated_at
270
271
  FROM ${sql.raw(`"${schema}"."user_identities"`)}
271
- WHERE user_id = ${userId}
272
+ WHERE uid = ${uid}
272
273
  `);
273
274
 
274
275
  return result.rows.map((row: Record<string, unknown>) => ({
275
276
  id: row.id as string,
276
- userId: row.user_id as string,
277
+ uid: row.uid as string,
277
278
  provider: row.provider as string,
278
279
  providerId: row.provider_id as string,
279
280
  profileData: (row.profile_data as Record<string, unknown> | null) ?? null,
@@ -282,9 +283,9 @@ export class UserService implements UserRepository {
282
283
  }));
283
284
  }
284
285
 
285
- async linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
286
+ async linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
286
287
  await this.withServerContext(async (db) => db.insert(this.userIdentitiesTable).values({
287
- userId,
288
+ uid,
288
289
  provider,
289
290
  providerId,
290
291
  profileData: profileData || null
@@ -452,10 +453,10 @@ export class UserService implements UserRepository {
452
453
  /**
453
454
  * Get roles for a user from database (inline TEXT[] column)
454
455
  */
455
- async getUserRoles(userId: string): Promise<Role[]> {
456
+ async getUserRoles(uid: string): Promise<Role[]> {
456
457
  const usersTableName = this.getQualifiedUsersTableName();
457
458
  const result = await this.db.execute(sql`
458
- SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${userId}
459
+ SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${uid}
459
460
  `);
460
461
 
461
462
  if (result.rows.length === 0) return [];
@@ -475,10 +476,10 @@ export class UserService implements UserRepository {
475
476
  /**
476
477
  * Get role IDs for a user
477
478
  */
478
- async getUserRoleIds(userId: string): Promise<string[]> {
479
+ async getUserRoleIds(uid: string): Promise<string[]> {
479
480
  const usersTableName = this.getQualifiedUsersTableName();
480
481
  const result = await this.db.execute(sql`
481
- SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${userId}
482
+ SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${uid}
482
483
  `);
483
484
 
484
485
  if (result.rows.length === 0) return [];
@@ -490,36 +491,36 @@ export class UserService implements UserRepository {
490
491
  /**
491
492
  * Set roles for a user (replaces existing roles)
492
493
  */
493
- async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
494
+ async setUserRoles(uid: string, roleIds: string[]): Promise<void> {
494
495
  const usersTableName = this.getQualifiedUsersTableName();
495
496
  const rolesArray = `{${roleIds.join(",")}}`;
496
497
  await this.withServerContext(async (db) => db.execute(sql`
497
498
  UPDATE ${sql.raw(usersTableName)}
498
499
  SET roles = ${rolesArray}::text[], updated_at = NOW()
499
- WHERE id = ${userId}
500
+ WHERE id = ${uid}
500
501
  `));
501
502
  }
502
503
 
503
504
  /**
504
505
  * Assign a specific role to new user (appends if not present)
505
506
  */
506
- async assignDefaultRole(userId: string, roleId: string): Promise<void> {
507
+ async assignDefaultRole(uid: string, roleId: string): Promise<void> {
507
508
  const usersTableName = this.getQualifiedUsersTableName();
508
509
  await this.withServerContext(async (db) => db.execute(sql`
509
510
  UPDATE ${sql.raw(usersTableName)}
510
511
  SET roles = array_append(roles, ${roleId}), updated_at = NOW()
511
- WHERE id = ${userId} AND NOT (${roleId} = ANY(roles))
512
+ WHERE id = ${uid} AND NOT (${roleId} = ANY(roles))
512
513
  `));
513
514
  }
514
515
 
515
516
  /**
516
517
  * Get user with their roles
517
518
  */
518
- async getUserWithRoles(userId: string): Promise<{ user: UserData; roles: Role[] } | null> {
519
- const user = await this.getUserById(userId);
519
+ async getUserWithRoles(uid: string): Promise<{ user: UserData; roles: Role[] } | null> {
520
+ const user = await this.getUserById(uid);
520
521
  if (!user) return null;
521
522
 
522
- const roles = await this.getUserRoles(userId);
523
+ const roles = await this.getUserRoles(uid);
523
524
  return { user,
524
525
  roles };
525
526
  }
@@ -540,20 +541,20 @@ export class RefreshTokenService {
540
541
  }
541
542
  }
542
543
 
543
- async createToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
544
+ async createToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
544
545
  // 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.
546
+ // We want (uid, NULL, NULL) to collide and overwrite, so we map undefined/null to empty strings.
546
547
  const safeUserAgent = userAgent || "";
547
548
  const safeIpAddress = ipAddress || "";
548
549
 
549
- // Atomic upsert keyed on the device session (user_id, user_agent, ip_address).
550
+ // Atomic upsert keyed on the device session (uid, user_agent, ip_address).
550
551
  // A DELETE-then-INSERT races under concurrent refreshes — cookie-mode boot can
551
552
  // fire two /refresh calls at once (both carrying the same refresh cookie): both
552
553
  // DELETE, then the second INSERT violates `unique_device_session` and 500s.
553
554
  // A single INSERT ... ON CONFLICT DO UPDATE rotates the token atomically.
554
555
  await this.db.insert(this.refreshTokensTable)
555
556
  .values({
556
- userId,
557
+ uid,
557
558
  tokenHash,
558
559
  expiresAt,
559
560
  userAgent: safeUserAgent,
@@ -561,7 +562,7 @@ export class RefreshTokenService {
561
562
  })
562
563
  .onConflictDoUpdate({
563
564
  target: [
564
- this.refreshTokensTable.userId,
565
+ this.refreshTokensTable.uid,
565
566
  this.refreshTokensTable.userAgent,
566
567
  this.refreshTokensTable.ipAddress
567
568
  ],
@@ -573,7 +574,7 @@ export class RefreshTokenService {
573
574
  const [token] = await this.db
574
575
  .select({
575
576
  id: this.refreshTokensTable.id,
576
- userId: this.refreshTokensTable.userId,
577
+ uid: this.refreshTokensTable.uid,
577
578
  tokenHash: this.refreshTokensTable.tokenHash,
578
579
  expiresAt: this.refreshTokensTable.expiresAt,
579
580
  createdAt: this.refreshTokensTable.createdAt,
@@ -590,15 +591,15 @@ export class RefreshTokenService {
590
591
  await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
591
592
  }
592
593
 
593
- async deleteAllForUser(userId: string): Promise<void> {
594
- await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.userId, userId));
594
+ async deleteAllForUser(uid: string): Promise<void> {
595
+ await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.uid, uid));
595
596
  }
596
597
 
597
- async listForUser(userId: string): Promise<RefreshTokenInfo[]> {
598
+ async listForUser(uid: string): Promise<RefreshTokenInfo[]> {
598
599
  const tokens = await this.db
599
600
  .select({
600
601
  id: this.refreshTokensTable.id,
601
- userId: this.refreshTokensTable.userId,
602
+ uid: this.refreshTokensTable.uid,
602
603
  tokenHash: this.refreshTokensTable.tokenHash,
603
604
  expiresAt: this.refreshTokensTable.expiresAt,
604
605
  createdAt: this.refreshTokensTable.createdAt,
@@ -606,15 +607,15 @@ export class RefreshTokenService {
606
607
  ipAddress: this.refreshTokensTable.ipAddress
607
608
  })
608
609
  .from(this.refreshTokensTable)
609
- .where(eq(this.refreshTokensTable.userId, userId))
610
+ .where(eq(this.refreshTokensTable.uid, uid))
610
611
  .orderBy(this.refreshTokensTable.createdAt);
611
612
 
612
613
  return tokens as RefreshTokenInfo[];
613
614
  }
614
615
 
615
- async deleteById(id: string, userId: string): Promise<void> {
616
+ async deleteById(id: string, uid: string): Promise<void> {
616
617
  await this.db.delete(this.refreshTokensTable)
617
- .where(sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.userId} = ${userId}`);
618
+ .where(sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.uid} = ${uid}`);
618
619
  }
619
620
  }
620
621
 
@@ -644,16 +645,16 @@ export class PasswordResetTokenService {
644
645
  /**
645
646
  * Create a password reset token
646
647
  */
647
- async createToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
648
+ async createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
648
649
  // Delete any existing unused tokens for this user
649
650
  const tableName = this.getQualifiedPasswordResetTokensTableName();
650
651
  await this.db.execute(sql`
651
652
  DELETE FROM ${sql.raw(tableName)}
652
- WHERE user_id = ${userId} AND used_at IS NULL
653
+ WHERE uid = ${uid} AND used_at IS NULL
653
654
  `);
654
655
 
655
656
  await this.db.insert(this.passwordResetTokensTable).values({
656
- userId,
657
+ uid,
657
658
  tokenHash,
658
659
  expiresAt
659
660
  });
@@ -662,21 +663,21 @@ export class PasswordResetTokenService {
662
663
  /**
663
664
  * Find a valid (not expired, not used) token by hash
664
665
  */
665
- async findValidByHash(tokenHash: string): Promise<{ userId: string; expiresAt: Date } | null> {
666
+ async findValidByHash(tokenHash: string): Promise<{ uid: string; expiresAt: Date } | null> {
666
667
  const [token] = await this.db
667
668
  .select({
668
- userId: this.passwordResetTokensTable.userId,
669
+ uid: this.passwordResetTokensTable.uid,
669
670
  expiresAt: this.passwordResetTokensTable.expiresAt
670
671
  })
671
672
  .from(this.passwordResetTokensTable)
672
- .where(eq(this.passwordResetTokensTable.tokenHash, tokenHash)) as unknown as Array<{ userId: string; expiresAt: Date }>;
673
+ .where(eq(this.passwordResetTokensTable.tokenHash, tokenHash)) as unknown as Array<{ uid: string; expiresAt: Date }>;
673
674
 
674
675
  if (!token) return null;
675
676
 
676
677
  // Check if expired or used
677
678
  const tableName = this.getQualifiedPasswordResetTokensTableName();
678
679
  const result = await this.db.execute(sql`
679
- SELECT user_id, expires_at
680
+ SELECT uid, expires_at
680
681
  FROM ${sql.raw(tableName)}
681
682
  WHERE token_hash = ${tokenHash}
682
683
  AND used_at IS NULL
@@ -685,9 +686,9 @@ export class PasswordResetTokenService {
685
686
 
686
687
  if (result.rows.length === 0) return null;
687
688
 
688
- const row = result.rows[0] as { user_id: string; expires_at: string | number | Date };
689
+ const row = result.rows[0] as { uid: string; expires_at: string | number | Date };
689
690
  return {
690
- userId: row.user_id,
691
+ uid: row.uid,
691
692
  expiresAt: new Date(row.expires_at)
692
693
  };
693
694
  }
@@ -705,8 +706,8 @@ export class PasswordResetTokenService {
705
706
  /**
706
707
  * Delete all tokens for a user
707
708
  */
708
- async deleteAllForUser(userId: string): Promise<void> {
709
- await this.db.delete(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.userId, userId));
709
+ async deleteAllForUser(uid: string): Promise<void> {
710
+ await this.db.delete(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.uid, uid));
710
711
  }
711
712
 
712
713
  /**
@@ -741,16 +742,16 @@ export class MagicLinkTokenService {
741
742
  return `"${schema}"."${name}"`;
742
743
  }
743
744
 
744
- async createToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
745
+ async createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
745
746
  // Delete any existing unused tokens for this user
746
747
  const tableName = this.getQualifiedTableName();
747
748
  await this.db.execute(sql`
748
749
  DELETE FROM ${sql.raw(tableName)}
749
- WHERE user_id = ${userId} AND used_at IS NULL
750
+ WHERE uid = ${uid} AND used_at IS NULL
750
751
  `);
751
752
 
752
753
  await this.db.insert(this.magicLinkTokensTable).values({
753
- userId,
754
+ uid,
754
755
  tokenHash,
755
756
  expiresAt
756
757
  });
@@ -759,7 +760,7 @@ export class MagicLinkTokenService {
759
760
  async findValidByHash(tokenHash: string): Promise<MagicLinkTokenInfo | null> {
760
761
  const tableName = this.getQualifiedTableName();
761
762
  const result = await this.db.execute(sql`
762
- SELECT user_id, expires_at
763
+ SELECT uid, expires_at
763
764
  FROM ${sql.raw(tableName)}
764
765
  WHERE token_hash = ${tokenHash}
765
766
  AND used_at IS NULL
@@ -768,9 +769,9 @@ export class MagicLinkTokenService {
768
769
 
769
770
  if (result.rows.length === 0) return null;
770
771
 
771
- const row = result.rows[0] as { user_id: string; expires_at: string | number | Date };
772
+ const row = result.rows[0] as { uid: string; expires_at: string | number | Date };
772
773
  return {
773
- userId: row.user_id,
774
+ uid: row.uid,
774
775
  expiresAt: new Date(row.expires_at)
775
776
  };
776
777
  }
@@ -803,8 +804,8 @@ export class PostgresTokenRepository implements TokenRepository {
803
804
 
804
805
  // Refresh token operations
805
806
 
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);
807
+ async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
808
+ await this.refreshTokenService.createToken(uid, tokenHash, expiresAt, userAgent, ipAddress);
808
809
  }
809
810
 
810
811
  async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
@@ -815,22 +816,22 @@ export class PostgresTokenRepository implements TokenRepository {
815
816
  await this.refreshTokenService.deleteByHash(tokenHash);
816
817
  }
817
818
 
818
- async deleteAllRefreshTokensForUser(userId: string): Promise<void> {
819
- await this.refreshTokenService.deleteAllForUser(userId);
819
+ async deleteAllRefreshTokensForUser(uid: string): Promise<void> {
820
+ await this.refreshTokenService.deleteAllForUser(uid);
820
821
  }
821
822
 
822
- async listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]> {
823
- return this.refreshTokenService.listForUser(userId);
823
+ async listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]> {
824
+ return this.refreshTokenService.listForUser(uid);
824
825
  }
825
826
 
826
- async deleteRefreshTokenById(id: string, userId: string): Promise<void> {
827
- await this.refreshTokenService.deleteById(id, userId);
827
+ async deleteRefreshTokenById(id: string, uid: string): Promise<void> {
828
+ await this.refreshTokenService.deleteById(id, uid);
828
829
  }
829
830
 
830
831
  // Password reset token operations
831
832
 
832
- async createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
833
- await this.passwordResetTokenService.createToken(userId, tokenHash, expiresAt);
833
+ async createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
834
+ await this.passwordResetTokenService.createToken(uid, tokenHash, expiresAt);
834
835
  }
835
836
 
836
837
  async findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null> {
@@ -841,8 +842,8 @@ export class PostgresTokenRepository implements TokenRepository {
841
842
  await this.passwordResetTokenService.markAsUsed(tokenHash);
842
843
  }
843
844
 
844
- async deleteAllPasswordResetTokensForUser(userId: string): Promise<void> {
845
- await this.passwordResetTokenService.deleteAllForUser(userId);
845
+ async deleteAllPasswordResetTokensForUser(uid: string): Promise<void> {
846
+ await this.passwordResetTokenService.deleteAllForUser(uid);
846
847
  }
847
848
 
848
849
  async deleteExpiredTokens(): Promise<void> {
@@ -851,8 +852,8 @@ export class PostgresTokenRepository implements TokenRepository {
851
852
 
852
853
  // Magic link token operations
853
854
 
854
- async createMagicLinkToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
855
- await this.magicLinkTokenService.createToken(userId, tokenHash, expiresAt);
855
+ async createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
856
+ await this.magicLinkTokenService.createToken(uid, tokenHash, expiresAt);
856
857
  }
857
858
 
858
859
  async findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null> {
@@ -899,12 +900,12 @@ export class PostgresAuthRepository implements AuthRepository {
899
900
  return this.userService.getUserByIdentity(provider, providerId);
900
901
  }
901
902
 
902
- async getUserIdentities(userId: string): Promise<UserIdentityData[]> {
903
- return this.userService.getUserIdentities(userId);
903
+ async getUserIdentities(uid: string): Promise<UserIdentityData[]> {
904
+ return this.userService.getUserIdentities(uid);
904
905
  }
905
906
 
906
- async linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
907
- return this.userService.linkUserIdentity(userId, provider, providerId, profileData);
907
+ async linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
908
+ return this.userService.linkUserIdentity(uid, provider, providerId, profileData);
908
909
  }
909
910
 
910
911
  async updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null> {
@@ -939,24 +940,24 @@ export class PostgresAuthRepository implements AuthRepository {
939
940
  return this.userService.getUserByVerificationToken(token);
940
941
  }
941
942
 
942
- async getUserRoles(userId: string): Promise<RoleData[]> {
943
- return this.userService.getUserRoles(userId);
943
+ async getUserRoles(uid: string): Promise<RoleData[]> {
944
+ return this.userService.getUserRoles(uid);
944
945
  }
945
946
 
946
- async getUserRoleIds(userId: string): Promise<string[]> {
947
- return this.userService.getUserRoleIds(userId);
947
+ async getUserRoleIds(uid: string): Promise<string[]> {
948
+ return this.userService.getUserRoleIds(uid);
948
949
  }
949
950
 
950
- async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
951
- await this.userService.setUserRoles(userId, roleIds);
951
+ async setUserRoles(uid: string, roleIds: string[]): Promise<void> {
952
+ await this.userService.setUserRoles(uid, roleIds);
952
953
  }
953
954
 
954
- async assignDefaultRole(userId: string, roleId: string): Promise<void> {
955
- await this.userService.assignDefaultRole(userId, roleId);
955
+ async assignDefaultRole(uid: string, roleId: string): Promise<void> {
956
+ await this.userService.assignDefaultRole(uid, roleId);
956
957
  }
957
958
 
958
- async getUserWithRoles(userId: string): Promise<{ user: UserData; roles: RoleData[] } | null> {
959
- return this.userService.getUserWithRoles(userId);
959
+ async getUserWithRoles(uid: string): Promise<{ user: UserData; roles: RoleData[] } | null> {
960
+ return this.userService.getUserWithRoles(uid);
960
961
  }
961
962
 
962
963
  // Role operations (roles are inline on users, synthesized from string IDs)
@@ -1017,8 +1018,8 @@ collectionPermissions: null }
1017
1018
 
1018
1019
  // Token operations (delegate to PostgresTokenRepository)
1019
1020
 
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);
1021
+ async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
1022
+ await this.tokenRepository.createRefreshToken(uid, tokenHash, expiresAt, userAgent, ipAddress);
1022
1023
  }
1023
1024
 
1024
1025
  async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
@@ -1029,20 +1030,20 @@ collectionPermissions: null }
1029
1030
  await this.tokenRepository.deleteRefreshToken(tokenHash);
1030
1031
  }
1031
1032
 
1032
- async deleteAllRefreshTokensForUser(userId: string): Promise<void> {
1033
- await this.tokenRepository.deleteAllRefreshTokensForUser(userId);
1033
+ async deleteAllRefreshTokensForUser(uid: string): Promise<void> {
1034
+ await this.tokenRepository.deleteAllRefreshTokensForUser(uid);
1034
1035
  }
1035
1036
 
1036
- async listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]> {
1037
- return this.tokenRepository.listRefreshTokensForUser(userId);
1037
+ async listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]> {
1038
+ return this.tokenRepository.listRefreshTokensForUser(uid);
1038
1039
  }
1039
1040
 
1040
- async deleteRefreshTokenById(id: string, userId: string): Promise<void> {
1041
- await this.tokenRepository.deleteRefreshTokenById(id, userId);
1041
+ async deleteRefreshTokenById(id: string, uid: string): Promise<void> {
1042
+ await this.tokenRepository.deleteRefreshTokenById(id, uid);
1042
1043
  }
1043
1044
 
1044
- async createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
1045
- await this.tokenRepository.createPasswordResetToken(userId, tokenHash, expiresAt);
1045
+ async createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
1046
+ await this.tokenRepository.createPasswordResetToken(uid, tokenHash, expiresAt);
1046
1047
  }
1047
1048
 
1048
1049
  async findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null> {
@@ -1053,8 +1054,8 @@ collectionPermissions: null }
1053
1054
  await this.tokenRepository.markPasswordResetTokenUsed(tokenHash);
1054
1055
  }
1055
1056
 
1056
- async deleteAllPasswordResetTokensForUser(userId: string): Promise<void> {
1057
- await this.tokenRepository.deleteAllPasswordResetTokensForUser(userId);
1057
+ async deleteAllPasswordResetTokensForUser(uid: string): Promise<void> {
1058
+ await this.tokenRepository.deleteAllPasswordResetTokensForUser(uid);
1058
1059
  }
1059
1060
 
1060
1061
  async deleteExpiredTokens(): Promise<void> {
@@ -1063,8 +1064,8 @@ collectionPermissions: null }
1063
1064
 
1064
1065
  // Magic link token operations
1065
1066
 
1066
- async createMagicLinkToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
1067
- await this.tokenRepository.createMagicLinkToken(userId, tokenHash, expiresAt);
1067
+ async createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
1068
+ await this.tokenRepository.createMagicLinkToken(uid, tokenHash, expiresAt);
1068
1069
  }
1069
1070
 
1070
1071
  async findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null> {
@@ -1085,12 +1086,12 @@ collectionPermissions: null }
1085
1086
  return this._mfaService;
1086
1087
  }
1087
1088
 
1088
- async createMfaFactor(userId: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor> {
1089
- return this.getMfaService().createMfaFactor(userId, factorType, secretEncrypted, friendlyName);
1089
+ async createMfaFactor(uid: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor> {
1090
+ return this.getMfaService().createMfaFactor(uid, factorType, secretEncrypted, friendlyName);
1090
1091
  }
1091
1092
 
1092
- async getMfaFactors(userId: string): Promise<MfaFactor[]> {
1093
- return this.getMfaService().getMfaFactors(userId);
1093
+ async getMfaFactors(uid: string): Promise<MfaFactor[]> {
1094
+ return this.getMfaService().getMfaFactors(uid);
1094
1095
  }
1095
1096
 
1096
1097
  async getMfaFactorById(factorId: string): Promise<(MfaFactor & { secretEncrypted: string }) | null> {
@@ -1101,8 +1102,8 @@ collectionPermissions: null }
1101
1102
  return this.getMfaService().verifyMfaFactor(factorId);
1102
1103
  }
1103
1104
 
1104
- async deleteMfaFactor(factorId: string, userId: string): Promise<void> {
1105
- return this.getMfaService().deleteMfaFactor(factorId, userId);
1105
+ async deleteMfaFactor(factorId: string, uid: string): Promise<void> {
1106
+ return this.getMfaService().deleteMfaFactor(factorId, uid);
1106
1107
  }
1107
1108
 
1108
1109
  async createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo> {
@@ -1117,24 +1118,24 @@ collectionPermissions: null }
1117
1118
  return this.getMfaService().verifyMfaChallenge(challengeId);
1118
1119
  }
1119
1120
 
1120
- async createRecoveryCodes(userId: string, codeHashes: string[]): Promise<void> {
1121
- return this.getMfaService().createRecoveryCodes(userId, codeHashes);
1121
+ async createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void> {
1122
+ return this.getMfaService().createRecoveryCodes(uid, codeHashes);
1122
1123
  }
1123
1124
 
1124
- async useRecoveryCode(userId: string, codeHash: string): Promise<boolean> {
1125
- return this.getMfaService().useRecoveryCode(userId, codeHash);
1125
+ async useRecoveryCode(uid: string, codeHash: string): Promise<boolean> {
1126
+ return this.getMfaService().useRecoveryCode(uid, codeHash);
1126
1127
  }
1127
1128
 
1128
- async getUnusedRecoveryCodeCount(userId: string): Promise<number> {
1129
- return this.getMfaService().getUnusedRecoveryCodeCount(userId);
1129
+ async getUnusedRecoveryCodeCount(uid: string): Promise<number> {
1130
+ return this.getMfaService().getUnusedRecoveryCodeCount(uid);
1130
1131
  }
1131
1132
 
1132
- async deleteAllRecoveryCodes(userId: string): Promise<void> {
1133
- return this.getMfaService().deleteAllRecoveryCodes(userId);
1133
+ async deleteAllRecoveryCodes(uid: string): Promise<void> {
1134
+ return this.getMfaService().deleteAllRecoveryCodes(uid);
1134
1135
  }
1135
1136
 
1136
- async hasVerifiedMfaFactors(userId: string): Promise<boolean> {
1137
- return this.getMfaService().hasVerifiedMfaFactors(userId);
1137
+ async hasVerifiedMfaFactors(uid: string): Promise<boolean> {
1138
+ return this.getMfaService().hasVerifiedMfaFactors(uid);
1138
1139
  }
1139
1140
  }
1140
1141
 
@@ -1154,22 +1155,22 @@ export class MfaService implements MfaRepository {
1154
1155
  }
1155
1156
 
1156
1157
  async createMfaFactor(
1157
- userId: string,
1158
+ uid: string,
1158
1159
  factorType: "totp",
1159
1160
  secretEncrypted: string,
1160
1161
  friendlyName?: string
1161
1162
  ): Promise<MfaFactor> {
1162
1163
  const tableName = this.qualify("mfa_factors");
1163
1164
  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
1165
+ INSERT INTO ${sql.raw(tableName)} (uid, factor_type, secret_encrypted, friendly_name)
1166
+ VALUES (${uid}, ${factorType}, ${secretEncrypted}, ${friendlyName ?? null})
1167
+ RETURNING id, uid, factor_type, friendly_name, verified, created_at, updated_at
1167
1168
  `);
1168
1169
 
1169
1170
  const row = result.rows[0] as Record<string, unknown>;
1170
1171
  return {
1171
1172
  id: row.id as string,
1172
- userId: row.user_id as string,
1173
+ uid: row.uid as string,
1173
1174
  factorType: row.factor_type as "totp",
1174
1175
  friendlyName: (row.friendly_name as string | null) ?? undefined,
1175
1176
  verified: row.verified as boolean,
@@ -1178,18 +1179,18 @@ export class MfaService implements MfaRepository {
1178
1179
  };
1179
1180
  }
1180
1181
 
1181
- async getMfaFactors(userId: string): Promise<MfaFactor[]> {
1182
+ async getMfaFactors(uid: string): Promise<MfaFactor[]> {
1182
1183
  const tableName = this.qualify("mfa_factors");
1183
1184
  const result = await this.db.execute(sql`
1184
- SELECT id, user_id, factor_type, friendly_name, verified, created_at, updated_at
1185
+ SELECT id, uid, factor_type, friendly_name, verified, created_at, updated_at
1185
1186
  FROM ${sql.raw(tableName)}
1186
- WHERE user_id = ${userId}
1187
+ WHERE uid = ${uid}
1187
1188
  ORDER BY created_at
1188
1189
  `);
1189
1190
 
1190
1191
  return (result.rows as Array<Record<string, unknown>>).map(row => ({
1191
1192
  id: row.id as string,
1192
- userId: row.user_id as string,
1193
+ uid: row.uid as string,
1193
1194
  factorType: row.factor_type as "totp",
1194
1195
  friendlyName: (row.friendly_name as string | null) ?? undefined,
1195
1196
  verified: row.verified as boolean,
@@ -1201,7 +1202,7 @@ export class MfaService implements MfaRepository {
1201
1202
  async getMfaFactorById(factorId: string): Promise<(MfaFactor & { secretEncrypted: string }) | null> {
1202
1203
  const tableName = this.qualify("mfa_factors");
1203
1204
  const result = await this.db.execute(sql`
1204
- SELECT id, user_id, factor_type, secret_encrypted, friendly_name, verified, created_at, updated_at
1205
+ SELECT id, uid, factor_type, secret_encrypted, friendly_name, verified, created_at, updated_at
1205
1206
  FROM ${sql.raw(tableName)}
1206
1207
  WHERE id = ${factorId}
1207
1208
  `);
@@ -1211,7 +1212,7 @@ export class MfaService implements MfaRepository {
1211
1212
  const row = result.rows[0] as Record<string, unknown>;
1212
1213
  return {
1213
1214
  id: row.id as string,
1214
- userId: row.user_id as string,
1215
+ uid: row.uid as string,
1215
1216
  factorType: row.factor_type as "totp",
1216
1217
  secretEncrypted: row.secret_encrypted as string,
1217
1218
  friendlyName: (row.friendly_name as string | null) ?? undefined,
@@ -1230,11 +1231,11 @@ export class MfaService implements MfaRepository {
1230
1231
  `);
1231
1232
  }
1232
1233
 
1233
- async deleteMfaFactor(factorId: string, userId: string): Promise<void> {
1234
+ async deleteMfaFactor(factorId: string, uid: string): Promise<void> {
1234
1235
  const tableName = this.qualify("mfa_factors");
1235
1236
  await this.db.execute(sql`
1236
1237
  DELETE FROM ${sql.raw(tableName)}
1237
- WHERE id = ${factorId} AND user_id = ${userId}
1238
+ WHERE id = ${factorId} AND uid = ${uid}
1238
1239
  `);
1239
1240
  }
1240
1241
 
@@ -1287,56 +1288,56 @@ export class MfaService implements MfaRepository {
1287
1288
  `);
1288
1289
  }
1289
1290
 
1290
- async createRecoveryCodes(userId: string, codeHashes: string[]): Promise<void> {
1291
+ async createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void> {
1291
1292
  const tableName = this.qualify("recovery_codes");
1292
1293
  // Delete existing codes first
1293
1294
  await this.db.execute(sql`
1294
- DELETE FROM ${sql.raw(tableName)} WHERE user_id = ${userId}
1295
+ DELETE FROM ${sql.raw(tableName)} WHERE uid = ${uid}
1295
1296
  `);
1296
1297
 
1297
1298
  // Insert new codes
1298
1299
  for (const hash of codeHashes) {
1299
1300
  await this.db.execute(sql`
1300
- INSERT INTO ${sql.raw(tableName)} (user_id, code_hash)
1301
- VALUES (${userId}, ${hash})
1301
+ INSERT INTO ${sql.raw(tableName)} (uid, code_hash)
1302
+ VALUES (${uid}, ${hash})
1302
1303
  `);
1303
1304
  }
1304
1305
  }
1305
1306
 
1306
- async useRecoveryCode(userId: string, codeHash: string): Promise<boolean> {
1307
+ async useRecoveryCode(uid: string, codeHash: string): Promise<boolean> {
1307
1308
  const tableName = this.qualify("recovery_codes");
1308
1309
  const result = await this.db.execute(sql`
1309
1310
  UPDATE ${sql.raw(tableName)}
1310
1311
  SET used_at = NOW()
1311
- WHERE user_id = ${userId} AND code_hash = ${codeHash} AND used_at IS NULL
1312
+ WHERE uid = ${uid} AND code_hash = ${codeHash} AND used_at IS NULL
1312
1313
  RETURNING id
1313
1314
  `);
1314
1315
 
1315
1316
  return result.rows.length > 0;
1316
1317
  }
1317
1318
 
1318
- async getUnusedRecoveryCodeCount(userId: string): Promise<number> {
1319
+ async getUnusedRecoveryCodeCount(uid: string): Promise<number> {
1319
1320
  const tableName = this.qualify("recovery_codes");
1320
1321
  const result = await this.db.execute(sql`
1321
1322
  SELECT COUNT(*)::int as count FROM ${sql.raw(tableName)}
1322
- WHERE user_id = ${userId} AND used_at IS NULL
1323
+ WHERE uid = ${uid} AND used_at IS NULL
1323
1324
  `);
1324
1325
 
1325
1326
  return (result.rows[0] as { count: number }).count;
1326
1327
  }
1327
1328
 
1328
- async deleteAllRecoveryCodes(userId: string): Promise<void> {
1329
+ async deleteAllRecoveryCodes(uid: string): Promise<void> {
1329
1330
  const tableName = this.qualify("recovery_codes");
1330
1331
  await this.db.execute(sql`
1331
- DELETE FROM ${sql.raw(tableName)} WHERE user_id = ${userId}
1332
+ DELETE FROM ${sql.raw(tableName)} WHERE uid = ${uid}
1332
1333
  `);
1333
1334
  }
1334
1335
 
1335
- async hasVerifiedMfaFactors(userId: string): Promise<boolean> {
1336
+ async hasVerifiedMfaFactors(uid: string): Promise<boolean> {
1336
1337
  const tableName = this.qualify("mfa_factors");
1337
1338
  const result = await this.db.execute(sql`
1338
1339
  SELECT COUNT(*)::int as count FROM ${sql.raw(tableName)}
1339
- WHERE user_id = ${userId} AND verified = TRUE
1340
+ WHERE uid = ${uid} AND verified = TRUE
1340
1341
  `);
1341
1342
 
1342
1343
  return (result.rows[0] as { count: number }).count > 0;