@shipfox/api-auth 10.2.0 → 12.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 (58) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +40 -0
  3. package/README.md +48 -32
  4. package/dist/core/auth.d.ts.map +1 -1
  5. package/dist/core/auth.js +16 -8
  6. package/dist/core/auth.js.map +1 -1
  7. package/dist/core/job-lease-token.d.ts +1 -1
  8. package/dist/core/job-lease-token.d.ts.map +1 -1
  9. package/dist/core/job-lease-token.js +1 -1
  10. package/dist/core/job-lease-token.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/core/runner-session-token.d.ts +1 -1
  16. package/dist/core/runner-session-token.d.ts.map +1 -1
  17. package/dist/core/runner-session-token.js +1 -1
  18. package/dist/core/runner-session-token.js.map +1 -1
  19. package/dist/db/rate-limits.d.ts +6 -15
  20. package/dist/db/rate-limits.d.ts.map +1 -1
  21. package/dist/db/rate-limits.js +17 -18
  22. package/dist/db/rate-limits.js.map +1 -1
  23. package/dist/db/refresh-tokens.d.ts +3 -7
  24. package/dist/db/refresh-tokens.d.ts.map +1 -1
  25. package/dist/db/refresh-tokens.js +3 -9
  26. package/dist/db/refresh-tokens.js.map +1 -1
  27. package/dist/presentation/auth/jwt-auth.d.ts +1 -4
  28. package/dist/presentation/auth/jwt-auth.d.ts.map +1 -1
  29. package/dist/presentation/auth/jwt-auth.js +5 -29
  30. package/dist/presentation/auth/jwt-auth.js.map +1 -1
  31. package/dist/presentation/auth/lease-token-auth.d.ts +1 -1
  32. package/dist/presentation/auth/lease-token-auth.js +1 -1
  33. package/dist/presentation/auth/lease-token-auth.js.map +1 -1
  34. package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
  35. package/dist/presentation/routes/rate-limit.js +32 -53
  36. package/dist/presentation/routes/rate-limit.js.map +1 -1
  37. package/dist/tsconfig.test.tsbuildinfo +1 -1
  38. package/package.json +13 -13
  39. package/src/core/auth.test.ts +29 -10
  40. package/src/core/auth.ts +27 -12
  41. package/src/core/job-lease-token.ts +1 -1
  42. package/src/core/jwt.test.ts +20 -4
  43. package/src/core/jwt.ts +2 -1
  44. package/src/core/runner-session-token.ts +1 -1
  45. package/src/db/rate-limits.ts +28 -42
  46. package/src/db/refresh-tokens.test.ts +0 -19
  47. package/src/db/refresh-tokens.ts +3 -26
  48. package/src/presentation/auth/jwt-auth.test.ts +33 -8
  49. package/src/presentation/auth/jwt-auth.ts +3 -26
  50. package/src/presentation/auth/lease-token-auth.ts +1 -1
  51. package/src/presentation/routes/administration.test.ts +9 -1
  52. package/src/presentation/routes/password/change-password.test.ts +6 -0
  53. package/src/presentation/routes/password/password-reset-confirm.test.ts +14 -0
  54. package/src/presentation/routes/rate-limit.ts +31 -64
  55. package/src/presentation/routes/registration/signup.test.ts +2 -2
  56. package/src/presentation/routes/session/logout.test.ts +6 -0
  57. package/src/presentation/routes/session/refresh.test.ts +11 -0
  58. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 67 files with swc (347.36ms)
2
+ Successfully compiled: 67 files with swc (326.91ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # @shipfox/api-auth
2
2
 
3
+ ## 12.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - f78740d: Remove Unicode dash punctuation from package prose and source comments.
8
+ - 9ebc5b4: Add an authenticated, rate-limited workspace slug availability endpoint.
9
+ - Updated dependencies [f78740d]
10
+ - Updated dependencies [94aba88]
11
+ - Updated dependencies [f13e8bb]
12
+ - Updated dependencies [9ebc5b4]
13
+ - Updated dependencies [34a5639]
14
+ - Updated dependencies [e1efaee]
15
+ - @shipfox/api-auth-dto@12.0.0
16
+ - @shipfox/api-common-dto@12.0.0
17
+ - @shipfox/inter-module@0.2.3
18
+ - @shipfox/node-fastify@0.4.1
19
+ - @shipfox/node-module@1.0.5
20
+ - @shipfox/api-workspaces-dto@12.0.0
21
+ - @shipfox/node-jwt@0.4.0
22
+ - @shipfox/node-postgres@0.5.0
23
+ - @shipfox/node-rate-limit@0.4.0
24
+ - @shipfox/api-auth-context@12.0.0
25
+ - @shipfox/api-email-challenges@1.1.7
26
+ - @shipfox/node-drizzle@0.3.5
27
+ - @shipfox/node-outbox@0.2.6
28
+
29
+ ## 11.0.0
30
+
31
+ ### Major Changes
32
+
33
+ - 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.
34
+
35
+ `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.
36
+
37
+ ### Patch Changes
38
+
39
+ - Updated dependencies [25158c8]
40
+ - @shipfox/api-auth-context@11.0.0
41
+ - @shipfox/api-workspaces-dto@11.0.0
42
+
3
43
  ## 10.2.0
4
44
 
5
45
  ### Minor Changes
package/README.md CHANGED
@@ -60,7 +60,7 @@ Required environment:
60
60
  | `AUTH_JOB_LEASE_TOKEN_EXPIRES_IN` | `90m` | Job lease token lifetime. |
61
61
  | `AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN` | `1h` | Runner session token lifetime. |
62
62
  | `AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS` | `14` | Refresh token and cookie lifetime. |
63
- | `AUTH_REFRESH_ROTATION_GRACE_SECONDS` | `30` | Grace window for accepting a just-rotated refresh token during concurrent refreshes. |
63
+ | `AUTH_REFRESH_ROTATION_GRACE_SECONDS` | `30` | Grace window for accepting a recently rotated refresh token during concurrent refreshes. |
64
64
  | `AUTH_REFRESH_COOKIE_NAME` | `shipfox_refresh_token` | HTTP cookie name for refresh sessions. |
65
65
  | `AUTH_PASSWORD_ENABLED` | `true` | Enables password and email-verification routes. Set to `false` when another module provides login. Server construction fails if no login method is available. |
66
66
  | `AUTH_SIGNUP_GATE_ENABLED` | `false` | Restricts new account creation to the configured signup email allowlist. Requires `AUTH_SIGNUP_ALLOWED_EMAIL_DOMAINS` or `AUTH_SIGNUP_ALLOWED_EMAILS` to be set. |
@@ -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,24 +110,25 @@ 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
107
- memberships" never a single resource.
119
+ memberships", never a single resource.
108
120
  - **Lifecycle:**
109
- - *Emit* issued on login, signup verification, and password reset.
110
- - *Exchange* sent on each request; a longer-lived refresh session mints a
121
+ - *Emit*: issued on login, signup verification, and password reset.
122
+ - *Exchange*: sent on each request; a longer-lived refresh session mints a
111
123
  fresh token when it expires.
112
- - *Store* held in client memory only, never persisted to disk or local
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.
116
- - **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.
126
+ - *Discard*: expires at the configured `exp` time. Revoking the refresh
127
+ session blocks renewal but does not invalidate the issued access token.
128
+ - **Tradeoff: stale memberships:** because memberships ride in the claims, a
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
 
@@ -125,7 +137,7 @@ The runner exchanges its long-lived registration token at startup, then uses the
125
137
  short-lived session token to claim jobs. Heartbeat and step/report/log operations
126
138
  use the per-job lease token instead.
127
139
 
128
- - **Scope one workspace's runner data plane.** The token names one runner
140
+ - **Scope: one workspace's runner data plane.** The token names one runner
129
141
  session, one workspace, the fixed `workspace` scope, and the session's immutable
130
142
  label set. It can claim jobs for that workspace. It is not a user identity and
131
143
  does not authorize dashboard management routes.
@@ -136,7 +148,7 @@ use the per-job lease token instead.
136
148
  - **Mechanics.** Signed with HMAC-SHA256 using the derived runner-session key
137
149
  and the `runner-session` audience. The default lifetime is `1h`, short enough
138
150
  to bound the residual claim window when a registration token is revoked.
139
- - **Tradeoff no per-session revocation in v1.** Claim stays stateless and does
151
+ - **Tradeoff: no per-session revocation in v1.** Claim stays stateless and does
140
152
  not check the runner session row on each poll. Revoking the registration token
141
153
  blocks new sessions, but an existing session can keep claiming until
142
154
  `AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN` elapses. A job execution claimed before
@@ -151,7 +163,7 @@ A single-job **capability** token, not a session. It is the means by which a
151
163
  runner proves it is the legitimate holder of one specific job while it reports
152
164
  progress and drives that job to completion.
153
165
 
154
- - **Scope exactly one job execution.** The token authorizes action on the
166
+ - **Scope: exactly one job execution.** The token authorizes action on the
155
167
  single job execution it names, for the runner holding it. It is **not** a runner
156
168
  identity, it is **not** workspace-wide, and it is **not** a substitute for the
157
169
  long-lived runner credential used to claim work in the first place. The
@@ -163,9 +175,9 @@ progress and drives that job to completion.
163
175
  - **Trust boundary.** There is exactly one issuer: the scheduling side mints a
164
176
  lease when a runner claims a job execution, next-step re-scopes it to the
165
177
  dispatched step attempt, and heartbeat re-mints the same narrow capability
166
- after server state accepts the heartbeat. Everything
167
- downstream only *verifies* an in-process signature check, with no callback to
168
- the issuer. The runner, and the untrusted agent workload it hosts, never mints
178
+ after server state accepts the heartbeat. Everything downstream only *verifies*
179
+ the token with an in-process signature check. It makes no callback to the
180
+ issuer. The runner, and the untrusted agent workload it hosts, never mints
169
181
  or modifies a token; it only presents the one it was handed.
170
182
  - **Mechanics.** Signed with HMAC-SHA256 using the job-lease key derived from
171
183
  `AUTH_ROOT_KEY`. Its claims name the job, job execution, and surrounding
@@ -175,13 +187,13 @@ progress and drives that job to completion.
175
187
  execution. The root key is supplied through configuration, never embedded in
176
188
  code or committed. The raw token must **never** be written to logs, traces, or
177
189
  error payloads. There is no automatic redaction to fall back on.
178
- - **Defense in depth server state is the final authority.** A valid token is
190
+ - **Defense in depth: server state is the final authority.** A valid token is
179
191
  never sufficient on its own to advance work. On the lease's own request path the
180
192
  gate is server-side step and progression state: a report against a step that has
181
193
  already reached a terminal state (finished, failed, or cancelled) is ignored, so
182
194
  a still-valid token cannot re-drive work that is already done. Job-level
183
195
  finalization is enforced outside the lease path. Cancellation flows the other way
184
- as well the server can ask the runner to stop at any point, and that request
196
+ as well: the server can ask the runner to stop at any point, and that request
185
197
  rides on the response to each heartbeat rather than depending on the token.
186
198
  - **Log append scope.** A step-scoped lease is a signed membership and attempt
187
199
  check for append-log authorization. It is not an active-step proof: until the
@@ -195,7 +207,7 @@ progress and drives that job to completion.
195
207
  other workspaces. If `AUTH_ROOT_KEY` leaks, rotate it. This invalidates every
196
208
  live token and email challenge derived from the old root, including every live
197
209
  lease, and forces fresh claims.
198
- - **Tradeoff no per-token revocation.** A single outstanding lease cannot be
210
+ - **Tradeoff: no per-token revocation.** A single outstanding lease cannot be
199
211
  revoked on its own by token ID. The claiming runner's credential is not
200
212
  re-checked on each request, so a lease for already-claimed work can continue to
201
213
  be renewed by heartbeat until server state cancels, completes, times out, or
@@ -205,7 +217,7 @@ progress and drives that job to completion.
205
217
  - **Guidelines for future changes.** Keep the authority narrow: do not add claims
206
218
  that grant access beyond the single job, keep the lifetime bounded, keep a single
207
219
  issuer, and keep every other side verify-only. If the no-revocation window ever
208
- proves too wide, the right fix is to bind the lease to live runner or job state
220
+ proves too wide, the right fix is to bind the lease to live runner or job state,
209
221
  not to broaden what the token itself can authorize.
210
222
 
211
223
  ## Routes
@@ -279,9 +291,9 @@ All routes are mounted under `/admin/auth/users` and require an authenticated be
279
291
 
280
292
  | Method | Path | Required role | Result |
281
293
  | --- | --- | --- | --- |
282
- | `POST` | `/:userId/suspend` | `admin-operator` | Suspends the user, revokes all active sessions, and returns the final safe user summary with a correlation ID. |
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`. |
283
295
  | `POST` | `/:userId/reactivate` | `admin-operator` | Restores sign-in eligibility without restoring revoked sessions. |
284
- | `POST` | `/:userId/revoke-sessions` | `admin-operator` | Revokes all active sessions without changing account status and returns the number of affected 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`. |
285
297
 
286
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`.
287
299
 
@@ -311,7 +323,7 @@ It also exports lower-level pieces for tests and advanced integration:
311
323
  - `issueJobLeaseToken(claims)` / `verifyJobLeaseToken(token)`: mint and verify job lease tokens.
312
324
  - `createEnvironmentSignupPolicy()`: creates the environment-backed signup policy used by default. Pass `signupPolicy` to `createAuthModule` to replace it.
313
325
  - `getClientContext(request)`: reads the authenticated user context from a Fastify request.
314
- - `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.
315
327
  - `findUserByEmail({email})`: read-only lookup of the current owner of a normalized email; see below.
316
328
  - Entity types: `User`, `UserStatus`, `RefreshToken`, `PasswordReset`, and `EmailOwner`.
317
329
 
@@ -416,12 +428,12 @@ const session = await getAuthenticatedSessionContext(request);
416
428
  ```
417
429
 
418
430
  The refresh-session ID remains the same when its refresh token rotates. The
419
- helper verifies that the session is active and belongs to the authenticated user,
420
- and returns the standard `401 unauthorized` error for a missing, malformed,
421
- 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
422
434
  storage details.
423
435
 
424
- ## Data Model
436
+ ## Data model
425
437
 
426
438
  The module creates tables with the `auth_` prefix:
427
439
 
@@ -434,14 +446,18 @@ The module creates tables with the `auth_` prefix:
434
446
 
435
447
  Passwords use Argon2id. Password reset tokens and refresh tokens are opaque tokens stored as hashes. Email verification uses the shared email-challenges module.
436
448
 
437
- ## Behavior Notes
449
+ ## Behavior notes
438
450
 
439
451
  - Signup sends an eight-digit verification code and returns the new user with its challenge ID.
440
452
  - Signup gating blocks account creation only. Existing users keep signing in, even when their address is not on the current allowlist.
441
453
  - A denied password signup returns `403` with error code `signup-not-allowed`. The policy message is capped at 500 characters.
442
454
  - Invitation signup validates the invitation and bypasses the signup policy. Invitations remain a separate authorization path.
443
455
  - Login only succeeds for active users with verified email addresses and a password hash.
444
- - Authenticated requests reject suspended or deleted users. Session-bound access tokens also reject a revoked refresh session.
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.
445
461
  - Refresh tokens rotate on each refresh.
446
462
  - Password reset tokens and email challenge proofs are consumed once.
447
463
  - Password change revokes other refresh sessions. It keeps the current session when the current refresh cookie is valid.
@@ -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
@@ -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
@@ -83,7 +86,8 @@ async function createRefreshSession(user, refreshSessionId) {
83
86
  }
84
87
  async function createSessionTokens(user, workspaces) {
85
88
  const refreshSessionId = crypto.randomUUID();
86
- const token = await signAccessToken(user, workspaces, refreshSessionId);
89
+ const memberships = await loadTokenMemberships(user.id, workspaces);
90
+ const token = await signAccessToken(user, memberships, refreshSessionId);
87
91
  const adminRole = await getCurrentAdminRole({
88
92
  userId: user.id
89
93
  });
@@ -248,7 +252,7 @@ export async function signupWithInvitation(params) {
248
252
  };
249
253
  }
250
254
  }
251
- // Step 4: Issue session. signAccessToken reads memberships through the
255
+ // Step 4: Issue session. createSessionTokens reads memberships through the
252
256
  // workspaces module API, so a successful accept is reflected in the JWT.
253
257
  const { token, refreshToken, adminRole } = await createSessionTokens(user, params.workspaces);
254
258
  if (acceptError) {
@@ -367,7 +371,8 @@ export async function refreshAccessToken(params) {
367
371
  // second tab); past it, reuse of a retired token means a compromised session.
368
372
  if (current.rotatedAt) {
369
373
  if (isWithinRotationGrace(current)) {
370
- 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);
371
376
  recordRefreshOutcome('grace');
372
377
  return {
373
378
  token,
@@ -382,6 +387,9 @@ export async function refreshAccessToken(params) {
382
387
  recordRefreshOutcome('rejected');
383
388
  throw new TokenInvalidError('Refresh token reused after rotation');
384
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);
385
393
  // Losing the CAS requires a state check: another request may have rotated,
386
394
  // revoked, or expired the token before this refresh could claim it.
387
395
  const nextRefreshToken = generateOpaqueToken('refreshToken');
@@ -407,7 +415,7 @@ export async function refreshAccessToken(params) {
407
415
  recordRefreshOutcome('rejected');
408
416
  throw new TokenInvalidError('Refresh token is invalid or expired');
409
417
  }
410
- const token = await signAccessToken(user, params.workspaces, latest.sessionId);
418
+ const token = await signAccessToken(user, memberships, latest.sessionId);
411
419
  recordRefreshOutcome('grace');
412
420
  return {
413
421
  token,
@@ -416,7 +424,7 @@ export async function refreshAccessToken(params) {
416
424
  adminRole
417
425
  };
418
426
  }
419
- const token = await signAccessToken(user, params.workspaces, current.sessionId);
427
+ const token = await signAccessToken(user, memberships, current.sessionId);
420
428
  recordRefreshOutcome('rotated');
421
429
  return {
422
430
  token,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/auth.ts"],"sourcesContent":["import {type AdminRole, emailSchema} from '@shipfox/api-auth-dto';\nimport {\n confirmEmailChallenge,\n consumeEmailChallengeProof,\n createEmailChallenge,\n resendEmailChallenge,\n} from '@shipfox/api-email-challenges';\nimport {\n type WorkspacesInterModuleClient,\n workspacesInterModuleContract,\n} from '@shipfox/api-workspaces-dto/inter-module';\nimport {isInterModuleKnownError} from '@shipfox/inter-module';\nimport {userAccessTokenKey} from '@shipfox/node-auth-root-key';\nimport {generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';\nimport {config} from '#config.js';\nimport {consumePasswordReset, createPasswordReset} from '#db/password-resets.js';\nimport {\n createRefreshTokenForActiveUser,\n findActiveRefreshTokenByHash,\n findRefreshTokenByHash,\n revokeRefreshSession,\n revokeRefreshTokensForUser,\n rotateRefreshToken,\n} from '#db/refresh-tokens.js';\nimport {\n createUser as createDbUser,\n findUserByEmail,\n findUserById,\n markEmailVerified,\n provisionUser as provisionDbUser,\n updateUserPassword,\n} from '#db/users.js';\nimport {type AuthTokenRefreshOutcome, recordTokenRefreshed} from '#metrics/index.js';\nimport {getCurrentAdminRole} from './admin-role.js';\nimport type {RefreshToken} from './entities/refresh-token.js';\nimport type {User} from './entities/user.js';\nimport {\n AuthDependencyUnavailableError,\n EmailNotVerifiedError,\n EmailTakenError,\n InvalidCredentialsError,\n InvitationEmailMismatchError,\n SignupNotAllowedError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n UserNotFoundError,\n} from './errors.js';\nimport {signUserToken} from './jwt.js';\nimport {hashPassword, verifyPassword} from './password.js';\nimport type {SignupPolicy} from './ports.js';\nimport {createEnvironmentSignupPolicy} from './signup-policy.js';\n\nconst RESET_TTL_HOURS = 1;\nconst PASSWORD_VERIFICATION_PURPOSE = 'password-verification';\nconst DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE =\n 'This Shipfox deployment does not accept new accounts right now.';\n\nconst defaultSignupPolicy: SignupPolicy = createEnvironmentSignupPolicy();\n\nasync function assertSignupAllowed(params: {\n signupPolicy?: SignupPolicy | undefined;\n email: string;\n emailVerified: boolean;\n source: string;\n}): Promise<void> {\n const result = await (params.signupPolicy ?? defaultSignupPolicy).isSignupAllowed({\n email: params.email,\n emailVerified: params.emailVerified,\n source: params.source,\n });\n\n if (!result.allowed) {\n throw new SignupNotAllowedError(result.message ?? DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE);\n }\n}\n\nlet dummyHashCache: string | undefined;\nasync function getDummyHash(): Promise<string> {\n if (!dummyHashCache) {\n dummyHashCache = await hashPassword({password: 'dummy-for-timing-parity'});\n }\n return dummyHashCache;\n}\n\nfunction hoursFromNow(hours: number): Date {\n return new Date(Date.now() + hours * 60 * 60 * 1000);\n}\n\nfunction daysFromNow(days: number): Date {\n return new Date(Date.now() + days * 24 * 60 * 60 * 1000);\n}\n\nfunction recordRefreshOutcome(outcome: AuthTokenRefreshOutcome): void {\n recordTokenRefreshed(outcome);\n}\n\nasync function signAccessToken(\n user: User,\n workspaces: WorkspacesInterModuleClient,\n refreshSessionId: string,\n): Promise<string> {\n const memberships = await workspaces\n .listMembershipsForTokenClaims({userId: user.id})\n .catch((error: unknown) => {\n throw new AuthDependencyUnavailableError('workspaces', error);\n });\n return await signUserToken({\n userId: user.id,\n email: user.email,\n name: user.name,\n memberships: memberships.memberships,\n refreshSessionId,\n secret: userAccessTokenKey(),\n expiresIn: config.AUTH_JWT_EXPIRES_IN,\n });\n}\n\nfunction isWithinRotationGrace(refreshToken: RefreshToken): boolean {\n if (!refreshToken.rotatedAt) return false;\n const graceMs = config.AUTH_REFRESH_ROTATION_GRACE_SECONDS * 1000;\n return Date.now() - refreshToken.rotatedAt.getTime() <= graceMs;\n}\n\nasync function createRefreshSession(\n user: User,\n refreshSessionId: string,\n): Promise<{refreshToken: string; refreshSessionId: string}> {\n const refreshToken = generateOpaqueToken('refreshToken');\n const session = await createRefreshTokenForActiveUser({\n sessionId: refreshSessionId,\n userId: user.id,\n hashedToken: hashOpaqueToken(refreshToken),\n expiresAt: daysFromNow(config.AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS),\n });\n if (!session) throw new InvalidCredentialsError();\n return {refreshToken, refreshSessionId: session.sessionId};\n}\n\nasync function createSessionTokens(\n user: User,\n workspaces: WorkspacesInterModuleClient,\n): Promise<{token: string; refreshToken: string; adminRole: AdminRole | null}> {\n const refreshSessionId = crypto.randomUUID();\n const token = await signAccessToken(user, workspaces, refreshSessionId);\n const adminRole = await getCurrentAdminRole({userId: user.id});\n const {refreshToken} = await createRefreshSession(user, refreshSessionId);\n return {token, refreshToken, adminRole};\n}\n\nfunction passwordResetLink(rawToken: string): string {\n return `${config.CLIENT_BASE_URL}/auth/reset?token=${rawToken}`;\n}\n\nfunction invitationErrorFromKnownError(\n error: unknown,\n method: 'preflightInvitationAcceptance' | 'acceptInvitation',\n): Error {\n const contractMethod = workspacesInterModuleContract.methods[method];\n if (!isInterModuleKnownError(contractMethod, error)) return error as Error;\n\n if (error.code === 'invitation-token-invalid')\n return new TokenInvalidError('Invitation token is invalid');\n if (error.code === 'invitation-token-used') return new TokenAlreadyUsedError();\n if (error.code === 'invitation-token-expired') return new TokenExpiredError();\n return new InvitationEmailMismatchError();\n}\n\nfunction invitationErrorMessage(\n code:\n | 'invitation-token-invalid'\n | 'invitation-token-used'\n | 'invitation-token-expired'\n | 'invitation-email-mismatch',\n): string {\n if (code === 'invitation-token-invalid') return 'Invitation token is invalid';\n if (code === 'invitation-token-used') return 'Invitation has already been accepted';\n if (code === 'invitation-token-expired') return 'Invitation has expired';\n return 'Signup email does not match the invitation';\n}\n\nexport interface SignupParams {\n email: string;\n password: string;\n name?: string | undefined;\n signupPolicy?: SignupPolicy | undefined;\n}\n\nexport interface ProvisionUserParams {\n email: string;\n name?: string | null | undefined;\n signupPolicy?: SignupPolicy | undefined;\n}\n\n/**\n * Creates a verified, password-less user for an external identity provider.\n * Existing users are returned unchanged, including their password and profile.\n */\nexport async function provisionUser(params: ProvisionUserParams): Promise<User> {\n const email = emailSchema.parse(params.email);\n const existing = await findUserByEmail({email});\n if (existing) return existing;\n\n await assertSignupAllowed({\n signupPolicy: params.signupPolicy,\n email,\n emailVerified: true,\n source: 'external-identity',\n });\n\n return await provisionDbUser({\n email,\n name: params.name ?? null,\n });\n}\n\nexport type SignupResult = User & {\n emailChallenge: {id: string; nextResendAvailableAt: Date};\n};\n\nexport async function signup(params: SignupParams & {sourceIp?: string}): Promise<SignupResult> {\n const email = emailSchema.parse(params.email);\n const existing = await findUserByEmail({email});\n if (existing) {\n const canResumeVerification =\n existing.status === 'active' &&\n existing.emailVerifiedAt === null &&\n existing.hashedPassword !== null &&\n (await verifyPassword({password: params.password, hash: existing.hashedPassword}));\n if (canResumeVerification) {\n const emailChallenge = await createEmailChallenge({\n email: existing.email,\n purpose: PASSWORD_VERIFICATION_PURPOSE,\n continuation: existing.id,\n idempotencyKey: existing.id,\n sourceIp: params.sourceIp ?? '0.0.0.0',\n });\n return {...existing, emailChallenge};\n }\n throw new EmailTakenError(email);\n }\n\n await assertSignupAllowed({\n signupPolicy: params.signupPolicy,\n email,\n emailVerified: false,\n source: 'password',\n });\n\n const hashedPassword = await hashPassword({password: params.password});\n const user = await createDbUser({\n email,\n hashedPassword,\n name: params.name ?? null,\n signedUp: {viaInvitation: false},\n });\n\n const emailChallenge = await createEmailChallenge({\n email: user.email,\n purpose: PASSWORD_VERIFICATION_PURPOSE,\n continuation: user.id,\n idempotencyKey: user.id,\n sourceIp: params.sourceIp ?? '0.0.0.0',\n });\n\n return {...user, emailChallenge};\n}\n\nexport interface SignupWithInvitationParams extends SignupParams {\n invitationToken: string;\n workspaces: WorkspacesInterModuleClient;\n}\n\nexport interface SignupWithInvitationResult extends LoginResult {\n membership: {id: string; userId: string; workspaceId: string} | null;\n acceptError?: {code: string; message: string};\n}\n\nexport async function signupWithInvitation(\n params: SignupWithInvitationParams,\n): Promise<SignupWithInvitationResult> {\n // Step 1: Pre-validate the invitation BEFORE any user write so an invalid\n // token never produces an orphan user. Re-validation happens again inside\n // acceptWorkspaceInvitation (race-safe) after the user is created.\n try {\n await params.workspaces.preflightInvitationAcceptance({\n token: params.invitationToken,\n email: params.email,\n });\n } catch (error) {\n throw invitationErrorFromKnownError(error, 'preflightInvitationAcceptance');\n }\n\n // Step 2: Create the user as verified. The invitation email is the proof of\n // ownership, and this avoids a second auth-table write just to mark verified.\n const existing = await findUserByEmail({email: params.email});\n if (existing) {\n throw new EmailTakenError(params.email);\n }\n const hashedPassword = await hashPassword({password: params.password});\n const user = await createDbUser({\n email: params.email,\n hashedPassword,\n name: params.name ?? null,\n emailVerifiedAt: new Date(),\n signedUp: {viaInvitation: true},\n });\n\n // Step 3: Accept the invitation through the workspaces module boundary.\n // This is intentionally not one database transaction with auth; treat it like\n // calling another service. If it fails after user creation, return a verified\n // signed-in user and an explicit retryable accept error.\n let membership: SignupWithInvitationResult['membership'] = null;\n let acceptError: SignupWithInvitationResult['acceptError'];\n try {\n const result = await params.workspaces.acceptInvitation({\n token: params.invitationToken,\n userId: user.id,\n email: user.email,\n name: user.name,\n });\n membership = {\n id: result.membership.id,\n userId: result.membership.userId,\n workspaceId: result.membership.workspaceId,\n };\n } catch (error) {\n if (isInterModuleKnownError(workspacesInterModuleContract.methods.acceptInvitation, error)) {\n acceptError = {code: error.code, message: invitationErrorMessage(error.code)};\n } else {\n acceptError = {\n code: 'AcceptFailed',\n message: 'Could not accept the invitation; please retry from the invite link.',\n };\n }\n }\n\n // Step 4: Issue session. signAccessToken reads memberships through the\n // workspaces module API, so a successful accept is reflected in the JWT.\n const {token, refreshToken, adminRole} = await createSessionTokens(user, params.workspaces);\n\n if (acceptError) {\n return {token, refreshToken, user, membership, acceptError, adminRole};\n }\n return {token, refreshToken, user, membership, adminRole};\n}\n\nexport interface CreateUserParams extends SignupParams {\n verified: boolean;\n}\n\nexport async function createUser(params: CreateUserParams): Promise<User> {\n const existing = await findUserByEmail({email: params.email});\n if (existing) {\n throw new EmailTakenError(params.email);\n }\n\n const hashedPassword = await hashPassword({password: params.password});\n const user = await createDbUser({\n email: params.email,\n hashedPassword,\n name: params.name ?? null,\n emailVerifiedAt: params.verified ? new Date() : null,\n });\n\n return user;\n}\n\nexport interface LoginParams {\n email: string;\n password: string;\n workspaces: WorkspacesInterModuleClient;\n}\n\nexport interface LoginResult {\n token: string;\n refreshToken: string;\n user: User;\n adminRole: AdminRole | null;\n}\n\nexport async function login(params: LoginParams): Promise<LoginResult> {\n const user = await findUserByEmail({email: params.email});\n\n if (!user) {\n await verifyPassword({password: params.password, hash: await getDummyHash()});\n throw new InvalidCredentialsError();\n }\n\n const hasPassword = user.hashedPassword !== null;\n const ok = await verifyPassword({\n password: params.password,\n hash: user.hashedPassword ?? (await getDummyHash()),\n });\n if (!hasPassword || !ok || user.status !== 'active') {\n throw new InvalidCredentialsError();\n }\n\n if (user.emailVerifiedAt === null) {\n throw new EmailNotVerifiedError();\n }\n\n const {token, refreshToken, adminRole} = await createSessionTokens(user, params.workspaces);\n\n return {token, refreshToken, user, adminRole};\n}\n\nexport interface CreateSessionForUserParams {\n userId?: string | undefined;\n email?: string | undefined;\n workspaces: WorkspacesInterModuleClient;\n}\n\nexport interface CreateSessionForUserResult {\n token: string;\n refreshToken: string;\n user: User;\n adminRole: AdminRole | null;\n}\n\nexport type CreateSessionForUserError =\n | AuthDependencyUnavailableError\n | EmailNotVerifiedError\n | InvalidCredentialsError\n | UserNotFoundError;\n\nexport async function createSessionForUser(\n params: CreateSessionForUserParams,\n): Promise<CreateSessionForUserResult> {\n const user = params.userId\n ? await findUserById({id: params.userId})\n : params.email\n ? await findUserByEmail({email: emailSchema.parse(params.email)})\n : undefined;\n\n if (!user) {\n throw new UserNotFoundError(params.userId ?? params.email ?? 'unknown');\n }\n if (user.emailVerifiedAt === null) {\n throw new EmailNotVerifiedError();\n }\n if (user.status !== 'active') {\n throw new InvalidCredentialsError();\n }\n\n const {token, refreshToken, adminRole} = await createSessionTokens(user, params.workspaces);\n\n return {token, refreshToken, user, adminRole};\n}\n\nexport interface RefreshAccessTokenResult {\n token: string;\n /** Undefined on a grace-window hit: keep the existing cookie instead of rotating it. */\n refreshToken: string | undefined;\n user: User;\n adminRole: AdminRole | null;\n}\n\nexport async function refreshAccessToken(params: {\n refreshToken: string;\n workspaces: WorkspacesInterModuleClient;\n}): Promise<RefreshAccessTokenResult> {\n const currentHashedToken = hashOpaqueToken(params.refreshToken);\n const current = await findRefreshTokenByHash({hashedToken: currentHashedToken});\n if (!current) {\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token is invalid or expired');\n }\n\n const user = await findUserById({id: current.userId});\n if (user?.status !== 'active') {\n await revokeRefreshSession({sessionId: current.sessionId, userId: current.userId});\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token is invalid or expired');\n }\n const adminRole = await getCurrentAdminRole({userId: user.id});\n\n // Within the grace window a rotated token means a concurrent refresh (e.g. a\n // second tab); past it, reuse of a retired token means a compromised session.\n if (current.rotatedAt) {\n if (isWithinRotationGrace(current)) {\n const token = await signAccessToken(user, params.workspaces, current.sessionId);\n recordRefreshOutcome('grace');\n return {token, refreshToken: undefined, user, adminRole};\n }\n await revokeRefreshTokensForUser({userId: user.id});\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token reused after rotation');\n }\n\n // Losing the CAS requires a state check: another request may have rotated,\n // revoked, or expired the token before this refresh could claim it.\n const nextRefreshToken = generateOpaqueToken('refreshToken');\n const rotated = await rotateRefreshToken({\n userId: current.userId,\n id: current.id,\n currentHashedToken,\n nextHashedToken: hashOpaqueToken(nextRefreshToken),\n expiresAt: daysFromNow(config.AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS),\n });\n if (!rotated) {\n const latestUser = await findUserById({id: current.userId});\n if (latestUser?.status !== 'active') {\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token is invalid or expired');\n }\n const latest = await findRefreshTokenByHash({hashedToken: currentHashedToken});\n if (!latest || !isWithinRotationGrace(latest)) {\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token is invalid or expired');\n }\n const token = await signAccessToken(user, params.workspaces, latest.sessionId);\n recordRefreshOutcome('grace');\n return {token, refreshToken: undefined, user, adminRole};\n }\n\n const token = await signAccessToken(user, params.workspaces, current.sessionId);\n recordRefreshOutcome('rotated');\n return {token, refreshToken: nextRefreshToken, user, adminRole};\n}\n\nexport interface ConfirmEmailVerificationResult {\n token: string;\n refreshToken: string;\n user: User;\n adminRole: AdminRole | null;\n}\n\nexport interface ResendEmailVerificationResult {\n nextResendAvailableAt: Date;\n}\n\nexport async function confirmEmailVerification(params: {\n email: string;\n challengeId: string;\n code: string;\n workspaces: WorkspacesInterModuleClient;\n}): Promise<ConfirmEmailVerificationResult> {\n const user = await findUserByEmail({email: emailSchema.parse(params.email)});\n if (!user) throw new TokenInvalidError('Verification code is invalid or expired');\n await confirmEmailChallenge({id: params.challengeId, code: params.code, continuation: user.id});\n await consumeEmailChallengeProof({\n id: params.challengeId,\n purpose: PASSWORD_VERIFICATION_PURPOSE,\n continuation: user.id,\n });\n\n const verifiedUser = await markEmailVerified({userId: user.id});\n if (verifiedUser?.status !== 'active') {\n throw new TokenInvalidError('Verification code is invalid or expired');\n }\n\n const {token, refreshToken, adminRole} = await createSessionTokens(\n verifiedUser,\n params.workspaces,\n );\n\n return {token, refreshToken, user: verifiedUser, adminRole};\n}\n\nexport async function resendEmailVerification(params: {\n email: string;\n challengeId: string;\n sourceIp: string;\n}): Promise<ResendEmailVerificationResult> {\n const user = await findUserByEmail({email: emailSchema.parse(params.email)});\n if (!user || user.emailVerifiedAt || user.status !== 'active') {\n return {nextResendAvailableAt: new Date()};\n }\n const result = await resendEmailChallenge({\n id: params.challengeId,\n continuation: user.id,\n sourceIp: params.sourceIp,\n });\n\n return {nextResendAvailableAt: result.nextResendAvailableAt};\n}\n\nexport async function requestPasswordReset(params: {email: string}): Promise<void> {\n const user = await findUserByEmail({email: params.email});\n if (user?.status !== 'active' || user.hashedPassword === null) {\n return;\n }\n\n const rawToken = generateOpaqueToken('passwordReset');\n await createPasswordReset({\n userId: user.id,\n hashedToken: hashOpaqueToken(rawToken),\n expiresAt: hoursFromNow(RESET_TTL_HOURS),\n sendEmail: {\n email: user.email,\n resetLink: passwordResetLink(rawToken),\n expiresInHours: RESET_TTL_HOURS,\n },\n });\n}\n\nexport interface ConfirmPasswordResetResult {\n token: string;\n refreshToken: string;\n user: User;\n adminRole: AdminRole | null;\n}\n\nexport async function confirmPasswordReset(params: {\n token: string;\n newPassword: string;\n workspaces: WorkspacesInterModuleClient;\n}): Promise<ConfirmPasswordResetResult> {\n const consumed = await consumePasswordReset({hashedToken: hashOpaqueToken(params.token)});\n if (!consumed) {\n throw new TokenInvalidError('Reset token is invalid or expired');\n }\n\n const existingUser = await findUserById({id: consumed.userId});\n if (existingUser?.status !== 'active' || existingUser.hashedPassword === null) {\n throw new TokenInvalidError('Reset token is invalid or expired');\n }\n\n const hashedPassword = await hashPassword({password: params.newPassword});\n const user = await updateUserPassword({userId: consumed.userId, hashedPassword});\n if (user?.status !== 'active') {\n throw new TokenInvalidError('Reset token is invalid or expired');\n }\n\n await revokeRefreshTokensForUser({userId: consumed.userId});\n\n const {token, refreshToken, adminRole} = await createSessionTokens(user, params.workspaces);\n\n return {token, refreshToken, user, adminRole};\n}\n\nexport async function changePassword(params: {\n userId: string;\n currentPassword: string;\n newPassword: string;\n refreshToken?: string | undefined;\n}): Promise<void> {\n const user = await findUserById({id: params.userId});\n if (!user) {\n throw new UserNotFoundError(params.userId);\n }\n\n const hasPassword = user.hashedPassword !== null;\n const ok = await verifyPassword({\n password: params.currentPassword,\n hash: user.hashedPassword ?? (await getDummyHash()),\n });\n if (!hasPassword || !ok) {\n throw new InvalidCredentialsError();\n }\n\n const hashedPassword = await hashPassword({password: params.newPassword});\n const currentRefreshSession = params.refreshToken\n ? await findActiveRefreshTokenByHash({hashedToken: hashOpaqueToken(params.refreshToken)})\n : undefined;\n const exceptRefreshTokenId =\n currentRefreshSession?.userId === user.id ? currentRefreshSession.id : undefined;\n\n await updateUserPassword({userId: user.id, hashedPassword});\n await revokeRefreshTokensForUser({userId: user.id, exceptRefreshTokenId});\n}\n\nexport async function logout(params: {refreshToken?: string | undefined}): Promise<void> {\n if (!params.refreshToken) return;\n const refreshToken = await findRefreshTokenByHash({\n hashedToken: hashOpaqueToken(params.refreshToken),\n });\n if (!refreshToken) return;\n await revokeRefreshSession({sessionId: refreshToken.sessionId, userId: refreshToken.userId});\n}\n\nexport interface GetCurrentUserResult {\n user: User;\n}\n\nexport async function getCurrentUser(params: {userId: string}): Promise<GetCurrentUserResult> {\n const user = await findUserById({id: params.userId});\n if (!user) {\n throw new UserNotFoundError(params.userId);\n }\n\n return {user};\n}\n"],"names":["emailSchema","confirmEmailChallenge","consumeEmailChallengeProof","createEmailChallenge","resendEmailChallenge","workspacesInterModuleContract","isInterModuleKnownError","userAccessTokenKey","generateOpaqueToken","hashOpaqueToken","config","consumePasswordReset","createPasswordReset","createRefreshTokenForActiveUser","findActiveRefreshTokenByHash","findRefreshTokenByHash","revokeRefreshSession","revokeRefreshTokensForUser","rotateRefreshToken","createUser","createDbUser","findUserByEmail","findUserById","markEmailVerified","provisionUser","provisionDbUser","updateUserPassword","recordTokenRefreshed","getCurrentAdminRole","AuthDependencyUnavailableError","EmailNotVerifiedError","EmailTakenError","InvalidCredentialsError","InvitationEmailMismatchError","SignupNotAllowedError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","UserNotFoundError","signUserToken","hashPassword","verifyPassword","createEnvironmentSignupPolicy","RESET_TTL_HOURS","PASSWORD_VERIFICATION_PURPOSE","DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE","defaultSignupPolicy","assertSignupAllowed","params","result","signupPolicy","isSignupAllowed","email","emailVerified","source","allowed","message","dummyHashCache","getDummyHash","password","hoursFromNow","hours","Date","now","daysFromNow","days","recordRefreshOutcome","outcome","signAccessToken","user","workspaces","refreshSessionId","memberships","listMembershipsForTokenClaims","userId","id","catch","error","name","secret","expiresIn","AUTH_JWT_EXPIRES_IN","isWithinRotationGrace","refreshToken","rotatedAt","graceMs","AUTH_REFRESH_ROTATION_GRACE_SECONDS","getTime","createRefreshSession","session","sessionId","hashedToken","expiresAt","AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS","createSessionTokens","crypto","randomUUID","token","adminRole","passwordResetLink","rawToken","CLIENT_BASE_URL","invitationErrorFromKnownError","method","contractMethod","methods","code","invitationErrorMessage","parse","existing","signup","canResumeVerification","status","emailVerifiedAt","hashedPassword","hash","emailChallenge","purpose","continuation","idempotencyKey","sourceIp","signedUp","viaInvitation","signupWithInvitation","preflightInvitationAcceptance","invitationToken","membership","acceptError","acceptInvitation","workspaceId","verified","login","hasPassword","ok","createSessionForUser","undefined","refreshAccessToken","currentHashedToken","current","nextRefreshToken","rotated","nextHashedToken","latestUser","latest","confirmEmailVerification","challengeId","verifiedUser","resendEmailVerification","nextResendAvailableAt","requestPasswordReset","sendEmail","resetLink","expiresInHours","confirmPasswordReset","consumed","existingUser","newPassword","changePassword","currentPassword","currentRefreshSession","exceptRefreshTokenId","logout","getCurrentUser"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/auth.ts"],"sourcesContent":["import {type AdminRole, emailSchema} from '@shipfox/api-auth-dto';\nimport {\n confirmEmailChallenge,\n consumeEmailChallengeProof,\n createEmailChallenge,\n resendEmailChallenge,\n} from '@shipfox/api-email-challenges';\nimport {\n type WorkspacesInterModuleClient,\n workspacesInterModuleContract,\n} from '@shipfox/api-workspaces-dto/inter-module';\nimport {isInterModuleKnownError} from '@shipfox/inter-module';\nimport {userAccessTokenKey} from '@shipfox/node-auth-root-key';\nimport {generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';\nimport {config} from '#config.js';\nimport {consumePasswordReset, createPasswordReset} from '#db/password-resets.js';\nimport {\n createRefreshTokenForActiveUser,\n findActiveRefreshTokenByHash,\n findRefreshTokenByHash,\n revokeRefreshSession,\n revokeRefreshTokensForUser,\n rotateRefreshToken,\n} from '#db/refresh-tokens.js';\nimport {\n createUser as createDbUser,\n findUserByEmail,\n findUserById,\n markEmailVerified,\n provisionUser as provisionDbUser,\n updateUserPassword,\n} from '#db/users.js';\nimport {type AuthTokenRefreshOutcome, recordTokenRefreshed} from '#metrics/index.js';\nimport {getCurrentAdminRole} from './admin-role.js';\nimport type {RefreshToken} from './entities/refresh-token.js';\nimport type {User} from './entities/user.js';\nimport {\n AuthDependencyUnavailableError,\n EmailNotVerifiedError,\n EmailTakenError,\n InvalidCredentialsError,\n InvitationEmailMismatchError,\n SignupNotAllowedError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n UserNotFoundError,\n} from './errors.js';\nimport {signUserToken, type TokenMembership} from './jwt.js';\nimport {hashPassword, verifyPassword} from './password.js';\nimport type {SignupPolicy} from './ports.js';\nimport {createEnvironmentSignupPolicy} from './signup-policy.js';\n\nconst RESET_TTL_HOURS = 1;\nconst PASSWORD_VERIFICATION_PURPOSE = 'password-verification';\nconst DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE =\n 'This Shipfox deployment does not accept new accounts right now.';\n\nconst defaultSignupPolicy: SignupPolicy = createEnvironmentSignupPolicy();\n\nasync function assertSignupAllowed(params: {\n signupPolicy?: SignupPolicy | undefined;\n email: string;\n emailVerified: boolean;\n source: string;\n}): Promise<void> {\n const result = await (params.signupPolicy ?? defaultSignupPolicy).isSignupAllowed({\n email: params.email,\n emailVerified: params.emailVerified,\n source: params.source,\n });\n\n if (!result.allowed) {\n throw new SignupNotAllowedError(result.message ?? DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE);\n }\n}\n\nlet dummyHashCache: string | undefined;\nasync function getDummyHash(): Promise<string> {\n if (!dummyHashCache) {\n dummyHashCache = await hashPassword({password: 'dummy-for-timing-parity'});\n }\n return dummyHashCache;\n}\n\nfunction hoursFromNow(hours: number): Date {\n return new Date(Date.now() + hours * 60 * 60 * 1000);\n}\n\nfunction daysFromNow(days: number): Date {\n return new Date(Date.now() + days * 24 * 60 * 60 * 1000);\n}\n\nfunction recordRefreshOutcome(outcome: AuthTokenRefreshOutcome): void {\n recordTokenRefreshed(outcome);\n}\n\ntype TokenMemberships = TokenMembership[];\n\nasync function loadTokenMemberships(\n userId: string,\n workspaces: WorkspacesInterModuleClient,\n): Promise<TokenMemberships> {\n const memberships = await workspaces\n .listMembershipsForTokenClaims({userId})\n .catch((error: unknown) => {\n throw new AuthDependencyUnavailableError('workspaces', error);\n });\n return memberships.memberships;\n}\n\nasync function signAccessToken(\n user: User,\n memberships: TokenMemberships,\n refreshSessionId: string,\n): Promise<string> {\n return await signUserToken({\n userId: user.id,\n email: user.email,\n name: user.name,\n memberships,\n refreshSessionId,\n secret: userAccessTokenKey(),\n expiresIn: config.AUTH_JWT_EXPIRES_IN,\n });\n}\n\nfunction isWithinRotationGrace(refreshToken: RefreshToken): boolean {\n if (!refreshToken.rotatedAt) return false;\n const graceMs = config.AUTH_REFRESH_ROTATION_GRACE_SECONDS * 1000;\n return Date.now() - refreshToken.rotatedAt.getTime() <= graceMs;\n}\n\nasync function createRefreshSession(\n user: User,\n refreshSessionId: string,\n): Promise<{refreshToken: string; refreshSessionId: string}> {\n const refreshToken = generateOpaqueToken('refreshToken');\n const session = await createRefreshTokenForActiveUser({\n sessionId: refreshSessionId,\n userId: user.id,\n hashedToken: hashOpaqueToken(refreshToken),\n expiresAt: daysFromNow(config.AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS),\n });\n if (!session) throw new InvalidCredentialsError();\n return {refreshToken, refreshSessionId: session.sessionId};\n}\n\nasync function createSessionTokens(\n user: User,\n workspaces: WorkspacesInterModuleClient,\n): Promise<{token: string; refreshToken: string; adminRole: AdminRole | null}> {\n const refreshSessionId = crypto.randomUUID();\n const memberships = await loadTokenMemberships(user.id, workspaces);\n const token = await signAccessToken(user, memberships, refreshSessionId);\n const adminRole = await getCurrentAdminRole({userId: user.id});\n const {refreshToken} = await createRefreshSession(user, refreshSessionId);\n return {token, refreshToken, adminRole};\n}\n\nfunction passwordResetLink(rawToken: string): string {\n return `${config.CLIENT_BASE_URL}/auth/reset?token=${rawToken}`;\n}\n\nfunction invitationErrorFromKnownError(\n error: unknown,\n method: 'preflightInvitationAcceptance' | 'acceptInvitation',\n): Error {\n const contractMethod = workspacesInterModuleContract.methods[method];\n if (!isInterModuleKnownError(contractMethod, error)) return error as Error;\n\n if (error.code === 'invitation-token-invalid')\n return new TokenInvalidError('Invitation token is invalid');\n if (error.code === 'invitation-token-used') return new TokenAlreadyUsedError();\n if (error.code === 'invitation-token-expired') return new TokenExpiredError();\n return new InvitationEmailMismatchError();\n}\n\nfunction invitationErrorMessage(\n code:\n | 'invitation-token-invalid'\n | 'invitation-token-used'\n | 'invitation-token-expired'\n | 'invitation-email-mismatch',\n): string {\n if (code === 'invitation-token-invalid') return 'Invitation token is invalid';\n if (code === 'invitation-token-used') return 'Invitation has already been accepted';\n if (code === 'invitation-token-expired') return 'Invitation has expired';\n return 'Signup email does not match the invitation';\n}\n\nexport interface SignupParams {\n email: string;\n password: string;\n name?: string | undefined;\n signupPolicy?: SignupPolicy | undefined;\n}\n\nexport interface ProvisionUserParams {\n email: string;\n name?: string | null | undefined;\n signupPolicy?: SignupPolicy | undefined;\n}\n\n/**\n * Creates a verified, password-less user for an external identity provider.\n * Existing users are returned unchanged, including their password and profile.\n */\nexport async function provisionUser(params: ProvisionUserParams): Promise<User> {\n const email = emailSchema.parse(params.email);\n const existing = await findUserByEmail({email});\n if (existing) return existing;\n\n await assertSignupAllowed({\n signupPolicy: params.signupPolicy,\n email,\n emailVerified: true,\n source: 'external-identity',\n });\n\n return await provisionDbUser({\n email,\n name: params.name ?? null,\n });\n}\n\nexport type SignupResult = User & {\n emailChallenge: {id: string; nextResendAvailableAt: Date};\n};\n\nexport async function signup(params: SignupParams & {sourceIp?: string}): Promise<SignupResult> {\n const email = emailSchema.parse(params.email);\n const existing = await findUserByEmail({email});\n if (existing) {\n const canResumeVerification =\n existing.status === 'active' &&\n existing.emailVerifiedAt === null &&\n existing.hashedPassword !== null &&\n (await verifyPassword({password: params.password, hash: existing.hashedPassword}));\n if (canResumeVerification) {\n const emailChallenge = await createEmailChallenge({\n email: existing.email,\n purpose: PASSWORD_VERIFICATION_PURPOSE,\n continuation: existing.id,\n idempotencyKey: existing.id,\n sourceIp: params.sourceIp ?? '0.0.0.0',\n });\n return {...existing, emailChallenge};\n }\n throw new EmailTakenError(email);\n }\n\n await assertSignupAllowed({\n signupPolicy: params.signupPolicy,\n email,\n emailVerified: false,\n source: 'password',\n });\n\n const hashedPassword = await hashPassword({password: params.password});\n const user = await createDbUser({\n email,\n hashedPassword,\n name: params.name ?? null,\n signedUp: {viaInvitation: false},\n });\n\n const emailChallenge = await createEmailChallenge({\n email: user.email,\n purpose: PASSWORD_VERIFICATION_PURPOSE,\n continuation: user.id,\n idempotencyKey: user.id,\n sourceIp: params.sourceIp ?? '0.0.0.0',\n });\n\n return {...user, emailChallenge};\n}\n\nexport interface SignupWithInvitationParams extends SignupParams {\n invitationToken: string;\n workspaces: WorkspacesInterModuleClient;\n}\n\nexport interface SignupWithInvitationResult extends LoginResult {\n membership: {id: string; userId: string; workspaceId: string} | null;\n acceptError?: {code: string; message: string};\n}\n\nexport async function signupWithInvitation(\n params: SignupWithInvitationParams,\n): Promise<SignupWithInvitationResult> {\n // Step 1: Pre-validate the invitation BEFORE any user write so an invalid\n // token never produces an orphan user. Re-validation happens again inside\n // acceptWorkspaceInvitation (race-safe) after the user is created.\n try {\n await params.workspaces.preflightInvitationAcceptance({\n token: params.invitationToken,\n email: params.email,\n });\n } catch (error) {\n throw invitationErrorFromKnownError(error, 'preflightInvitationAcceptance');\n }\n\n // Step 2: Create the user as verified. The invitation email is the proof of\n // ownership, and this avoids a second auth-table write just to mark verified.\n const existing = await findUserByEmail({email: params.email});\n if (existing) {\n throw new EmailTakenError(params.email);\n }\n const hashedPassword = await hashPassword({password: params.password});\n const user = await createDbUser({\n email: params.email,\n hashedPassword,\n name: params.name ?? null,\n emailVerifiedAt: new Date(),\n signedUp: {viaInvitation: true},\n });\n\n // Step 3: Accept the invitation through the workspaces module boundary.\n // This is intentionally not one database transaction with auth; treat it like\n // calling another service. If it fails after user creation, return a verified\n // signed-in user and an explicit retryable accept error.\n let membership: SignupWithInvitationResult['membership'] = null;\n let acceptError: SignupWithInvitationResult['acceptError'];\n try {\n const result = await params.workspaces.acceptInvitation({\n token: params.invitationToken,\n userId: user.id,\n email: user.email,\n name: user.name,\n });\n membership = {\n id: result.membership.id,\n userId: result.membership.userId,\n workspaceId: result.membership.workspaceId,\n };\n } catch (error) {\n if (isInterModuleKnownError(workspacesInterModuleContract.methods.acceptInvitation, error)) {\n acceptError = {code: error.code, message: invitationErrorMessage(error.code)};\n } else {\n acceptError = {\n code: 'AcceptFailed',\n message: 'Could not accept the invitation; please retry from the invite link.',\n };\n }\n }\n\n // Step 4: Issue session. createSessionTokens reads memberships through the\n // workspaces module API, so a successful accept is reflected in the JWT.\n const {token, refreshToken, adminRole} = await createSessionTokens(user, params.workspaces);\n\n if (acceptError) {\n return {token, refreshToken, user, membership, acceptError, adminRole};\n }\n return {token, refreshToken, user, membership, adminRole};\n}\n\nexport interface CreateUserParams extends SignupParams {\n verified: boolean;\n}\n\nexport async function createUser(params: CreateUserParams): Promise<User> {\n const existing = await findUserByEmail({email: params.email});\n if (existing) {\n throw new EmailTakenError(params.email);\n }\n\n const hashedPassword = await hashPassword({password: params.password});\n const user = await createDbUser({\n email: params.email,\n hashedPassword,\n name: params.name ?? null,\n emailVerifiedAt: params.verified ? new Date() : null,\n });\n\n return user;\n}\n\nexport interface LoginParams {\n email: string;\n password: string;\n workspaces: WorkspacesInterModuleClient;\n}\n\nexport interface LoginResult {\n token: string;\n refreshToken: string;\n user: User;\n adminRole: AdminRole | null;\n}\n\nexport async function login(params: LoginParams): Promise<LoginResult> {\n const user = await findUserByEmail({email: params.email});\n\n if (!user) {\n await verifyPassword({password: params.password, hash: await getDummyHash()});\n throw new InvalidCredentialsError();\n }\n\n const hasPassword = user.hashedPassword !== null;\n const ok = await verifyPassword({\n password: params.password,\n hash: user.hashedPassword ?? (await getDummyHash()),\n });\n if (!hasPassword || !ok || user.status !== 'active') {\n throw new InvalidCredentialsError();\n }\n\n if (user.emailVerifiedAt === null) {\n throw new EmailNotVerifiedError();\n }\n\n const {token, refreshToken, adminRole} = await createSessionTokens(user, params.workspaces);\n\n return {token, refreshToken, user, adminRole};\n}\n\nexport interface CreateSessionForUserParams {\n userId?: string | undefined;\n email?: string | undefined;\n workspaces: WorkspacesInterModuleClient;\n}\n\nexport interface CreateSessionForUserResult {\n token: string;\n refreshToken: string;\n user: User;\n adminRole: AdminRole | null;\n}\n\nexport type CreateSessionForUserError =\n | AuthDependencyUnavailableError\n | EmailNotVerifiedError\n | InvalidCredentialsError\n | UserNotFoundError;\n\nexport async function createSessionForUser(\n params: CreateSessionForUserParams,\n): Promise<CreateSessionForUserResult> {\n const user = params.userId\n ? await findUserById({id: params.userId})\n : params.email\n ? await findUserByEmail({email: emailSchema.parse(params.email)})\n : undefined;\n\n if (!user) {\n throw new UserNotFoundError(params.userId ?? params.email ?? 'unknown');\n }\n if (user.emailVerifiedAt === null) {\n throw new EmailNotVerifiedError();\n }\n if (user.status !== 'active') {\n throw new InvalidCredentialsError();\n }\n\n const {token, refreshToken, adminRole} = await createSessionTokens(user, params.workspaces);\n\n return {token, refreshToken, user, adminRole};\n}\n\nexport interface RefreshAccessTokenResult {\n token: string;\n /** Undefined on a grace-window hit: keep the existing cookie instead of rotating it. */\n refreshToken: string | undefined;\n user: User;\n adminRole: AdminRole | null;\n}\n\nexport async function refreshAccessToken(params: {\n refreshToken: string;\n workspaces: WorkspacesInterModuleClient;\n}): Promise<RefreshAccessTokenResult> {\n const currentHashedToken = hashOpaqueToken(params.refreshToken);\n const current = await findRefreshTokenByHash({hashedToken: currentHashedToken});\n if (!current) {\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token is invalid or expired');\n }\n\n const user = await findUserById({id: current.userId});\n if (user?.status !== 'active') {\n await revokeRefreshSession({sessionId: current.sessionId, userId: current.userId});\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token is invalid or expired');\n }\n const adminRole = await getCurrentAdminRole({userId: user.id});\n\n // Within the grace window a rotated token means a concurrent refresh (e.g. a\n // second tab); past it, reuse of a retired token means a compromised session.\n if (current.rotatedAt) {\n if (isWithinRotationGrace(current)) {\n const memberships = await loadTokenMemberships(user.id, params.workspaces);\n const token = await signAccessToken(user, memberships, current.sessionId);\n recordRefreshOutcome('grace');\n return {token, refreshToken: undefined, user, adminRole};\n }\n await revokeRefreshTokensForUser({userId: user.id});\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token reused after rotation');\n }\n\n // Keep the external membership snapshot ahead of rotation so an outage\n // cannot retire a refresh token that the caller can still retry.\n const memberships = await loadTokenMemberships(user.id, params.workspaces);\n\n // Losing the CAS requires a state check: another request may have rotated,\n // revoked, or expired the token before this refresh could claim it.\n const nextRefreshToken = generateOpaqueToken('refreshToken');\n const rotated = await rotateRefreshToken({\n userId: current.userId,\n id: current.id,\n currentHashedToken,\n nextHashedToken: hashOpaqueToken(nextRefreshToken),\n expiresAt: daysFromNow(config.AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS),\n });\n if (!rotated) {\n const latestUser = await findUserById({id: current.userId});\n if (latestUser?.status !== 'active') {\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token is invalid or expired');\n }\n const latest = await findRefreshTokenByHash({hashedToken: currentHashedToken});\n if (!latest || !isWithinRotationGrace(latest)) {\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token is invalid or expired');\n }\n const token = await signAccessToken(user, memberships, latest.sessionId);\n recordRefreshOutcome('grace');\n return {token, refreshToken: undefined, user, adminRole};\n }\n\n const token = await signAccessToken(user, memberships, current.sessionId);\n recordRefreshOutcome('rotated');\n return {token, refreshToken: nextRefreshToken, user, adminRole};\n}\n\nexport interface ConfirmEmailVerificationResult {\n token: string;\n refreshToken: string;\n user: User;\n adminRole: AdminRole | null;\n}\n\nexport interface ResendEmailVerificationResult {\n nextResendAvailableAt: Date;\n}\n\nexport async function confirmEmailVerification(params: {\n email: string;\n challengeId: string;\n code: string;\n workspaces: WorkspacesInterModuleClient;\n}): Promise<ConfirmEmailVerificationResult> {\n const user = await findUserByEmail({email: emailSchema.parse(params.email)});\n if (!user) throw new TokenInvalidError('Verification code is invalid or expired');\n await confirmEmailChallenge({id: params.challengeId, code: params.code, continuation: user.id});\n await consumeEmailChallengeProof({\n id: params.challengeId,\n purpose: PASSWORD_VERIFICATION_PURPOSE,\n continuation: user.id,\n });\n\n const verifiedUser = await markEmailVerified({userId: user.id});\n if (verifiedUser?.status !== 'active') {\n throw new TokenInvalidError('Verification code is invalid or expired');\n }\n\n const {token, refreshToken, adminRole} = await createSessionTokens(\n verifiedUser,\n params.workspaces,\n );\n\n return {token, refreshToken, user: verifiedUser, adminRole};\n}\n\nexport async function resendEmailVerification(params: {\n email: string;\n challengeId: string;\n sourceIp: string;\n}): Promise<ResendEmailVerificationResult> {\n const user = await findUserByEmail({email: emailSchema.parse(params.email)});\n if (!user || user.emailVerifiedAt || user.status !== 'active') {\n return {nextResendAvailableAt: new Date()};\n }\n const result = await resendEmailChallenge({\n id: params.challengeId,\n continuation: user.id,\n sourceIp: params.sourceIp,\n });\n\n return {nextResendAvailableAt: result.nextResendAvailableAt};\n}\n\nexport async function requestPasswordReset(params: {email: string}): Promise<void> {\n const user = await findUserByEmail({email: params.email});\n if (user?.status !== 'active' || user.hashedPassword === null) {\n return;\n }\n\n const rawToken = generateOpaqueToken('passwordReset');\n await createPasswordReset({\n userId: user.id,\n hashedToken: hashOpaqueToken(rawToken),\n expiresAt: hoursFromNow(RESET_TTL_HOURS),\n sendEmail: {\n email: user.email,\n resetLink: passwordResetLink(rawToken),\n expiresInHours: RESET_TTL_HOURS,\n },\n });\n}\n\nexport interface ConfirmPasswordResetResult {\n token: string;\n refreshToken: string;\n user: User;\n adminRole: AdminRole | null;\n}\n\nexport async function confirmPasswordReset(params: {\n token: string;\n newPassword: string;\n workspaces: WorkspacesInterModuleClient;\n}): Promise<ConfirmPasswordResetResult> {\n const consumed = await consumePasswordReset({hashedToken: hashOpaqueToken(params.token)});\n if (!consumed) {\n throw new TokenInvalidError('Reset token is invalid or expired');\n }\n\n const existingUser = await findUserById({id: consumed.userId});\n if (existingUser?.status !== 'active' || existingUser.hashedPassword === null) {\n throw new TokenInvalidError('Reset token is invalid or expired');\n }\n\n const hashedPassword = await hashPassword({password: params.newPassword});\n const user = await updateUserPassword({userId: consumed.userId, hashedPassword});\n if (user?.status !== 'active') {\n throw new TokenInvalidError('Reset token is invalid or expired');\n }\n\n await revokeRefreshTokensForUser({userId: consumed.userId});\n\n const {token, refreshToken, adminRole} = await createSessionTokens(user, params.workspaces);\n\n return {token, refreshToken, user, adminRole};\n}\n\nexport async function changePassword(params: {\n userId: string;\n currentPassword: string;\n newPassword: string;\n refreshToken?: string | undefined;\n}): Promise<void> {\n const user = await findUserById({id: params.userId});\n if (!user) {\n throw new UserNotFoundError(params.userId);\n }\n\n const hasPassword = user.hashedPassword !== null;\n const ok = await verifyPassword({\n password: params.currentPassword,\n hash: user.hashedPassword ?? (await getDummyHash()),\n });\n if (!hasPassword || !ok) {\n throw new InvalidCredentialsError();\n }\n\n const hashedPassword = await hashPassword({password: params.newPassword});\n const currentRefreshSession = params.refreshToken\n ? await findActiveRefreshTokenByHash({hashedToken: hashOpaqueToken(params.refreshToken)})\n : undefined;\n const exceptRefreshTokenId =\n currentRefreshSession?.userId === user.id ? currentRefreshSession.id : undefined;\n\n await updateUserPassword({userId: user.id, hashedPassword});\n await revokeRefreshTokensForUser({userId: user.id, exceptRefreshTokenId});\n}\n\nexport async function logout(params: {refreshToken?: string | undefined}): Promise<void> {\n if (!params.refreshToken) return;\n const refreshToken = await findRefreshTokenByHash({\n hashedToken: hashOpaqueToken(params.refreshToken),\n });\n if (!refreshToken) return;\n await revokeRefreshSession({sessionId: refreshToken.sessionId, userId: refreshToken.userId});\n}\n\nexport interface GetCurrentUserResult {\n user: User;\n}\n\nexport async function getCurrentUser(params: {userId: string}): Promise<GetCurrentUserResult> {\n const user = await findUserById({id: params.userId});\n if (!user) {\n throw new UserNotFoundError(params.userId);\n }\n\n return {user};\n}\n"],"names":["emailSchema","confirmEmailChallenge","consumeEmailChallengeProof","createEmailChallenge","resendEmailChallenge","workspacesInterModuleContract","isInterModuleKnownError","userAccessTokenKey","generateOpaqueToken","hashOpaqueToken","config","consumePasswordReset","createPasswordReset","createRefreshTokenForActiveUser","findActiveRefreshTokenByHash","findRefreshTokenByHash","revokeRefreshSession","revokeRefreshTokensForUser","rotateRefreshToken","createUser","createDbUser","findUserByEmail","findUserById","markEmailVerified","provisionUser","provisionDbUser","updateUserPassword","recordTokenRefreshed","getCurrentAdminRole","AuthDependencyUnavailableError","EmailNotVerifiedError","EmailTakenError","InvalidCredentialsError","InvitationEmailMismatchError","SignupNotAllowedError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","UserNotFoundError","signUserToken","hashPassword","verifyPassword","createEnvironmentSignupPolicy","RESET_TTL_HOURS","PASSWORD_VERIFICATION_PURPOSE","DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE","defaultSignupPolicy","assertSignupAllowed","params","result","signupPolicy","isSignupAllowed","email","emailVerified","source","allowed","message","dummyHashCache","getDummyHash","password","hoursFromNow","hours","Date","now","daysFromNow","days","recordRefreshOutcome","outcome","loadTokenMemberships","userId","workspaces","memberships","listMembershipsForTokenClaims","catch","error","signAccessToken","user","refreshSessionId","id","name","secret","expiresIn","AUTH_JWT_EXPIRES_IN","isWithinRotationGrace","refreshToken","rotatedAt","graceMs","AUTH_REFRESH_ROTATION_GRACE_SECONDS","getTime","createRefreshSession","session","sessionId","hashedToken","expiresAt","AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS","createSessionTokens","crypto","randomUUID","token","adminRole","passwordResetLink","rawToken","CLIENT_BASE_URL","invitationErrorFromKnownError","method","contractMethod","methods","code","invitationErrorMessage","parse","existing","signup","canResumeVerification","status","emailVerifiedAt","hashedPassword","hash","emailChallenge","purpose","continuation","idempotencyKey","sourceIp","signedUp","viaInvitation","signupWithInvitation","preflightInvitationAcceptance","invitationToken","membership","acceptError","acceptInvitation","workspaceId","verified","login","hasPassword","ok","createSessionForUser","undefined","refreshAccessToken","currentHashedToken","current","nextRefreshToken","rotated","nextHashedToken","latestUser","latest","confirmEmailVerification","challengeId","verifiedUser","resendEmailVerification","nextResendAvailableAt","requestPasswordReset","sendEmail","resetLink","expiresInHours","confirmPasswordReset","consumed","existingUser","newPassword","changePassword","currentPassword","currentRefreshSession","exceptRefreshTokenId","logout","getCurrentUser"],"mappings":"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"}
@@ -6,7 +6,7 @@ export declare function jobLeaseParamsFrom(source: JobLeaseParamSource, stepScop
6
6
  currentStepAttempt: number;
7
7
  }): IssueJobLeaseTokenParams;
8
8
  export declare function issueJobLeaseToken(claims: IssueJobLeaseTokenParams): Promise<string>;
9
- /** Returns the claims on success, or `null` for any invalid input never throws. */
9
+ /** Returns the claims on success, or `null` for any invalid input. Never throws. */
10
10
  export declare function verifyJobLeaseToken(token: string): Promise<JobLeaseTokenClaims | null>;
11
11
  export {};
12
12
  //# sourceMappingURL=job-lease-token.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"job-lease-token.d.ts","sourceRoot":"","sources":["../../src/core/job-lease-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,uBAAuB,CAAC;AAO/B,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,mBAAmB,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC,CAAC;AAExF,KAAK,mBAAmB,GAAG,IAAI,CAC7B,mBAAmB,EACjB,eAAe,GACf,sBAAsB,GACtB,OAAO,GACP,gBAAgB,GAChB,WAAW,GACX,aAAa,GACb,iBAAiB,CACpB,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,mBAAmB,EAC3B,SAAS,CAAC,EAAE;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAC,GAC9D,wBAAwB,CAW1B;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0B1F;AAED,qFAAqF;AACrF,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAc5F"}
1
+ {"version":3,"file":"job-lease-token.d.ts","sourceRoot":"","sources":["../../src/core/job-lease-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,uBAAuB,CAAC;AAO/B,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,mBAAmB,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC,CAAC;AAExF,KAAK,mBAAmB,GAAG,IAAI,CAC7B,mBAAmB,EACjB,eAAe,GACf,sBAAsB,GACtB,OAAO,GACP,gBAAgB,GAChB,WAAW,GACX,aAAa,GACb,iBAAiB,CACpB,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,mBAAmB,EAC3B,SAAS,CAAC,EAAE;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAC,GAC9D,wBAAwB,CAW1B;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0B1F;AAED,oFAAoF;AACpF,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAc5F"}
@@ -40,7 +40,7 @@ export async function issueJobLeaseToken(claims) {
40
40
  recordTokenIssued('job_lease');
41
41
  return token;
42
42
  }
43
- /** Returns the claims on success, or `null` for any invalid input never throws. */ export async function verifyJobLeaseToken(token) {
43
+ /** Returns the claims on success, or `null` for any invalid input. Never throws. */ export async function verifyJobLeaseToken(token) {
44
44
  try {
45
45
  const claims = await verifyHs256({
46
46
  token,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/job-lease-token.ts"],"sourcesContent":["import {\n JOB_LEASE_TOKEN_AUDIENCE,\n type JobLeaseTokenClaims,\n jobLeaseTokenClaimsSchema,\n} from '@shipfox/api-auth-dto';\nimport {jobLeaseTokenKey} from '@shipfox/node-auth-root-key';\nimport {signHs256, verifyHs256} from '@shipfox/node-jwt';\nimport {config} from '#config.js';\nimport {recordTokenIssued, recordTokenVerified} from '#metrics/index.js';\n\n// `aud`, `iat` and `exp` are set by the codec (jose); callers supply the business ids only.\nexport type IssueJobLeaseTokenParams = Omit<JobLeaseTokenClaims, 'aud' | 'iat' | 'exp'>;\n\ntype JobLeaseParamSource = Pick<\n JobLeaseTokenClaims,\n | 'workflowRunId'\n | 'workflowRunAttemptId'\n | 'jobId'\n | 'jobExecutionId'\n | 'projectId'\n | 'workspaceId'\n | 'runnerSessionId'\n>;\n\nexport function jobLeaseParamsFrom(\n source: JobLeaseParamSource,\n stepScope?: {currentStepId: string; currentStepAttempt: number},\n): IssueJobLeaseTokenParams {\n return {\n workflowRunId: source.workflowRunId,\n workflowRunAttemptId: source.workflowRunAttemptId,\n jobId: source.jobId,\n jobExecutionId: source.jobExecutionId,\n projectId: source.projectId,\n workspaceId: source.workspaceId,\n runnerSessionId: source.runnerSessionId,\n ...(stepScope ? stepScope : {}),\n };\n}\n\nexport async function issueJobLeaseToken(claims: IssueJobLeaseTokenParams): Promise<string> {\n const token = await signHs256({\n payload: {\n workflowRunId: claims.workflowRunId,\n ...(claims.workflowRunAttempt === undefined\n ? {}\n : {workflowRunAttempt: claims.workflowRunAttempt}),\n workflowRunAttemptId: claims.workflowRunAttemptId,\n jobId: claims.jobId,\n jobExecutionId: claims.jobExecutionId,\n projectId: claims.projectId,\n workspaceId: claims.workspaceId,\n runnerSessionId: claims.runnerSessionId,\n ...(claims.currentStepId && claims.currentStepAttempt !== undefined\n ? {\n currentStepId: claims.currentStepId,\n currentStepAttempt: claims.currentStepAttempt,\n }\n : {}),\n },\n secret: jobLeaseTokenKey(),\n expiresIn: config.AUTH_JOB_LEASE_TOKEN_EXPIRES_IN,\n audience: JOB_LEASE_TOKEN_AUDIENCE,\n });\n recordTokenIssued('job_lease');\n return token;\n}\n\n/** Returns the claims on success, or `null` for any invalid input never throws. */\nexport async function verifyJobLeaseToken(token: string): Promise<JobLeaseTokenClaims | null> {\n try {\n const claims = await verifyHs256({\n token,\n secret: jobLeaseTokenKey(),\n schema: jobLeaseTokenClaimsSchema,\n audience: JOB_LEASE_TOKEN_AUDIENCE,\n });\n recordTokenVerified('job_lease', 'ok');\n return claims;\n } catch {\n recordTokenVerified('job_lease', 'rejected');\n return null;\n }\n}\n"],"names":["JOB_LEASE_TOKEN_AUDIENCE","jobLeaseTokenClaimsSchema","jobLeaseTokenKey","signHs256","verifyHs256","config","recordTokenIssued","recordTokenVerified","jobLeaseParamsFrom","source","stepScope","workflowRunId","workflowRunAttemptId","jobId","jobExecutionId","projectId","workspaceId","runnerSessionId","issueJobLeaseToken","claims","token","payload","workflowRunAttempt","undefined","currentStepId","currentStepAttempt","secret","expiresIn","AUTH_JOB_LEASE_TOKEN_EXPIRES_IN","audience","verifyJobLeaseToken","schema"],"mappings":"AAAA,SACEA,wBAAwB,EAExBC,yBAAyB,QACpB,wBAAwB;AAC/B,SAAQC,gBAAgB,QAAO,8BAA8B;AAC7D,SAAQC,SAAS,EAAEC,WAAW,QAAO,oBAAoB;AACzD,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,iBAAiB,EAAEC,mBAAmB,QAAO,oBAAoB;AAgBzE,OAAO,SAASC,mBACdC,MAA2B,EAC3BC,SAA+D;IAE/D,OAAO;QACLC,eAAeF,OAAOE,aAAa;QACnCC,sBAAsBH,OAAOG,oBAAoB;QACjDC,OAAOJ,OAAOI,KAAK;QACnBC,gBAAgBL,OAAOK,cAAc;QACrCC,WAAWN,OAAOM,SAAS;QAC3BC,aAAaP,OAAOO,WAAW;QAC/BC,iBAAiBR,OAAOQ,eAAe;QACvC,GAAIP,YAAYA,YAAY,CAAC,CAAC;IAChC;AACF;AAEA,OAAO,eAAeQ,mBAAmBC,MAAgC;IACvE,MAAMC,QAAQ,MAAMjB,UAAU;QAC5BkB,SAAS;YACPV,eAAeQ,OAAOR,aAAa;YACnC,GAAIQ,OAAOG,kBAAkB,KAAKC,YAC9B,CAAC,IACD;gBAACD,oBAAoBH,OAAOG,kBAAkB;YAAA,CAAC;YACnDV,sBAAsBO,OAAOP,oBAAoB;YACjDC,OAAOM,OAAON,KAAK;YACnBC,gBAAgBK,OAAOL,cAAc;YACrCC,WAAWI,OAAOJ,SAAS;YAC3BC,aAAaG,OAAOH,WAAW;YAC/BC,iBAAiBE,OAAOF,eAAe;YACvC,GAAIE,OAAOK,aAAa,IAAIL,OAAOM,kBAAkB,KAAKF,YACtD;gBACEC,eAAeL,OAAOK,aAAa;gBACnCC,oBAAoBN,OAAOM,kBAAkB;YAC/C,IACA,CAAC,CAAC;QACR;QACAC,QAAQxB;QACRyB,WAAWtB,OAAOuB,+BAA+B;QACjDC,UAAU7B;IACZ;IACAM,kBAAkB;IAClB,OAAOc;AACT;AAEA,mFAAmF,GACnF,OAAO,eAAeU,oBAAoBV,KAAa;IACrD,IAAI;QACF,MAAMD,SAAS,MAAMf,YAAY;YAC/BgB;YACAM,QAAQxB;YACR6B,QAAQ9B;YACR4B,UAAU7B;QACZ;QACAO,oBAAoB,aAAa;QACjC,OAAOY;IACT,EAAE,OAAM;QACNZ,oBAAoB,aAAa;QACjC,OAAO;IACT;AACF"}
1
+ {"version":3,"sources":["../../src/core/job-lease-token.ts"],"sourcesContent":["import {\n JOB_LEASE_TOKEN_AUDIENCE,\n type JobLeaseTokenClaims,\n jobLeaseTokenClaimsSchema,\n} from '@shipfox/api-auth-dto';\nimport {jobLeaseTokenKey} from '@shipfox/node-auth-root-key';\nimport {signHs256, verifyHs256} from '@shipfox/node-jwt';\nimport {config} from '#config.js';\nimport {recordTokenIssued, recordTokenVerified} from '#metrics/index.js';\n\n// `aud`, `iat` and `exp` are set by the codec (jose); callers supply the business ids only.\nexport type IssueJobLeaseTokenParams = Omit<JobLeaseTokenClaims, 'aud' | 'iat' | 'exp'>;\n\ntype JobLeaseParamSource = Pick<\n JobLeaseTokenClaims,\n | 'workflowRunId'\n | 'workflowRunAttemptId'\n | 'jobId'\n | 'jobExecutionId'\n | 'projectId'\n | 'workspaceId'\n | 'runnerSessionId'\n>;\n\nexport function jobLeaseParamsFrom(\n source: JobLeaseParamSource,\n stepScope?: {currentStepId: string; currentStepAttempt: number},\n): IssueJobLeaseTokenParams {\n return {\n workflowRunId: source.workflowRunId,\n workflowRunAttemptId: source.workflowRunAttemptId,\n jobId: source.jobId,\n jobExecutionId: source.jobExecutionId,\n projectId: source.projectId,\n workspaceId: source.workspaceId,\n runnerSessionId: source.runnerSessionId,\n ...(stepScope ? stepScope : {}),\n };\n}\n\nexport async function issueJobLeaseToken(claims: IssueJobLeaseTokenParams): Promise<string> {\n const token = await signHs256({\n payload: {\n workflowRunId: claims.workflowRunId,\n ...(claims.workflowRunAttempt === undefined\n ? {}\n : {workflowRunAttempt: claims.workflowRunAttempt}),\n workflowRunAttemptId: claims.workflowRunAttemptId,\n jobId: claims.jobId,\n jobExecutionId: claims.jobExecutionId,\n projectId: claims.projectId,\n workspaceId: claims.workspaceId,\n runnerSessionId: claims.runnerSessionId,\n ...(claims.currentStepId && claims.currentStepAttempt !== undefined\n ? {\n currentStepId: claims.currentStepId,\n currentStepAttempt: claims.currentStepAttempt,\n }\n : {}),\n },\n secret: jobLeaseTokenKey(),\n expiresIn: config.AUTH_JOB_LEASE_TOKEN_EXPIRES_IN,\n audience: JOB_LEASE_TOKEN_AUDIENCE,\n });\n recordTokenIssued('job_lease');\n return token;\n}\n\n/** Returns the claims on success, or `null` for any invalid input. Never throws. */\nexport async function verifyJobLeaseToken(token: string): Promise<JobLeaseTokenClaims | null> {\n try {\n const claims = await verifyHs256({\n token,\n secret: jobLeaseTokenKey(),\n schema: jobLeaseTokenClaimsSchema,\n audience: JOB_LEASE_TOKEN_AUDIENCE,\n });\n recordTokenVerified('job_lease', 'ok');\n return claims;\n } catch {\n recordTokenVerified('job_lease', 'rejected');\n return null;\n }\n}\n"],"names":["JOB_LEASE_TOKEN_AUDIENCE","jobLeaseTokenClaimsSchema","jobLeaseTokenKey","signHs256","verifyHs256","config","recordTokenIssued","recordTokenVerified","jobLeaseParamsFrom","source","stepScope","workflowRunId","workflowRunAttemptId","jobId","jobExecutionId","projectId","workspaceId","runnerSessionId","issueJobLeaseToken","claims","token","payload","workflowRunAttempt","undefined","currentStepId","currentStepAttempt","secret","expiresIn","AUTH_JOB_LEASE_TOKEN_EXPIRES_IN","audience","verifyJobLeaseToken","schema"],"mappings":"AAAA,SACEA,wBAAwB,EAExBC,yBAAyB,QACpB,wBAAwB;AAC/B,SAAQC,gBAAgB,QAAO,8BAA8B;AAC7D,SAAQC,SAAS,EAAEC,WAAW,QAAO,oBAAoB;AACzD,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,iBAAiB,EAAEC,mBAAmB,QAAO,oBAAoB;AAgBzE,OAAO,SAASC,mBACdC,MAA2B,EAC3BC,SAA+D;IAE/D,OAAO;QACLC,eAAeF,OAAOE,aAAa;QACnCC,sBAAsBH,OAAOG,oBAAoB;QACjDC,OAAOJ,OAAOI,KAAK;QACnBC,gBAAgBL,OAAOK,cAAc;QACrCC,WAAWN,OAAOM,SAAS;QAC3BC,aAAaP,OAAOO,WAAW;QAC/BC,iBAAiBR,OAAOQ,eAAe;QACvC,GAAIP,YAAYA,YAAY,CAAC,CAAC;IAChC;AACF;AAEA,OAAO,eAAeQ,mBAAmBC,MAAgC;IACvE,MAAMC,QAAQ,MAAMjB,UAAU;QAC5BkB,SAAS;YACPV,eAAeQ,OAAOR,aAAa;YACnC,GAAIQ,OAAOG,kBAAkB,KAAKC,YAC9B,CAAC,IACD;gBAACD,oBAAoBH,OAAOG,kBAAkB;YAAA,CAAC;YACnDV,sBAAsBO,OAAOP,oBAAoB;YACjDC,OAAOM,OAAON,KAAK;YACnBC,gBAAgBK,OAAOL,cAAc;YACrCC,WAAWI,OAAOJ,SAAS;YAC3BC,aAAaG,OAAOH,WAAW;YAC/BC,iBAAiBE,OAAOF,eAAe;YACvC,GAAIE,OAAOK,aAAa,IAAIL,OAAOM,kBAAkB,KAAKF,YACtD;gBACEC,eAAeL,OAAOK,aAAa;gBACnCC,oBAAoBN,OAAOM,kBAAkB;YAC/C,IACA,CAAC,CAAC;QACR;QACAC,QAAQxB;QACRyB,WAAWtB,OAAOuB,+BAA+B;QACjDC,UAAU7B;IACZ;IACAM,kBAAkB;IAClB,OAAOc;AACT;AAEA,kFAAkF,GAClF,OAAO,eAAeU,oBAAoBV,KAAa;IACrD,IAAI;QACF,MAAMD,SAAS,MAAMf,YAAY;YAC/BgB;YACAM,QAAQxB;YACR6B,QAAQ9B;YACR4B,UAAU7B;QACZ;QACAO,oBAAoB,aAAa;QACjC,OAAOY;IACT,EAAE,OAAM;QACNZ,oBAAoB,aAAa;QACjC,OAAO;IACT;AACF"}