@sixb/pg 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-storage/auth-storage.d.ts +2 -1
- package/dist/auth-storage/auth-storage.d.ts.map +1 -1
- package/dist/auth-storage/rows.d.ts +1 -1
- package/dist/auth-storage/rows.d.ts.map +1 -1
- package/dist/index.js +39 -47
- package/dist/index.js.map +5 -5
- package/dist/migrations.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/auth-storage/auth-storage.ts +43 -75
- package/src/auth-storage/rows.ts +1 -1
- package/src/migrations/050-directory-group-memberships.sql +5 -0
- package/src/migrations.ts +4 -0
package/dist/migrations.d.ts.map
CHANGED
|
@@ -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;
|
|
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;AAuG3B,OAAO,KAAK,EAAE,GAAG,EAAa,MAAM,aAAa,CAAA;AAEjD,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;AA6KD,eAAO,MAAM,yBAAyB,wCAsDpC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sixb/pg",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
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.
|
|
43
|
+
"@sixb/core": "0.1.14"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@sixb/core": "0.1.
|
|
46
|
+
"@sixb/core": "0.1.14",
|
|
47
47
|
"@types/bun": "latest",
|
|
48
48
|
"typescript": "^5.7.0"
|
|
49
49
|
},
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
CompleteSignInResult,
|
|
8
8
|
DeviceAuthorizationRecord,
|
|
9
9
|
GroupMembershipRecord,
|
|
10
|
+
GroupMembershipSource,
|
|
10
11
|
InvitationRecord,
|
|
11
12
|
SessionRecord,
|
|
12
13
|
SuspendUserAndRevokeSessionsInput,
|
|
@@ -232,27 +233,6 @@ export class PgAuthStorage implements AuthStorage {
|
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
235
|
|
|
235
|
-
if (
|
|
236
|
-
shouldCreateUser &&
|
|
237
|
-
!activeInvitation &&
|
|
238
|
-
input.allowUserCreationWithoutInvitation &&
|
|
239
|
-
input.requireNoActiveUsersForUserCreation &&
|
|
240
|
-
(await hasActiveUsers(tx, projectId))
|
|
241
|
-
) {
|
|
242
|
-
await consumeMagicLink(tx, {
|
|
243
|
-
projectId,
|
|
244
|
-
id: input.magicLinkId,
|
|
245
|
-
tokenHash: input.tokenHash,
|
|
246
|
-
consumedAt: completedAt,
|
|
247
|
-
})
|
|
248
|
-
return {
|
|
249
|
-
error: new AuthStorageError(
|
|
250
|
-
"user_creation_not_allowed",
|
|
251
|
-
`[Sixb] Magic link '${input.magicLinkId}' cannot create a user for project '${projectId}'.`
|
|
252
|
-
),
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
236
|
await validateCompleteSessionInput(tx, projectId, input.session)
|
|
257
237
|
assertSignInSessionAudience(projectId, input.session.audience, magicLink.audience)
|
|
258
238
|
|
|
@@ -294,8 +274,9 @@ export class PgAuthStorage implements AuthStorage {
|
|
|
294
274
|
projectId,
|
|
295
275
|
user,
|
|
296
276
|
})
|
|
297
|
-
const groupMemberships = await this.
|
|
277
|
+
const groupMemberships = await this.applyGroups(tx, {
|
|
298
278
|
completedAt,
|
|
279
|
+
source: "manual",
|
|
299
280
|
existing: invitation.groupMemberships,
|
|
300
281
|
groupIds: manualGroupIds,
|
|
301
282
|
projectId,
|
|
@@ -392,16 +373,6 @@ export class PgAuthStorage implements AuthStorage {
|
|
|
392
373
|
}
|
|
393
374
|
}
|
|
394
375
|
|
|
395
|
-
if (!identity && userRow && (!input.autoLinkByVerifiedEmail || !input.emailVerified)) {
|
|
396
|
-
await this.consumeOidcAttempt(input, completedAt, projectId, tx)
|
|
397
|
-
return {
|
|
398
|
-
error: new AuthStorageError(
|
|
399
|
-
"email_link_not_allowed",
|
|
400
|
-
`[Sixb] OIDC identity cannot auto-link to user '${userRow.id}' for project '${projectId}'.`
|
|
401
|
-
),
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
376
|
if (userRow?.status === "suspended") {
|
|
406
377
|
await this.consumeOidcAttempt(input, completedAt, projectId, tx)
|
|
407
378
|
return {
|
|
@@ -412,16 +383,6 @@ export class PgAuthStorage implements AuthStorage {
|
|
|
412
383
|
}
|
|
413
384
|
}
|
|
414
385
|
|
|
415
|
-
if (shouldCreateUser && !input.emailVerified) {
|
|
416
|
-
await this.consumeOidcAttempt(input, completedAt, projectId, tx)
|
|
417
|
-
return {
|
|
418
|
-
error: new AuthStorageError(
|
|
419
|
-
"user_creation_not_allowed",
|
|
420
|
-
`[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`
|
|
421
|
-
),
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
386
|
if (shouldCreateUser && !activeInvitation && !input.allowUserCreationWithoutInvitation) {
|
|
426
387
|
await this.consumeOidcAttempt(input, completedAt, projectId, tx)
|
|
427
388
|
return {
|
|
@@ -432,22 +393,6 @@ export class PgAuthStorage implements AuthStorage {
|
|
|
432
393
|
}
|
|
433
394
|
}
|
|
434
395
|
|
|
435
|
-
if (
|
|
436
|
-
shouldCreateUser &&
|
|
437
|
-
!activeInvitation &&
|
|
438
|
-
input.allowUserCreationWithoutInvitation &&
|
|
439
|
-
input.requireNoActiveUsersForUserCreation &&
|
|
440
|
-
(await hasActiveUsers(tx, projectId))
|
|
441
|
-
) {
|
|
442
|
-
await this.consumeOidcAttempt(input, completedAt, projectId, tx)
|
|
443
|
-
return {
|
|
444
|
-
error: new AuthStorageError(
|
|
445
|
-
"user_creation_not_allowed",
|
|
446
|
-
`[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`
|
|
447
|
-
),
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
396
|
await validateCompleteSessionInput(tx, projectId, input.session)
|
|
452
397
|
assertSignInSessionAudience(projectId, input.session.audience, attempt.audience)
|
|
453
398
|
|
|
@@ -497,10 +442,17 @@ export class PgAuthStorage implements AuthStorage {
|
|
|
497
442
|
projectId,
|
|
498
443
|
user,
|
|
499
444
|
})
|
|
500
|
-
const groupMemberships = await this.
|
|
445
|
+
const groupMemberships = await this.syncDirectoryGroups(tx, {
|
|
501
446
|
completedAt,
|
|
502
|
-
existing:
|
|
503
|
-
|
|
447
|
+
existing: await this.applyGroups(tx, {
|
|
448
|
+
completedAt,
|
|
449
|
+
source: "manual",
|
|
450
|
+
existing: invitation.groupMemberships,
|
|
451
|
+
groupIds: manualGroupIds,
|
|
452
|
+
projectId,
|
|
453
|
+
userId: user.id,
|
|
454
|
+
}),
|
|
455
|
+
groupIds: input.directoryGroupIds,
|
|
504
456
|
projectId,
|
|
505
457
|
userId: user.id,
|
|
506
458
|
})
|
|
@@ -703,13 +655,14 @@ export class PgAuthStorage implements AuthStorage {
|
|
|
703
655
|
return { invitation, groupMemberships }
|
|
704
656
|
}
|
|
705
657
|
|
|
706
|
-
private async
|
|
658
|
+
private async applyGroups(
|
|
707
659
|
sql: SQLClient,
|
|
708
660
|
input: {
|
|
709
661
|
readonly completedAt: Date
|
|
710
662
|
readonly existing: readonly GroupMembershipRecord[]
|
|
711
663
|
readonly groupIds: readonly string[]
|
|
712
664
|
readonly projectId: string
|
|
665
|
+
readonly source: GroupMembershipSource
|
|
713
666
|
readonly userId: string
|
|
714
667
|
}
|
|
715
668
|
): Promise<readonly GroupMembershipRecord[]> {
|
|
@@ -721,7 +674,7 @@ export class PgAuthStorage implements AuthStorage {
|
|
|
721
674
|
projectId: input.projectId,
|
|
722
675
|
userId: input.userId,
|
|
723
676
|
groupId,
|
|
724
|
-
source:
|
|
677
|
+
source: input.source,
|
|
725
678
|
createdAt: input.completedAt,
|
|
726
679
|
})
|
|
727
680
|
)
|
|
@@ -729,6 +682,33 @@ export class PgAuthStorage implements AuthStorage {
|
|
|
729
682
|
|
|
730
683
|
return groupMemberships
|
|
731
684
|
}
|
|
685
|
+
// Directory memberships become exactly `groupIds`. A group the user already holds from another
|
|
686
|
+
// source keeps that source, and memberships from other sources are never removed.
|
|
687
|
+
private async syncDirectoryGroups(
|
|
688
|
+
sql: SQLClient,
|
|
689
|
+
input: {
|
|
690
|
+
readonly completedAt: Date
|
|
691
|
+
readonly existing: readonly GroupMembershipRecord[]
|
|
692
|
+
readonly groupIds?: readonly string[]
|
|
693
|
+
readonly projectId: string
|
|
694
|
+
readonly userId: string
|
|
695
|
+
}
|
|
696
|
+
): Promise<readonly GroupMembershipRecord[]> {
|
|
697
|
+
if (!input.groupIds) {
|
|
698
|
+
return input.existing
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const groupIds = normalizeGroupIds(input.groupIds)
|
|
702
|
+
await sql`
|
|
703
|
+
DELETE FROM auth_group_memberships
|
|
704
|
+
WHERE project_id = ${input.projectId}
|
|
705
|
+
AND user_id = ${input.userId}
|
|
706
|
+
AND source = 'directory'
|
|
707
|
+
${groupIds.length > 0 ? sql`AND group_id NOT IN ${sql(groupIds)}` : sql``}
|
|
708
|
+
`
|
|
709
|
+
|
|
710
|
+
return this.applyGroups(sql, { ...input, groupIds, source: "directory" })
|
|
711
|
+
}
|
|
732
712
|
|
|
733
713
|
private async createSignInSession(
|
|
734
714
|
sql: SQLClient,
|
|
@@ -815,18 +795,6 @@ export class PgAuthStorage implements AuthStorage {
|
|
|
815
795
|
}
|
|
816
796
|
}
|
|
817
797
|
|
|
818
|
-
async function hasActiveUsers(sql: SQLClient, projectId: string): Promise<boolean> {
|
|
819
|
-
const rows = (await sql`
|
|
820
|
-
SELECT 1 AS active
|
|
821
|
-
FROM auth_users
|
|
822
|
-
WHERE project_id = ${projectId}
|
|
823
|
-
AND status = 'active'
|
|
824
|
-
LIMIT 1
|
|
825
|
-
`) as Array<{ readonly active: number }>
|
|
826
|
-
|
|
827
|
-
return rows.length > 0
|
|
828
|
-
}
|
|
829
|
-
|
|
830
798
|
function assertSignInSessionAudience(
|
|
831
799
|
projectId: string,
|
|
832
800
|
sessionAudience: string,
|
package/src/auth-storage/rows.ts
CHANGED
|
@@ -58,7 +58,7 @@ export interface PgAuthServiceAccountGroupMembershipRow {
|
|
|
58
58
|
readonly project_id: string
|
|
59
59
|
readonly service_account_id: string
|
|
60
60
|
readonly group_id: string
|
|
61
|
-
readonly source:
|
|
61
|
+
readonly source: ServiceAccountGroupMembershipRecord["source"]
|
|
62
62
|
readonly created_at: PgDate
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
-- A membership the identity provider owns is recorded with source 'directory' and synced to the
|
|
2
|
+
-- provider's groups on every sign-in. Service accounts never sign in, so their table is unchanged.
|
|
3
|
+
ALTER TABLE auth_group_memberships DROP CONSTRAINT auth_group_memberships_source_check;
|
|
4
|
+
ALTER TABLE auth_group_memberships ADD CONSTRAINT auth_group_memberships_source_check
|
|
5
|
+
CHECK (source IN ('invitation', 'manual', 'agent', 'directory'));
|
package/src/migrations.ts
CHANGED
|
@@ -106,6 +106,9 @@ import ontologyCommitAttributionSql from "./migrations/047-ontology-commit-attri
|
|
|
106
106
|
type: "text",
|
|
107
107
|
}
|
|
108
108
|
import nativeSessionsSql from "./migrations/049-native-sessions.sql" with { type: "text" }
|
|
109
|
+
import directoryGroupMembershipsSql from "./migrations/050-directory-group-memberships.sql" with {
|
|
110
|
+
type: "text",
|
|
111
|
+
}
|
|
109
112
|
import type { SQL, SQLClient } from "./pg-client"
|
|
110
113
|
|
|
111
114
|
export interface PostgresMigrationContext {
|
|
@@ -408,6 +411,7 @@ export const postgresStorageMigrations = defineMigrations<PostgresMigrationConte
|
|
|
408
411
|
pgSql("047-ontology-commit-attribution", ontologyCommitAttributionSql),
|
|
409
412
|
pgSql("048-file-upload-sessions", fileUploadSessionsSql),
|
|
410
413
|
pgSql("049-native-sessions", nativeSessionsSql),
|
|
414
|
+
pgSql("050-directory-group-memberships", directoryGroupMembershipsSql),
|
|
411
415
|
],
|
|
412
416
|
})
|
|
413
417
|
|