@shipfox/api-auth 10.0.0 → 10.1.0

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 (37) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +5 -1
  4. package/dist/core/administration.d.ts +1 -0
  5. package/dist/core/administration.d.ts.map +1 -1
  6. package/dist/core/administration.js +4 -1
  7. package/dist/core/administration.js.map +1 -1
  8. package/dist/db/admin-grants.d.ts +9 -0
  9. package/dist/db/admin-grants.d.ts.map +1 -1
  10. package/dist/db/admin-grants.js +19 -10
  11. package/dist/db/admin-grants.js.map +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/metrics/instance.d.ts +1 -1
  16. package/dist/metrics/instance.d.ts.map +1 -1
  17. package/dist/metrics/instance.js.map +1 -1
  18. package/dist/presentation/routes/administration.d.ts +1 -0
  19. package/dist/presentation/routes/administration.d.ts.map +1 -1
  20. package/dist/presentation/routes/administration.js +24 -2
  21. package/dist/presentation/routes/administration.js.map +1 -1
  22. package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
  23. package/dist/presentation/routes/rate-limit.js +6 -0
  24. package/dist/presentation/routes/rate-limit.js.map +1 -1
  25. package/dist/tsconfig.test.tsbuildinfo +1 -1
  26. package/package.json +3 -3
  27. package/src/core/administration.ts +5 -0
  28. package/src/db/admin-grants.test.ts +17 -1
  29. package/src/db/admin-grants.ts +33 -35
  30. package/src/index.test.ts +4 -1
  31. package/src/index.ts +2 -0
  32. package/src/metrics/instance.ts +6 -1
  33. package/src/presentation/routes/administration.test.ts +128 -0
  34. package/src/presentation/routes/administration.ts +18 -0
  35. package/src/presentation/routes/rate-limit.ts +3 -0
  36. package/test/routes.ts +7 -1
  37. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 64 files with swc (349.3ms)
2
+ Successfully compiled: 64 files with swc (496.09ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @shipfox/api-auth
2
2
 
3
+ ## 10.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fb34b6a: Expose authenticated administrator bootstrap availability through the Auth API.
8
+
9
+ ### Patch Changes
10
+
11
+ - 3db7189: Treats suspended users as inactive admin owners during bootstrap recovery.
12
+ - Updated dependencies [fb34b6a]
13
+ - @shipfox/api-auth-dto@10.1.0
14
+ - @shipfox/api-auth-context@10.1.0
15
+
3
16
  ## 10.0.0
4
17
 
5
18
  ### Major Changes
package/README.md CHANGED
@@ -234,7 +234,9 @@ When password login is disabled, the password and email-verification rows in thi
234
234
 
235
235
  ### Administrator grants
236
236
 
237
- All routes are mounted under `/admin/auth/admin-grants` and require an authenticated bearer token. Administrator role checks are enforced in the core layer, not by route-level middleware.
237
+ `GET /admin/auth/bootstrap-state` requires an authenticated bearer token and returns exactly `{state: 'available' | 'closed'}`. It reports `available` only while no active `admin-owner` exists and never reports deployment-token configuration.
238
+
239
+ Bootstrap and grant-management routes are mounted under `/admin/auth/admin-grants` and require an authenticated bearer token. Administrator role checks are enforced in the core layer, not by route-level middleware.
238
240
 
239
241
  | Method | Path | Required role | Result |
240
242
  | --- | --- | --- | --- |
@@ -245,6 +247,8 @@ All routes are mounted under `/admin/auth/admin-grants` and require an authentic
245
247
 
246
248
  `POST` and `DELETE` routes require an `Idempotency-Key` header; a repeated key replays the same result, and reusing a key with a different command returns `409 idempotency-key-reused`. Every mutation writes a redacted `administration.action.performed` outbox event.
247
249
 
250
+ Bootstrap remains closed only while an active `admin-owner` exists. An owner is active when its grant is not revoked and its user account has `active` status. A suspended owner does not count as active, so suspending the sole owner intentionally reopens deployment-token bootstrap for recovery. The suspended grant remains stored; reactivating that user closes bootstrap again.
251
+
248
252
  ### Rate limiting
249
253
 
250
254
  The public auth endpoints include an application-layer abuse baseline for open source installs:
@@ -13,6 +13,7 @@ export interface AdministrationMutationContext {
13
13
  export declare function bootstrapFirstAdminOwner(params: AdministrationMutationContext & {
14
14
  bootstrapToken: string;
15
15
  }): Promise<AdminGrant>;
16
+ export declare function getAdminBootstrapState(): Promise<'available' | 'closed'>;
16
17
  export declare function findAdministratorUserSummary(params: {
17
18
  actorId: string;
18
19
  } & ({
@@ -1 +1 @@
1
- {"version":3,"file":"administration.d.ts","sourceRoot":"","sources":["../../src/core/administration.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAErD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,uBAAuB,CAAC;AAU7D,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EACV,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,6BAA6B,EAC7B,+BAA+B,EAC/B,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAQrB,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAExF;AAkCD,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,6BAA6B,GAAG;IAAC,cAAc,EAAE,MAAM,CAAA;CAAC,GAC/D,OAAO,CAAC,UAAU,CAAC,CAuBrB;AAED,wBAAsB,4BAA4B,CAChD,MAAM,EAAE;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GAAG,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAC,GAAG;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,KAAK,CAAA;CAAC,CAAC,GACtF,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAS/C;AAED,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B,GAAG,OAAO,CAAC;IACV,MAAM,EAAE,yBAAyB,EAAE,CAAC;IACpC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACtC,CAAC,CAiBD;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,6BAA6B,GAAG;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,GACxF,OAAO,CAAC,UAAU,CAAC,CA0BrB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,6BAA6B,GAAG;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,GACxE,OAAO,CAAC,UAAU,CAAC,CAwBrB;AAED,OAAO,EACL,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,6BAA6B,EAC7B,+BAA+B,EAC/B,mBAAmB,EACnB,iBAAiB,GAClB,CAAC"}
1
+ {"version":3,"file":"administration.d.ts","sourceRoot":"","sources":["../../src/core/administration.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAErD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,uBAAuB,CAAC;AAW7D,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EACV,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,6BAA6B,EAC7B,+BAA+B,EAC/B,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAQrB,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAExF;AAkCD,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,6BAA6B,GAAG;IAAC,cAAc,EAAE,MAAM,CAAA;CAAC,GAC/D,OAAO,CAAC,UAAU,CAAC,CAuBrB;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,WAAW,GAAG,QAAQ,CAAC,CAE9E;AAED,wBAAsB,4BAA4B,CAChD,MAAM,EAAE;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GAAG,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAC,GAAG;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,KAAK,CAAA;CAAC,CAAC,GACtF,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAS/C;AAED,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B,GAAG,OAAO,CAAC;IACV,MAAM,EAAE,yBAAyB,EAAE,CAAC;IACpC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACtC,CAAC,CAiBD;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,6BAA6B,GAAG;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,GACxF,OAAO,CAAC,UAAU,CAAC,CA0BrB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,6BAA6B,GAAG;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,GACxE,OAAO,CAAC,UAAU,CAAC,CAwBrB;AAED,OAAO,EACL,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,6BAA6B,EAC7B,+BAA+B,EAC/B,mBAAmB,EACnB,iBAAiB,GAClB,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { createHash, timingSafeEqual } from 'node:crypto';
2
2
  import { createAdministrationActionEvent } from '@shipfox/api-common-dto';
3
3
  import { config } from '#config.js';
4
- import { bootstrapFirstAdminOwner as bootstrapFirstAdminOwnerInDb, grantAdminRoleWithAudit, listAdminGrantSummaries, revokeAdminGrantWithAudit } from '#db/admin-grants.js';
4
+ import { bootstrapFirstAdminOwner as bootstrapFirstAdminOwnerInDb, grantAdminRoleWithAudit, hasActiveAdminOwner, listAdminGrantSummaries, revokeAdminGrantWithAudit } from '#db/admin-grants.js';
5
5
  import { findAdministratorUser as findAdministratorUserInDb } from '#db/admin-users.js';
6
6
  import { requireAdminRole } from './admin-role.js';
7
7
  import { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, InvalidAdminBootstrapTokenError, LastAdminOwnerError, UserNotFoundError } from './errors.js';
@@ -60,6 +60,9 @@ export async function bootstrapFirstAdminOwner(params) {
60
60
  })
61
61
  });
62
62
  }
63
+ export async function getAdminBootstrapState() {
64
+ return await hasActiveAdminOwner() ? 'closed' : 'available';
65
+ }
63
66
  export async function findAdministratorUserSummary(params) {
64
67
  await requireAdminRole({
65
68
  userId: params.actorId,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/administration.ts"],"sourcesContent":["import {createHash, timingSafeEqual} from 'node:crypto';\nimport type {AdminRole} from '@shipfox/api-auth-dto';\nimport {createAdministrationActionEvent} from '@shipfox/api-common-dto';\nimport type {TimestampIdCursor} from '@shipfox/node-drizzle';\nimport {config} from '#config.js';\nimport {\n bootstrapFirstAdminOwner as bootstrapFirstAdminOwnerInDb,\n grantAdminRoleWithAudit,\n listAdminGrantSummaries,\n revokeAdminGrantWithAudit,\n} from '#db/admin-grants.js';\nimport {findAdministratorUser as findAdministratorUserInDb} from '#db/admin-users.js';\nimport {requireAdminRole} from './admin-role.js';\nimport type {AdminGrant} from './entities/admin-grant.js';\nimport type {\n AdministratorGrantSummary,\n AdministratorUserSummary,\n} from './entities/administrator-read-model.js';\nimport {\n AdminBootstrapClosedError,\n AdminGrantAlreadyExistsError,\n AdminGrantNotFoundError,\n AdminIdempotencyKeyReuseError,\n InvalidAdminBootstrapTokenError,\n LastAdminOwnerError,\n UserNotFoundError,\n} from './errors.js';\n\nconst ADMIN_OWNER_ROLE: AdminRole = 'admin-owner';\nconst ADMIN_OBSERVER_ROLE: AdminRole = 'admin-observer';\nconst BOOTSTRAP_COMMAND = 'auth.admin_grant.bootstrap';\nconst GRANT_COMMAND = 'auth.admin_grant.grant';\nconst REVOKE_COMMAND = 'auth.admin_grant.revoke';\n\nexport function hashAdministrationValue(value: string): string {\n return createHash('sha256').update(value).digest('hex');\n}\n\nexport function administrationCommandFingerprint(command: string, input: unknown): string {\n return hashAdministrationValue(`${command}:${JSON.stringify(input)}`);\n}\n\nfunction bootstrapTokenMatches(candidate: string, expected: string | undefined): boolean {\n if (!expected) return false;\n const candidateHash = Buffer.from(hashAdministrationValue(candidate), 'hex');\n const expectedHash = Buffer.from(hashAdministrationValue(expected), 'hex');\n return timingSafeEqual(candidateHash, expectedHash);\n}\n\nfunction administrationEvent(params: {\n actorId: string;\n actorRole: AdminRole;\n command: string;\n targetType: string;\n targetId: string;\n reason: string;\n correlationId: string;\n idempotencyKeyFingerprint: string;\n}) {\n return createAdministrationActionEvent({\n actorId: params.actorId,\n actorRole: params.actorRole,\n requiredRole: ADMIN_OWNER_ROLE,\n command: params.command,\n targetType: params.targetType,\n targetId: params.targetId,\n reason: params.reason,\n result: 'succeeded',\n correlationId: params.correlationId,\n idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,\n occurredAt: new Date().toISOString(),\n });\n}\n\nexport interface AdministrationMutationContext {\n actorId: string;\n idempotencyKey: string;\n correlationId: string;\n}\n\nexport async function bootstrapFirstAdminOwner(\n params: AdministrationMutationContext & {bootstrapToken: string},\n): Promise<AdminGrant> {\n if (!bootstrapTokenMatches(params.bootstrapToken, config.ADMIN_BOOTSTRAP_TOKEN)) {\n throw new InvalidAdminBootstrapTokenError();\n }\n\n return await bootstrapFirstAdminOwnerInDb({\n userId: params.actorId,\n actorId: params.actorId,\n idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),\n requestFingerprint: administrationCommandFingerprint(BOOTSTRAP_COMMAND, {\n actorId: params.actorId,\n }),\n event: administrationEvent({\n actorId: params.actorId,\n actorRole: ADMIN_OWNER_ROLE,\n command: BOOTSTRAP_COMMAND,\n targetType: 'user',\n targetId: params.actorId,\n reason: 'Initial administrator owner bootstrap',\n correlationId: params.correlationId,\n idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),\n }),\n });\n}\n\nexport async function findAdministratorUserSummary(\n params: {actorId: string} & ({id: string; email?: never} | {email: string; id?: never}),\n): Promise<AdministratorUserSummary | undefined> {\n await requireAdminRole({userId: params.actorId, minimumRole: ADMIN_OBSERVER_ROLE});\n\n const user = await findAdministratorUserInDb(\n 'id' in params ? {id: params.id} : {email: params.email},\n );\n if (!user) return undefined;\n\n return user;\n}\n\nexport async function listAdministratorGrantSummaries(params: {\n actorId: string;\n limit: number;\n cursor?: TimestampIdCursor;\n}): Promise<{\n grants: AdministratorGrantSummary[];\n nextCursor: TimestampIdCursor | null;\n}> {\n await requireAdminRole({userId: params.actorId, minimumRole: ADMIN_OBSERVER_ROLE});\n\n const result = await listAdminGrantSummaries({\n limit: params.limit,\n ...(params.cursor ? {cursor: params.cursor} : {}),\n });\n return {\n grants: result.rows.map((row) => ({\n grantId: row.id,\n role: row.role,\n createdAt: row.createdAt,\n revokedAt: row.revokedAt,\n user: row.user,\n })),\n nextCursor: result.nextCursor,\n };\n}\n\nexport async function grantAdministratorRole(\n params: AdministrationMutationContext & {userId: string; role: AdminRole; reason: string},\n): Promise<AdminGrant> {\n const actorRole = await requireAdminRole({\n userId: params.actorId,\n minimumRole: ADMIN_OWNER_ROLE,\n });\n return await grantAdminRoleWithAudit({\n userId: params.userId,\n role: params.role,\n actorId: params.actorId,\n idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),\n requestFingerprint: administrationCommandFingerprint(GRANT_COMMAND, {\n userId: params.userId,\n role: params.role,\n reason: params.reason,\n }),\n event: administrationEvent({\n actorId: params.actorId,\n actorRole,\n command: GRANT_COMMAND,\n targetType: 'user',\n targetId: params.userId,\n reason: params.reason,\n correlationId: params.correlationId,\n idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),\n }),\n });\n}\n\nexport async function revokeAdministratorGrant(\n params: AdministrationMutationContext & {grantId: string; reason: string},\n): Promise<AdminGrant> {\n const actorRole = await requireAdminRole({\n userId: params.actorId,\n minimumRole: ADMIN_OWNER_ROLE,\n });\n return await revokeAdminGrantWithAudit({\n grantId: params.grantId,\n actorId: params.actorId,\n idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),\n requestFingerprint: administrationCommandFingerprint(REVOKE_COMMAND, {\n grantId: params.grantId,\n reason: params.reason,\n }),\n event: administrationEvent({\n actorId: params.actorId,\n actorRole,\n command: REVOKE_COMMAND,\n targetType: 'admin-grant',\n targetId: params.grantId,\n reason: params.reason,\n correlationId: params.correlationId,\n idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),\n }),\n });\n}\n\nexport {\n AdminBootstrapClosedError,\n AdminGrantAlreadyExistsError,\n AdminGrantNotFoundError,\n AdminIdempotencyKeyReuseError,\n InvalidAdminBootstrapTokenError,\n LastAdminOwnerError,\n UserNotFoundError,\n};\n"],"names":["createHash","timingSafeEqual","createAdministrationActionEvent","config","bootstrapFirstAdminOwner","bootstrapFirstAdminOwnerInDb","grantAdminRoleWithAudit","listAdminGrantSummaries","revokeAdminGrantWithAudit","findAdministratorUser","findAdministratorUserInDb","requireAdminRole","AdminBootstrapClosedError","AdminGrantAlreadyExistsError","AdminGrantNotFoundError","AdminIdempotencyKeyReuseError","InvalidAdminBootstrapTokenError","LastAdminOwnerError","UserNotFoundError","ADMIN_OWNER_ROLE","ADMIN_OBSERVER_ROLE","BOOTSTRAP_COMMAND","GRANT_COMMAND","REVOKE_COMMAND","hashAdministrationValue","value","update","digest","administrationCommandFingerprint","command","input","JSON","stringify","bootstrapTokenMatches","candidate","expected","candidateHash","Buffer","from","expectedHash","administrationEvent","params","actorId","actorRole","requiredRole","targetType","targetId","reason","result","correlationId","idempotencyKeyFingerprint","occurredAt","Date","toISOString","bootstrapToken","ADMIN_BOOTSTRAP_TOKEN","userId","idempotencyKey","requestFingerprint","event","findAdministratorUserSummary","minimumRole","user","id","email","undefined","listAdministratorGrantSummaries","limit","cursor","grants","rows","map","row","grantId","role","createdAt","revokedAt","nextCursor","grantAdministratorRole","revokeAdministratorGrant"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/administration.ts"],"sourcesContent":["import {createHash, timingSafeEqual} from 'node:crypto';\nimport type {AdminRole} from '@shipfox/api-auth-dto';\nimport {createAdministrationActionEvent} from '@shipfox/api-common-dto';\nimport type {TimestampIdCursor} from '@shipfox/node-drizzle';\nimport {config} from '#config.js';\nimport {\n bootstrapFirstAdminOwner as bootstrapFirstAdminOwnerInDb,\n grantAdminRoleWithAudit,\n hasActiveAdminOwner,\n listAdminGrantSummaries,\n revokeAdminGrantWithAudit,\n} from '#db/admin-grants.js';\nimport {findAdministratorUser as findAdministratorUserInDb} from '#db/admin-users.js';\nimport {requireAdminRole} from './admin-role.js';\nimport type {AdminGrant} from './entities/admin-grant.js';\nimport type {\n AdministratorGrantSummary,\n AdministratorUserSummary,\n} from './entities/administrator-read-model.js';\nimport {\n AdminBootstrapClosedError,\n AdminGrantAlreadyExistsError,\n AdminGrantNotFoundError,\n AdminIdempotencyKeyReuseError,\n InvalidAdminBootstrapTokenError,\n LastAdminOwnerError,\n UserNotFoundError,\n} from './errors.js';\n\nconst ADMIN_OWNER_ROLE: AdminRole = 'admin-owner';\nconst ADMIN_OBSERVER_ROLE: AdminRole = 'admin-observer';\nconst BOOTSTRAP_COMMAND = 'auth.admin_grant.bootstrap';\nconst GRANT_COMMAND = 'auth.admin_grant.grant';\nconst REVOKE_COMMAND = 'auth.admin_grant.revoke';\n\nexport function hashAdministrationValue(value: string): string {\n return createHash('sha256').update(value).digest('hex');\n}\n\nexport function administrationCommandFingerprint(command: string, input: unknown): string {\n return hashAdministrationValue(`${command}:${JSON.stringify(input)}`);\n}\n\nfunction bootstrapTokenMatches(candidate: string, expected: string | undefined): boolean {\n if (!expected) return false;\n const candidateHash = Buffer.from(hashAdministrationValue(candidate), 'hex');\n const expectedHash = Buffer.from(hashAdministrationValue(expected), 'hex');\n return timingSafeEqual(candidateHash, expectedHash);\n}\n\nfunction administrationEvent(params: {\n actorId: string;\n actorRole: AdminRole;\n command: string;\n targetType: string;\n targetId: string;\n reason: string;\n correlationId: string;\n idempotencyKeyFingerprint: string;\n}) {\n return createAdministrationActionEvent({\n actorId: params.actorId,\n actorRole: params.actorRole,\n requiredRole: ADMIN_OWNER_ROLE,\n command: params.command,\n targetType: params.targetType,\n targetId: params.targetId,\n reason: params.reason,\n result: 'succeeded',\n correlationId: params.correlationId,\n idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,\n occurredAt: new Date().toISOString(),\n });\n}\n\nexport interface AdministrationMutationContext {\n actorId: string;\n idempotencyKey: string;\n correlationId: string;\n}\n\nexport async function bootstrapFirstAdminOwner(\n params: AdministrationMutationContext & {bootstrapToken: string},\n): Promise<AdminGrant> {\n if (!bootstrapTokenMatches(params.bootstrapToken, config.ADMIN_BOOTSTRAP_TOKEN)) {\n throw new InvalidAdminBootstrapTokenError();\n }\n\n return await bootstrapFirstAdminOwnerInDb({\n userId: params.actorId,\n actorId: params.actorId,\n idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),\n requestFingerprint: administrationCommandFingerprint(BOOTSTRAP_COMMAND, {\n actorId: params.actorId,\n }),\n event: administrationEvent({\n actorId: params.actorId,\n actorRole: ADMIN_OWNER_ROLE,\n command: BOOTSTRAP_COMMAND,\n targetType: 'user',\n targetId: params.actorId,\n reason: 'Initial administrator owner bootstrap',\n correlationId: params.correlationId,\n idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),\n }),\n });\n}\n\nexport async function getAdminBootstrapState(): Promise<'available' | 'closed'> {\n return (await hasActiveAdminOwner()) ? 'closed' : 'available';\n}\n\nexport async function findAdministratorUserSummary(\n params: {actorId: string} & ({id: string; email?: never} | {email: string; id?: never}),\n): Promise<AdministratorUserSummary | undefined> {\n await requireAdminRole({userId: params.actorId, minimumRole: ADMIN_OBSERVER_ROLE});\n\n const user = await findAdministratorUserInDb(\n 'id' in params ? {id: params.id} : {email: params.email},\n );\n if (!user) return undefined;\n\n return user;\n}\n\nexport async function listAdministratorGrantSummaries(params: {\n actorId: string;\n limit: number;\n cursor?: TimestampIdCursor;\n}): Promise<{\n grants: AdministratorGrantSummary[];\n nextCursor: TimestampIdCursor | null;\n}> {\n await requireAdminRole({userId: params.actorId, minimumRole: ADMIN_OBSERVER_ROLE});\n\n const result = await listAdminGrantSummaries({\n limit: params.limit,\n ...(params.cursor ? {cursor: params.cursor} : {}),\n });\n return {\n grants: result.rows.map((row) => ({\n grantId: row.id,\n role: row.role,\n createdAt: row.createdAt,\n revokedAt: row.revokedAt,\n user: row.user,\n })),\n nextCursor: result.nextCursor,\n };\n}\n\nexport async function grantAdministratorRole(\n params: AdministrationMutationContext & {userId: string; role: AdminRole; reason: string},\n): Promise<AdminGrant> {\n const actorRole = await requireAdminRole({\n userId: params.actorId,\n minimumRole: ADMIN_OWNER_ROLE,\n });\n return await grantAdminRoleWithAudit({\n userId: params.userId,\n role: params.role,\n actorId: params.actorId,\n idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),\n requestFingerprint: administrationCommandFingerprint(GRANT_COMMAND, {\n userId: params.userId,\n role: params.role,\n reason: params.reason,\n }),\n event: administrationEvent({\n actorId: params.actorId,\n actorRole,\n command: GRANT_COMMAND,\n targetType: 'user',\n targetId: params.userId,\n reason: params.reason,\n correlationId: params.correlationId,\n idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),\n }),\n });\n}\n\nexport async function revokeAdministratorGrant(\n params: AdministrationMutationContext & {grantId: string; reason: string},\n): Promise<AdminGrant> {\n const actorRole = await requireAdminRole({\n userId: params.actorId,\n minimumRole: ADMIN_OWNER_ROLE,\n });\n return await revokeAdminGrantWithAudit({\n grantId: params.grantId,\n actorId: params.actorId,\n idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),\n requestFingerprint: administrationCommandFingerprint(REVOKE_COMMAND, {\n grantId: params.grantId,\n reason: params.reason,\n }),\n event: administrationEvent({\n actorId: params.actorId,\n actorRole,\n command: REVOKE_COMMAND,\n targetType: 'admin-grant',\n targetId: params.grantId,\n reason: params.reason,\n correlationId: params.correlationId,\n idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),\n }),\n });\n}\n\nexport {\n AdminBootstrapClosedError,\n AdminGrantAlreadyExistsError,\n AdminGrantNotFoundError,\n AdminIdempotencyKeyReuseError,\n InvalidAdminBootstrapTokenError,\n LastAdminOwnerError,\n UserNotFoundError,\n};\n"],"names":["createHash","timingSafeEqual","createAdministrationActionEvent","config","bootstrapFirstAdminOwner","bootstrapFirstAdminOwnerInDb","grantAdminRoleWithAudit","hasActiveAdminOwner","listAdminGrantSummaries","revokeAdminGrantWithAudit","findAdministratorUser","findAdministratorUserInDb","requireAdminRole","AdminBootstrapClosedError","AdminGrantAlreadyExistsError","AdminGrantNotFoundError","AdminIdempotencyKeyReuseError","InvalidAdminBootstrapTokenError","LastAdminOwnerError","UserNotFoundError","ADMIN_OWNER_ROLE","ADMIN_OBSERVER_ROLE","BOOTSTRAP_COMMAND","GRANT_COMMAND","REVOKE_COMMAND","hashAdministrationValue","value","update","digest","administrationCommandFingerprint","command","input","JSON","stringify","bootstrapTokenMatches","candidate","expected","candidateHash","Buffer","from","expectedHash","administrationEvent","params","actorId","actorRole","requiredRole","targetType","targetId","reason","result","correlationId","idempotencyKeyFingerprint","occurredAt","Date","toISOString","bootstrapToken","ADMIN_BOOTSTRAP_TOKEN","userId","idempotencyKey","requestFingerprint","event","getAdminBootstrapState","findAdministratorUserSummary","minimumRole","user","id","email","undefined","listAdministratorGrantSummaries","limit","cursor","grants","rows","map","row","grantId","role","createdAt","revokedAt","nextCursor","grantAdministratorRole","revokeAdministratorGrant"],"mappings":"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"}
@@ -3,6 +3,9 @@ import type { AdministrationActionEvent } from '@shipfox/api-common-dto';
3
3
  import { type TimestampIdCursor } from '@shipfox/node-drizzle';
4
4
  import type { AdminGrant } from '#core/entities/admin-grant.js';
5
5
  import type { AdministratorGrantSummary } from '#core/entities/administrator-read-model.js';
6
+ import { db } from './db.js';
7
+ type Tx = Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0];
8
+ type AdminOwnerQueryExecutor = ReturnType<typeof db> | Tx;
6
9
  export interface CreateAdminGrantParams {
7
10
  userId: string;
8
11
  role: AdminRole;
@@ -25,6 +28,12 @@ export declare function listAdminGrantSummaries(params: {
25
28
  export declare function findCurrentAdminRole(params: {
26
29
  userId: string;
27
30
  }): Promise<AdminRole | null>;
31
+ /**
32
+ * An owner is active only while both the grant and its user account remain
33
+ * active. A suspended owner's grant is retained, but it intentionally does
34
+ * not prevent deployment-bound bootstrap recovery.
35
+ */
36
+ export declare function hasActiveAdminOwner(executor?: AdminOwnerQueryExecutor): Promise<boolean>;
28
37
  export declare function revokeAdminGrant(params: {
29
38
  grantId: string;
30
39
  }): Promise<AdminGrant | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"admin-grants.d.ts","sourceRoot":"","sources":["../../src/db/admin-grants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,uBAAuB,CAAC;AAI/B,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,4CAA4C,CAAC;AAqB1F,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAQ1F;AAED,MAAM,WAAW,+BAA+B;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,yBAAyB,CAAC,MAAM,CAAC,CAAC;CACzC;AAED,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B,GAAG,OAAO,CAAC;IACV,IAAI,EAAE,+BAA+B,EAAE,CAAC;IACxC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACtC,CAAC,CA+BD;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAc9F;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAqCjG;AAED,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB,EAAE,MAAM,CAAC;IAClC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,KAAK,EAAE,yBAAyB,CAAC;CAClC;AAoFD,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,yBAAyB,GAAG;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,GACnD,OAAO,CAAC,UAAU,CAAC,CA6CrB;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,yBAAyB,GAAG;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,GACpE,OAAO,CAAC,UAAU,CAAC,CA4CrB;AAED,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,yBAAyB,GAAG;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GACpD,OAAO,CAAC,UAAU,CAAC,CA+CrB"}
1
+ {"version":3,"file":"admin-grants.d.ts","sourceRoot":"","sources":["../../src/db/admin-grants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,uBAAuB,CAAC;AAI/B,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,4CAA4C,CAAC;AAS1F,OAAO,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAU3B,KAAK,EAAE,GAAG,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7E,KAAK,uBAAuB,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC;AAmB1D,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAQ1F;AAED,MAAM,WAAW,+BAA+B;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,yBAAyB,CAAC,MAAM,CAAC,CAAC;CACzC;AAED,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B,GAAG,OAAO,CAAC;IACV,IAAI,EAAE,+BAA+B,EAAE,CAAC;IACxC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACtC,CAAC,CA+BD;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAc9F;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,GAAE,uBAA8B,GACvC,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CA2BjG;AAED,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB,EAAE,MAAM,CAAC;IAClC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,KAAK,EAAE,yBAAyB,CAAC;CAClC;AAoFD,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,yBAAyB,GAAG;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,GACnD,OAAO,CAAC,UAAU,CAAC,CAiCrB;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,yBAAyB,GAAG;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,GACpE,OAAO,CAAC,UAAU,CAAC,CA4CrB;AAED,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,yBAAyB,GAAG;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GACpD,OAAO,CAAC,UAAU,CAAC,CAqCrB"}
@@ -8,6 +8,14 @@ import { adminCommandResults } from './schema/admin-command-results.js';
8
8
  import { adminGrants, toAdminGrant } from './schema/admin-grants.js';
9
9
  import { authOutbox } from './schema/outbox.js';
10
10
  import { users } from './schema/users.js';
11
+ function activeAdminOwnerWhere() {
12
+ return and(eq(adminGrants.role, 'admin-owner'), isNull(adminGrants.revokedAt), eq(users.status, 'active'));
13
+ }
14
+ async function listActiveAdminOwners(executor) {
15
+ return await executor.select({
16
+ id: adminGrants.id
17
+ }).from(adminGrants).innerJoin(users, eq(adminGrants.userId, users.id)).where(activeAdminOwnerWhere()).limit(2);
18
+ }
11
19
  export async function createAdminGrant(params) {
12
20
  const rows = await db().insert(adminGrants).values({
13
21
  userId: params.userId,
@@ -54,6 +62,14 @@ export async function findCurrentAdminRole(params) {
54
62
  }).from(adminGrants).innerJoin(users, eq(adminGrants.userId, users.id)).where(and(eq(adminGrants.userId, params.userId), isNull(adminGrants.revokedAt), eq(users.status, 'active')));
55
63
  return highestAdminRole(rows.map(({ role })=>role));
56
64
  }
65
+ /**
66
+ * An owner is active only while both the grant and its user account remain
67
+ * active. A suspended owner's grant is retained, but it intentionally does
68
+ * not prevent deployment-bound bootstrap recovery.
69
+ */ export async function hasActiveAdminOwner(executor = db()) {
70
+ const rows = await listActiveAdminOwners(executor);
71
+ return rows.length > 0;
72
+ }
57
73
  export async function revokeAdminGrant(params) {
58
74
  return await db().transaction(async (tx)=>{
59
75
  // All owner grant changes share one lock so concurrent revocations cannot
@@ -63,9 +79,7 @@ export async function revokeAdminGrant(params) {
63
79
  const grant = rows[0];
64
80
  if (!grant) return undefined;
65
81
  if (grant.role === 'admin-owner') {
66
- const activeOwners = await tx.select({
67
- id: adminGrants.id
68
- }).from(adminGrants).innerJoin(users, eq(adminGrants.userId, users.id)).where(and(eq(adminGrants.role, 'admin-owner'), isNull(adminGrants.revokedAt), eq(users.status, 'active')));
82
+ const activeOwners = await listActiveAdminOwners(tx);
69
83
  if (activeOwners.length <= 1) throw new LastAdminOwnerError();
70
84
  }
71
85
  const updated = await tx.update(adminGrants).set({
@@ -134,10 +148,7 @@ export async function bootstrapFirstAdminOwner(params) {
134
148
  command: params.event.command
135
149
  });
136
150
  if (existing) return existing;
137
- const activeOwners = await tx.select({
138
- id: adminGrants.id
139
- }).from(adminGrants).innerJoin(users, eq(adminGrants.userId, users.id)).where(and(eq(adminGrants.role, 'admin-owner'), isNull(adminGrants.revokedAt), eq(users.status, 'active'))).limit(1);
140
- if (activeOwners.length > 0) throw new AdminBootstrapClosedError();
151
+ if (await hasActiveAdminOwner(tx)) throw new AdminBootstrapClosedError();
141
152
  const userRows = await tx.select({
142
153
  id: users.id,
143
154
  status: users.status
@@ -200,9 +211,7 @@ export async function revokeAdminGrantWithAudit(params) {
200
211
  const grant = rows[0];
201
212
  if (!grant) throw new AdminGrantNotFoundError();
202
213
  if (grant.role === 'admin-owner') {
203
- const activeOwners = await tx.select({
204
- id: adminGrants.id
205
- }).from(adminGrants).innerJoin(users, eq(adminGrants.userId, users.id)).where(and(eq(adminGrants.role, 'admin-owner'), isNull(adminGrants.revokedAt), eq(users.status, 'active')));
214
+ const activeOwners = await listActiveAdminOwners(tx);
206
215
  if (activeOwners.length <= 1) throw new LastAdminOwnerError();
207
216
  }
208
217
  const updated = await tx.update(adminGrants).set({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/admin-grants.ts"],"sourcesContent":["import type {AdminRole} from '@shipfox/api-auth-dto';\nimport type {\n AdministrationActionEvent,\n AdministrationActionEventMap,\n} from '@shipfox/api-common-dto';\nimport {\n paginateTimestampIdRows,\n type TimestampIdCursor,\n timestampIdCursorWhere,\n} from '@shipfox/node-drizzle';\nimport {writeOutboxEvent} from '@shipfox/node-outbox';\nimport {and, desc, eq, isNull, sql} from 'drizzle-orm';\nimport {highestAdminRole} from '#core/admin-role-model.js';\nimport type {AdminGrant} from '#core/entities/admin-grant.js';\nimport type {AdministratorGrantSummary} from '#core/entities/administrator-read-model.js';\nimport {\n AdminBootstrapClosedError,\n AdminGrantAlreadyExistsError,\n AdminGrantNotFoundError,\n AdminIdempotencyKeyReuseError,\n LastAdminOwnerError,\n UserNotFoundError,\n} from '#core/errors.js';\nimport {db} from './db.js';\nimport {\n type AdminCommandResultDb,\n adminCommandResults,\n type StoredAdminGrant,\n} from './schema/admin-command-results.js';\nimport {adminGrants, toAdminGrant} from './schema/admin-grants.js';\nimport {authOutbox} from './schema/outbox.js';\nimport {users} from './schema/users.js';\n\ntype Tx = Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0];\n\nexport interface CreateAdminGrantParams {\n userId: string;\n role: AdminRole;\n}\n\nexport async function createAdminGrant(params: CreateAdminGrantParams): Promise<AdminGrant> {\n const rows = await db()\n .insert(adminGrants)\n .values({userId: params.userId, role: params.role})\n .returning();\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n return toAdminGrant(row);\n}\n\nexport interface AdministratorGrantSummaryRecord {\n id: string;\n role: AdminRole;\n createdAt: Date;\n revokedAt: Date | null;\n user: AdministratorGrantSummary['user'];\n}\n\nexport async function listAdminGrantSummaries(params: {\n limit: number;\n cursor?: TimestampIdCursor;\n}): Promise<{\n rows: AdministratorGrantSummaryRecord[];\n nextCursor: TimestampIdCursor | null;\n}> {\n const cursorCondition = timestampIdCursorWhere({\n timestampColumn: adminGrants.createdAt,\n idColumn: adminGrants.id,\n cursor: params.cursor,\n });\n const conditions = cursorCondition ? [cursorCondition] : [];\n const rows = await db()\n .select({\n id: adminGrants.id,\n role: adminGrants.role,\n createdAt: adminGrants.createdAt,\n revokedAt: adminGrants.revokedAt,\n user: {\n id: users.id,\n email: users.email,\n name: users.name,\n status: users.status,\n },\n })\n .from(adminGrants)\n .innerJoin(users, eq(adminGrants.userId, users.id))\n .where(and(...conditions))\n .orderBy(desc(adminGrants.createdAt), desc(adminGrants.id))\n .limit(params.limit + 1);\n\n const page = paginateTimestampIdRows({rows, limit: params.limit, timestampKey: 'createdAt'});\n return {\n rows: page.pageRows,\n nextCursor: page.nextCursor,\n };\n}\n\nexport async function findCurrentAdminRole(params: {userId: string}): Promise<AdminRole | null> {\n const rows = await db()\n .select({role: adminGrants.role})\n .from(adminGrants)\n .innerJoin(users, eq(adminGrants.userId, users.id))\n .where(\n and(\n eq(adminGrants.userId, params.userId),\n isNull(adminGrants.revokedAt),\n eq(users.status, 'active'),\n ),\n );\n\n return highestAdminRole(rows.map(({role}) => role));\n}\n\nexport async function revokeAdminGrant(params: {grantId: string}): Promise<AdminGrant | undefined> {\n return await db().transaction(async (tx) => {\n // All owner grant changes share one lock so concurrent revocations cannot\n // both observe the same final owner and leave the instance ownerless.\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext('auth_admin_owner_grants'))`);\n\n const rows = await tx\n .select()\n .from(adminGrants)\n .where(and(eq(adminGrants.id, params.grantId), isNull(adminGrants.revokedAt)))\n .limit(1);\n const grant = rows[0];\n if (!grant) return undefined;\n\n if (grant.role === 'admin-owner') {\n const activeOwners = await tx\n .select({id: adminGrants.id})\n .from(adminGrants)\n .innerJoin(users, eq(adminGrants.userId, users.id))\n .where(\n and(\n eq(adminGrants.role, 'admin-owner'),\n isNull(adminGrants.revokedAt),\n eq(users.status, 'active'),\n ),\n );\n if (activeOwners.length <= 1) throw new LastAdminOwnerError();\n }\n\n const updated = await tx\n .update(adminGrants)\n .set({revokedAt: new Date(), updatedAt: new Date()})\n .where(and(eq(adminGrants.id, grant.id), isNull(adminGrants.revokedAt)))\n .returning();\n const row = updated[0];\n return row ? toAdminGrant(row) : undefined;\n });\n}\n\ninterface AuditedAdminCommandParams {\n actorId: string;\n idempotencyKeyFingerprint: string;\n requestFingerprint: string;\n event: AdministrationActionEvent;\n}\n\nfunction toStoredAdminGrant(grant: AdminGrant): StoredAdminGrant {\n return {\n id: grant.id,\n userId: grant.userId,\n role: grant.role,\n revokedAt: grant.revokedAt?.toISOString() ?? null,\n createdAt: grant.createdAt.toISOString(),\n updatedAt: grant.updatedAt.toISOString(),\n };\n}\n\nfunction fromStoredAdminGrant(grant: StoredAdminGrant): AdminGrant {\n return {\n id: grant.id,\n userId: grant.userId,\n role: grant.role,\n revokedAt: grant.revokedAt ? new Date(grant.revokedAt) : null,\n createdAt: new Date(grant.createdAt),\n updatedAt: new Date(grant.updatedAt),\n };\n}\n\nasync function findCommandResult(\n tx: Tx,\n params: Pick<\n AuditedAdminCommandParams,\n 'actorId' | 'idempotencyKeyFingerprint' | 'requestFingerprint'\n > & {\n command: string;\n },\n): Promise<AdminGrant | undefined> {\n const rows = await tx\n .select()\n .from(adminCommandResults)\n .where(\n and(\n eq(adminCommandResults.actorId, params.actorId),\n eq(adminCommandResults.idempotencyKeyFingerprint, params.idempotencyKeyFingerprint),\n ),\n )\n .limit(1);\n const result: AdminCommandResultDb | undefined = rows[0];\n if (!result) return undefined;\n if (\n result.command !== params.command ||\n result.requestFingerprint !== params.requestFingerprint\n ) {\n throw new AdminIdempotencyKeyReuseError();\n }\n return fromStoredAdminGrant(result.result.grant);\n}\n\nasync function storeCommandResult(\n tx: Tx,\n params: AuditedAdminCommandParams,\n grant: AdminGrant,\n): Promise<void> {\n await tx.insert(adminCommandResults).values({\n actorId: params.actorId,\n idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,\n command: params.event.command,\n requestFingerprint: params.requestFingerprint,\n result: {grant: toStoredAdminGrant(grant)},\n });\n}\n\nasync function writeAdminAction(tx: Tx, event: AdministrationActionEvent): Promise<void> {\n await writeOutboxEvent<AdministrationActionEventMap>(tx, authOutbox, {\n type: 'administration.action.performed',\n payload: event,\n });\n}\n\nasync function lockAdminCommand(\n tx: Tx,\n params: {actorId: string; idempotencyKeyFingerprint: string},\n) {\n await tx.execute(\n sql`select pg_advisory_xact_lock(hashtext(${`auth_admin_command:${params.actorId}:${params.idempotencyKeyFingerprint}`}))`,\n );\n}\n\nexport async function bootstrapFirstAdminOwner(\n params: AuditedAdminCommandParams & {userId: string},\n): Promise<AdminGrant> {\n return await db().transaction(async (tx) => {\n await lockAdminCommand(tx, params);\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext('auth_admin_owner_grants'))`);\n\n const existing = await findCommandResult(tx, {\n ...params,\n command: params.event.command,\n });\n if (existing) return existing;\n\n const activeOwners = await tx\n .select({id: adminGrants.id})\n .from(adminGrants)\n .innerJoin(users, eq(adminGrants.userId, users.id))\n .where(\n and(\n eq(adminGrants.role, 'admin-owner'),\n isNull(adminGrants.revokedAt),\n eq(users.status, 'active'),\n ),\n )\n .limit(1);\n if (activeOwners.length > 0) throw new AdminBootstrapClosedError();\n\n const userRows = await tx\n .select({id: users.id, status: users.status})\n .from(users)\n .where(eq(users.id, params.userId))\n .limit(1);\n const user = userRows[0];\n if (user?.status !== 'active') throw new UserNotFoundError(params.userId);\n\n const rows = await tx\n .insert(adminGrants)\n .values({userId: params.userId, role: 'admin-owner'})\n .returning();\n const row = rows[0];\n if (!row) throw new Error('Bootstrap grant insert returned no rows');\n\n const grant = toAdminGrant(row);\n await writeAdminAction(tx, params.event);\n await storeCommandResult(tx, params, grant);\n return grant;\n });\n}\n\nexport async function grantAdminRoleWithAudit(\n params: AuditedAdminCommandParams & {userId: string; role: AdminRole},\n): Promise<AdminGrant> {\n return await db().transaction(async (tx) => {\n await lockAdminCommand(tx, params);\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext('auth_admin_owner_grants'))`);\n\n const existing = await findCommandResult(tx, {\n ...params,\n command: params.event.command,\n });\n if (existing) return existing;\n\n const userRows = await tx\n .select({id: users.id, status: users.status})\n .from(users)\n .where(eq(users.id, params.userId))\n .limit(1);\n const user = userRows[0];\n if (user?.status !== 'active') throw new UserNotFoundError(params.userId);\n\n const activeRows = await tx\n .select({id: adminGrants.id})\n .from(adminGrants)\n .where(\n and(\n eq(adminGrants.userId, params.userId),\n eq(adminGrants.role, params.role),\n isNull(adminGrants.revokedAt),\n ),\n )\n .limit(1);\n if (activeRows.length > 0) throw new AdminGrantAlreadyExistsError();\n\n const rows = await tx\n .insert(adminGrants)\n .values({userId: params.userId, role: params.role})\n .returning();\n const row = rows[0];\n if (!row) throw new Error('Administrator grant insert returned no rows');\n\n const grant = toAdminGrant(row);\n await writeAdminAction(tx, params.event);\n await storeCommandResult(tx, params, grant);\n return grant;\n });\n}\n\nexport async function revokeAdminGrantWithAudit(\n params: AuditedAdminCommandParams & {grantId: string},\n): Promise<AdminGrant> {\n return await db().transaction(async (tx) => {\n await lockAdminCommand(tx, params);\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext('auth_admin_owner_grants'))`);\n\n const existing = await findCommandResult(tx, {\n ...params,\n command: params.event.command,\n });\n if (existing) return existing;\n\n const rows = await tx\n .select()\n .from(adminGrants)\n .where(and(eq(adminGrants.id, params.grantId), isNull(adminGrants.revokedAt)))\n .limit(1);\n const grant = rows[0];\n if (!grant) throw new AdminGrantNotFoundError();\n\n if (grant.role === 'admin-owner') {\n const activeOwners = await tx\n .select({id: adminGrants.id})\n .from(adminGrants)\n .innerJoin(users, eq(adminGrants.userId, users.id))\n .where(\n and(\n eq(adminGrants.role, 'admin-owner'),\n isNull(adminGrants.revokedAt),\n eq(users.status, 'active'),\n ),\n );\n if (activeOwners.length <= 1) throw new LastAdminOwnerError();\n }\n\n const updated = await tx\n .update(adminGrants)\n .set({revokedAt: new Date(), updatedAt: new Date()})\n .where(and(eq(adminGrants.id, grant.id), isNull(adminGrants.revokedAt)))\n .returning();\n const updatedRow = updated[0];\n if (!updatedRow) throw new AdminGrantNotFoundError();\n\n const revoked = toAdminGrant(updatedRow);\n await writeAdminAction(tx, params.event);\n await storeCommandResult(tx, params, revoked);\n return revoked;\n });\n}\n"],"names":["paginateTimestampIdRows","timestampIdCursorWhere","writeOutboxEvent","and","desc","eq","isNull","sql","highestAdminRole","AdminBootstrapClosedError","AdminGrantAlreadyExistsError","AdminGrantNotFoundError","AdminIdempotencyKeyReuseError","LastAdminOwnerError","UserNotFoundError","db","adminCommandResults","adminGrants","toAdminGrant","authOutbox","users","createAdminGrant","params","rows","insert","values","userId","role","returning","row","Error","listAdminGrantSummaries","cursorCondition","timestampColumn","createdAt","idColumn","id","cursor","conditions","select","revokedAt","user","email","name","status","from","innerJoin","where","orderBy","limit","page","timestampKey","pageRows","nextCursor","findCurrentAdminRole","map","revokeAdminGrant","transaction","tx","execute","grantId","grant","undefined","activeOwners","length","updated","update","set","Date","updatedAt","toStoredAdminGrant","toISOString","fromStoredAdminGrant","findCommandResult","actorId","idempotencyKeyFingerprint","result","command","requestFingerprint","storeCommandResult","event","writeAdminAction","type","payload","lockAdminCommand","bootstrapFirstAdminOwner","existing","userRows","grantAdminRoleWithAudit","activeRows","revokeAdminGrantWithAudit","updatedRow","revoked"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/db/admin-grants.ts"],"sourcesContent":["import type {AdminRole} from '@shipfox/api-auth-dto';\nimport type {\n AdministrationActionEvent,\n AdministrationActionEventMap,\n} from '@shipfox/api-common-dto';\nimport {\n paginateTimestampIdRows,\n type TimestampIdCursor,\n timestampIdCursorWhere,\n} from '@shipfox/node-drizzle';\nimport {writeOutboxEvent} from '@shipfox/node-outbox';\nimport {and, desc, eq, isNull, sql} from 'drizzle-orm';\nimport {highestAdminRole} from '#core/admin-role-model.js';\nimport type {AdminGrant} from '#core/entities/admin-grant.js';\nimport type {AdministratorGrantSummary} from '#core/entities/administrator-read-model.js';\nimport {\n AdminBootstrapClosedError,\n AdminGrantAlreadyExistsError,\n AdminGrantNotFoundError,\n AdminIdempotencyKeyReuseError,\n LastAdminOwnerError,\n UserNotFoundError,\n} from '#core/errors.js';\nimport {db} from './db.js';\nimport {\n type AdminCommandResultDb,\n adminCommandResults,\n type StoredAdminGrant,\n} from './schema/admin-command-results.js';\nimport {adminGrants, toAdminGrant} from './schema/admin-grants.js';\nimport {authOutbox} from './schema/outbox.js';\nimport {users} from './schema/users.js';\n\ntype Tx = Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0];\ntype AdminOwnerQueryExecutor = ReturnType<typeof db> | Tx;\n\nfunction activeAdminOwnerWhere() {\n return and(\n eq(adminGrants.role, 'admin-owner'),\n isNull(adminGrants.revokedAt),\n eq(users.status, 'active'),\n );\n}\n\nasync function listActiveAdminOwners(executor: AdminOwnerQueryExecutor) {\n return await executor\n .select({id: adminGrants.id})\n .from(adminGrants)\n .innerJoin(users, eq(adminGrants.userId, users.id))\n .where(activeAdminOwnerWhere())\n .limit(2);\n}\n\nexport interface CreateAdminGrantParams {\n userId: string;\n role: AdminRole;\n}\n\nexport async function createAdminGrant(params: CreateAdminGrantParams): Promise<AdminGrant> {\n const rows = await db()\n .insert(adminGrants)\n .values({userId: params.userId, role: params.role})\n .returning();\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n return toAdminGrant(row);\n}\n\nexport interface AdministratorGrantSummaryRecord {\n id: string;\n role: AdminRole;\n createdAt: Date;\n revokedAt: Date | null;\n user: AdministratorGrantSummary['user'];\n}\n\nexport async function listAdminGrantSummaries(params: {\n limit: number;\n cursor?: TimestampIdCursor;\n}): Promise<{\n rows: AdministratorGrantSummaryRecord[];\n nextCursor: TimestampIdCursor | null;\n}> {\n const cursorCondition = timestampIdCursorWhere({\n timestampColumn: adminGrants.createdAt,\n idColumn: adminGrants.id,\n cursor: params.cursor,\n });\n const conditions = cursorCondition ? [cursorCondition] : [];\n const rows = await db()\n .select({\n id: adminGrants.id,\n role: adminGrants.role,\n createdAt: adminGrants.createdAt,\n revokedAt: adminGrants.revokedAt,\n user: {\n id: users.id,\n email: users.email,\n name: users.name,\n status: users.status,\n },\n })\n .from(adminGrants)\n .innerJoin(users, eq(adminGrants.userId, users.id))\n .where(and(...conditions))\n .orderBy(desc(adminGrants.createdAt), desc(adminGrants.id))\n .limit(params.limit + 1);\n\n const page = paginateTimestampIdRows({rows, limit: params.limit, timestampKey: 'createdAt'});\n return {\n rows: page.pageRows,\n nextCursor: page.nextCursor,\n };\n}\n\nexport async function findCurrentAdminRole(params: {userId: string}): Promise<AdminRole | null> {\n const rows = await db()\n .select({role: adminGrants.role})\n .from(adminGrants)\n .innerJoin(users, eq(adminGrants.userId, users.id))\n .where(\n and(\n eq(adminGrants.userId, params.userId),\n isNull(adminGrants.revokedAt),\n eq(users.status, 'active'),\n ),\n );\n\n return highestAdminRole(rows.map(({role}) => role));\n}\n\n/**\n * An owner is active only while both the grant and its user account remain\n * active. A suspended owner's grant is retained, but it intentionally does\n * not prevent deployment-bound bootstrap recovery.\n */\nexport async function hasActiveAdminOwner(\n executor: AdminOwnerQueryExecutor = db(),\n): Promise<boolean> {\n const rows = await listActiveAdminOwners(executor);\n return rows.length > 0;\n}\n\nexport async function revokeAdminGrant(params: {grantId: string}): Promise<AdminGrant | undefined> {\n return await db().transaction(async (tx) => {\n // All owner grant changes share one lock so concurrent revocations cannot\n // both observe the same final owner and leave the instance ownerless.\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext('auth_admin_owner_grants'))`);\n\n const rows = await tx\n .select()\n .from(adminGrants)\n .where(and(eq(adminGrants.id, params.grantId), isNull(adminGrants.revokedAt)))\n .limit(1);\n const grant = rows[0];\n if (!grant) return undefined;\n\n if (grant.role === 'admin-owner') {\n const activeOwners = await listActiveAdminOwners(tx);\n if (activeOwners.length <= 1) throw new LastAdminOwnerError();\n }\n\n const updated = await tx\n .update(adminGrants)\n .set({revokedAt: new Date(), updatedAt: new Date()})\n .where(and(eq(adminGrants.id, grant.id), isNull(adminGrants.revokedAt)))\n .returning();\n const row = updated[0];\n return row ? toAdminGrant(row) : undefined;\n });\n}\n\ninterface AuditedAdminCommandParams {\n actorId: string;\n idempotencyKeyFingerprint: string;\n requestFingerprint: string;\n event: AdministrationActionEvent;\n}\n\nfunction toStoredAdminGrant(grant: AdminGrant): StoredAdminGrant {\n return {\n id: grant.id,\n userId: grant.userId,\n role: grant.role,\n revokedAt: grant.revokedAt?.toISOString() ?? null,\n createdAt: grant.createdAt.toISOString(),\n updatedAt: grant.updatedAt.toISOString(),\n };\n}\n\nfunction fromStoredAdminGrant(grant: StoredAdminGrant): AdminGrant {\n return {\n id: grant.id,\n userId: grant.userId,\n role: grant.role,\n revokedAt: grant.revokedAt ? new Date(grant.revokedAt) : null,\n createdAt: new Date(grant.createdAt),\n updatedAt: new Date(grant.updatedAt),\n };\n}\n\nasync function findCommandResult(\n tx: Tx,\n params: Pick<\n AuditedAdminCommandParams,\n 'actorId' | 'idempotencyKeyFingerprint' | 'requestFingerprint'\n > & {\n command: string;\n },\n): Promise<AdminGrant | undefined> {\n const rows = await tx\n .select()\n .from(adminCommandResults)\n .where(\n and(\n eq(adminCommandResults.actorId, params.actorId),\n eq(adminCommandResults.idempotencyKeyFingerprint, params.idempotencyKeyFingerprint),\n ),\n )\n .limit(1);\n const result: AdminCommandResultDb | undefined = rows[0];\n if (!result) return undefined;\n if (\n result.command !== params.command ||\n result.requestFingerprint !== params.requestFingerprint\n ) {\n throw new AdminIdempotencyKeyReuseError();\n }\n return fromStoredAdminGrant(result.result.grant);\n}\n\nasync function storeCommandResult(\n tx: Tx,\n params: AuditedAdminCommandParams,\n grant: AdminGrant,\n): Promise<void> {\n await tx.insert(adminCommandResults).values({\n actorId: params.actorId,\n idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,\n command: params.event.command,\n requestFingerprint: params.requestFingerprint,\n result: {grant: toStoredAdminGrant(grant)},\n });\n}\n\nasync function writeAdminAction(tx: Tx, event: AdministrationActionEvent): Promise<void> {\n await writeOutboxEvent<AdministrationActionEventMap>(tx, authOutbox, {\n type: 'administration.action.performed',\n payload: event,\n });\n}\n\nasync function lockAdminCommand(\n tx: Tx,\n params: {actorId: string; idempotencyKeyFingerprint: string},\n) {\n await tx.execute(\n sql`select pg_advisory_xact_lock(hashtext(${`auth_admin_command:${params.actorId}:${params.idempotencyKeyFingerprint}`}))`,\n );\n}\n\nexport async function bootstrapFirstAdminOwner(\n params: AuditedAdminCommandParams & {userId: string},\n): Promise<AdminGrant> {\n return await db().transaction(async (tx) => {\n await lockAdminCommand(tx, params);\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext('auth_admin_owner_grants'))`);\n\n const existing = await findCommandResult(tx, {\n ...params,\n command: params.event.command,\n });\n if (existing) return existing;\n\n if (await hasActiveAdminOwner(tx)) throw new AdminBootstrapClosedError();\n\n const userRows = await tx\n .select({id: users.id, status: users.status})\n .from(users)\n .where(eq(users.id, params.userId))\n .limit(1);\n const user = userRows[0];\n if (user?.status !== 'active') throw new UserNotFoundError(params.userId);\n\n const rows = await tx\n .insert(adminGrants)\n .values({userId: params.userId, role: 'admin-owner'})\n .returning();\n const row = rows[0];\n if (!row) throw new Error('Bootstrap grant insert returned no rows');\n\n const grant = toAdminGrant(row);\n await writeAdminAction(tx, params.event);\n await storeCommandResult(tx, params, grant);\n return grant;\n });\n}\n\nexport async function grantAdminRoleWithAudit(\n params: AuditedAdminCommandParams & {userId: string; role: AdminRole},\n): Promise<AdminGrant> {\n return await db().transaction(async (tx) => {\n await lockAdminCommand(tx, params);\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext('auth_admin_owner_grants'))`);\n\n const existing = await findCommandResult(tx, {\n ...params,\n command: params.event.command,\n });\n if (existing) return existing;\n\n const userRows = await tx\n .select({id: users.id, status: users.status})\n .from(users)\n .where(eq(users.id, params.userId))\n .limit(1);\n const user = userRows[0];\n if (user?.status !== 'active') throw new UserNotFoundError(params.userId);\n\n const activeRows = await tx\n .select({id: adminGrants.id})\n .from(adminGrants)\n .where(\n and(\n eq(adminGrants.userId, params.userId),\n eq(adminGrants.role, params.role),\n isNull(adminGrants.revokedAt),\n ),\n )\n .limit(1);\n if (activeRows.length > 0) throw new AdminGrantAlreadyExistsError();\n\n const rows = await tx\n .insert(adminGrants)\n .values({userId: params.userId, role: params.role})\n .returning();\n const row = rows[0];\n if (!row) throw new Error('Administrator grant insert returned no rows');\n\n const grant = toAdminGrant(row);\n await writeAdminAction(tx, params.event);\n await storeCommandResult(tx, params, grant);\n return grant;\n });\n}\n\nexport async function revokeAdminGrantWithAudit(\n params: AuditedAdminCommandParams & {grantId: string},\n): Promise<AdminGrant> {\n return await db().transaction(async (tx) => {\n await lockAdminCommand(tx, params);\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext('auth_admin_owner_grants'))`);\n\n const existing = await findCommandResult(tx, {\n ...params,\n command: params.event.command,\n });\n if (existing) return existing;\n\n const rows = await tx\n .select()\n .from(adminGrants)\n .where(and(eq(adminGrants.id, params.grantId), isNull(adminGrants.revokedAt)))\n .limit(1);\n const grant = rows[0];\n if (!grant) throw new AdminGrantNotFoundError();\n\n if (grant.role === 'admin-owner') {\n const activeOwners = await listActiveAdminOwners(tx);\n if (activeOwners.length <= 1) throw new LastAdminOwnerError();\n }\n\n const updated = await tx\n .update(adminGrants)\n .set({revokedAt: new Date(), updatedAt: new Date()})\n .where(and(eq(adminGrants.id, grant.id), isNull(adminGrants.revokedAt)))\n .returning();\n const updatedRow = updated[0];\n if (!updatedRow) throw new AdminGrantNotFoundError();\n\n const revoked = toAdminGrant(updatedRow);\n await writeAdminAction(tx, params.event);\n await storeCommandResult(tx, params, revoked);\n return revoked;\n });\n}\n"],"names":["paginateTimestampIdRows","timestampIdCursorWhere","writeOutboxEvent","and","desc","eq","isNull","sql","highestAdminRole","AdminBootstrapClosedError","AdminGrantAlreadyExistsError","AdminGrantNotFoundError","AdminIdempotencyKeyReuseError","LastAdminOwnerError","UserNotFoundError","db","adminCommandResults","adminGrants","toAdminGrant","authOutbox","users","activeAdminOwnerWhere","role","revokedAt","status","listActiveAdminOwners","executor","select","id","from","innerJoin","userId","where","limit","createAdminGrant","params","rows","insert","values","returning","row","Error","listAdminGrantSummaries","cursorCondition","timestampColumn","createdAt","idColumn","cursor","conditions","user","email","name","orderBy","page","timestampKey","pageRows","nextCursor","findCurrentAdminRole","map","hasActiveAdminOwner","length","revokeAdminGrant","transaction","tx","execute","grantId","grant","undefined","activeOwners","updated","update","set","Date","updatedAt","toStoredAdminGrant","toISOString","fromStoredAdminGrant","findCommandResult","actorId","idempotencyKeyFingerprint","result","command","requestFingerprint","storeCommandResult","event","writeAdminAction","type","payload","lockAdminCommand","bootstrapFirstAdminOwner","existing","userRows","grantAdminRoleWithAudit","activeRows","revokeAdminGrantWithAudit","updatedRow","revoked"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AAGxD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAoBjD,YAAY,EAAC,SAAS,EAAE,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,uBAAuB,CAAC;AACpG,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAC,oBAAoB,EAAE,aAAa,EAAC,MAAM,eAAe,CAAC;AAClE,YAAY,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAC,UAAU,EAAC,MAAM,+BAA+B,CAAC;AAC9D,YAAY,EAAC,IAAI,EAAE,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EACL,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,6BAA6B,EAC7B,sBAAsB,EACtB,8BAA8B,EAC9B,qBAAqB,EACrB,+BAA+B,EAC/B,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AACjD,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,6BAA6B,EAC7B,kCAAkC,GACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,2BAA2B,EAChC,mBAAmB,EACnB,8BAA8B,EAC9B,KAAK,gBAAgB,EACrB,KAAK,MAAM,GACZ,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAC,0BAA0B,EAAC,MAAM,wCAAwC,CAAC;AAClF,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAC,6BAA6B,EAAC,MAAM,2CAA2C,CAAC;AAIxF,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,OAAO,0CAA0C,EAAE,2BAA2B,CAAC;IAC3F,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,YAA8C,GAC/C,EAAE,uBAAuB,GAAG,aAAa,CAgBzC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AAGxD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAqBjD,YAAY,EAAC,SAAS,EAAE,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,uBAAuB,CAAC;AACpG,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAC,oBAAoB,EAAE,aAAa,EAAC,MAAM,eAAe,CAAC;AAClE,YAAY,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAC,UAAU,EAAC,MAAM,+BAA+B,CAAC;AAC9D,YAAY,EAAC,IAAI,EAAE,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EACL,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,6BAA6B,EAC7B,sBAAsB,EACtB,8BAA8B,EAC9B,qBAAqB,EACrB,+BAA+B,EAC/B,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AACjD,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,6BAA6B,EAC7B,kCAAkC,GACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,2BAA2B,EAChC,mBAAmB,EACnB,8BAA8B,EAC9B,KAAK,gBAAgB,EACrB,KAAK,MAAM,GACZ,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAC,0BAA0B,EAAC,MAAM,wCAAwC,CAAC;AAClF,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAC,6BAA6B,EAAC,MAAM,2CAA2C,CAAC;AAIxF,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,OAAO,0CAA0C,EAAE,2BAA2B,CAAC;IAC3F,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,YAA8C,GAC/C,EAAE,uBAAuB,GAAG,aAAa,CAiBzC"}
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { createLeaseTokenAuthMethod } from '#presentation/auth/lease-token-auth.
11
11
  import { createRunnerSessionAuthMethod } from '#presentation/auth/runner-session-auth.js';
12
12
  import { createAuthE2eRoutes } from '#presentation/e2eRoutes/index.js';
13
13
  import { createAuthInterModulePresentation } from '#presentation/inter-module.js';
14
- import { administrationRoutes, administrationUserRoutes } from '#presentation/routes/administration.js';
14
+ import { administrationBootstrapRoutes, administrationRoutes, administrationUserRoutes } from '#presentation/routes/administration.js';
15
15
  import { buildAuthRoutes } from '#presentation/routes/index.js';
16
16
  import { onPasswordResetSendRequested } from '#presentation/subscribers/index.js';
17
17
  import { passwordLoginMethods } from './login-methods.js';
@@ -48,6 +48,7 @@ export function createAuthModule({ workspaces, signupPolicy = createEnvironmentS
48
48
  loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),
49
49
  routes: [
50
50
  buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces, signupPolicy),
51
+ administrationBootstrapRoutes,
51
52
  administrationRoutes,
52
53
  administrationUserRoutes
53
54
  ],
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n AUTH_PASSWORD_RESET_SEND_REQUESTED,\n type AuthEventMap,\n authEventSchemas,\n} from '@shipfox/api-auth-dto';\nimport {administrationActionEventSchemas} from '@shipfox/api-common-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {config} from '#config.js';\nimport type {SignupPolicy} from '#core/ports.js';\nimport {createEnvironmentSignupPolicy} from '#core/signup-policy.js';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {authOutbox} from '#db/schema/outbox.js';\nimport {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';\nimport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nimport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\nimport {createAuthE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createAuthInterModulePresentation} from '#presentation/inter-module.js';\nimport {\n administrationRoutes,\n administrationUserRoutes,\n} from '#presentation/routes/administration.js';\nimport {buildAuthRoutes} from '#presentation/routes/index.js';\nimport {onPasswordResetSendRequested} from '#presentation/subscribers/index.js';\nimport {passwordLoginMethods} from './login-methods.js';\n\nconst authPublisherEventSchemas = {...authEventSchemas, ...administrationActionEventSchemas};\n\nexport type {AdminRole, JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';\nexport {\n ADMIN_ROLES,\n getCurrentAdminRole,\n hasMinimumAdminRole,\n highestAdminRole,\n requireAdminRole,\n revokeAdminGrant,\n} from '#core/admin-role.js';\nexport {\n bootstrapFirstAdminOwner,\n grantAdministratorRole,\n revokeAdministratorGrant,\n} from '#core/administration.js';\nexport type {\n CreateSessionForUserError,\n CreateSessionForUserParams,\n CreateSessionForUserResult,\n ProvisionUserParams,\n} from '#core/auth.js';\nexport {createSessionForUser, provisionUser} from '#core/auth.js';\nexport type {EmailOwner, FindUserByEmailParams} from '#core/email-owner.js';\nexport {findUserByEmail} from '#core/email-owner.js';\nexport type {AdminGrant} from '#core/entities/admin-grant.js';\nexport type {User, UserStatus} from '#core/entities/user.js';\nexport {\n AdminBootstrapClosedError,\n AdminGrantAlreadyExistsError,\n AdminGrantNotFoundError,\n AdminIdempotencyKeyReuseError,\n AdminRoleRequiredError,\n AuthDependencyUnavailableError,\n EmailNotVerifiedError,\n InvalidAdminBootstrapTokenError,\n InvalidCredentialsError,\n LastAdminOwnerError,\n SignupNotAllowedError,\n UserNotFoundError,\n} from '#core/errors.js';\nexport {\n issueJobLeaseToken,\n jobLeaseParamsFrom,\n verifyJobLeaseToken,\n} from '#core/job-lease-token.js';\nexport type {SignupPolicy} from '#core/ports.js';\nexport {\n issueRunnerSessionToken,\n verifyRunnerSessionToken,\n} from '#core/runner-session-token.js';\nexport {\n createEnvironmentSignupPolicy,\n DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE,\n} from '#core/signup-policy.js';\nexport {\n type AuthenticatedSessionContext,\n createJwtAuthMethod,\n getAuthenticatedSessionContext,\n type RefreshSessionId,\n type UserId,\n} from '#presentation/auth/jwt-auth.js';\nexport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nexport {\n authCookiePlugin,\n clearRefreshTokenCookie,\n getRefreshTokenCookie,\n setRefreshTokenCookie,\n} from '#presentation/auth/refresh-cookie.js';\nexport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\n\nconst subscriber = subscriberFactory<AuthEventMap>();\n\nexport interface CreateAuthModuleOptions {\n workspaces: import('@shipfox/api-workspaces-dto/inter-module').WorkspacesInterModuleClient;\n signupPolicy?: SignupPolicy;\n}\n\nexport function createAuthModule({\n workspaces,\n signupPolicy = createEnvironmentSignupPolicy(),\n}: CreateAuthModuleOptions): ShipfoxModule {\n return {\n name: 'auth',\n database: {db, migrationsPath, databaseNamespace: 'auth'},\n auth: [createJwtAuthMethod(), createLeaseTokenAuthMethod(), createRunnerSessionAuthMethod()],\n loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),\n routes: [\n buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces, signupPolicy),\n administrationRoutes,\n administrationUserRoutes,\n ],\n e2eRoutes: [createAuthE2eRoutes(workspaces)],\n publishers: [{name: 'auth', table: authOutbox, db, eventSchemas: authPublisherEventSchemas}],\n subscribers: [subscriber(AUTH_PASSWORD_RESET_SEND_REQUESTED, onPasswordResetSendRequested)],\n interModulePresentations: [createAuthInterModulePresentation()],\n };\n}\n"],"names":["AUTH_PASSWORD_RESET_SEND_REQUESTED","authEventSchemas","administrationActionEventSchemas","subscriberFactory","config","createEnvironmentSignupPolicy","db","migrationsPath","authOutbox","createJwtAuthMethod","createLeaseTokenAuthMethod","createRunnerSessionAuthMethod","createAuthE2eRoutes","createAuthInterModulePresentation","administrationRoutes","administrationUserRoutes","buildAuthRoutes","onPasswordResetSendRequested","passwordLoginMethods","authPublisherEventSchemas","ADMIN_ROLES","getCurrentAdminRole","hasMinimumAdminRole","highestAdminRole","requireAdminRole","revokeAdminGrant","bootstrapFirstAdminOwner","grantAdministratorRole","revokeAdministratorGrant","createSessionForUser","provisionUser","findUserByEmail","AdminBootstrapClosedError","AdminGrantAlreadyExistsError","AdminGrantNotFoundError","AdminIdempotencyKeyReuseError","AdminRoleRequiredError","AuthDependencyUnavailableError","EmailNotVerifiedError","InvalidAdminBootstrapTokenError","InvalidCredentialsError","LastAdminOwnerError","SignupNotAllowedError","UserNotFoundError","issueJobLeaseToken","jobLeaseParamsFrom","verifyJobLeaseToken","issueRunnerSessionToken","verifyRunnerSessionToken","DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE","getAuthenticatedSessionContext","authCookiePlugin","clearRefreshTokenCookie","getRefreshTokenCookie","setRefreshTokenCookie","subscriber","createAuthModule","workspaces","signupPolicy","name","database","databaseNamespace","auth","loginMethods","AUTH_PASSWORD_ENABLED","routes","e2eRoutes","publishers","table","eventSchemas","subscribers","interModulePresentations"],"mappings":"AAAA,SACEA,kCAAkC,EAElCC,gBAAgB,QACX,wBAAwB;AAC/B,SAAQC,gCAAgC,QAAO,0BAA0B;AAEzE,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,MAAM,QAAO,aAAa;AAElC,SAAQC,6BAA6B,QAAO,yBAAyB;AACrE,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,UAAU,QAAO,uBAAuB;AAChD,SAAQC,mBAAmB,QAAO,iCAAiC;AACnE,SAAQC,0BAA0B,QAAO,yCAAyC;AAClF,SAAQC,6BAA6B,QAAO,4CAA4C;AACxF,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SAAQC,iCAAiC,QAAO,gCAAgC;AAChF,SACEC,oBAAoB,EACpBC,wBAAwB,QACnB,yCAAyC;AAChD,SAAQC,eAAe,QAAO,gCAAgC;AAC9D,SAAQC,4BAA4B,QAAO,qCAAqC;AAChF,SAAQC,oBAAoB,QAAO,qBAAqB;AAExD,MAAMC,4BAA4B;IAAC,GAAGlB,gBAAgB;IAAE,GAAGC,gCAAgC;AAAA;AAG3F,SACEkB,WAAW,EACXC,mBAAmB,EACnBC,mBAAmB,EACnBC,gBAAgB,EAChBC,gBAAgB,EAChBC,gBAAgB,QACX,sBAAsB;AAC7B,SACEC,wBAAwB,EACxBC,sBAAsB,EACtBC,wBAAwB,QACnB,0BAA0B;AAOjC,SAAQC,oBAAoB,EAAEC,aAAa,QAAO,gBAAgB;AAElE,SAAQC,eAAe,QAAO,uBAAuB;AAGrD,SACEC,yBAAyB,EACzBC,4BAA4B,EAC5BC,uBAAuB,EACvBC,6BAA6B,EAC7BC,sBAAsB,EACtBC,8BAA8B,EAC9BC,qBAAqB,EACrBC,+BAA+B,EAC/BC,uBAAuB,EACvBC,mBAAmB,EACnBC,qBAAqB,EACrBC,iBAAiB,QACZ,kBAAkB;AACzB,SACEC,kBAAkB,EAClBC,kBAAkB,EAClBC,mBAAmB,QACd,2BAA2B;AAElC,SACEC,uBAAuB,EACvBC,wBAAwB,QACnB,gCAAgC;AACvC,SACE3C,6BAA6B,EAC7B4C,kCAAkC,QAC7B,yBAAyB;AAChC,SAEExC,mBAAmB,EACnByC,8BAA8B,QAGzB,iCAAiC;AACxC,SAAQxC,0BAA0B,QAAO,yCAAyC;AAClF,SACEyC,gBAAgB,EAChBC,uBAAuB,EACvBC,qBAAqB,EACrBC,qBAAqB,QAChB,uCAAuC;AAC9C,SAAQ3C,6BAA6B,QAAO,4CAA4C;AAExF,MAAM4C,aAAapD;AAOnB,OAAO,SAASqD,iBAAiB,EAC/BC,UAAU,EACVC,eAAerD,+BAA+B,EACtB;IACxB,OAAO;QACLsD,MAAM;QACNC,UAAU;YAACtD;YAAIC;YAAgBsD,mBAAmB;QAAM;QACxDC,MAAM;YAACrD;YAAuBC;YAA8BC;SAAgC;QAC5FoD,cAAc7C,qBAAqBd,OAAO4D,qBAAqB;QAC/DC,QAAQ;YACNjD,gBAAgBZ,OAAO4D,qBAAqB,EAAEP,YAAYC;YAC1D5C;YACAC;SACD;QACDmD,WAAW;YAACtD,oBAAoB6C;SAAY;QAC5CU,YAAY;YAAC;gBAACR,MAAM;gBAAQS,OAAO5D;gBAAYF;gBAAI+D,cAAclD;YAAyB;SAAE;QAC5FmD,aAAa;YAACf,WAAWvD,oCAAoCiB;SAA8B;QAC3FsD,0BAA0B;YAAC1D;SAAoC;IACjE;AACF"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n AUTH_PASSWORD_RESET_SEND_REQUESTED,\n type AuthEventMap,\n authEventSchemas,\n} from '@shipfox/api-auth-dto';\nimport {administrationActionEventSchemas} from '@shipfox/api-common-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {config} from '#config.js';\nimport type {SignupPolicy} from '#core/ports.js';\nimport {createEnvironmentSignupPolicy} from '#core/signup-policy.js';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {authOutbox} from '#db/schema/outbox.js';\nimport {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';\nimport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nimport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\nimport {createAuthE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createAuthInterModulePresentation} from '#presentation/inter-module.js';\nimport {\n administrationBootstrapRoutes,\n administrationRoutes,\n administrationUserRoutes,\n} from '#presentation/routes/administration.js';\nimport {buildAuthRoutes} from '#presentation/routes/index.js';\nimport {onPasswordResetSendRequested} from '#presentation/subscribers/index.js';\nimport {passwordLoginMethods} from './login-methods.js';\n\nconst authPublisherEventSchemas = {...authEventSchemas, ...administrationActionEventSchemas};\n\nexport type {AdminRole, JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';\nexport {\n ADMIN_ROLES,\n getCurrentAdminRole,\n hasMinimumAdminRole,\n highestAdminRole,\n requireAdminRole,\n revokeAdminGrant,\n} from '#core/admin-role.js';\nexport {\n bootstrapFirstAdminOwner,\n grantAdministratorRole,\n revokeAdministratorGrant,\n} from '#core/administration.js';\nexport type {\n CreateSessionForUserError,\n CreateSessionForUserParams,\n CreateSessionForUserResult,\n ProvisionUserParams,\n} from '#core/auth.js';\nexport {createSessionForUser, provisionUser} from '#core/auth.js';\nexport type {EmailOwner, FindUserByEmailParams} from '#core/email-owner.js';\nexport {findUserByEmail} from '#core/email-owner.js';\nexport type {AdminGrant} from '#core/entities/admin-grant.js';\nexport type {User, UserStatus} from '#core/entities/user.js';\nexport {\n AdminBootstrapClosedError,\n AdminGrantAlreadyExistsError,\n AdminGrantNotFoundError,\n AdminIdempotencyKeyReuseError,\n AdminRoleRequiredError,\n AuthDependencyUnavailableError,\n EmailNotVerifiedError,\n InvalidAdminBootstrapTokenError,\n InvalidCredentialsError,\n LastAdminOwnerError,\n SignupNotAllowedError,\n UserNotFoundError,\n} from '#core/errors.js';\nexport {\n issueJobLeaseToken,\n jobLeaseParamsFrom,\n verifyJobLeaseToken,\n} from '#core/job-lease-token.js';\nexport type {SignupPolicy} from '#core/ports.js';\nexport {\n issueRunnerSessionToken,\n verifyRunnerSessionToken,\n} from '#core/runner-session-token.js';\nexport {\n createEnvironmentSignupPolicy,\n DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE,\n} from '#core/signup-policy.js';\nexport {\n type AuthenticatedSessionContext,\n createJwtAuthMethod,\n getAuthenticatedSessionContext,\n type RefreshSessionId,\n type UserId,\n} from '#presentation/auth/jwt-auth.js';\nexport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nexport {\n authCookiePlugin,\n clearRefreshTokenCookie,\n getRefreshTokenCookie,\n setRefreshTokenCookie,\n} from '#presentation/auth/refresh-cookie.js';\nexport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\n\nconst subscriber = subscriberFactory<AuthEventMap>();\n\nexport interface CreateAuthModuleOptions {\n workspaces: import('@shipfox/api-workspaces-dto/inter-module').WorkspacesInterModuleClient;\n signupPolicy?: SignupPolicy;\n}\n\nexport function createAuthModule({\n workspaces,\n signupPolicy = createEnvironmentSignupPolicy(),\n}: CreateAuthModuleOptions): ShipfoxModule {\n return {\n name: 'auth',\n database: {db, migrationsPath, databaseNamespace: 'auth'},\n auth: [createJwtAuthMethod(), createLeaseTokenAuthMethod(), createRunnerSessionAuthMethod()],\n loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),\n routes: [\n buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces, signupPolicy),\n administrationBootstrapRoutes,\n administrationRoutes,\n administrationUserRoutes,\n ],\n e2eRoutes: [createAuthE2eRoutes(workspaces)],\n publishers: [{name: 'auth', table: authOutbox, db, eventSchemas: authPublisherEventSchemas}],\n subscribers: [subscriber(AUTH_PASSWORD_RESET_SEND_REQUESTED, onPasswordResetSendRequested)],\n interModulePresentations: [createAuthInterModulePresentation()],\n };\n}\n"],"names":["AUTH_PASSWORD_RESET_SEND_REQUESTED","authEventSchemas","administrationActionEventSchemas","subscriberFactory","config","createEnvironmentSignupPolicy","db","migrationsPath","authOutbox","createJwtAuthMethod","createLeaseTokenAuthMethod","createRunnerSessionAuthMethod","createAuthE2eRoutes","createAuthInterModulePresentation","administrationBootstrapRoutes","administrationRoutes","administrationUserRoutes","buildAuthRoutes","onPasswordResetSendRequested","passwordLoginMethods","authPublisherEventSchemas","ADMIN_ROLES","getCurrentAdminRole","hasMinimumAdminRole","highestAdminRole","requireAdminRole","revokeAdminGrant","bootstrapFirstAdminOwner","grantAdministratorRole","revokeAdministratorGrant","createSessionForUser","provisionUser","findUserByEmail","AdminBootstrapClosedError","AdminGrantAlreadyExistsError","AdminGrantNotFoundError","AdminIdempotencyKeyReuseError","AdminRoleRequiredError","AuthDependencyUnavailableError","EmailNotVerifiedError","InvalidAdminBootstrapTokenError","InvalidCredentialsError","LastAdminOwnerError","SignupNotAllowedError","UserNotFoundError","issueJobLeaseToken","jobLeaseParamsFrom","verifyJobLeaseToken","issueRunnerSessionToken","verifyRunnerSessionToken","DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE","getAuthenticatedSessionContext","authCookiePlugin","clearRefreshTokenCookie","getRefreshTokenCookie","setRefreshTokenCookie","subscriber","createAuthModule","workspaces","signupPolicy","name","database","databaseNamespace","auth","loginMethods","AUTH_PASSWORD_ENABLED","routes","e2eRoutes","publishers","table","eventSchemas","subscribers","interModulePresentations"],"mappings":"AAAA,SACEA,kCAAkC,EAElCC,gBAAgB,QACX,wBAAwB;AAC/B,SAAQC,gCAAgC,QAAO,0BAA0B;AAEzE,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,MAAM,QAAO,aAAa;AAElC,SAAQC,6BAA6B,QAAO,yBAAyB;AACrE,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,UAAU,QAAO,uBAAuB;AAChD,SAAQC,mBAAmB,QAAO,iCAAiC;AACnE,SAAQC,0BAA0B,QAAO,yCAAyC;AAClF,SAAQC,6BAA6B,QAAO,4CAA4C;AACxF,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SAAQC,iCAAiC,QAAO,gCAAgC;AAChF,SACEC,6BAA6B,EAC7BC,oBAAoB,EACpBC,wBAAwB,QACnB,yCAAyC;AAChD,SAAQC,eAAe,QAAO,gCAAgC;AAC9D,SAAQC,4BAA4B,QAAO,qCAAqC;AAChF,SAAQC,oBAAoB,QAAO,qBAAqB;AAExD,MAAMC,4BAA4B;IAAC,GAAGnB,gBAAgB;IAAE,GAAGC,gCAAgC;AAAA;AAG3F,SACEmB,WAAW,EACXC,mBAAmB,EACnBC,mBAAmB,EACnBC,gBAAgB,EAChBC,gBAAgB,EAChBC,gBAAgB,QACX,sBAAsB;AAC7B,SACEC,wBAAwB,EACxBC,sBAAsB,EACtBC,wBAAwB,QACnB,0BAA0B;AAOjC,SAAQC,oBAAoB,EAAEC,aAAa,QAAO,gBAAgB;AAElE,SAAQC,eAAe,QAAO,uBAAuB;AAGrD,SACEC,yBAAyB,EACzBC,4BAA4B,EAC5BC,uBAAuB,EACvBC,6BAA6B,EAC7BC,sBAAsB,EACtBC,8BAA8B,EAC9BC,qBAAqB,EACrBC,+BAA+B,EAC/BC,uBAAuB,EACvBC,mBAAmB,EACnBC,qBAAqB,EACrBC,iBAAiB,QACZ,kBAAkB;AACzB,SACEC,kBAAkB,EAClBC,kBAAkB,EAClBC,mBAAmB,QACd,2BAA2B;AAElC,SACEC,uBAAuB,EACvBC,wBAAwB,QACnB,gCAAgC;AACvC,SACE5C,6BAA6B,EAC7B6C,kCAAkC,QAC7B,yBAAyB;AAChC,SAEEzC,mBAAmB,EACnB0C,8BAA8B,QAGzB,iCAAiC;AACxC,SAAQzC,0BAA0B,QAAO,yCAAyC;AAClF,SACE0C,gBAAgB,EAChBC,uBAAuB,EACvBC,qBAAqB,EACrBC,qBAAqB,QAChB,uCAAuC;AAC9C,SAAQ5C,6BAA6B,QAAO,4CAA4C;AAExF,MAAM6C,aAAarD;AAOnB,OAAO,SAASsD,iBAAiB,EAC/BC,UAAU,EACVC,eAAetD,+BAA+B,EACtB;IACxB,OAAO;QACLuD,MAAM;QACNC,UAAU;YAACvD;YAAIC;YAAgBuD,mBAAmB;QAAM;QACxDC,MAAM;YAACtD;YAAuBC;YAA8BC;SAAgC;QAC5FqD,cAAc7C,qBAAqBf,OAAO6D,qBAAqB;QAC/DC,QAAQ;YACNjD,gBAAgBb,OAAO6D,qBAAqB,EAAEP,YAAYC;YAC1D7C;YACAC;YACAC;SACD;QACDmD,WAAW;YAACvD,oBAAoB8C;SAAY;QAC5CU,YAAY;YAAC;gBAACR,MAAM;gBAAQS,OAAO7D;gBAAYF;gBAAIgE,cAAclD;YAAyB;SAAE;QAC5FmD,aAAa;YAACf,WAAWxD,oCAAoCkB;SAA8B;QAC3FsD,0BAA0B;YAAC3D;SAAoC;IACjE;AACF"}
@@ -1,7 +1,7 @@
1
1
  export type AuthTokenType = 'session' | 'job_lease' | 'runner_session';
2
2
  export type AuthTokenVerificationOutcome = 'ok' | 'rejected';
3
3
  export type AuthTokenRefreshOutcome = 'rotated' | 'grace' | 'rejected';
4
- export type AuthRateLimitAction = 'login' | 'email-send' | 'bootstrap' | 'lookup';
4
+ export type AuthRateLimitAction = 'login' | 'email-send' | 'bootstrap' | 'bootstrap-state' | 'lookup';
5
5
  export type AuthRateLimitScope = 'ip' | 'email';
6
6
  export type AuthRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';
7
7
  export declare function recordTokenIssued(tokenType: AuthTokenType): void;
@@ -1 +1 @@
1
- {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/metrics/instance.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,gBAAgB,CAAC;AACvE,MAAM,MAAM,4BAA4B,GAAG,IAAI,GAAG,UAAU,CAAC;AAC7D,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;AAClF,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC;AAChD,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;AAsCzE,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAEhE;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,4BAA4B,GACpC,IAAI,CAEN;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAE3E;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,oBAAoB,CAAC;CAC/B,GAAG,IAAI,CAQP;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD"}
1
+ {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/metrics/instance.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,gBAAgB,CAAC;AACvE,MAAM,MAAM,4BAA4B,GAAG,IAAI,GAAG,UAAU,CAAC;AAC7D,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAC3B,OAAO,GACP,YAAY,GACZ,WAAW,GACX,iBAAiB,GACjB,QAAQ,CAAC;AACb,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC;AAChD,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;AAsCzE,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAEhE;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,4BAA4B,GACpC,IAAI,CAEN;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAE3E;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,oBAAoB,CAAC;CAC/B,GAAG,IAAI,CAQP;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/metrics/instance.ts"],"sourcesContent":["import {instanceMetrics} from '@shipfox/node-opentelemetry';\n\nexport type AuthTokenType = 'session' | 'job_lease' | 'runner_session';\nexport type AuthTokenVerificationOutcome = 'ok' | 'rejected';\nexport type AuthTokenRefreshOutcome = 'rotated' | 'grace' | 'rejected';\nexport type AuthRateLimitAction = 'login' | 'email-send' | 'bootstrap' | 'lookup';\nexport type AuthRateLimitScope = 'ip' | 'email';\nexport type AuthRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';\n\nconst meter = instanceMetrics.getMeter('auth');\n\nconst tokenIssuedCount = meter.createCounter<{token_type: AuthTokenType}>('auth_token_issued', {\n description: 'Tokens issued by token type',\n});\n\nconst tokenVerifiedCount = meter.createCounter<{\n token_type: AuthTokenType;\n outcome: AuthTokenVerificationOutcome;\n}>('auth_token_verified', {description: 'Token verification attempts by token type and outcome'});\n\nconst tokenRefreshedCount = meter.createCounter<{outcome: AuthTokenRefreshOutcome}>(\n 'auth_token_refreshed',\n {description: 'Refresh-token exchanges by outcome'},\n);\n\nconst rateLimitCheckCount = meter.createCounter<{\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n outcome: AuthRateLimitOutcome;\n}>('auth_rate_limit_checks', {\n description: 'Authentication rate limit checks by action, scope, and outcome',\n});\n\nconst rateLimitPruneFailureCount = meter.createCounter('auth_rate_limit_prune_failures', {\n description: 'Authentication rate limit prune failures',\n});\n\nfunction recordMetric(record: () => void): void {\n try {\n record();\n } catch {\n // Metrics must not affect authentication outcomes.\n }\n}\n\nexport function recordTokenIssued(tokenType: AuthTokenType): void {\n recordMetric(() => tokenIssuedCount.add(1, {token_type: tokenType}));\n}\n\nexport function recordTokenVerified(\n tokenType: AuthTokenType,\n outcome: AuthTokenVerificationOutcome,\n): void {\n recordMetric(() => tokenVerifiedCount.add(1, {token_type: tokenType, outcome}));\n}\n\nexport function recordTokenRefreshed(outcome: AuthTokenRefreshOutcome): void {\n recordMetric(() => tokenRefreshedCount.add(1, {outcome}));\n}\n\nexport function recordAuthRateLimitCheck(params: {\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n outcome: AuthRateLimitOutcome;\n}): void {\n recordMetric(() =>\n rateLimitCheckCount.add(1, {\n action: params.action,\n scope: params.scope,\n outcome: params.outcome,\n }),\n );\n}\n\nexport function recordAuthRateLimitPruneFailure(): void {\n recordMetric(() => rateLimitPruneFailureCount.add(1));\n}\n"],"names":["instanceMetrics","meter","getMeter","tokenIssuedCount","createCounter","description","tokenVerifiedCount","tokenRefreshedCount","rateLimitCheckCount","rateLimitPruneFailureCount","recordMetric","record","recordTokenIssued","tokenType","add","token_type","recordTokenVerified","outcome","recordTokenRefreshed","recordAuthRateLimitCheck","params","action","scope","recordAuthRateLimitPruneFailure"],"mappings":"AAAA,SAAQA,eAAe,QAAO,8BAA8B;AAS5D,MAAMC,QAAQD,gBAAgBE,QAAQ,CAAC;AAEvC,MAAMC,mBAAmBF,MAAMG,aAAa,CAA8B,qBAAqB;IAC7FC,aAAa;AACf;AAEA,MAAMC,qBAAqBL,MAAMG,aAAa,CAG3C,uBAAuB;IAACC,aAAa;AAAuD;AAE/F,MAAME,sBAAsBN,MAAMG,aAAa,CAC7C,wBACA;IAACC,aAAa;AAAoC;AAGpD,MAAMG,sBAAsBP,MAAMG,aAAa,CAI5C,0BAA0B;IAC3BC,aAAa;AACf;AAEA,MAAMI,6BAA6BR,MAAMG,aAAa,CAAC,kCAAkC;IACvFC,aAAa;AACf;AAEA,SAASK,aAAaC,MAAkB;IACtC,IAAI;QACFA;IACF,EAAE,OAAM;IACN,mDAAmD;IACrD;AACF;AAEA,OAAO,SAASC,kBAAkBC,SAAwB;IACxDH,aAAa,IAAMP,iBAAiBW,GAAG,CAAC,GAAG;YAACC,YAAYF;QAAS;AACnE;AAEA,OAAO,SAASG,oBACdH,SAAwB,EACxBI,OAAqC;IAErCP,aAAa,IAAMJ,mBAAmBQ,GAAG,CAAC,GAAG;YAACC,YAAYF;YAAWI;QAAO;AAC9E;AAEA,OAAO,SAASC,qBAAqBD,OAAgC;IACnEP,aAAa,IAAMH,oBAAoBO,GAAG,CAAC,GAAG;YAACG;QAAO;AACxD;AAEA,OAAO,SAASE,yBAAyBC,MAIxC;IACCV,aAAa,IACXF,oBAAoBM,GAAG,CAAC,GAAG;YACzBO,QAAQD,OAAOC,MAAM;YACrBC,OAAOF,OAAOE,KAAK;YACnBL,SAASG,OAAOH,OAAO;QACzB;AAEJ;AAEA,OAAO,SAASM;IACdb,aAAa,IAAMD,2BAA2BK,GAAG,CAAC;AACpD"}
1
+ {"version":3,"sources":["../../src/metrics/instance.ts"],"sourcesContent":["import {instanceMetrics} from '@shipfox/node-opentelemetry';\n\nexport type AuthTokenType = 'session' | 'job_lease' | 'runner_session';\nexport type AuthTokenVerificationOutcome = 'ok' | 'rejected';\nexport type AuthTokenRefreshOutcome = 'rotated' | 'grace' | 'rejected';\nexport type AuthRateLimitAction =\n | 'login'\n | 'email-send'\n | 'bootstrap'\n | 'bootstrap-state'\n | 'lookup';\nexport type AuthRateLimitScope = 'ip' | 'email';\nexport type AuthRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';\n\nconst meter = instanceMetrics.getMeter('auth');\n\nconst tokenIssuedCount = meter.createCounter<{token_type: AuthTokenType}>('auth_token_issued', {\n description: 'Tokens issued by token type',\n});\n\nconst tokenVerifiedCount = meter.createCounter<{\n token_type: AuthTokenType;\n outcome: AuthTokenVerificationOutcome;\n}>('auth_token_verified', {description: 'Token verification attempts by token type and outcome'});\n\nconst tokenRefreshedCount = meter.createCounter<{outcome: AuthTokenRefreshOutcome}>(\n 'auth_token_refreshed',\n {description: 'Refresh-token exchanges by outcome'},\n);\n\nconst rateLimitCheckCount = meter.createCounter<{\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n outcome: AuthRateLimitOutcome;\n}>('auth_rate_limit_checks', {\n description: 'Authentication rate limit checks by action, scope, and outcome',\n});\n\nconst rateLimitPruneFailureCount = meter.createCounter('auth_rate_limit_prune_failures', {\n description: 'Authentication rate limit prune failures',\n});\n\nfunction recordMetric(record: () => void): void {\n try {\n record();\n } catch {\n // Metrics must not affect authentication outcomes.\n }\n}\n\nexport function recordTokenIssued(tokenType: AuthTokenType): void {\n recordMetric(() => tokenIssuedCount.add(1, {token_type: tokenType}));\n}\n\nexport function recordTokenVerified(\n tokenType: AuthTokenType,\n outcome: AuthTokenVerificationOutcome,\n): void {\n recordMetric(() => tokenVerifiedCount.add(1, {token_type: tokenType, outcome}));\n}\n\nexport function recordTokenRefreshed(outcome: AuthTokenRefreshOutcome): void {\n recordMetric(() => tokenRefreshedCount.add(1, {outcome}));\n}\n\nexport function recordAuthRateLimitCheck(params: {\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n outcome: AuthRateLimitOutcome;\n}): void {\n recordMetric(() =>\n rateLimitCheckCount.add(1, {\n action: params.action,\n scope: params.scope,\n outcome: params.outcome,\n }),\n );\n}\n\nexport function recordAuthRateLimitPruneFailure(): void {\n recordMetric(() => rateLimitPruneFailureCount.add(1));\n}\n"],"names":["instanceMetrics","meter","getMeter","tokenIssuedCount","createCounter","description","tokenVerifiedCount","tokenRefreshedCount","rateLimitCheckCount","rateLimitPruneFailureCount","recordMetric","record","recordTokenIssued","tokenType","add","token_type","recordTokenVerified","outcome","recordTokenRefreshed","recordAuthRateLimitCheck","params","action","scope","recordAuthRateLimitPruneFailure"],"mappings":"AAAA,SAAQA,eAAe,QAAO,8BAA8B;AAc5D,MAAMC,QAAQD,gBAAgBE,QAAQ,CAAC;AAEvC,MAAMC,mBAAmBF,MAAMG,aAAa,CAA8B,qBAAqB;IAC7FC,aAAa;AACf;AAEA,MAAMC,qBAAqBL,MAAMG,aAAa,CAG3C,uBAAuB;IAACC,aAAa;AAAuD;AAE/F,MAAME,sBAAsBN,MAAMG,aAAa,CAC7C,wBACA;IAACC,aAAa;AAAoC;AAGpD,MAAMG,sBAAsBP,MAAMG,aAAa,CAI5C,0BAA0B;IAC3BC,aAAa;AACf;AAEA,MAAMI,6BAA6BR,MAAMG,aAAa,CAAC,kCAAkC;IACvFC,aAAa;AACf;AAEA,SAASK,aAAaC,MAAkB;IACtC,IAAI;QACFA;IACF,EAAE,OAAM;IACN,mDAAmD;IACrD;AACF;AAEA,OAAO,SAASC,kBAAkBC,SAAwB;IACxDH,aAAa,IAAMP,iBAAiBW,GAAG,CAAC,GAAG;YAACC,YAAYF;QAAS;AACnE;AAEA,OAAO,SAASG,oBACdH,SAAwB,EACxBI,OAAqC;IAErCP,aAAa,IAAMJ,mBAAmBQ,GAAG,CAAC,GAAG;YAACC,YAAYF;YAAWI;QAAO;AAC9E;AAEA,OAAO,SAASC,qBAAqBD,OAAgC;IACnEP,aAAa,IAAMH,oBAAoBO,GAAG,CAAC,GAAG;YAACG;QAAO;AACxD;AAEA,OAAO,SAASE,yBAAyBC,MAIxC;IACCV,aAAa,IACXF,oBAAoBM,GAAG,CAAC,GAAG;YACzBO,QAAQD,OAAOC,MAAM;YACrBC,OAAOF,OAAOE,KAAK;YACnBL,SAASG,OAAOH,OAAO;QACzB;AAEJ;AAEA,OAAO,SAASM;IACdb,aAAa,IAAMD,2BAA2BK,GAAG,CAAC;AACpD"}
@@ -1,4 +1,5 @@
1
1
  import { type RouteGroup } from '@shipfox/node-fastify';
2
2
  export declare const administrationRoutes: RouteGroup;
3
+ export declare const administrationBootstrapRoutes: RouteGroup;
3
4
  export declare const administrationUserRoutes: RouteGroup;
4
5
  //# sourceMappingURL=administration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"administration.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/administration.ts"],"names":[],"mappings":"AAcA,OAAO,EAA2B,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AA2PhF,eAAO,MAAM,oBAAoB,EAAE,UAIlC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,UAItC,CAAC"}
1
+ {"version":3,"file":"administration.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/administration.ts"],"names":[],"mappings":"AAeA,OAAO,EAA2B,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AAsQhF,eAAO,MAAM,oBAAoB,EAAE,UAIlC,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,UAI3C,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,UAItC,CAAC"}
@@ -1,10 +1,10 @@
1
1
  import { AUTH_USER } from '@shipfox/api-auth-context';
2
- import { administratorUserLookupQuerySchema, administratorUserSummarySchema, bootstrapAdminOwnerBodySchema, bootstrapAdminOwnerResponseSchema, grantAdminRoleBodySchema, grantAdminRoleResponseSchema, listAdminGrantsQuerySchema, listAdminGrantsResponseSchema, revokeAdminGrantBodySchema, revokeAdminGrantResponseSchema } from '@shipfox/api-auth-dto';
2
+ import { adminBootstrapStateSchema, administratorUserLookupQuerySchema, administratorUserSummarySchema, bootstrapAdminOwnerBodySchema, bootstrapAdminOwnerResponseSchema, grantAdminRoleBodySchema, grantAdminRoleResponseSchema, listAdminGrantsQuerySchema, listAdminGrantsResponseSchema, revokeAdminGrantBodySchema, revokeAdminGrantResponseSchema } from '@shipfox/api-auth-dto';
3
3
  import { decodeTimestampIdCursor, encodeTimestampIdCursor } from '@shipfox/node-drizzle';
4
4
  import { ClientError, defineRoute } from '@shipfox/node-fastify';
5
5
  import { z } from 'zod';
6
6
  import { requireAdminRole } from '#core/admin-role.js';
7
- import { bootstrapFirstAdminOwner, findAdministratorUserSummary, grantAdministratorRole, listAdministratorGrantSummaries, revokeAdministratorGrant } from '#core/administration.js';
7
+ import { bootstrapFirstAdminOwner, findAdministratorUserSummary, getAdminBootstrapState, grantAdministratorRole, listAdministratorGrantSummaries, revokeAdministratorGrant } from '#core/administration.js';
8
8
  import { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, AdminRoleRequiredError, InvalidAdminBootstrapTokenError, LastAdminOwnerError, UserNotFoundError } from '#core/errors.js';
9
9
  import { getClientContext } from '#presentation/auth/jwt-auth.js';
10
10
  import { createAuthIpRateLimitPreHandler } from './rate-limit.js';
@@ -134,6 +134,21 @@ const bootstrapRoute = defineRoute({
134
134
  return toAdminGrantDto(grant);
135
135
  }
136
136
  });
137
+ const bootstrapStateRoute = defineRoute({
138
+ method: 'GET',
139
+ path: '/bootstrap-state',
140
+ description: 'Read whether first administrator owner bootstrap is available.',
141
+ schema: {
142
+ response: {
143
+ 200: adminBootstrapStateSchema
144
+ }
145
+ },
146
+ preHandler: createAuthIpRateLimitPreHandler('bootstrap-state'),
147
+ errorHandler: translateAdministrationError,
148
+ handler: async ()=>({
149
+ state: await getAdminBootstrapState()
150
+ })
151
+ });
137
152
  const listRoute = defineRoute({
138
153
  method: 'GET',
139
154
  path: '/',
@@ -256,6 +271,13 @@ export const administrationRoutes = {
256
271
  revokeRoute
257
272
  ]
258
273
  };
274
+ export const administrationBootstrapRoutes = {
275
+ prefix: '/admin/auth',
276
+ auth: AUTH_USER,
277
+ routes: [
278
+ bootstrapStateRoute
279
+ ]
280
+ };
259
281
  export const administrationUserRoutes = {
260
282
  prefix: '/admin/auth/users',
261
283
  auth: AUTH_USER,