@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.
@@ -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;AAqH3B,eAAO,MAAM,yBAAyB,sBAAsB,CAAA;AAC5D,eAAO,MAAM,mBAAmB,mBAAmB,CAAA;AAEnD,eAAO,MAAM,uBAAuB,wBAwFlC,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"}
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.13",
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.13"
40
+ "@sixb/core": "0.1.14"
41
41
  },
42
42
  "devDependencies": {
43
- "@sixb/core": "0.1.13",
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.applyManualGroups({
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.applyManualGroups({
386
+ const groupMemberships = this.syncDirectoryGroups({
442
387
  completedAt,
443
- existing: invitation.groupMemberships,
444
- groupIds: manualGroupIds,
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 applyManualGroups(input: {
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: "manual",
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,
@@ -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: GroupMembershipSource
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