@shipfox/api-auth 10.1.0 → 11.0.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 (84) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +30 -0
  3. package/README.md +42 -11
  4. package/dist/core/administration.d.ts +17 -1
  5. package/dist/core/administration.d.ts.map +1 -1
  6. package/dist/core/administration.js +107 -14
  7. package/dist/core/administration.js.map +1 -1
  8. package/dist/core/auth.d.ts.map +1 -1
  9. package/dist/core/auth.js +27 -10
  10. package/dist/core/auth.js.map +1 -1
  11. package/dist/core/jwt.d.ts +10 -0
  12. package/dist/core/jwt.d.ts.map +1 -1
  13. package/dist/core/jwt.js +3 -2
  14. package/dist/core/jwt.js.map +1 -1
  15. package/dist/db/admin-command.d.ts +18 -0
  16. package/dist/db/admin-command.d.ts.map +1 -0
  17. package/dist/db/admin-command.js +37 -0
  18. package/dist/db/admin-command.js.map +1 -0
  19. package/dist/db/admin-grants.d.ts +1 -1
  20. package/dist/db/admin-grants.d.ts.map +1 -1
  21. package/dist/db/admin-grants.js +12 -30
  22. package/dist/db/admin-grants.js.map +1 -1
  23. package/dist/db/admin-user-moderation.d.ts +29 -0
  24. package/dist/db/admin-user-moderation.d.ts.map +1 -0
  25. package/dist/db/admin-user-moderation.js +156 -0
  26. package/dist/db/admin-user-moderation.js.map +1 -0
  27. package/dist/db/admin-user-summary.d.ts +14 -0
  28. package/dist/db/admin-user-summary.d.ts.map +1 -0
  29. package/dist/db/admin-user-summary.js +31 -0
  30. package/dist/db/admin-user-summary.js.map +1 -0
  31. package/dist/db/admin-users.d.ts +2 -11
  32. package/dist/db/admin-users.d.ts.map +1 -1
  33. package/dist/db/admin-users.js +2 -27
  34. package/dist/db/admin-users.js.map +1 -1
  35. package/dist/db/refresh-tokens.d.ts +6 -4
  36. package/dist/db/refresh-tokens.d.ts.map +1 -1
  37. package/dist/db/refresh-tokens.js +31 -6
  38. package/dist/db/refresh-tokens.js.map +1 -1
  39. package/dist/db/schema/admin-command-results.d.ts +19 -2
  40. package/dist/db/schema/admin-command-results.d.ts.map +1 -1
  41. package/dist/db/schema/admin-command-results.js.map +1 -1
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +1 -1
  45. package/dist/index.js.map +1 -1
  46. package/dist/presentation/auth/bearer-token-auth.d.ts +1 -0
  47. package/dist/presentation/auth/bearer-token-auth.d.ts.map +1 -1
  48. package/dist/presentation/auth/bearer-token-auth.js +4 -3
  49. package/dist/presentation/auth/bearer-token-auth.js.map +1 -1
  50. package/dist/presentation/auth/jwt-auth.d.ts +1 -4
  51. package/dist/presentation/auth/jwt-auth.d.ts.map +1 -1
  52. package/dist/presentation/auth/jwt-auth.js +23 -19
  53. package/dist/presentation/auth/jwt-auth.js.map +1 -1
  54. package/dist/presentation/routes/administration.d.ts.map +1 -1
  55. package/dist/presentation/routes/administration.js +94 -3
  56. package/dist/presentation/routes/administration.js.map +1 -1
  57. package/dist/tsconfig.test.tsbuildinfo +1 -1
  58. package/package.json +4 -4
  59. package/src/core/administration.ts +137 -15
  60. package/src/core/auth.test.ts +29 -10
  61. package/src/core/auth.ts +36 -14
  62. package/src/core/jwt.test.ts +20 -4
  63. package/src/core/jwt.ts +2 -1
  64. package/src/db/admin-command.ts +85 -0
  65. package/src/db/admin-grants.ts +19 -57
  66. package/src/db/admin-user-moderation.ts +230 -0
  67. package/src/db/admin-user-summary.ts +51 -0
  68. package/src/db/admin-users.ts +4 -49
  69. package/src/db/refresh-tokens.test.ts +0 -19
  70. package/src/db/refresh-tokens.ts +54 -23
  71. package/src/db/schema/admin-command-results.ts +23 -2
  72. package/src/index.ts +3 -0
  73. package/src/presentation/auth/bearer-token-auth.test.ts +17 -0
  74. package/src/presentation/auth/bearer-token-auth.ts +9 -2
  75. package/src/presentation/auth/jwt-auth.test.ts +34 -9
  76. package/src/presentation/auth/jwt-auth.ts +19 -13
  77. package/src/presentation/routes/administration.test.ts +253 -2
  78. package/src/presentation/routes/administration.ts +88 -1
  79. package/src/presentation/routes/password/change-password.test.ts +6 -0
  80. package/src/presentation/routes/password/password-reset-confirm.test.ts +14 -0
  81. package/src/presentation/routes/registration/signup.test.ts +2 -2
  82. package/src/presentation/routes/session/logout.test.ts +6 -0
  83. package/src/presentation/routes/session/refresh.test.ts +11 -0
  84. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 64 files with swc (496.09ms)
2
+ Successfully compiled: 67 files with swc (446.61ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @shipfox/api-auth
2
2
 
3
+ ## 11.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 25158c8: Carry workspace lifecycle status in JWT membership claims and enforce suspended or inactive access at the stateless workspace gate while keeping access-token verification stateless.
8
+
9
+ `getAuthenticatedSessionContext()` now reads refresh-session metadata from verified access-token claims without checking active refresh-session state; revoking a refresh session does not invalidate an already-issued access token.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [25158c8]
14
+ - @shipfox/api-auth-context@11.0.0
15
+ - @shipfox/api-workspaces-dto@11.0.0
16
+
17
+ ## 10.2.0
18
+
19
+ ### Minor Changes
20
+
21
+ - 95d1456: Add administrator user suspension, reactivation, and all-session revocation commands.
22
+
23
+ ### Patch Changes
24
+
25
+ - 07e7371: Add idempotent administrator workspace suspension and reactivation commands with atomic redacted administration events.
26
+ - Updated dependencies [95d1456]
27
+ - Updated dependencies [0773b85]
28
+ - Updated dependencies [07e7371]
29
+ - @shipfox/api-auth-dto@10.2.0
30
+ - @shipfox/api-workspaces-dto@10.2.0
31
+ - @shipfox/api-auth-context@10.2.0
32
+
3
33
  ## 10.1.0
4
34
 
5
35
  ### Minor Changes
package/README.md CHANGED
@@ -70,6 +70,17 @@ Required environment:
70
70
  | `CLIENT_BASE_URL` | `http://localhost:5173` | Base URL used in email verification and password reset links. |
71
71
  | `ADMIN_BOOTSTRAP_TOKEN` | none | Deployment secret accepted once to create the first administrator owner. Set it before bootstrap and remove or rotate it after successful bootstrap. |
72
72
 
73
+ The recommended access-token lifetime is 15 minutes. Because user JWTs are
74
+ verified from their claims, a token issued before logout, password changes,
75
+ session revocation, or workspace suspension remains usable until its configured
76
+ `exp` time. Logout and session revocation block refresh-session renewal, while
77
+ password changes revoke other refresh sessions and retain the current session
78
+ when its refresh cookie is valid. Workspace suspension is carried into the next
79
+ status-aware snapshot, where the stateless workspace gate rejects member access;
80
+ none of these actions retroactively invalidate an issued access token. Newly
81
+ issued and refreshed tokens carry workspace lifecycle status so the gate can
82
+ return the stable suspended or inactive access result.
83
+
73
84
  Email delivery uses the shared `@shipfox/node-mailer` configuration.
74
85
 
75
86
  The signup gate is off by default. When it is on, provide at least one non-empty
@@ -99,8 +110,9 @@ itself.
99
110
  ### User session token
100
111
 
101
112
  - **Design:** a stateless session token. It carries the user's identity and
102
- current membership list in its claims so protected routes can authorize without
103
- a per-request user or membership lookup.
113
+ workspace membership list, including each workspace's lifecycle status, in
114
+ its claims so protected routes can authorize without a per-request user or
115
+ membership lookup.
104
116
  - **Audience:** signed-in users on first-party clients (web app, CLI).
105
117
  - **Grants:** the user's own account routes and, downstream, any route that
106
118
  authorizes against the membership claims. Its scope is "this user, with these
@@ -111,12 +123,12 @@ itself.
111
123
  fresh token when it expires.
112
124
  - *Store* — held in client memory only, never persisted to disk or local
113
125
  storage. Refresh sessions are stored server-side as hashes, never in raw form.
114
- - *Discard* — expires on its own (minutes, not hours); the refresh session is
115
- revoked on logout and on password change.
126
+ - *Discard* — expires at the configured `exp` time. Revoking the refresh
127
+ session blocks renewal but does not invalidate the issued access token.
116
128
  - **Tradeoff — stale memberships:** because memberships ride in the claims, a
117
- membership change only takes effect on the next refresh. Accepted because the
118
- token is short-lived, so the staleness window is bounded and a per-request
119
- membership read is avoided.
129
+ membership or workspace-status change only takes effect on the next token
130
+ issuance or refresh. Accepted because the recommended lifetime is 15 minutes,
131
+ so the staleness window is bounded and a per-request membership read is avoided.
120
132
 
121
133
  ### Runner session token
122
134
 
@@ -273,6 +285,20 @@ This app-layer limiter protects semantic auth work such as Argon2 verification a
273
285
 
274
286
  `GET /admin/auth/admin-grants` also requires `admin-observer`. It returns at most 100 newest-first `AdministratorGrantSummary` rows per page, with an opaque cursor and a safe embedded user identity. It does not return credentials, sessions, provider payloads, OAuth tokens, refresh tokens, or raw authentication metadata.
275
287
 
288
+ ### Administrator user moderation
289
+
290
+ All routes are mounted under `/admin/auth/users` and require an authenticated bearer token.
291
+
292
+ | Method | Path | Required role | Result |
293
+ | --- | --- | --- | --- |
294
+ | `POST` | `/:userId/suspend` | `admin-operator` | Suspends the user, revokes all refresh sessions, and returns the final safe user summary with a correlation ID. Issued access tokens remain valid until `exp`. |
295
+ | `POST` | `/:userId/reactivate` | `admin-operator` | Restores sign-in eligibility without restoring revoked sessions. |
296
+ | `POST` | `/:userId/revoke-sessions` | `admin-operator` | Revokes all refresh sessions without changing account status and returns the number of affected sessions. Issued access tokens remain valid until `exp`. |
297
+
298
+ Each command requires an `Idempotency-Key`. Suspension requires a bounded reason; reactivation and session revocation accept an optional bounded reason. Repeating a successful command returns its committed result. Reusing a key for a different command or request returns `409 idempotency-key-reused`.
299
+
300
+ Suspension preserves the user record and rejects the final active administrator owner. State, the command result, and one redacted `administration.action.performed` outbox event commit atomically. The event stores only the idempotency-key fingerprint, never the raw key or session material.
301
+
276
302
  ## API
277
303
 
278
304
  The package exports a module factory. Pass it the Workspaces inter-module client from the
@@ -297,7 +323,7 @@ It also exports lower-level pieces for tests and advanced integration:
297
323
  - `issueJobLeaseToken(claims)` / `verifyJobLeaseToken(token)`: mint and verify job lease tokens.
298
324
  - `createEnvironmentSignupPolicy()`: creates the environment-backed signup policy used by default. Pass `signupPolicy` to `createAuthModule` to replace it.
299
325
  - `getClientContext(request)`: reads the authenticated user context from a Fastify request.
300
- - `getAuthenticatedSessionContext(request)`: resolves an authenticated request to its user ID and active refresh-session ID.
326
+ - `getAuthenticatedSessionContext(request)`: reads the user ID and required refresh-session ID from verified access-token claims. It does not check whether the refresh session is still active.
301
327
  - `findUserByEmail({email})`: read-only lookup of the current owner of a normalized email; see below.
302
328
  - Entity types: `User`, `UserStatus`, `RefreshToken`, `PasswordReset`, and `EmailOwner`.
303
329
 
@@ -402,9 +428,9 @@ const session = await getAuthenticatedSessionContext(request);
402
428
  ```
403
429
 
404
430
  The refresh-session ID remains the same when its refresh token rotates. The
405
- helper verifies that the session is active and belongs to the authenticated user,
406
- and returns the standard `401 unauthorized` error for a missing, malformed,
407
- expired, or revoked session. It exposes neither refresh-token material nor
431
+ helper does not query the refresh-session table. It returns the standard `401
432
+ unauthorized` error when the verified request context is missing, inconsistent,
433
+ or has no refresh-session ID. It exposes neither refresh-token material nor
408
434
  storage details.
409
435
 
410
436
  ## Data Model
@@ -427,6 +453,11 @@ Passwords use Argon2id. Password reset tokens and refresh tokens are opaque toke
427
453
  - A denied password signup returns `403` with error code `signup-not-allowed`. The policy message is capped at 500 characters.
428
454
  - Invitation signup validates the invitation and bypasses the signup policy. Invitations remain a separate authorization path.
429
455
  - Login only succeeds for active users with verified email addresses and a password hash.
456
+ - Login and refresh reject suspended or deleted users. Current-state reads such
457
+ as `/auth/me` still return the persisted suspended-user status. Ordinary JWT
458
+ verification does not query user or refresh-session rows.
459
+ - Revoking a refresh session blocks renewal. An already-issued access token
460
+ remains valid until its configured `exp` time.
430
461
  - Refresh tokens rotate on each refresh.
431
462
  - Password reset tokens and email challenge proofs are consumed once.
432
463
  - Password change revokes other refresh sessions. It keeps the current session when the current refresh cookie is valid.
@@ -3,13 +3,17 @@ import type { TimestampIdCursor } from '@shipfox/node-drizzle';
3
3
  import type { AdminGrant } from './entities/admin-grant.js';
4
4
  import type { AdministratorGrantSummary, AdministratorUserSummary } from './entities/administrator-read-model.js';
5
5
  import { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, InvalidAdminBootstrapTokenError, LastAdminOwnerError, UserNotFoundError } from './errors.js';
6
- export declare function hashAdministrationValue(value: string): string;
7
6
  export declare function administrationCommandFingerprint(command: string, input: unknown): string;
8
7
  export interface AdministrationMutationContext {
9
8
  actorId: string;
10
9
  idempotencyKey: string;
11
10
  correlationId: string;
12
11
  }
12
+ export interface AdministratorUserMutationResult {
13
+ user: AdministratorUserSummary;
14
+ correlationId: string;
15
+ sessionsRevoked: number;
16
+ }
13
17
  export declare function bootstrapFirstAdminOwner(params: AdministrationMutationContext & {
14
18
  bootstrapToken: string;
15
19
  }): Promise<AdminGrant>;
@@ -40,5 +44,17 @@ export declare function revokeAdministratorGrant(params: AdministrationMutationC
40
44
  grantId: string;
41
45
  reason: string;
42
46
  }): Promise<AdminGrant>;
47
+ export declare function suspendAdministratorUser(params: AdministrationMutationContext & {
48
+ userId: string;
49
+ reason: string;
50
+ }): Promise<AdministratorUserMutationResult>;
51
+ export declare function reactivateAdministratorUser(params: AdministrationMutationContext & {
52
+ userId: string;
53
+ reason?: string;
54
+ }): Promise<AdministratorUserMutationResult>;
55
+ export declare function revokeAdministratorUserSessions(params: AdministrationMutationContext & {
56
+ userId: string;
57
+ reason?: string;
58
+ }): Promise<AdministratorUserMutationResult>;
43
59
  export { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, InvalidAdminBootstrapTokenError, LastAdminOwnerError, UserNotFoundError, };
44
60
  //# sourceMappingURL=administration.d.ts.map
@@ -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;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
+ {"version":3,"file":"administration.d.ts","sourceRoot":"","sources":["../../src/core/administration.ts"],"names":[],"mappings":"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"}
@@ -1,32 +1,35 @@
1
- import { createHash, timingSafeEqual } from 'node:crypto';
1
+ import { timingSafeEqual } from 'node:crypto';
2
2
  import { createAdministrationActionEvent } from '@shipfox/api-common-dto';
3
+ import { hashOpaqueToken } from '@shipfox/node-tokens';
3
4
  import { config } from '#config.js';
4
5
  import { bootstrapFirstAdminOwner as bootstrapFirstAdminOwnerInDb, grantAdminRoleWithAudit, hasActiveAdminOwner, listAdminGrantSummaries, revokeAdminGrantWithAudit } from '#db/admin-grants.js';
6
+ import { reactivateUserWithAudit, revokeUserSessionsWithAudit, suspendUserWithAudit } from '#db/admin-user-moderation.js';
5
7
  import { findAdministratorUser as findAdministratorUserInDb } from '#db/admin-users.js';
6
8
  import { requireAdminRole } from './admin-role.js';
7
9
  import { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, InvalidAdminBootstrapTokenError, LastAdminOwnerError, UserNotFoundError } from './errors.js';
8
10
  const ADMIN_OWNER_ROLE = 'admin-owner';
9
11
  const ADMIN_OBSERVER_ROLE = 'admin-observer';
12
+ const ADMIN_OPERATOR_ROLE = 'admin-operator';
10
13
  const BOOTSTRAP_COMMAND = 'auth.admin_grant.bootstrap';
11
14
  const GRANT_COMMAND = 'auth.admin_grant.grant';
12
15
  const REVOKE_COMMAND = 'auth.admin_grant.revoke';
13
- export function hashAdministrationValue(value) {
14
- return createHash('sha256').update(value).digest('hex');
15
- }
16
+ const SUSPEND_USER_COMMAND = 'auth.user.suspend';
17
+ const REACTIVATE_USER_COMMAND = 'auth.user.reactivate';
18
+ const REVOKE_USER_SESSIONS_COMMAND = 'auth.user.revoke-sessions';
16
19
  export function administrationCommandFingerprint(command, input) {
17
- return hashAdministrationValue(`${command}:${JSON.stringify(input)}`);
20
+ return hashOpaqueToken(`${command}:${JSON.stringify(input)}`);
18
21
  }
19
22
  function bootstrapTokenMatches(candidate, expected) {
20
23
  if (!expected) return false;
21
- const candidateHash = Buffer.from(hashAdministrationValue(candidate), 'hex');
22
- const expectedHash = Buffer.from(hashAdministrationValue(expected), 'hex');
24
+ const candidateHash = Buffer.from(hashOpaqueToken(candidate), 'hex');
25
+ const expectedHash = Buffer.from(hashOpaqueToken(expected), 'hex');
23
26
  return timingSafeEqual(candidateHash, expectedHash);
24
27
  }
25
28
  function administrationEvent(params) {
26
29
  return createAdministrationActionEvent({
27
30
  actorId: params.actorId,
28
31
  actorRole: params.actorRole,
29
- requiredRole: ADMIN_OWNER_ROLE,
32
+ requiredRole: params.requiredRole ?? ADMIN_OWNER_ROLE,
30
33
  command: params.command,
31
34
  targetType: params.targetType,
32
35
  targetId: params.targetId,
@@ -37,6 +40,13 @@ function administrationEvent(params) {
37
40
  occurredAt: new Date().toISOString()
38
41
  });
39
42
  }
43
+ function toAdministratorUserMutationResult(result) {
44
+ return {
45
+ user: result.user,
46
+ correlationId: result.correlationId,
47
+ sessionsRevoked: result.sessionsRevoked
48
+ };
49
+ }
40
50
  export async function bootstrapFirstAdminOwner(params) {
41
51
  if (!bootstrapTokenMatches(params.bootstrapToken, config.ADMIN_BOOTSTRAP_TOKEN)) {
42
52
  throw new InvalidAdminBootstrapTokenError();
@@ -44,7 +54,7 @@ export async function bootstrapFirstAdminOwner(params) {
44
54
  return await bootstrapFirstAdminOwnerInDb({
45
55
  userId: params.actorId,
46
56
  actorId: params.actorId,
47
- idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),
57
+ idempotencyKeyFingerprint: hashOpaqueToken(params.idempotencyKey),
48
58
  requestFingerprint: administrationCommandFingerprint(BOOTSTRAP_COMMAND, {
49
59
  actorId: params.actorId
50
60
  }),
@@ -56,7 +66,7 @@ export async function bootstrapFirstAdminOwner(params) {
56
66
  targetId: params.actorId,
57
67
  reason: 'Initial administrator owner bootstrap',
58
68
  correlationId: params.correlationId,
59
- idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey)
69
+ idempotencyKeyFingerprint: hashOpaqueToken(params.idempotencyKey)
60
70
  })
61
71
  });
62
72
  }
@@ -107,7 +117,7 @@ export async function grantAdministratorRole(params) {
107
117
  userId: params.userId,
108
118
  role: params.role,
109
119
  actorId: params.actorId,
110
- idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),
120
+ idempotencyKeyFingerprint: hashOpaqueToken(params.idempotencyKey),
111
121
  requestFingerprint: administrationCommandFingerprint(GRANT_COMMAND, {
112
122
  userId: params.userId,
113
123
  role: params.role,
@@ -121,7 +131,7 @@ export async function grantAdministratorRole(params) {
121
131
  targetId: params.userId,
122
132
  reason: params.reason,
123
133
  correlationId: params.correlationId,
124
- idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey)
134
+ idempotencyKeyFingerprint: hashOpaqueToken(params.idempotencyKey)
125
135
  })
126
136
  });
127
137
  }
@@ -133,7 +143,7 @@ export async function revokeAdministratorGrant(params) {
133
143
  return await revokeAdminGrantWithAudit({
134
144
  grantId: params.grantId,
135
145
  actorId: params.actorId,
136
- idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey),
146
+ idempotencyKeyFingerprint: hashOpaqueToken(params.idempotencyKey),
137
147
  requestFingerprint: administrationCommandFingerprint(REVOKE_COMMAND, {
138
148
  grantId: params.grantId,
139
149
  reason: params.reason
@@ -146,9 +156,92 @@ export async function revokeAdministratorGrant(params) {
146
156
  targetId: params.grantId,
147
157
  reason: params.reason,
148
158
  correlationId: params.correlationId,
149
- idempotencyKeyFingerprint: hashAdministrationValue(params.idempotencyKey)
159
+ idempotencyKeyFingerprint: hashOpaqueToken(params.idempotencyKey)
160
+ })
161
+ });
162
+ }
163
+ export async function suspendAdministratorUser(params) {
164
+ const actorRole = await requireAdminRole({
165
+ userId: params.actorId,
166
+ minimumRole: ADMIN_OPERATOR_ROLE
167
+ });
168
+ const idempotencyKeyFingerprint = hashOpaqueToken(params.idempotencyKey);
169
+ return toAdministratorUserMutationResult(await suspendUserWithAudit({
170
+ actorId: params.actorId,
171
+ userId: params.userId,
172
+ idempotencyKeyFingerprint,
173
+ requestFingerprint: administrationCommandFingerprint(SUSPEND_USER_COMMAND, {
174
+ userId: params.userId,
175
+ reason: params.reason
176
+ }),
177
+ event: administrationEvent({
178
+ actorId: params.actorId,
179
+ actorRole,
180
+ requiredRole: ADMIN_OPERATOR_ROLE,
181
+ command: SUSPEND_USER_COMMAND,
182
+ targetType: 'user',
183
+ targetId: params.userId,
184
+ reason: params.reason,
185
+ correlationId: params.correlationId,
186
+ idempotencyKeyFingerprint
150
187
  })
188
+ }));
189
+ }
190
+ export async function reactivateAdministratorUser(params) {
191
+ const actorRole = await requireAdminRole({
192
+ userId: params.actorId,
193
+ minimumRole: ADMIN_OPERATOR_ROLE
151
194
  });
195
+ const reason = params.reason ?? 'User reactivation requested by an administrator';
196
+ const idempotencyKeyFingerprint = hashOpaqueToken(params.idempotencyKey);
197
+ return toAdministratorUserMutationResult(await reactivateUserWithAudit({
198
+ actorId: params.actorId,
199
+ userId: params.userId,
200
+ idempotencyKeyFingerprint,
201
+ requestFingerprint: administrationCommandFingerprint(REACTIVATE_USER_COMMAND, {
202
+ userId: params.userId,
203
+ reason
204
+ }),
205
+ event: administrationEvent({
206
+ actorId: params.actorId,
207
+ actorRole,
208
+ requiredRole: ADMIN_OPERATOR_ROLE,
209
+ command: REACTIVATE_USER_COMMAND,
210
+ targetType: 'user',
211
+ targetId: params.userId,
212
+ reason,
213
+ correlationId: params.correlationId,
214
+ idempotencyKeyFingerprint
215
+ })
216
+ }));
217
+ }
218
+ export async function revokeAdministratorUserSessions(params) {
219
+ const actorRole = await requireAdminRole({
220
+ userId: params.actorId,
221
+ minimumRole: ADMIN_OPERATOR_ROLE
222
+ });
223
+ const reason = params.reason ?? 'All active user sessions revoked by an administrator';
224
+ const idempotencyKeyFingerprint = hashOpaqueToken(params.idempotencyKey);
225
+ return toAdministratorUserMutationResult(await revokeUserSessionsWithAudit({
226
+ actorId: params.actorId,
227
+ userId: params.userId,
228
+ idempotencyKeyFingerprint,
229
+ requestFingerprint: administrationCommandFingerprint(REVOKE_USER_SESSIONS_COMMAND, {
230
+ userId: params.userId,
231
+ reason
232
+ }),
233
+ event: administrationEvent({
234
+ actorId: params.actorId,
235
+ actorRole,
236
+ requiredRole: ADMIN_OPERATOR_ROLE,
237
+ command: REVOKE_USER_SESSIONS_COMMAND,
238
+ targetType: 'user',
239
+ targetId: params.userId,
240
+ reason,
241
+ correlationId: params.correlationId,
242
+ idempotencyKeyFingerprint
243
+ })
244
+ }));
152
245
  }
153
246
  export { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, InvalidAdminBootstrapTokenError, LastAdminOwnerError, UserNotFoundError };
154
247
 
@@ -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 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":"AAAA,SAAQA,UAAU,EAAEC,eAAe,QAAO,cAAc;AAExD,SAAQC,+BAA+B,QAAO,0BAA0B;AAExE,SAAQC,MAAM,QAAO,aAAa;AAClC,SACEC,4BAA4BC,4BAA4B,EACxDC,uBAAuB,EACvBC,mBAAmB,EACnBC,uBAAuB,EACvBC,yBAAyB,QACpB,sBAAsB;AAC7B,SAAQC,yBAAyBC,yBAAyB,QAAO,qBAAqB;AACtF,SAAQC,gBAAgB,QAAO,kBAAkB;AAMjD,SACEC,yBAAyB,EACzBC,4BAA4B,EAC5BC,uBAAuB,EACvBC,6BAA6B,EAC7BC,+BAA+B,EAC/BC,mBAAmB,EACnBC,iBAAiB,QACZ,cAAc;AAErB,MAAMC,mBAA8B;AACpC,MAAMC,sBAAiC;AACvC,MAAMC,oBAAoB;AAC1B,MAAMC,gBAAgB;AACtB,MAAMC,iBAAiB;AAEvB,OAAO,SAASC,wBAAwBC,KAAa;IACnD,OAAO1B,WAAW,UAAU2B,MAAM,CAACD,OAAOE,MAAM,CAAC;AACnD;AAEA,OAAO,SAASC,iCAAiCC,OAAe,EAAEC,KAAc;IAC9E,OAAON,wBAAwB,GAAGK,QAAQ,CAAC,EAAEE,KAAKC,SAAS,CAACF,QAAQ;AACtE;AAEA,SAASG,sBAAsBC,SAAiB,EAAEC,QAA4B;IAC5E,IAAI,CAACA,UAAU,OAAO;IACtB,MAAMC,gBAAgBC,OAAOC,IAAI,CAACd,wBAAwBU,YAAY;IACtE,MAAMK,eAAeF,OAAOC,IAAI,CAACd,wBAAwBW,WAAW;IACpE,OAAOnC,gBAAgBoC,eAAeG;AACxC;AAEA,SAASC,oBAAoBC,MAS5B;IACC,OAAOxC,gCAAgC;QACrCyC,SAASD,OAAOC,OAAO;QACvBC,WAAWF,OAAOE,SAAS;QAC3BC,cAAczB;QACdU,SAASY,OAAOZ,OAAO;QACvBgB,YAAYJ,OAAOI,UAAU;QAC7BC,UAAUL,OAAOK,QAAQ;QACzBC,QAAQN,OAAOM,MAAM;QACrBC,QAAQ;QACRC,eAAeR,OAAOQ,aAAa;QACnCC,2BAA2BT,OAAOS,yBAAyB;QAC3DC,YAAY,IAAIC,OAAOC,WAAW;IACpC;AACF;AAQA,OAAO,eAAelD,yBACpBsC,MAAgE;IAEhE,IAAI,CAACR,sBAAsBQ,OAAOa,cAAc,EAAEpD,OAAOqD,qBAAqB,GAAG;QAC/E,MAAM,IAAIvC;IACZ;IAEA,OAAO,MAAMZ,6BAA6B;QACxCoD,QAAQf,OAAOC,OAAO;QACtBA,SAASD,OAAOC,OAAO;QACvBQ,2BAA2B1B,wBAAwBiB,OAAOgB,cAAc;QACxEC,oBAAoB9B,iCAAiCP,mBAAmB;YACtEqB,SAASD,OAAOC,OAAO;QACzB;QACAiB,OAAOnB,oBAAoB;YACzBE,SAASD,OAAOC,OAAO;YACvBC,WAAWxB;YACXU,SAASR;YACTwB,YAAY;YACZC,UAAUL,OAAOC,OAAO;YACxBK,QAAQ;YACRE,eAAeR,OAAOQ,aAAa;YACnCC,2BAA2B1B,wBAAwBiB,OAAOgB,cAAc;QAC1E;IACF;AACF;AAEA,OAAO,eAAeG;IACpB,OAAO,AAAC,MAAMtD,wBAAyB,WAAW;AACpD;AAEA,OAAO,eAAeuD,6BACpBpB,MAAuF;IAEvF,MAAM9B,iBAAiB;QAAC6C,QAAQf,OAAOC,OAAO;QAAEoB,aAAa1C;IAAmB;IAEhF,MAAM2C,OAAO,MAAMrD,0BACjB,QAAQ+B,SAAS;QAACuB,IAAIvB,OAAOuB,EAAE;IAAA,IAAI;QAACC,OAAOxB,OAAOwB,KAAK;IAAA;IAEzD,IAAI,CAACF,MAAM,OAAOG;IAElB,OAAOH;AACT;AAEA,OAAO,eAAeI,gCAAgC1B,MAIrD;IAIC,MAAM9B,iBAAiB;QAAC6C,QAAQf,OAAOC,OAAO;QAAEoB,aAAa1C;IAAmB;IAEhF,MAAM4B,SAAS,MAAMzC,wBAAwB;QAC3C6D,OAAO3B,OAAO2B,KAAK;QACnB,GAAI3B,OAAO4B,MAAM,GAAG;YAACA,QAAQ5B,OAAO4B,MAAM;QAAA,IAAI,CAAC,CAAC;IAClD;IACA,OAAO;QACLC,QAAQtB,OAAOuB,IAAI,CAACC,GAAG,CAAC,CAACC,MAAS,CAAA;gBAChCC,SAASD,IAAIT,EAAE;gBACfW,MAAMF,IAAIE,IAAI;gBACdC,WAAWH,IAAIG,SAAS;gBACxBC,WAAWJ,IAAII,SAAS;gBACxBd,MAAMU,IAAIV,IAAI;YAChB,CAAA;QACAe,YAAY9B,OAAO8B,UAAU;IAC/B;AACF;AAEA,OAAO,eAAeC,uBACpBtC,MAAyF;IAEzF,MAAME,YAAY,MAAMhC,iBAAiB;QACvC6C,QAAQf,OAAOC,OAAO;QACtBoB,aAAa3C;IACf;IACA,OAAO,MAAMd,wBAAwB;QACnCmD,QAAQf,OAAOe,MAAM;QACrBmB,MAAMlC,OAAOkC,IAAI;QACjBjC,SAASD,OAAOC,OAAO;QACvBQ,2BAA2B1B,wBAAwBiB,OAAOgB,cAAc;QACxEC,oBAAoB9B,iCAAiCN,eAAe;YAClEkC,QAAQf,OAAOe,MAAM;YACrBmB,MAAMlC,OAAOkC,IAAI;YACjB5B,QAAQN,OAAOM,MAAM;QACvB;QACAY,OAAOnB,oBAAoB;YACzBE,SAASD,OAAOC,OAAO;YACvBC;YACAd,SAASP;YACTuB,YAAY;YACZC,UAAUL,OAAOe,MAAM;YACvBT,QAAQN,OAAOM,MAAM;YACrBE,eAAeR,OAAOQ,aAAa;YACnCC,2BAA2B1B,wBAAwBiB,OAAOgB,cAAc;QAC1E;IACF;AACF;AAEA,OAAO,eAAeuB,yBACpBvC,MAAyE;IAEzE,MAAME,YAAY,MAAMhC,iBAAiB;QACvC6C,QAAQf,OAAOC,OAAO;QACtBoB,aAAa3C;IACf;IACA,OAAO,MAAMX,0BAA0B;QACrCkE,SAASjC,OAAOiC,OAAO;QACvBhC,SAASD,OAAOC,OAAO;QACvBQ,2BAA2B1B,wBAAwBiB,OAAOgB,cAAc;QACxEC,oBAAoB9B,iCAAiCL,gBAAgB;YACnEmD,SAASjC,OAAOiC,OAAO;YACvB3B,QAAQN,OAAOM,MAAM;QACvB;QACAY,OAAOnB,oBAAoB;YACzBE,SAASD,OAAOC,OAAO;YACvBC;YACAd,SAASN;YACTsB,YAAY;YACZC,UAAUL,OAAOiC,OAAO;YACxB3B,QAAQN,OAAOM,MAAM;YACrBE,eAAeR,OAAOQ,aAAa;YACnCC,2BAA2B1B,wBAAwBiB,OAAOgB,cAAc;QAC1E;IACF;AACF;AAEA,SACE7C,yBAAyB,EACzBC,4BAA4B,EAC5BC,uBAAuB,EACvBC,6BAA6B,EAC7BC,+BAA+B,EAC/BC,mBAAmB,EACnBC,iBAAiB,GACjB"}
1
+ {"version":3,"sources":["../../src/core/administration.ts"],"sourcesContent":["import {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 {hashOpaqueToken} from '@shipfox/node-tokens';\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 {\n reactivateUserWithAudit,\n revokeUserSessionsWithAudit,\n suspendUserWithAudit,\n type UserModerationResult,\n} from '#db/admin-user-moderation.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 ADMIN_OPERATOR_ROLE: AdminRole = 'admin-operator';\nconst BOOTSTRAP_COMMAND = 'auth.admin_grant.bootstrap';\nconst GRANT_COMMAND = 'auth.admin_grant.grant';\nconst REVOKE_COMMAND = 'auth.admin_grant.revoke';\nconst SUSPEND_USER_COMMAND = 'auth.user.suspend';\nconst REACTIVATE_USER_COMMAND = 'auth.user.reactivate';\nconst REVOKE_USER_SESSIONS_COMMAND = 'auth.user.revoke-sessions';\n\nexport function administrationCommandFingerprint(command: string, input: unknown): string {\n return hashOpaqueToken(`${command}:${JSON.stringify(input)}`);\n}\n\nfunction bootstrapTokenMatches(candidate: string, expected: string | undefined): boolean {\n if (!expected) return false;\n const candidateHash = Buffer.from(hashOpaqueToken(candidate), 'hex');\n const expectedHash = Buffer.from(hashOpaqueToken(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 requiredRole?: AdminRole;\n correlationId: string;\n idempotencyKeyFingerprint: string;\n}) {\n return createAdministrationActionEvent({\n actorId: params.actorId,\n actorRole: params.actorRole,\n requiredRole: params.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 interface AdministratorUserMutationResult {\n user: AdministratorUserSummary;\n correlationId: string;\n sessionsRevoked: number;\n}\n\nfunction toAdministratorUserMutationResult(\n result: UserModerationResult,\n): AdministratorUserMutationResult {\n return {\n user: result.user,\n correlationId: result.correlationId,\n sessionsRevoked: result.sessionsRevoked,\n };\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: hashOpaqueToken(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: hashOpaqueToken(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: hashOpaqueToken(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: hashOpaqueToken(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: hashOpaqueToken(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: hashOpaqueToken(params.idempotencyKey),\n }),\n });\n}\n\nexport async function suspendAdministratorUser(\n params: AdministrationMutationContext & {userId: string; reason: string},\n): Promise<AdministratorUserMutationResult> {\n const actorRole = await requireAdminRole({\n userId: params.actorId,\n minimumRole: ADMIN_OPERATOR_ROLE,\n });\n const idempotencyKeyFingerprint = hashOpaqueToken(params.idempotencyKey);\n return toAdministratorUserMutationResult(\n await suspendUserWithAudit({\n actorId: params.actorId,\n userId: params.userId,\n idempotencyKeyFingerprint,\n requestFingerprint: administrationCommandFingerprint(SUSPEND_USER_COMMAND, {\n userId: params.userId,\n reason: params.reason,\n }),\n event: administrationEvent({\n actorId: params.actorId,\n actorRole,\n requiredRole: ADMIN_OPERATOR_ROLE,\n command: SUSPEND_USER_COMMAND,\n targetType: 'user',\n targetId: params.userId,\n reason: params.reason,\n correlationId: params.correlationId,\n idempotencyKeyFingerprint,\n }),\n }),\n );\n}\n\nexport async function reactivateAdministratorUser(\n params: AdministrationMutationContext & {userId: string; reason?: string},\n): Promise<AdministratorUserMutationResult> {\n const actorRole = await requireAdminRole({\n userId: params.actorId,\n minimumRole: ADMIN_OPERATOR_ROLE,\n });\n const reason = params.reason ?? 'User reactivation requested by an administrator';\n const idempotencyKeyFingerprint = hashOpaqueToken(params.idempotencyKey);\n return toAdministratorUserMutationResult(\n await reactivateUserWithAudit({\n actorId: params.actorId,\n userId: params.userId,\n idempotencyKeyFingerprint,\n requestFingerprint: administrationCommandFingerprint(REACTIVATE_USER_COMMAND, {\n userId: params.userId,\n reason,\n }),\n event: administrationEvent({\n actorId: params.actorId,\n actorRole,\n requiredRole: ADMIN_OPERATOR_ROLE,\n command: REACTIVATE_USER_COMMAND,\n targetType: 'user',\n targetId: params.userId,\n reason,\n correlationId: params.correlationId,\n idempotencyKeyFingerprint,\n }),\n }),\n );\n}\n\nexport async function revokeAdministratorUserSessions(\n params: AdministrationMutationContext & {userId: string; reason?: string},\n): Promise<AdministratorUserMutationResult> {\n const actorRole = await requireAdminRole({\n userId: params.actorId,\n minimumRole: ADMIN_OPERATOR_ROLE,\n });\n const reason = params.reason ?? 'All active user sessions revoked by an administrator';\n const idempotencyKeyFingerprint = hashOpaqueToken(params.idempotencyKey);\n return toAdministratorUserMutationResult(\n await revokeUserSessionsWithAudit({\n actorId: params.actorId,\n userId: params.userId,\n idempotencyKeyFingerprint,\n requestFingerprint: administrationCommandFingerprint(REVOKE_USER_SESSIONS_COMMAND, {\n userId: params.userId,\n reason,\n }),\n event: administrationEvent({\n actorId: params.actorId,\n actorRole,\n requiredRole: ADMIN_OPERATOR_ROLE,\n command: REVOKE_USER_SESSIONS_COMMAND,\n targetType: 'user',\n targetId: params.userId,\n reason,\n correlationId: params.correlationId,\n idempotencyKeyFingerprint,\n }),\n }),\n );\n}\n\nexport {\n AdminBootstrapClosedError,\n AdminGrantAlreadyExistsError,\n AdminGrantNotFoundError,\n AdminIdempotencyKeyReuseError,\n InvalidAdminBootstrapTokenError,\n LastAdminOwnerError,\n UserNotFoundError,\n};\n"],"names":["timingSafeEqual","createAdministrationActionEvent","hashOpaqueToken","config","bootstrapFirstAdminOwner","bootstrapFirstAdminOwnerInDb","grantAdminRoleWithAudit","hasActiveAdminOwner","listAdminGrantSummaries","revokeAdminGrantWithAudit","reactivateUserWithAudit","revokeUserSessionsWithAudit","suspendUserWithAudit","findAdministratorUser","findAdministratorUserInDb","requireAdminRole","AdminBootstrapClosedError","AdminGrantAlreadyExistsError","AdminGrantNotFoundError","AdminIdempotencyKeyReuseError","InvalidAdminBootstrapTokenError","LastAdminOwnerError","UserNotFoundError","ADMIN_OWNER_ROLE","ADMIN_OBSERVER_ROLE","ADMIN_OPERATOR_ROLE","BOOTSTRAP_COMMAND","GRANT_COMMAND","REVOKE_COMMAND","SUSPEND_USER_COMMAND","REACTIVATE_USER_COMMAND","REVOKE_USER_SESSIONS_COMMAND","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","toAdministratorUserMutationResult","user","sessionsRevoked","bootstrapToken","ADMIN_BOOTSTRAP_TOKEN","userId","idempotencyKey","requestFingerprint","event","getAdminBootstrapState","findAdministratorUserSummary","minimumRole","id","email","undefined","listAdministratorGrantSummaries","limit","cursor","grants","rows","map","row","grantId","role","createdAt","revokedAt","nextCursor","grantAdministratorRole","revokeAdministratorGrant","suspendAdministratorUser","reactivateAdministratorUser","revokeAdministratorUserSessions"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"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"}
package/dist/core/auth.js CHANGED
@@ -6,7 +6,7 @@ import { userAccessTokenKey } from '@shipfox/node-auth-root-key';
6
6
  import { generateOpaqueToken, hashOpaqueToken } from '@shipfox/node-tokens';
7
7
  import { config } from '#config.js';
8
8
  import { consumePasswordReset, createPasswordReset } from '#db/password-resets.js';
9
- import { createRefreshToken, findActiveRefreshTokenByHash, findRefreshTokenByHash, revokeRefreshSession, revokeRefreshTokensForUser, rotateRefreshToken } from '#db/refresh-tokens.js';
9
+ import { createRefreshTokenForActiveUser, findActiveRefreshTokenByHash, findRefreshTokenByHash, revokeRefreshSession, revokeRefreshTokensForUser, rotateRefreshToken } from '#db/refresh-tokens.js';
10
10
  import { createUser as createDbUser, findUserByEmail, findUserById, markEmailVerified, provisionUser as provisionDbUser, updateUserPassword } from '#db/users.js';
11
11
  import { recordTokenRefreshed } from '#metrics/index.js';
12
12
  import { getCurrentAdminRole } from './admin-role.js';
@@ -46,17 +46,20 @@ function daysFromNow(days) {
46
46
  function recordRefreshOutcome(outcome) {
47
47
  recordTokenRefreshed(outcome);
48
48
  }
49
- async function signAccessToken(user, workspaces, refreshSessionId) {
49
+ async function loadTokenMemberships(userId, workspaces) {
50
50
  const memberships = await workspaces.listMembershipsForTokenClaims({
51
- userId: user.id
51
+ userId
52
52
  }).catch((error)=>{
53
53
  throw new AuthDependencyUnavailableError('workspaces', error);
54
54
  });
55
+ return memberships.memberships;
56
+ }
57
+ async function signAccessToken(user, memberships, refreshSessionId) {
55
58
  return await signUserToken({
56
59
  userId: user.id,
57
60
  email: user.email,
58
61
  name: user.name,
59
- memberships: memberships.memberships,
62
+ memberships,
60
63
  refreshSessionId,
61
64
  secret: userAccessTokenKey(),
62
65
  expiresIn: config.AUTH_JWT_EXPIRES_IN
@@ -69,12 +72,13 @@ function isWithinRotationGrace(refreshToken) {
69
72
  }
70
73
  async function createRefreshSession(user, refreshSessionId) {
71
74
  const refreshToken = generateOpaqueToken('refreshToken');
72
- const session = await createRefreshToken({
75
+ const session = await createRefreshTokenForActiveUser({
73
76
  sessionId: refreshSessionId,
74
77
  userId: user.id,
75
78
  hashedToken: hashOpaqueToken(refreshToken),
76
79
  expiresAt: daysFromNow(config.AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS)
77
80
  });
81
+ if (!session) throw new InvalidCredentialsError();
78
82
  return {
79
83
  refreshToken,
80
84
  refreshSessionId: session.sessionId
@@ -82,7 +86,8 @@ async function createRefreshSession(user, refreshSessionId) {
82
86
  }
83
87
  async function createSessionTokens(user, workspaces) {
84
88
  const refreshSessionId = crypto.randomUUID();
85
- const token = await signAccessToken(user, workspaces, refreshSessionId);
89
+ const memberships = await loadTokenMemberships(user.id, workspaces);
90
+ const token = await signAccessToken(user, memberships, refreshSessionId);
86
91
  const adminRole = await getCurrentAdminRole({
87
92
  userId: user.id
88
93
  });
@@ -247,7 +252,7 @@ export async function signupWithInvitation(params) {
247
252
  };
248
253
  }
249
254
  }
250
- // Step 4: Issue session. signAccessToken reads memberships through the
255
+ // Step 4: Issue session. createSessionTokens reads memberships through the
251
256
  // workspaces module API, so a successful accept is reflected in the JWT.
252
257
  const { token, refreshToken, adminRole } = await createSessionTokens(user, params.workspaces);
253
258
  if (acceptError) {
@@ -366,7 +371,8 @@ export async function refreshAccessToken(params) {
366
371
  // second tab); past it, reuse of a retired token means a compromised session.
367
372
  if (current.rotatedAt) {
368
373
  if (isWithinRotationGrace(current)) {
369
- const token = await signAccessToken(user, params.workspaces, current.sessionId);
374
+ const memberships = await loadTokenMemberships(user.id, params.workspaces);
375
+ const token = await signAccessToken(user, memberships, current.sessionId);
370
376
  recordRefreshOutcome('grace');
371
377
  return {
372
378
  token,
@@ -381,16 +387,27 @@ export async function refreshAccessToken(params) {
381
387
  recordRefreshOutcome('rejected');
382
388
  throw new TokenInvalidError('Refresh token reused after rotation');
383
389
  }
390
+ // Keep the external membership snapshot ahead of rotation so an outage
391
+ // cannot retire a refresh token that the caller can still retry.
392
+ const memberships = await loadTokenMemberships(user.id, params.workspaces);
384
393
  // Losing the CAS requires a state check: another request may have rotated,
385
394
  // revoked, or expired the token before this refresh could claim it.
386
395
  const nextRefreshToken = generateOpaqueToken('refreshToken');
387
396
  const rotated = await rotateRefreshToken({
397
+ userId: current.userId,
388
398
  id: current.id,
389
399
  currentHashedToken,
390
400
  nextHashedToken: hashOpaqueToken(nextRefreshToken),
391
401
  expiresAt: daysFromNow(config.AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS)
392
402
  });
393
403
  if (!rotated) {
404
+ const latestUser = await findUserById({
405
+ id: current.userId
406
+ });
407
+ if (latestUser?.status !== 'active') {
408
+ recordRefreshOutcome('rejected');
409
+ throw new TokenInvalidError('Refresh token is invalid or expired');
410
+ }
394
411
  const latest = await findRefreshTokenByHash({
395
412
  hashedToken: currentHashedToken
396
413
  });
@@ -398,7 +415,7 @@ export async function refreshAccessToken(params) {
398
415
  recordRefreshOutcome('rejected');
399
416
  throw new TokenInvalidError('Refresh token is invalid or expired');
400
417
  }
401
- const token = await signAccessToken(user, params.workspaces, latest.sessionId);
418
+ const token = await signAccessToken(user, memberships, latest.sessionId);
402
419
  recordRefreshOutcome('grace');
403
420
  return {
404
421
  token,
@@ -407,7 +424,7 @@ export async function refreshAccessToken(params) {
407
424
  adminRole
408
425
  };
409
426
  }
410
- const token = await signAccessToken(user, params.workspaces, current.sessionId);
427
+ const token = await signAccessToken(user, memberships, current.sessionId);
411
428
  recordRefreshOutcome('rotated');
412
429
  return {
413
430
  token,