@sixb/pg 0.1.19 → 0.1.20

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.
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAGV,YAAY,EACZ,aAAa,EACb,oBAAoB,EACrB,MAAM,oBAAoB,CAAA;AAqH3B,OAAO,KAAK,EAAe,GAAG,EAAa,MAAM,aAAa,CAAA;AAG9D,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC;AAED,eAAO,MAAM,2BAA2B,wBAAwB,CAAA;AAEhE,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,wBAAwB,CAAC,CAE1F;AAED,wBAAgB,MAAM,CACpB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC/D,OAAO,GAAE,oBAAyB,GACjC,aAAa,CAAC,wBAAwB,CAAC,CAEzC;AAED,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,MAAM,GACjB,SAAS,eAAe,EAAE,CAQ5B;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IACjB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC,wBAAwB,CAAC,CAAA;CAC5D,GAAG,eAAe,CAqBlB;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMxF;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAKrD;AAuKD,eAAO,MAAM,yBAAyB,wCAiEpC,CAAA"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAGV,YAAY,EACZ,aAAa,EACb,oBAAoB,EACrB,MAAM,oBAAoB,CAAA;AAqH3B,OAAO,KAAK,EAAe,GAAG,EAAa,MAAM,aAAa,CAAA;AAG9D,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC;AAED,eAAO,MAAM,2BAA2B,wBAAwB,CAAA;AAEhE,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,wBAAwB,CAAC,CAE1F;AAED,wBAAgB,MAAM,CACpB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC/D,OAAO,GAAE,oBAAyB,GACjC,aAAa,CAAC,wBAAwB,CAAC,CAEzC;AAED,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,MAAM,GACjB,SAAS,eAAe,EAAE,CAQ5B;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IACjB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC,wBAAwB,CAAC,CAAA;CAC5D,GAAG,eAAe,CAqBlB;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMxF;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAKrD;AAuKD,eAAO,MAAM,yBAAyB,wCAkEpC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sixb/pg",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "PostgreSQL storage provider for Sixb",
5
5
  "keywords": [
6
6
  "sixb",
@@ -40,10 +40,10 @@
40
40
  "postgres": "^3.4.5"
41
41
  },
42
42
  "peerDependencies": {
43
- "@sixb/core": "0.1.19"
43
+ "@sixb/core": "0.1.20"
44
44
  },
45
45
  "devDependencies": {
46
- "@sixb/core": "0.1.19",
46
+ "@sixb/core": "0.1.20",
47
47
  "@types/bun": "latest",
48
48
  "typescript": "^5.7.0"
49
49
  },
@@ -234,7 +234,6 @@ export class PgAuthStorage implements AuthStorage {
234
234
  }
235
235
 
236
236
  await validateCompleteSessionInput(tx, projectId, input.session)
237
- assertSignInSessionAudience(projectId, input.session.audience, magicLink.audience)
238
237
 
239
238
  let newUserId: string | undefined
240
239
  if (shouldCreateUser) {
@@ -394,7 +393,6 @@ export class PgAuthStorage implements AuthStorage {
394
393
  }
395
394
 
396
395
  await validateCompleteSessionInput(tx, projectId, input.session)
397
- assertSignInSessionAudience(projectId, input.session.audience, attempt.audience)
398
396
 
399
397
  let newUserId: string | undefined
400
398
  if (shouldCreateUser) {
@@ -719,17 +717,13 @@ export class PgAuthStorage implements AuthStorage {
719
717
  readonly userId: string
720
718
  }
721
719
  ): Promise<SessionRecord> {
722
- await lockAdvisoryKeys(sql, [
723
- authLockKey("sessions", input.projectId, input.userId),
724
- authLockKey("sessions", input.projectId, input.userId, input.session.audience),
725
- ])
720
+ await lockAdvisoryKeys(sql, [authLockKey("sessions", input.projectId, input.userId)])
726
721
 
727
722
  return createSession(sql, {
728
723
  id: input.session.id,
729
724
  projectId: input.projectId,
730
725
  userId: input.userId,
731
726
  strategyId: input.strategyId,
732
- audience: input.session.audience,
733
727
  tokenHash: input.session.tokenHash,
734
728
  createdAt: input.session.createdAt,
735
729
  expiresAt: input.session.expiresAt,
@@ -795,21 +789,6 @@ export class PgAuthStorage implements AuthStorage {
795
789
  }
796
790
  }
797
791
 
798
- function assertSignInSessionAudience(
799
- projectId: string,
800
- sessionAudience: string,
801
- storedAudience: string
802
- ): void {
803
- if (sessionAudience === storedAudience) {
804
- return
805
- }
806
-
807
- throw new AuthStorageError(
808
- "invalid_input",
809
- `[Sixb] Sign-in session audience '${sessionAudience}' does not match stored auth audience '${storedAudience}' for project '${projectId}'.`
810
- )
811
- }
812
-
813
792
  function assertCompletableDeviceAuthorization(
814
793
  authorization: DeviceAuthorizationRecord,
815
794
  input: CompleteDeviceAuthorizationInput
@@ -1,4 +1,3 @@
1
- import { resolveAuthSessionAudience } from "@sixb/core"
2
1
  import type {
3
2
  AuthMagicLinkStore,
4
3
  CreateAuthMagicLinkInput,
@@ -31,7 +30,6 @@ export class PgAuthMagicLinkStore implements AuthMagicLinkStore {
31
30
  const id = assertNonEmpty(input.id, "Magic link id")
32
31
  const projectId = assertNonEmpty(input.projectId, "Project id")
33
32
  const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
34
- const audience = resolveAuthSessionAudience(input.audience)
35
33
  const email = normalizeEmail(input.email)
36
34
  const tokenHash = assertNonEmpty(input.tokenHash, "Magic link token hash")
37
35
 
@@ -56,7 +54,6 @@ export class PgAuthMagicLinkStore implements AuthMagicLinkStore {
56
54
  project_id,
57
55
  id,
58
56
  strategy_id,
59
- audience,
60
57
  email,
61
58
  token_hash,
62
59
  return_to,
@@ -66,7 +63,6 @@ export class PgAuthMagicLinkStore implements AuthMagicLinkStore {
66
63
  ${projectId},
67
64
  ${id},
68
65
  ${strategyId},
69
- ${audience},
70
66
  ${email},
71
67
  ${tokenHash},
72
68
  ${input.returnTo ?? null},
@@ -1,4 +1,3 @@
1
- import { resolveAuthSessionAudience } from "@sixb/core"
2
1
  import type {
3
2
  AuthOidcAuthorizationAttemptStore,
4
3
  CreateOidcAuthorizationAttemptInput,
@@ -25,7 +24,6 @@ export class PgAuthOidcAuthorizationAttemptStore implements AuthOidcAuthorizatio
25
24
  const id = assertNonEmpty(input.id, "OIDC authorization attempt id")
26
25
  const projectId = assertNonEmpty(input.projectId, "Project id")
27
26
  const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
28
- const audience = resolveAuthSessionAudience(input.audience)
29
27
  const stateHash = assertNonEmpty(input.stateHash, "OIDC state hash")
30
28
  const nonceHash = assertNonEmpty(input.nonceHash, "OIDC nonce hash")
31
29
  const codeVerifier = assertNonEmpty(input.codeVerifier, "OIDC code verifier")
@@ -43,7 +41,6 @@ export class PgAuthOidcAuthorizationAttemptStore implements AuthOidcAuthorizatio
43
41
  project_id,
44
42
  id,
45
43
  strategy_id,
46
- audience,
47
44
  state_hash,
48
45
  nonce_hash,
49
46
  code_verifier,
@@ -54,7 +51,6 @@ export class PgAuthOidcAuthorizationAttemptStore implements AuthOidcAuthorizatio
54
51
  ${projectId},
55
52
  ${id},
56
53
  ${strategyId},
57
- ${audience},
58
54
  ${stateHash},
59
55
  ${nonceHash},
60
56
  ${codeVerifier},
@@ -67,7 +67,6 @@ export interface PgAuthSessionRow {
67
67
  readonly id: string
68
68
  readonly user_id: string
69
69
  readonly strategy_id: string
70
- readonly audience: SessionRecord["audience"]
71
70
  readonly token_hash: string
72
71
  readonly created_at: PgDate
73
72
  readonly expires_at: PgDate
@@ -132,7 +131,6 @@ export interface PgAuthMagicLinkRow {
132
131
  readonly project_id: string
133
132
  readonly id: string
134
133
  readonly strategy_id: string
135
- readonly audience: MagicLinkRecord["audience"]
136
134
  readonly email: string
137
135
  readonly token_hash: string
138
136
  readonly return_to: string | null
@@ -146,7 +144,6 @@ export interface PgAuthOidcAttemptRow {
146
144
  readonly project_id: string
147
145
  readonly id: string
148
146
  readonly strategy_id: string
149
- readonly audience: OidcAuthorizationAttemptRecord["audience"]
150
147
  readonly state_hash: string
151
148
  readonly nonce_hash: string
152
149
  readonly code_verifier: string
@@ -229,7 +226,6 @@ export function rowToSessionRecord(row: PgAuthSessionRow): SessionRecord {
229
226
  projectId: row.project_id,
230
227
  userId: row.user_id,
231
228
  strategyId: row.strategy_id,
232
- audience: row.audience,
233
229
  tokenHash: row.token_hash,
234
230
  createdAt: toDate(row.created_at),
235
231
  expiresAt: toDate(row.expires_at),
@@ -306,7 +302,6 @@ export function rowToMagicLinkRecord(row: PgAuthMagicLinkRow): MagicLinkRecord {
306
302
  id: row.id,
307
303
  projectId: row.project_id,
308
304
  strategyId: row.strategy_id,
309
- audience: row.audience,
310
305
  email: row.email,
311
306
  tokenHash: row.token_hash,
312
307
  returnTo: row.return_to ?? undefined,
@@ -324,7 +319,6 @@ export function rowToOidcAuthorizationAttemptRecord(
324
319
  id: row.id,
325
320
  projectId: row.project_id,
326
321
  strategyId: row.strategy_id,
327
- audience: row.audience,
328
322
  stateHash: row.state_hash,
329
323
  nonceHash: row.nonce_hash,
330
324
  codeVerifier: row.code_verifier,
@@ -1,4 +1,3 @@
1
- import type { AuthSessionAudience } from "@sixb/core"
2
1
  import type {
3
2
  AuthSessionStore,
4
3
  CreateAuthSessionInput,
@@ -38,28 +37,6 @@ export class PgAuthSessionStore implements AuthSessionStore {
38
37
  return getSessionById(this.sql, params)
39
38
  }
40
39
 
41
- async getActiveByUserId(params: {
42
- readonly projectId: string
43
- readonly userId: string
44
- readonly audience: AuthSessionAudience
45
- readonly now: Date
46
- }): Promise<SessionRecord | null> {
47
- const [row] = await this.sql<PgAuthSessionRow[]>`
48
- SELECT *
49
- FROM auth_sessions
50
- WHERE project_id = ${params.projectId}
51
- AND user_id = ${params.userId}
52
- AND audience = ${params.audience}
53
- AND revoked_at IS NULL
54
- AND expires_at > ${params.now}
55
- AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.now})
56
- ORDER BY created_at DESC, id DESC
57
- LIMIT 1
58
- `
59
-
60
- return row ? rowToSessionRecord(row) : null
61
- }
62
-
63
40
  async listActiveByUserId(params: {
64
41
  readonly projectId: string
65
42
  readonly userId: string
@@ -82,7 +59,6 @@ export class PgAuthSessionStore implements AuthSessionStore {
82
59
  async findValidByTokenHash(params: {
83
60
  readonly projectId: string
84
61
  readonly id: string
85
- readonly audience: AuthSessionAudience
86
62
  readonly tokenHash: string
87
63
  readonly now: Date
88
64
  }): Promise<SessionRecord | null> {
@@ -91,7 +67,6 @@ export class PgAuthSessionStore implements AuthSessionStore {
91
67
  if (
92
68
  !row ||
93
69
  row.refresh_token_hash !== null ||
94
- row.audience !== params.audience ||
95
70
  row.token_hash !== params.tokenHash ||
96
71
  !isLiveSessionRow(row, params.now)
97
72
  ) {
@@ -169,7 +144,6 @@ export class PgAuthSessionStore implements AuthSessionStore {
169
144
  async renewIfValid(params: {
170
145
  readonly projectId: string
171
146
  readonly id: string
172
- readonly audience: AuthSessionAudience
173
147
  readonly tokenHash: string
174
148
  readonly now: Date
175
149
  readonly expiresAt: Date
@@ -186,7 +160,6 @@ export class PgAuthSessionStore implements AuthSessionStore {
186
160
  last_seen_at = ${params.now}
187
161
  WHERE project_id = ${params.projectId}
188
162
  AND id = ${params.id}
189
- AND audience = ${params.audience}
190
163
  AND token_hash = ${params.tokenHash}
191
164
  AND refresh_token_hash IS NULL
192
165
  AND revoked_at IS NULL
@@ -228,15 +201,10 @@ export class PgAuthSessionStore implements AuthSessionStore {
228
201
  async revokeActiveForUser(params: {
229
202
  readonly projectId: string
230
203
  readonly userId: string
231
- readonly audience?: AuthSessionAudience
232
204
  readonly revokedAt: Date
233
205
  }): Promise<readonly SessionRecord[]> {
234
206
  return runPgTransaction(this.sql, async (tx) => {
235
- const locks = [authLockKey("sessions", params.projectId, params.userId)]
236
- if (params.audience) {
237
- locks.push(authLockKey("sessions", params.projectId, params.userId, params.audience))
238
- }
239
- await lockAdvisoryKeys(tx, locks)
207
+ await lockAdvisoryKeys(tx, [authLockKey("sessions", params.projectId, params.userId)])
240
208
  return revokeActiveSessionsForUser(tx, params)
241
209
  })
242
210
  }
@@ -1,4 +1,4 @@
1
- import type { AuthSessionAudience, Principal } from "@sixb/core"
1
+ import type { Principal } from "@sixb/core"
2
2
  import type {
3
3
  CompleteAuthSessionInput,
4
4
  CreateAuthSessionInput,
@@ -442,7 +442,6 @@ export async function validateCompleteSessionInput(
442
442
  session: CompleteAuthSessionInput
443
443
  ): Promise<void> {
444
444
  const sessionId = assertNonEmpty(session.id, "Session id")
445
- assertNonEmpty(session.audience, "Session audience")
446
445
  assertNonEmpty(session.tokenHash, "Session token hash")
447
446
  await assertSessionIdAvailable(sql, projectId, sessionId)
448
447
  }
@@ -468,7 +467,6 @@ export async function createSession(
468
467
  const projectId = assertNonEmpty(input.projectId, "Project id")
469
468
  const userId = assertNonEmpty(input.userId, "User id")
470
469
  const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
471
- const audience = assertNonEmpty(input.audience, "Session audience")
472
470
  const tokenHash = assertNonEmpty(input.tokenHash, "Session token hash")
473
471
 
474
472
  await requireUserById(sql, { projectId, id: userId })
@@ -481,7 +479,6 @@ export async function createSession(
481
479
  id,
482
480
  user_id,
483
481
  strategy_id,
484
- audience,
485
482
  token_hash,
486
483
  created_at,
487
484
  expires_at,
@@ -496,7 +493,6 @@ export async function createSession(
496
493
  ${id},
497
494
  ${userId},
498
495
  ${strategyId},
499
- ${audience},
500
496
  ${tokenHash},
501
497
  ${input.createdAt},
502
498
  ${input.expiresAt},
@@ -525,13 +521,10 @@ export async function revokeActiveSessionsForUser(
525
521
  params: {
526
522
  readonly projectId: string
527
523
  readonly userId: string
528
- readonly audience?: AuthSessionAudience
529
524
  readonly revokedAt: Date
530
525
  }
531
526
  ): Promise<readonly SessionRecord[]> {
532
- const rows =
533
- params.audience === undefined
534
- ? await sql<PgAuthSessionRow[]>`
527
+ const rows = await sql<PgAuthSessionRow[]>`
535
528
  SELECT *
536
529
  FROM auth_sessions
537
530
  WHERE project_id = ${params.projectId}
@@ -542,41 +535,16 @@ export async function revokeActiveSessionsForUser(
542
535
  ORDER BY created_at ASC, id ASC
543
536
  FOR UPDATE
544
537
  `
545
- : await sql<PgAuthSessionRow[]>`
546
- SELECT *
547
- FROM auth_sessions
548
- WHERE project_id = ${params.projectId}
549
- AND user_id = ${params.userId}
550
- AND audience = ${params.audience}
551
- AND revoked_at IS NULL
552
- AND expires_at > ${params.revokedAt}
553
- AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
554
- ORDER BY created_at ASC, id ASC
555
- FOR UPDATE
556
- `
557
538
 
558
- if (params.audience === undefined) {
559
- await sql`
560
- UPDATE auth_sessions
561
- SET revoked_at = ${params.revokedAt}
562
- WHERE project_id = ${params.projectId}
563
- AND user_id = ${params.userId}
564
- AND revoked_at IS NULL
565
- AND expires_at > ${params.revokedAt}
566
- AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
567
- `
568
- } else {
569
- await sql`
539
+ await sql`
570
540
  UPDATE auth_sessions
571
541
  SET revoked_at = ${params.revokedAt}
572
542
  WHERE project_id = ${params.projectId}
573
543
  AND user_id = ${params.userId}
574
- AND audience = ${params.audience}
575
544
  AND revoked_at IS NULL
576
545
  AND expires_at > ${params.revokedAt}
577
546
  AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
578
547
  `
579
- }
580
548
 
581
549
  return rows.map((row) =>
582
550
  rowToSessionRecord({
@@ -0,0 +1,19 @@
1
+ -- A project has one browser app, so sessions, magic links and sign-in attempts no longer name the
2
+ -- one they belong to. Atlas's browser sessions end here: their cookie is no longer read. Native
3
+ -- sessions carried Atlas's name only because the column needed a value, so they stay.
4
+ UPDATE auth_sessions
5
+ SET revoked_at = now()
6
+ WHERE audience = 'atlas'
7
+ AND refresh_token_hash IS NULL
8
+ AND revoked_at IS NULL;
9
+
10
+ -- Dropping the column drops the two indexes that name it.
11
+ ALTER TABLE auth_sessions DROP COLUMN audience;
12
+ CREATE INDEX idx_auth_sessions_user_active
13
+ ON auth_sessions (project_id, user_id, expires_at DESC, created_at DESC, id DESC)
14
+ WHERE revoked_at IS NULL;
15
+ CREATE INDEX idx_auth_sessions_user_created
16
+ ON auth_sessions (project_id, user_id, created_at DESC, id DESC);
17
+
18
+ ALTER TABLE auth_magic_links DROP COLUMN audience;
19
+ ALTER TABLE auth_oidc_authorization_attempts DROP COLUMN audience;
package/src/migrations.ts CHANGED
@@ -432,6 +432,7 @@ export const postgresStorageMigrations = defineMigrations<PostgresMigrationConte
432
432
  pgSql("059-file-download-grants", fileDownloadGrantsSql),
433
433
  pgSql("060-drained-source-versions", drainedSourceVersionsSql),
434
434
  pgSql("061-terminal-action-runs", terminalActionRunsSql),
435
+ pgSql("062-drop-auth-audience", dropAuthAudienceSql),
435
436
  ],
436
437
  })
437
438
 
@@ -461,3 +462,4 @@ import drainedSourceVersionsSql from "./migrations/060-drained-source-versions.s
461
462
  type: "text",
462
463
  }
463
464
  import terminalActionRunsSql from "./migrations/061-terminal-action-runs.sql" with { type: "text" }
465
+ import dropAuthAudienceSql from "./migrations/062-drop-auth-audience.sql" with { type: "text" }