@sixb/sqlite 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 +55 -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 +44 -79
- package/src/auth-storage/rows.ts +1 -1
- package/src/migrations/050-directory-group-memberships.sql +21 -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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAIrC,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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAIrC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAGV,YAAY,EACZ,aAAa,EACb,oBAAoB,EACrB,MAAM,oBAAoB,CAAA;AAwH3B,eAAO,MAAM,yBAAyB,sBAAsB,CAAA;AAC5D,eAAO,MAAM,mBAAmB,mBAAmB,CAAA;AAEnD,eAAO,MAAM,uBAAuB,wBAyFlC,CAAA;AAEF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAQ9E;AAED,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC1C,OAAO,GAAE,oBAAyB,GACjC,aAAa,CAAC,QAAQ,CAAC,CAEzB;AAED,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAS3D;AAED,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,eAAe,EAAE,CAOzF;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;CAC5C,GAAG,eAAe,CAoBlB;AAED,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM1E"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sixb/sqlite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "SQLite storage provider for Sixb",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sixb",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"prepack": "bun run build"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@sixb/core": "0.1.
|
|
40
|
+
"@sixb/core": "0.1.14"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@sixb/core": "0.1.
|
|
43
|
+
"@sixb/core": "0.1.14",
|
|
44
44
|
"@types/bun": "latest",
|
|
45
45
|
"typescript": "^5.7.0"
|
|
46
46
|
},
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
CompleteSignInResult,
|
|
9
9
|
DeviceAuthorizationRecord,
|
|
10
10
|
GroupMembershipRecord,
|
|
11
|
+
GroupMembershipSource,
|
|
11
12
|
InvitationRecord,
|
|
12
13
|
SessionRecord,
|
|
13
14
|
SuspendUserAndRevokeSessionsInput,
|
|
@@ -200,27 +201,6 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
200
201
|
}
|
|
201
202
|
}
|
|
202
203
|
|
|
203
|
-
if (
|
|
204
|
-
shouldCreateUser &&
|
|
205
|
-
!activeInvitation &&
|
|
206
|
-
input.allowUserCreationWithoutInvitation &&
|
|
207
|
-
input.requireNoActiveUsersForUserCreation &&
|
|
208
|
-
hasActiveUsers(this.db, projectId)
|
|
209
|
-
) {
|
|
210
|
-
consumeMagicLink(this.db, {
|
|
211
|
-
projectId,
|
|
212
|
-
id: input.magicLinkId,
|
|
213
|
-
tokenHash: input.tokenHash,
|
|
214
|
-
consumedAt: completedAt,
|
|
215
|
-
})
|
|
216
|
-
return {
|
|
217
|
-
error: new AuthStorageError(
|
|
218
|
-
"user_creation_not_allowed",
|
|
219
|
-
`[Sixb] Magic link '${input.magicLinkId}' cannot create a user for project '${projectId}'.`
|
|
220
|
-
),
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
204
|
validateCompleteSessionInput(this.db, projectId, input.session)
|
|
225
205
|
assertSignInSessionAudience(projectId, input.session.audience, magicLink.audience)
|
|
226
206
|
|
|
@@ -259,8 +239,9 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
259
239
|
projectId,
|
|
260
240
|
user,
|
|
261
241
|
})
|
|
262
|
-
const groupMemberships = this.
|
|
242
|
+
const groupMemberships = this.applyGroups({
|
|
263
243
|
completedAt,
|
|
244
|
+
source: "manual",
|
|
264
245
|
existing: invitation.groupMemberships,
|
|
265
246
|
groupIds: manualGroupIds,
|
|
266
247
|
projectId,
|
|
@@ -338,16 +319,6 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
338
319
|
}
|
|
339
320
|
}
|
|
340
321
|
|
|
341
|
-
if (!identity && userRow && (!input.autoLinkByVerifiedEmail || !input.emailVerified)) {
|
|
342
|
-
this.consumeOidcAttempt(input, completedAt, projectId)
|
|
343
|
-
return {
|
|
344
|
-
error: new AuthStorageError(
|
|
345
|
-
"email_link_not_allowed",
|
|
346
|
-
`[Sixb] OIDC identity cannot auto-link to user '${userRow.id}' for project '${projectId}'.`
|
|
347
|
-
),
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
322
|
if (userRow?.status === "suspended") {
|
|
352
323
|
this.consumeOidcAttempt(input, completedAt, projectId)
|
|
353
324
|
return {
|
|
@@ -358,16 +329,6 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
358
329
|
}
|
|
359
330
|
}
|
|
360
331
|
|
|
361
|
-
if (shouldCreateUser && !input.emailVerified) {
|
|
362
|
-
this.consumeOidcAttempt(input, completedAt, projectId)
|
|
363
|
-
return {
|
|
364
|
-
error: new AuthStorageError(
|
|
365
|
-
"user_creation_not_allowed",
|
|
366
|
-
`[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`
|
|
367
|
-
),
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
332
|
if (shouldCreateUser && !activeInvitation && !input.allowUserCreationWithoutInvitation) {
|
|
372
333
|
this.consumeOidcAttempt(input, completedAt, projectId)
|
|
373
334
|
return {
|
|
@@ -378,22 +339,6 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
378
339
|
}
|
|
379
340
|
}
|
|
380
341
|
|
|
381
|
-
if (
|
|
382
|
-
shouldCreateUser &&
|
|
383
|
-
!activeInvitation &&
|
|
384
|
-
input.allowUserCreationWithoutInvitation &&
|
|
385
|
-
input.requireNoActiveUsersForUserCreation &&
|
|
386
|
-
hasActiveUsers(this.db, projectId)
|
|
387
|
-
) {
|
|
388
|
-
this.consumeOidcAttempt(input, completedAt, projectId)
|
|
389
|
-
return {
|
|
390
|
-
error: new AuthStorageError(
|
|
391
|
-
"user_creation_not_allowed",
|
|
392
|
-
`[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`
|
|
393
|
-
),
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
342
|
validateCompleteSessionInput(this.db, projectId, input.session)
|
|
398
343
|
assertSignInSessionAudience(projectId, input.session.audience, attempt.audience)
|
|
399
344
|
|
|
@@ -438,10 +383,17 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
438
383
|
projectId,
|
|
439
384
|
user,
|
|
440
385
|
})
|
|
441
|
-
const groupMemberships = this.
|
|
386
|
+
const groupMemberships = this.syncDirectoryGroups({
|
|
442
387
|
completedAt,
|
|
443
|
-
existing:
|
|
444
|
-
|
|
388
|
+
existing: this.applyGroups({
|
|
389
|
+
completedAt,
|
|
390
|
+
source: "manual",
|
|
391
|
+
existing: invitation.groupMemberships,
|
|
392
|
+
groupIds: manualGroupIds,
|
|
393
|
+
projectId,
|
|
394
|
+
userId: user.id,
|
|
395
|
+
}),
|
|
396
|
+
groupIds: input.directoryGroupIds,
|
|
445
397
|
projectId,
|
|
446
398
|
userId: user.id,
|
|
447
399
|
})
|
|
@@ -684,11 +636,12 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
684
636
|
return { invitation, groupMemberships }
|
|
685
637
|
}
|
|
686
638
|
|
|
687
|
-
private
|
|
639
|
+
private applyGroups(input: {
|
|
688
640
|
readonly completedAt: Date
|
|
689
641
|
readonly existing: readonly GroupMembershipRecord[]
|
|
690
642
|
readonly groupIds: readonly string[]
|
|
691
643
|
readonly projectId: string
|
|
644
|
+
readonly source: GroupMembershipSource
|
|
692
645
|
readonly userId: string
|
|
693
646
|
}): readonly GroupMembershipRecord[] {
|
|
694
647
|
const groupMemberships = [...input.existing]
|
|
@@ -699,7 +652,7 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
699
652
|
projectId: input.projectId,
|
|
700
653
|
userId: input.userId,
|
|
701
654
|
groupId,
|
|
702
|
-
source:
|
|
655
|
+
source: input.source,
|
|
703
656
|
createdAt: input.completedAt,
|
|
704
657
|
})
|
|
705
658
|
)
|
|
@@ -707,6 +660,34 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
707
660
|
|
|
708
661
|
return groupMemberships
|
|
709
662
|
}
|
|
663
|
+
// Directory memberships become exactly `groupIds`. A group the user already holds from another
|
|
664
|
+
// source keeps that source, and memberships from other sources are never removed.
|
|
665
|
+
private syncDirectoryGroups(input: {
|
|
666
|
+
readonly completedAt: Date
|
|
667
|
+
readonly existing: readonly GroupMembershipRecord[]
|
|
668
|
+
readonly groupIds?: readonly string[]
|
|
669
|
+
readonly projectId: string
|
|
670
|
+
readonly userId: string
|
|
671
|
+
}): readonly GroupMembershipRecord[] {
|
|
672
|
+
if (!input.groupIds) {
|
|
673
|
+
return input.existing
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const groupIds = normalizeGroupIds(input.groupIds)
|
|
677
|
+
this.db
|
|
678
|
+
.query(
|
|
679
|
+
`
|
|
680
|
+
DELETE FROM auth_group_memberships
|
|
681
|
+
WHERE project_id = ?
|
|
682
|
+
AND user_id = ?
|
|
683
|
+
AND source = 'directory'
|
|
684
|
+
AND group_id NOT IN (${groupIds.map(() => "?").join(", ")})
|
|
685
|
+
`
|
|
686
|
+
)
|
|
687
|
+
.run(input.projectId, input.userId, ...groupIds)
|
|
688
|
+
|
|
689
|
+
return this.applyGroups({ ...input, groupIds, source: "directory" })
|
|
690
|
+
}
|
|
710
691
|
|
|
711
692
|
private createSignInSession(input: {
|
|
712
693
|
readonly projectId: string
|
|
@@ -792,22 +773,6 @@ export class SqliteAuthStorage implements AuthStorage {
|
|
|
792
773
|
}
|
|
793
774
|
}
|
|
794
775
|
|
|
795
|
-
function hasActiveUsers(db: Database, projectId: string): boolean {
|
|
796
|
-
const row = db
|
|
797
|
-
.query(
|
|
798
|
-
`
|
|
799
|
-
SELECT 1 AS active
|
|
800
|
-
FROM auth_users
|
|
801
|
-
WHERE project_id = ?
|
|
802
|
-
AND status = 'active'
|
|
803
|
-
LIMIT 1
|
|
804
|
-
`
|
|
805
|
-
)
|
|
806
|
-
.get(projectId) as { readonly active: number } | null
|
|
807
|
-
|
|
808
|
-
return row !== null
|
|
809
|
-
}
|
|
810
|
-
|
|
811
776
|
function assertSignInSessionAudience(
|
|
812
777
|
projectId: string,
|
|
813
778
|
sessionAudience: string,
|
package/src/auth-storage/rows.ts
CHANGED
|
@@ -55,7 +55,7 @@ export interface SqliteAuthServiceAccountGroupMembershipRow {
|
|
|
55
55
|
readonly project_id: string
|
|
56
56
|
readonly service_account_id: string
|
|
57
57
|
readonly group_id: string
|
|
58
|
-
readonly source:
|
|
58
|
+
readonly source: ServiceAccountGroupMembershipRecord["source"]
|
|
59
59
|
readonly created_at: string
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
-- SQLite cannot change a CHECK constraint in place, so the table is rebuilt.
|
|
4
|
+
CREATE TABLE auth_group_memberships_next (
|
|
5
|
+
project_id TEXT NOT NULL,
|
|
6
|
+
user_id TEXT NOT NULL,
|
|
7
|
+
group_id TEXT NOT NULL,
|
|
8
|
+
source TEXT NOT NULL CHECK (source IN ('invitation', 'manual', 'agent', 'directory')),
|
|
9
|
+
created_at TEXT NOT NULL,
|
|
10
|
+
PRIMARY KEY (project_id, user_id, group_id)
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
INSERT INTO auth_group_memberships_next (project_id, user_id, group_id, source, created_at)
|
|
14
|
+
SELECT project_id, user_id, group_id, source, created_at
|
|
15
|
+
FROM auth_group_memberships;
|
|
16
|
+
|
|
17
|
+
DROP TABLE auth_group_memberships;
|
|
18
|
+
ALTER TABLE auth_group_memberships_next RENAME TO auth_group_memberships;
|
|
19
|
+
|
|
20
|
+
CREATE INDEX idx_auth_group_memberships_group
|
|
21
|
+
ON auth_group_memberships(project_id, group_id);
|
package/src/migrations.ts
CHANGED
|
@@ -112,6 +112,9 @@ import ontologyCommitAttributionSql from "./migrations/047-ontology-commit-attri
|
|
|
112
112
|
type: "text",
|
|
113
113
|
}
|
|
114
114
|
import nativeSessionsSql from "./migrations/049-native-sessions.sql" with { type: "text" }
|
|
115
|
+
import directoryGroupMembershipsSql from "./migrations/050-directory-group-memberships.sql" with {
|
|
116
|
+
type: "text",
|
|
117
|
+
}
|
|
115
118
|
|
|
116
119
|
const MIGRATIONS_TABLE_SQL = `
|
|
117
120
|
CREATE TABLE IF NOT EXISTS sixb_migrations (
|
|
@@ -216,6 +219,7 @@ export const sqliteStorageMigrations = defineMigrations({
|
|
|
216
219
|
sqliteSql("047-ontology-commit-attribution", ontologyCommitAttributionSql),
|
|
217
220
|
sqliteSql("048-file-upload-sessions", fileUploadSessionsSql),
|
|
218
221
|
sqliteSql("049-native-sessions", nativeSessionsSql),
|
|
222
|
+
sqliteSql("050-directory-group-memberships", directoryGroupMembershipsSql),
|
|
219
223
|
],
|
|
220
224
|
})
|
|
221
225
|
|