@shipfox/api-auth 7.0.2 → 9.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +73 -0
- package/README.md +29 -8
- package/dist/core/auth.d.ts.map +1 -1
- package/dist/core/auth.js +38 -21
- package/dist/core/auth.js.map +1 -1
- package/dist/core/entities/refresh-token.d.ts +1 -0
- package/dist/core/entities/refresh-token.d.ts.map +1 -1
- package/dist/core/entities/refresh-token.js.map +1 -1
- package/dist/core/jwt.d.ts +2 -0
- package/dist/core/jwt.d.ts.map +1 -1
- package/dist/core/jwt.js +3 -1
- package/dist/core/jwt.js.map +1 -1
- package/dist/db/db.d.ts +34 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/refresh-tokens.d.ts +9 -0
- package/dist/db/refresh-tokens.d.ts.map +1 -1
- package/dist/db/refresh-tokens.js +14 -0
- package/dist/db/refresh-tokens.js.map +1 -1
- package/dist/db/schema/refresh-tokens.d.ts +17 -0
- package/dist/db/schema/refresh-tokens.d.ts.map +1 -1
- package/dist/db/schema/refresh-tokens.js +4 -0
- package/dist/db/schema/refresh-tokens.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/presentation/auth/jwt-auth.d.ts +14 -1
- package/dist/presentation/auth/jwt-auth.d.ts.map +1 -1
- package/dist/presentation/auth/jwt-auth.js +34 -0
- package/dist/presentation/auth/jwt-auth.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0000_initial.sql +2 -0
- package/drizzle/meta/0000_snapshot.json +29 -0
- package/package.json +22 -23
- package/src/core/auth.test.ts +42 -0
- package/src/core/auth.ts +36 -19
- package/src/core/entities/refresh-token.ts +1 -0
- package/src/core/jwt.ts +3 -0
- package/src/db/refresh-tokens.test.ts +42 -0
- package/src/db/refresh-tokens.ts +42 -0
- package/src/db/schema/refresh-tokens.ts +6 -0
- package/src/index.test.ts +1 -0
- package/src/index.ts +8 -2
- package/src/presentation/auth/jwt-auth.test.ts +102 -1
- package/src/presentation/auth/jwt-auth.ts +55 -1
- package/src/presentation/e2eRoutes/index.test.ts +1 -0
- package/src/presentation/routes/index.test.ts +1 -0
- package/test/globalSetup.ts +3 -10
- package/test/routes.ts +1 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$ shipfox-swc
|
|
2
|
-
Successfully compiled: 52 files with swc (
|
|
2
|
+
Successfully compiled: 52 files with swc (263.02ms)
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# @shipfox/api-auth
|
|
2
2
|
|
|
3
|
+
## 9.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 475ce59: Republishes all public packages after restoring release authorization.
|
|
8
|
+
- Updated dependencies [44c15c8]
|
|
9
|
+
- Updated dependencies [475ce59]
|
|
10
|
+
- Updated dependencies [154e03f]
|
|
11
|
+
- @shipfox/api-email-challenges@1.1.0
|
|
12
|
+
- @shipfox/api-auth-context@9.0.1
|
|
13
|
+
- @shipfox/api-auth-dto@9.0.1
|
|
14
|
+
- @shipfox/api-workspaces-dto@9.0.1
|
|
15
|
+
- @shipfox/config@1.2.3
|
|
16
|
+
- @shipfox/inter-module@0.2.1
|
|
17
|
+
- @shipfox/node-auth-root-key@0.2.2
|
|
18
|
+
- @shipfox/node-drizzle@0.3.3
|
|
19
|
+
- @shipfox/node-email@0.3.2
|
|
20
|
+
- @shipfox/node-fastify@0.3.1
|
|
21
|
+
- @shipfox/node-jwt@0.3.1
|
|
22
|
+
- @shipfox/node-mailer@0.2.2
|
|
23
|
+
- @shipfox/node-module@1.0.0
|
|
24
|
+
- @shipfox/node-opentelemetry@0.6.1
|
|
25
|
+
- @shipfox/node-outbox@0.2.5
|
|
26
|
+
- @shipfox/node-postgres@0.4.3
|
|
27
|
+
- @shipfox/node-rate-limit@0.3.1
|
|
28
|
+
- @shipfox/node-tokens@0.3.1
|
|
29
|
+
|
|
30
|
+
## 9.0.0
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- Updated dependencies [9c9d266]
|
|
35
|
+
- Updated dependencies [c279061]
|
|
36
|
+
- Updated dependencies [9083d20]
|
|
37
|
+
- @shipfox/api-workspaces-dto@9.0.0
|
|
38
|
+
- @shipfox/api-email-challenges@1.0.0
|
|
39
|
+
- @shipfox/api-auth-context@9.0.0
|
|
40
|
+
- @shipfox/api-auth-dto@7.1.0
|
|
41
|
+
- @shipfox/config@1.2.2
|
|
42
|
+
- @shipfox/inter-module@0.2.0
|
|
43
|
+
- @shipfox/node-auth-root-key@0.2.1
|
|
44
|
+
- @shipfox/node-drizzle@0.3.2
|
|
45
|
+
- @shipfox/node-email@0.3.1
|
|
46
|
+
- @shipfox/node-fastify@0.3.0
|
|
47
|
+
- @shipfox/node-jwt@0.3.0
|
|
48
|
+
- @shipfox/node-mailer@0.2.1
|
|
49
|
+
- @shipfox/node-module@0.5.0
|
|
50
|
+
- @shipfox/node-opentelemetry@0.6.0
|
|
51
|
+
- @shipfox/node-outbox@0.2.4
|
|
52
|
+
- @shipfox/node-postgres@0.4.2
|
|
53
|
+
- @shipfox/node-rate-limit@0.3.0
|
|
54
|
+
- @shipfox/node-tokens@0.3.0
|
|
55
|
+
|
|
56
|
+
## 7.1.0
|
|
57
|
+
|
|
58
|
+
### Minor Changes
|
|
59
|
+
|
|
60
|
+
- 2a7d951: Adds authenticated refresh-session context resolution with stable identity across access-token refreshes.
|
|
61
|
+
|
|
62
|
+
### Patch Changes
|
|
63
|
+
|
|
64
|
+
- Updated dependencies [ac42c96]
|
|
65
|
+
- Updated dependencies [769d919]
|
|
66
|
+
- Updated dependencies [6ce08c0]
|
|
67
|
+
- Updated dependencies [8bb32b2]
|
|
68
|
+
- @shipfox/node-fastify@0.3.0
|
|
69
|
+
- @shipfox/node-module@0.5.0
|
|
70
|
+
- @shipfox/api-auth-dto@7.1.0
|
|
71
|
+
- @shipfox/node-opentelemetry@0.6.0
|
|
72
|
+
- @shipfox/api-email-challenges@0.3.0
|
|
73
|
+
- @shipfox/api-auth-context@7.1.0
|
|
74
|
+
- @shipfox/node-mailer@0.2.1
|
|
75
|
+
|
|
3
76
|
## 7.0.2
|
|
4
77
|
|
|
5
78
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# Shipfox API Auth
|
|
2
2
|
|
|
3
|
-
Shipfox API Auth provides the server-side auth module for Shipfox APIs.
|
|
3
|
+
Shipfox API Auth provides the server-side auth module for Shipfox APIs. This
|
|
4
|
+
package owns user accounts, email verification, login, refresh sessions,
|
|
5
|
+
password reset, password change, JWT auth, and its PostgreSQL tables.
|
|
6
|
+
|
|
7
|
+
For repository-wide backend module, configuration, error, and observability
|
|
8
|
+
rules, read the [backend architecture guide](../../../docs/architecture/backend-architecture.md).
|
|
9
|
+
This README owns the local security model, package API, routes, data model, and
|
|
10
|
+
auth-specific operational constraints.
|
|
4
11
|
|
|
5
12
|
## Example
|
|
6
13
|
|
|
@@ -250,6 +257,7 @@ It also exports lower-level pieces for tests and advanced integration:
|
|
|
250
257
|
- `issueRunnerSessionToken(claims)` / `verifyRunnerSessionToken(token)`: mint and verify runner session tokens.
|
|
251
258
|
- `issueJobLeaseToken(claims)` / `verifyJobLeaseToken(token)`: mint and verify job lease tokens.
|
|
252
259
|
- `getClientContext(request)`: reads the authenticated user context from a Fastify request.
|
|
260
|
+
- `getAuthenticatedSessionContext(request)`: resolves an authenticated request to its user ID and active refresh-session ID.
|
|
253
261
|
- `findUserByEmail({email})`: read-only lookup of the current owner of a normalized email; see below.
|
|
254
262
|
- Entity types: `User`, `UserStatus`, `RefreshToken`, `PasswordReset`, and `EmailOwner`.
|
|
255
263
|
|
|
@@ -333,6 +341,24 @@ public contract.
|
|
|
333
341
|
`createLeaseTokenAuthMethod` make request-auth methods. They do not add a
|
|
334
342
|
user-facing login method.
|
|
335
343
|
|
|
344
|
+
### Authenticated session context
|
|
345
|
+
|
|
346
|
+
`getAuthenticatedSessionContext(request)` returns the stable refresh-session
|
|
347
|
+
identity behind an authenticated user request:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
import {getAuthenticatedSessionContext} from '@shipfox/api-auth';
|
|
351
|
+
|
|
352
|
+
const session = await getAuthenticatedSessionContext(request);
|
|
353
|
+
// {userId, refreshSessionId}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
The refresh-session ID remains the same when its refresh token rotates. The
|
|
357
|
+
helper verifies that the session is active and belongs to the authenticated user,
|
|
358
|
+
and returns the standard `401 unauthorized` error for a missing, malformed,
|
|
359
|
+
expired, or revoked session. It exposes neither refresh-token material nor
|
|
360
|
+
storage details.
|
|
361
|
+
|
|
336
362
|
## Data Model
|
|
337
363
|
|
|
338
364
|
The module creates tables with the `auth_` prefix:
|
|
@@ -364,13 +390,8 @@ turbo type --filter=@shipfox/api-auth
|
|
|
364
390
|
turbo test --filter=@shipfox/api-auth
|
|
365
391
|
```
|
|
366
392
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
```sh
|
|
370
|
-
docker compose up -d
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
The test environment uses the `api_test` database and sets fake auth secrets in `test/env.ts`.
|
|
393
|
+
For repository test conventions, read the [testing guide](../../../docs/guides/testing.md).
|
|
394
|
+
This package uses the `api_test` database and sets fake auth secrets in `test/env.ts`.
|
|
374
395
|
|
|
375
396
|
## License
|
|
376
397
|
|
package/dist/core/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,KAAK,2BAA2B,EAEjC,MAAM,0CAA0C,CAAC;AAwBlD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EACL,8BAA8B,EAC9B,qBAAqB,EAErB,uBAAuB,EAKvB,iBAAiB,EAClB,MAAM,aAAa,CAAC;
|
|
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,
|
|
9
|
+
import { createRefreshToken, 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 { AuthDependencyUnavailableError, EmailNotVerifiedError, EmailTakenError, InvalidCredentialsError, InvitationEmailMismatchError, TokenAlreadyUsedError, TokenExpiredError, TokenInvalidError, UserNotFoundError } from './errors.js';
|
|
@@ -32,7 +32,7 @@ function daysFromNow(days) {
|
|
|
32
32
|
function recordRefreshOutcome(outcome) {
|
|
33
33
|
recordTokenRefreshed(outcome);
|
|
34
34
|
}
|
|
35
|
-
async function signAccessToken(user, workspaces) {
|
|
35
|
+
async function signAccessToken(user, workspaces, refreshSessionId) {
|
|
36
36
|
const memberships = await workspaces.listMembershipsForTokenClaims({
|
|
37
37
|
userId: user.id
|
|
38
38
|
}).catch((error)=>{
|
|
@@ -43,6 +43,7 @@ async function signAccessToken(user, workspaces) {
|
|
|
43
43
|
email: user.email,
|
|
44
44
|
name: user.name,
|
|
45
45
|
memberships: memberships.memberships,
|
|
46
|
+
refreshSessionId,
|
|
46
47
|
secret: userAccessTokenKey(),
|
|
47
48
|
expiresIn: config.AUTH_JWT_EXPIRES_IN
|
|
48
49
|
});
|
|
@@ -52,14 +53,27 @@ function isWithinRotationGrace(refreshToken) {
|
|
|
52
53
|
const graceMs = config.AUTH_REFRESH_ROTATION_GRACE_SECONDS * 1000;
|
|
53
54
|
return Date.now() - refreshToken.rotatedAt.getTime() <= graceMs;
|
|
54
55
|
}
|
|
55
|
-
async function createRefreshSession(user) {
|
|
56
|
+
async function createRefreshSession(user, refreshSessionId) {
|
|
56
57
|
const refreshToken = generateOpaqueToken('refreshToken');
|
|
57
|
-
await createRefreshToken({
|
|
58
|
+
const session = await createRefreshToken({
|
|
59
|
+
sessionId: refreshSessionId,
|
|
58
60
|
userId: user.id,
|
|
59
61
|
hashedToken: hashOpaqueToken(refreshToken),
|
|
60
62
|
expiresAt: daysFromNow(config.AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS)
|
|
61
63
|
});
|
|
62
|
-
return
|
|
64
|
+
return {
|
|
65
|
+
refreshToken,
|
|
66
|
+
refreshSessionId: session.sessionId
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function createSessionTokens(user, workspaces) {
|
|
70
|
+
const refreshSessionId = crypto.randomUUID();
|
|
71
|
+
const token = await signAccessToken(user, workspaces, refreshSessionId);
|
|
72
|
+
const { refreshToken } = await createRefreshSession(user, refreshSessionId);
|
|
73
|
+
return {
|
|
74
|
+
token,
|
|
75
|
+
refreshToken
|
|
76
|
+
};
|
|
63
77
|
}
|
|
64
78
|
function passwordResetLink(rawToken) {
|
|
65
79
|
return `${config.CLIENT_BASE_URL}/auth/reset?token=${rawToken}`;
|
|
@@ -101,6 +115,7 @@ export async function signup(params) {
|
|
|
101
115
|
email: existing.email,
|
|
102
116
|
purpose: PASSWORD_VERIFICATION_PURPOSE,
|
|
103
117
|
continuation: existing.id,
|
|
118
|
+
idempotencyKey: existing.id,
|
|
104
119
|
sourceIp: params.sourceIp ?? '0.0.0.0'
|
|
105
120
|
});
|
|
106
121
|
return {
|
|
@@ -125,6 +140,7 @@ export async function signup(params) {
|
|
|
125
140
|
email: user.email,
|
|
126
141
|
purpose: PASSWORD_VERIFICATION_PURPOSE,
|
|
127
142
|
continuation: user.id,
|
|
143
|
+
idempotencyKey: user.id,
|
|
128
144
|
sourceIp: params.sourceIp ?? '0.0.0.0'
|
|
129
145
|
});
|
|
130
146
|
return {
|
|
@@ -197,8 +213,7 @@ export async function signupWithInvitation(params) {
|
|
|
197
213
|
}
|
|
198
214
|
// Step 4: Issue session. signAccessToken reads memberships through the
|
|
199
215
|
// workspaces module API, so a successful accept is reflected in the JWT.
|
|
200
|
-
const token = await
|
|
201
|
-
const refreshToken = await createRefreshSession(user);
|
|
216
|
+
const { token, refreshToken } = await createSessionTokens(user, params.workspaces);
|
|
202
217
|
if (acceptError) {
|
|
203
218
|
return {
|
|
204
219
|
token,
|
|
@@ -255,8 +270,7 @@ export async function login(params) {
|
|
|
255
270
|
if (user.emailVerifiedAt === null) {
|
|
256
271
|
throw new EmailNotVerifiedError();
|
|
257
272
|
}
|
|
258
|
-
const token = await
|
|
259
|
-
const refreshToken = await createRefreshSession(user);
|
|
273
|
+
const { token, refreshToken } = await createSessionTokens(user, params.workspaces);
|
|
260
274
|
return {
|
|
261
275
|
token,
|
|
262
276
|
refreshToken,
|
|
@@ -278,8 +292,7 @@ export async function createSessionForUser(params) {
|
|
|
278
292
|
if (user.status !== 'active') {
|
|
279
293
|
throw new InvalidCredentialsError();
|
|
280
294
|
}
|
|
281
|
-
const token = await
|
|
282
|
-
const refreshToken = await createRefreshSession(user);
|
|
295
|
+
const { token, refreshToken } = await createSessionTokens(user, params.workspaces);
|
|
283
296
|
return {
|
|
284
297
|
token,
|
|
285
298
|
refreshToken,
|
|
@@ -299,8 +312,9 @@ export async function refreshAccessToken(params) {
|
|
|
299
312
|
id: current.userId
|
|
300
313
|
});
|
|
301
314
|
if (user?.status !== 'active') {
|
|
302
|
-
await
|
|
303
|
-
|
|
315
|
+
await revokeRefreshSession({
|
|
316
|
+
sessionId: current.sessionId,
|
|
317
|
+
userId: current.userId
|
|
304
318
|
});
|
|
305
319
|
recordRefreshOutcome('rejected');
|
|
306
320
|
throw new TokenInvalidError('Refresh token is invalid or expired');
|
|
@@ -309,7 +323,7 @@ export async function refreshAccessToken(params) {
|
|
|
309
323
|
// second tab); past it, reuse of a retired token means a compromised session.
|
|
310
324
|
if (current.rotatedAt) {
|
|
311
325
|
if (isWithinRotationGrace(current)) {
|
|
312
|
-
const token = await signAccessToken(user, params.workspaces);
|
|
326
|
+
const token = await signAccessToken(user, params.workspaces, current.sessionId);
|
|
313
327
|
recordRefreshOutcome('grace');
|
|
314
328
|
return {
|
|
315
329
|
token,
|
|
@@ -340,7 +354,7 @@ export async function refreshAccessToken(params) {
|
|
|
340
354
|
recordRefreshOutcome('rejected');
|
|
341
355
|
throw new TokenInvalidError('Refresh token is invalid or expired');
|
|
342
356
|
}
|
|
343
|
-
const token = await signAccessToken(user, params.workspaces);
|
|
357
|
+
const token = await signAccessToken(user, params.workspaces, latest.sessionId);
|
|
344
358
|
recordRefreshOutcome('grace');
|
|
345
359
|
return {
|
|
346
360
|
token,
|
|
@@ -348,7 +362,7 @@ export async function refreshAccessToken(params) {
|
|
|
348
362
|
user
|
|
349
363
|
};
|
|
350
364
|
}
|
|
351
|
-
const token = await signAccessToken(user, params.workspaces);
|
|
365
|
+
const token = await signAccessToken(user, params.workspaces, current.sessionId);
|
|
352
366
|
recordRefreshOutcome('rotated');
|
|
353
367
|
return {
|
|
354
368
|
token,
|
|
@@ -377,8 +391,7 @@ export async function confirmEmailVerification(params) {
|
|
|
377
391
|
if (verifiedUser?.status !== 'active') {
|
|
378
392
|
throw new TokenInvalidError('Verification code is invalid or expired');
|
|
379
393
|
}
|
|
380
|
-
const token = await
|
|
381
|
-
const refreshToken = await createRefreshSession(verifiedUser);
|
|
394
|
+
const { token, refreshToken } = await createSessionTokens(verifiedUser, params.workspaces);
|
|
382
395
|
return {
|
|
383
396
|
token,
|
|
384
397
|
refreshToken,
|
|
@@ -448,8 +461,7 @@ export async function confirmPasswordReset(params) {
|
|
|
448
461
|
await revokeRefreshTokensForUser({
|
|
449
462
|
userId: consumed.userId
|
|
450
463
|
});
|
|
451
|
-
const token = await
|
|
452
|
-
const refreshToken = await createRefreshSession(user);
|
|
464
|
+
const { token, refreshToken } = await createSessionTokens(user, params.workspaces);
|
|
453
465
|
return {
|
|
454
466
|
token,
|
|
455
467
|
refreshToken,
|
|
@@ -489,9 +501,14 @@ export async function changePassword(params) {
|
|
|
489
501
|
}
|
|
490
502
|
export async function logout(params) {
|
|
491
503
|
if (!params.refreshToken) return;
|
|
492
|
-
await
|
|
504
|
+
const refreshToken = await findRefreshTokenByHash({
|
|
493
505
|
hashedToken: hashOpaqueToken(params.refreshToken)
|
|
494
506
|
});
|
|
507
|
+
if (!refreshToken) return;
|
|
508
|
+
await revokeRefreshSession({
|
|
509
|
+
sessionId: refreshToken.sessionId,
|
|
510
|
+
userId: refreshToken.userId
|
|
511
|
+
});
|
|
495
512
|
}
|
|
496
513
|
export async function getCurrentUser(params) {
|
|
497
514
|
const user = await findUserById({
|
package/dist/core/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/auth.ts"],"sourcesContent":["import {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 createRefreshToken,\n findActiveRefreshTokenByHash,\n findRefreshTokenByHash,\n revokeRefreshTokenByHash,\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 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 TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n UserNotFoundError,\n} from './errors.js';\nimport {signUserToken} from './jwt.js';\nimport {hashPassword, verifyPassword} from './password.js';\n\nconst RESET_TTL_HOURS = 1;\nconst PASSWORD_VERIFICATION_PURPOSE = 'password-verification';\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): 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 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(user: User): Promise<string> {\n const refreshToken = generateOpaqueToken('refreshToken');\n await createRefreshToken({\n userId: user.id,\n hashedToken: hashOpaqueToken(refreshToken),\n expiresAt: daysFromNow(config.AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS),\n });\n return refreshToken;\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}\n\nexport interface ProvisionUserParams {\n email: string;\n name?: string | null | 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 return await provisionDbUser({\n email: emailSchema.parse(params.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 existing = await findUserByEmail({email: params.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 sourceIp: params.sourceIp ?? '0.0.0.0',\n });\n return {...existing, emailChallenge};\n }\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 signedUp: {viaInvitation: false},\n });\n\n const emailChallenge = await createEmailChallenge({\n email: user.email,\n purpose: PASSWORD_VERIFICATION_PURPOSE,\n continuation: 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 = await signAccessToken(user, params.workspaces);\n const refreshToken = await createRefreshSession(user);\n\n if (acceptError) {\n return {token, refreshToken, user, membership, acceptError};\n }\n return {token, refreshToken, user, membership};\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}\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 = await signAccessToken(user, params.workspaces);\n const refreshToken = await createRefreshSession(user);\n\n return {token, refreshToken, user};\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}\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 = await signAccessToken(user, params.workspaces);\n const refreshToken = await createRefreshSession(user);\n\n return {token, refreshToken, user};\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}\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 revokeRefreshTokenByHash({hashedToken: currentHashedToken});\n recordRefreshOutcome('rejected');\n throw new TokenInvalidError('Refresh token is invalid or expired');\n }\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);\n recordRefreshOutcome('grace');\n return {token, refreshToken: undefined, user};\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 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 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);\n recordRefreshOutcome('grace');\n return {token, refreshToken: undefined, user};\n }\n\n const token = await signAccessToken(user, params.workspaces);\n recordRefreshOutcome('rotated');\n return {token, refreshToken: nextRefreshToken, user};\n}\n\nexport interface ConfirmEmailVerificationResult {\n token: string;\n refreshToken: string;\n user: User;\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 = await signAccessToken(verifiedUser, params.workspaces);\n const refreshToken = await createRefreshSession(verifiedUser);\n\n return {token, refreshToken, user: verifiedUser};\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}\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 = await signAccessToken(user, params.workspaces);\n const refreshToken = await createRefreshSession(user);\n\n return {token, refreshToken, user};\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 await revokeRefreshTokenByHash({hashedToken: hashOpaqueToken(params.refreshToken)});\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","createRefreshToken","findActiveRefreshTokenByHash","findRefreshTokenByHash","revokeRefreshTokenByHash","revokeRefreshTokensForUser","rotateRefreshToken","createUser","createDbUser","findUserByEmail","findUserById","markEmailVerified","provisionUser","provisionDbUser","updateUserPassword","recordTokenRefreshed","AuthDependencyUnavailableError","EmailNotVerifiedError","EmailTakenError","InvalidCredentialsError","InvitationEmailMismatchError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","UserNotFoundError","signUserToken","hashPassword","verifyPassword","RESET_TTL_HOURS","PASSWORD_VERIFICATION_PURPOSE","dummyHashCache","getDummyHash","password","hoursFromNow","hours","Date","now","daysFromNow","days","recordRefreshOutcome","outcome","signAccessToken","user","workspaces","memberships","listMembershipsForTokenClaims","userId","id","catch","error","email","name","secret","expiresIn","AUTH_JWT_EXPIRES_IN","isWithinRotationGrace","refreshToken","rotatedAt","graceMs","AUTH_REFRESH_ROTATION_GRACE_SECONDS","getTime","createRefreshSession","hashedToken","expiresAt","AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS","passwordResetLink","rawToken","CLIENT_BASE_URL","invitationErrorFromKnownError","method","contractMethod","methods","code","invitationErrorMessage","params","parse","signup","existing","canResumeVerification","status","emailVerifiedAt","hashedPassword","hash","emailChallenge","purpose","continuation","sourceIp","signedUp","viaInvitation","signupWithInvitation","preflightInvitationAcceptance","token","invitationToken","membership","acceptError","result","acceptInvitation","workspaceId","message","verified","login","hasPassword","ok","createSessionForUser","undefined","refreshAccessToken","currentHashedToken","current","nextRefreshToken","rotated","nextHashedToken","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 {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 createRefreshToken,\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 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 TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n UserNotFoundError,\n} from './errors.js';\nimport {signUserToken} from './jwt.js';\nimport {hashPassword, verifyPassword} from './password.js';\n\nconst RESET_TTL_HOURS = 1;\nconst PASSWORD_VERIFICATION_PURPOSE = 'password-verification';\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 createRefreshToken({\n sessionId: refreshSessionId,\n userId: user.id,\n hashedToken: hashOpaqueToken(refreshToken),\n expiresAt: daysFromNow(config.AUTH_REFRESH_TOKEN_EXPIRES_IN_DAYS),\n });\n return {refreshToken, refreshSessionId: session.sessionId};\n}\n\nasync function createSessionTokens(\n user: User,\n workspaces: WorkspacesInterModuleClient,\n): Promise<{token: string; refreshToken: string}> {\n const refreshSessionId = crypto.randomUUID();\n const token = await signAccessToken(user, workspaces, refreshSessionId);\n const {refreshToken} = await createRefreshSession(user, refreshSessionId);\n return {token, refreshToken};\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}\n\nexport interface ProvisionUserParams {\n email: string;\n name?: string | null | 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 return await provisionDbUser({\n email: emailSchema.parse(params.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 existing = await findUserByEmail({email: params.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(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 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} = await createSessionTokens(user, params.workspaces);\n\n if (acceptError) {\n return {token, refreshToken, user, membership, acceptError};\n }\n return {token, refreshToken, user, membership};\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}\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} = await createSessionTokens(user, params.workspaces);\n\n return {token, refreshToken, user};\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}\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} = await createSessionTokens(user, params.workspaces);\n\n return {token, refreshToken, user};\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}\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\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};\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 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 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};\n }\n\n const token = await signAccessToken(user, params.workspaces, current.sessionId);\n recordRefreshOutcome('rotated');\n return {token, refreshToken: nextRefreshToken, user};\n}\n\nexport interface ConfirmEmailVerificationResult {\n token: string;\n refreshToken: string;\n user: User;\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} = await createSessionTokens(verifiedUser, params.workspaces);\n\n return {token, refreshToken, user: verifiedUser};\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}\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} = await createSessionTokens(user, params.workspaces);\n\n return {token, refreshToken, user};\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","createRefreshToken","findActiveRefreshTokenByHash","findRefreshTokenByHash","revokeRefreshSession","revokeRefreshTokensForUser","rotateRefreshToken","createUser","createDbUser","findUserByEmail","findUserById","markEmailVerified","provisionUser","provisionDbUser","updateUserPassword","recordTokenRefreshed","AuthDependencyUnavailableError","EmailNotVerifiedError","EmailTakenError","InvalidCredentialsError","InvitationEmailMismatchError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","UserNotFoundError","signUserToken","hashPassword","verifyPassword","RESET_TTL_HOURS","PASSWORD_VERIFICATION_PURPOSE","dummyHashCache","getDummyHash","password","hoursFromNow","hours","Date","now","daysFromNow","days","recordRefreshOutcome","outcome","signAccessToken","user","workspaces","refreshSessionId","memberships","listMembershipsForTokenClaims","userId","id","catch","error","email","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","passwordResetLink","rawToken","CLIENT_BASE_URL","invitationErrorFromKnownError","method","contractMethod","methods","code","invitationErrorMessage","params","parse","signup","existing","canResumeVerification","status","emailVerifiedAt","hashedPassword","hash","emailChallenge","purpose","continuation","idempotencyKey","sourceIp","signedUp","viaInvitation","signupWithInvitation","preflightInvitationAcceptance","invitationToken","membership","acceptError","result","acceptInvitation","workspaceId","message","verified","login","hasPassword","ok","createSessionForUser","undefined","refreshAccessToken","currentHashedToken","current","nextRefreshToken","rotated","nextHashedToken","latest","confirmEmailVerification","challengeId","verifiedUser","resendEmailVerification","nextResendAvailableAt","requestPasswordReset","sendEmail","resetLink","expiresInHours","confirmPasswordReset","consumed","existingUser","newPassword","changePassword","currentPassword","currentRefreshSession","exceptRefreshTokenId","logout","getCurrentUser"],"mappings":"AAAA,SAAQA,WAAW,QAAO,wBAAwB;AAClD,SACEC,qBAAqB,EACrBC,0BAA0B,EAC1BC,oBAAoB,EACpBC,oBAAoB,QACf,gCAAgC;AACvC,SAEEC,6BAA6B,QACxB,2CAA2C;AAClD,SAAQC,uBAAuB,QAAO,wBAAwB;AAC9D,SAAQC,kBAAkB,QAAO,8BAA8B;AAC/D,SAAQC,mBAAmB,EAAEC,eAAe,QAAO,uBAAuB;AAC1E,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,oBAAoB,EAAEC,mBAAmB,QAAO,yBAAyB;AACjF,SACEC,kBAAkB,EAClBC,4BAA4B,EAC5BC,sBAAsB,EACtBC,oBAAoB,EACpBC,0BAA0B,EAC1BC,kBAAkB,QACb,wBAAwB;AAC/B,SACEC,cAAcC,YAAY,EAC1BC,eAAe,EACfC,YAAY,EACZC,iBAAiB,EACjBC,iBAAiBC,eAAe,EAChCC,kBAAkB,QACb,eAAe;AACtB,SAAsCC,oBAAoB,QAAO,oBAAoB;AAGrF,SACEC,8BAA8B,EAC9BC,qBAAqB,EACrBC,eAAe,EACfC,uBAAuB,EACvBC,4BAA4B,EAC5BC,qBAAqB,EACrBC,iBAAiB,EACjBC,iBAAiB,EACjBC,iBAAiB,QACZ,cAAc;AACrB,SAAQC,aAAa,QAAO,WAAW;AACvC,SAAQC,YAAY,EAAEC,cAAc,QAAO,gBAAgB;AAE3D,MAAMC,kBAAkB;AACxB,MAAMC,gCAAgC;AAEtC,IAAIC;AACJ,eAAeC;IACb,IAAI,CAACD,gBAAgB;QACnBA,iBAAiB,MAAMJ,aAAa;YAACM,UAAU;QAAyB;IAC1E;IACA,OAAOF;AACT;AAEA,SAASG,aAAaC,KAAa;IACjC,OAAO,IAAIC,KAAKA,KAAKC,GAAG,KAAKF,QAAQ,KAAK,KAAK;AACjD;AAEA,SAASG,YAAYC,IAAY;IAC/B,OAAO,IAAIH,KAAKA,KAAKC,GAAG,KAAKE,OAAO,KAAK,KAAK,KAAK;AACrD;AAEA,SAASC,qBAAqBC,OAAgC;IAC5DzB,qBAAqByB;AACvB;AAEA,eAAeC,gBACbC,IAAU,EACVC,UAAuC,EACvCC,gBAAwB;IAExB,MAAMC,cAAc,MAAMF,WACvBG,6BAA6B,CAAC;QAACC,QAAQL,KAAKM,EAAE;IAAA,GAC9CC,KAAK,CAAC,CAACC;QACN,MAAM,IAAIlC,+BAA+B,cAAckC;IACzD;IACF,OAAO,MAAMzB,cAAc;QACzBsB,QAAQL,KAAKM,EAAE;QACfG,OAAOT,KAAKS,KAAK;QACjBC,MAAMV,KAAKU,IAAI;QACfP,aAAaA,YAAYA,WAAW;QACpCD;QACAS,QAAQ1D;QACR2D,WAAWxD,OAAOyD,mBAAmB;IACvC;AACF;AAEA,SAASC,sBAAsBC,YAA0B;IACvD,IAAI,CAACA,aAAaC,SAAS,EAAE,OAAO;IACpC,MAAMC,UAAU7D,OAAO8D,mCAAmC,GAAG;IAC7D,OAAOzB,KAAKC,GAAG,KAAKqB,aAAaC,SAAS,CAACG,OAAO,MAAMF;AAC1D;AAEA,eAAeG,qBACbpB,IAAU,EACVE,gBAAwB;IAExB,MAAMa,eAAe7D,oBAAoB;IACzC,MAAMmE,UAAU,MAAM9D,mBAAmB;QACvC+D,WAAWpB;QACXG,QAAQL,KAAKM,EAAE;QACfiB,aAAapE,gBAAgB4D;QAC7BS,WAAW7B,YAAYvC,OAAOqE,kCAAkC;IAClE;IACA,OAAO;QAACV;QAAcb,kBAAkBmB,QAAQC,SAAS;IAAA;AAC3D;AAEA,eAAeI,oBACb1B,IAAU,EACVC,UAAuC;IAEvC,MAAMC,mBAAmByB,OAAOC,UAAU;IAC1C,MAAMC,QAAQ,MAAM9B,gBAAgBC,MAAMC,YAAYC;IACtD,MAAM,EAACa,YAAY,EAAC,GAAG,MAAMK,qBAAqBpB,MAAME;IACxD,OAAO;QAAC2B;QAAOd;IAAY;AAC7B;AAEA,SAASe,kBAAkBC,QAAgB;IACzC,OAAO,GAAG3E,OAAO4E,eAAe,CAAC,kBAAkB,EAAED,UAAU;AACjE;AAEA,SAASE,8BACPzB,KAAc,EACd0B,MAA4D;IAE5D,MAAMC,iBAAiBpF,8BAA8BqF,OAAO,CAACF,OAAO;IACpE,IAAI,CAAClF,wBAAwBmF,gBAAgB3B,QAAQ,OAAOA;IAE5D,IAAIA,MAAM6B,IAAI,KAAK,4BACjB,OAAO,IAAIxD,kBAAkB;IAC/B,IAAI2B,MAAM6B,IAAI,KAAK,yBAAyB,OAAO,IAAI1D;IACvD,IAAI6B,MAAM6B,IAAI,KAAK,4BAA4B,OAAO,IAAIzD;IAC1D,OAAO,IAAIF;AACb;AAEA,SAAS4D,uBACPD,IAI+B;IAE/B,IAAIA,SAAS,4BAA4B,OAAO;IAChD,IAAIA,SAAS,yBAAyB,OAAO;IAC7C,IAAIA,SAAS,4BAA4B,OAAO;IAChD,OAAO;AACT;AAaA;;;CAGC,GACD,OAAO,eAAenE,cAAcqE,MAA2B;IAC7D,OAAO,MAAMpE,gBAAgB;QAC3BsC,OAAO/D,YAAY8F,KAAK,CAACD,OAAO9B,KAAK;QACrCC,MAAM6B,OAAO7B,IAAI,IAAI;IACvB;AACF;AAMA,OAAO,eAAe+B,OAAOF,MAA0C;IACrE,MAAMG,WAAW,MAAM3E,gBAAgB;QAAC0C,OAAO8B,OAAO9B,KAAK;IAAA;IAC3D,IAAIiC,UAAU;QACZ,MAAMC,wBACJD,SAASE,MAAM,KAAK,YACpBF,SAASG,eAAe,KAAK,QAC7BH,SAASI,cAAc,KAAK,QAC3B,MAAM7D,eAAe;YAACK,UAAUiD,OAAOjD,QAAQ;YAAEyD,MAAML,SAASI,cAAc;QAAA;QACjF,IAAIH,uBAAuB;YACzB,MAAMK,iBAAiB,MAAMnG,qBAAqB;gBAChD4D,OAAOiC,SAASjC,KAAK;gBACrBwC,SAAS9D;gBACT+D,cAAcR,SAASpC,EAAE;gBACzB6C,gBAAgBT,SAASpC,EAAE;gBAC3B8C,UAAUb,OAAOa,QAAQ,IAAI;YAC/B;YACA,OAAO;gBAAC,GAAGV,QAAQ;gBAAEM;YAAc;QACrC;QACA,MAAM,IAAIxE,gBAAgB+D,OAAO9B,KAAK;IACxC;IAEA,MAAMqC,iBAAiB,MAAM9D,aAAa;QAACM,UAAUiD,OAAOjD,QAAQ;IAAA;IACpE,MAAMU,OAAO,MAAMlC,aAAa;QAC9B2C,OAAO8B,OAAO9B,KAAK;QACnBqC;QACApC,MAAM6B,OAAO7B,IAAI,IAAI;QACrB2C,UAAU;YAACC,eAAe;QAAK;IACjC;IAEA,MAAMN,iBAAiB,MAAMnG,qBAAqB;QAChD4D,OAAOT,KAAKS,KAAK;QACjBwC,SAAS9D;QACT+D,cAAclD,KAAKM,EAAE;QACrB6C,gBAAgBnD,KAAKM,EAAE;QACvB8C,UAAUb,OAAOa,QAAQ,IAAI;IAC/B;IAEA,OAAO;QAAC,GAAGpD,IAAI;QAAEgD;IAAc;AACjC;AAYA,OAAO,eAAeO,qBACpBhB,MAAkC;IAElC,0EAA0E;IAC1E,0EAA0E;IAC1E,mEAAmE;IACnE,IAAI;QACF,MAAMA,OAAOtC,UAAU,CAACuD,6BAA6B,CAAC;YACpD3B,OAAOU,OAAOkB,eAAe;YAC7BhD,OAAO8B,OAAO9B,KAAK;QACrB;IACF,EAAE,OAAOD,OAAO;QACd,MAAMyB,8BAA8BzB,OAAO;IAC7C;IAEA,4EAA4E;IAC5E,8EAA8E;IAC9E,MAAMkC,WAAW,MAAM3E,gBAAgB;QAAC0C,OAAO8B,OAAO9B,KAAK;IAAA;IAC3D,IAAIiC,UAAU;QACZ,MAAM,IAAIlE,gBAAgB+D,OAAO9B,KAAK;IACxC;IACA,MAAMqC,iBAAiB,MAAM9D,aAAa;QAACM,UAAUiD,OAAOjD,QAAQ;IAAA;IACpE,MAAMU,OAAO,MAAMlC,aAAa;QAC9B2C,OAAO8B,OAAO9B,KAAK;QACnBqC;QACApC,MAAM6B,OAAO7B,IAAI,IAAI;QACrBmC,iBAAiB,IAAIpD;QACrB4D,UAAU;YAACC,eAAe;QAAI;IAChC;IAEA,wEAAwE;IACxE,8EAA8E;IAC9E,8EAA8E;IAC9E,yDAAyD;IACzD,IAAII,aAAuD;IAC3D,IAAIC;IACJ,IAAI;QACF,MAAMC,SAAS,MAAMrB,OAAOtC,UAAU,CAAC4D,gBAAgB,CAAC;YACtDhC,OAAOU,OAAOkB,eAAe;YAC7BpD,QAAQL,KAAKM,EAAE;YACfG,OAAOT,KAAKS,KAAK;YACjBC,MAAMV,KAAKU,IAAI;QACjB;QACAgD,aAAa;YACXpD,IAAIsD,OAAOF,UAAU,CAACpD,EAAE;YACxBD,QAAQuD,OAAOF,UAAU,CAACrD,MAAM;YAChCyD,aAAaF,OAAOF,UAAU,CAACI,WAAW;QAC5C;IACF,EAAE,OAAOtD,OAAO;QACd,IAAIxD,wBAAwBD,8BAA8BqF,OAAO,CAACyB,gBAAgB,EAAErD,QAAQ;YAC1FmD,cAAc;gBAACtB,MAAM7B,MAAM6B,IAAI;gBAAE0B,SAASzB,uBAAuB9B,MAAM6B,IAAI;YAAC;QAC9E,OAAO;YACLsB,cAAc;gBACZtB,MAAM;gBACN0B,SAAS;YACX;QACF;IACF;IAEA,uEAAuE;IACvE,yEAAyE;IACzE,MAAM,EAAClC,KAAK,EAAEd,YAAY,EAAC,GAAG,MAAMW,oBAAoB1B,MAAMuC,OAAOtC,UAAU;IAE/E,IAAI0D,aAAa;QACf,OAAO;YAAC9B;YAAOd;YAAcf;YAAM0D;YAAYC;QAAW;IAC5D;IACA,OAAO;QAAC9B;QAAOd;QAAcf;QAAM0D;IAAU;AAC/C;AAMA,OAAO,eAAe7F,WAAW0E,MAAwB;IACvD,MAAMG,WAAW,MAAM3E,gBAAgB;QAAC0C,OAAO8B,OAAO9B,KAAK;IAAA;IAC3D,IAAIiC,UAAU;QACZ,MAAM,IAAIlE,gBAAgB+D,OAAO9B,KAAK;IACxC;IAEA,MAAMqC,iBAAiB,MAAM9D,aAAa;QAACM,UAAUiD,OAAOjD,QAAQ;IAAA;IACpE,MAAMU,OAAO,MAAMlC,aAAa;QAC9B2C,OAAO8B,OAAO9B,KAAK;QACnBqC;QACApC,MAAM6B,OAAO7B,IAAI,IAAI;QACrBmC,iBAAiBN,OAAOyB,QAAQ,GAAG,IAAIvE,SAAS;IAClD;IAEA,OAAOO;AACT;AAcA,OAAO,eAAeiE,MAAM1B,MAAmB;IAC7C,MAAMvC,OAAO,MAAMjC,gBAAgB;QAAC0C,OAAO8B,OAAO9B,KAAK;IAAA;IAEvD,IAAI,CAACT,MAAM;QACT,MAAMf,eAAe;YAACK,UAAUiD,OAAOjD,QAAQ;YAAEyD,MAAM,MAAM1D;QAAc;QAC3E,MAAM,IAAIZ;IACZ;IAEA,MAAMyF,cAAclE,KAAK8C,cAAc,KAAK;IAC5C,MAAMqB,KAAK,MAAMlF,eAAe;QAC9BK,UAAUiD,OAAOjD,QAAQ;QACzByD,MAAM/C,KAAK8C,cAAc,IAAK,MAAMzD;IACtC;IACA,IAAI,CAAC6E,eAAe,CAACC,MAAMnE,KAAK4C,MAAM,KAAK,UAAU;QACnD,MAAM,IAAInE;IACZ;IAEA,IAAIuB,KAAK6C,eAAe,KAAK,MAAM;QACjC,MAAM,IAAItE;IACZ;IAEA,MAAM,EAACsD,KAAK,EAAEd,YAAY,EAAC,GAAG,MAAMW,oBAAoB1B,MAAMuC,OAAOtC,UAAU;IAE/E,OAAO;QAAC4B;QAAOd;QAAcf;IAAI;AACnC;AAoBA,OAAO,eAAeoE,qBACpB7B,MAAkC;IAElC,MAAMvC,OAAOuC,OAAOlC,MAAM,GACtB,MAAMrC,aAAa;QAACsC,IAAIiC,OAAOlC,MAAM;IAAA,KACrCkC,OAAO9B,KAAK,GACV,MAAM1C,gBAAgB;QAAC0C,OAAO/D,YAAY8F,KAAK,CAACD,OAAO9B,KAAK;IAAC,KAC7D4D;IAEN,IAAI,CAACrE,MAAM;QACT,MAAM,IAAIlB,kBAAkByD,OAAOlC,MAAM,IAAIkC,OAAO9B,KAAK,IAAI;IAC/D;IACA,IAAIT,KAAK6C,eAAe,KAAK,MAAM;QACjC,MAAM,IAAItE;IACZ;IACA,IAAIyB,KAAK4C,MAAM,KAAK,UAAU;QAC5B,MAAM,IAAInE;IACZ;IAEA,MAAM,EAACoD,KAAK,EAAEd,YAAY,EAAC,GAAG,MAAMW,oBAAoB1B,MAAMuC,OAAOtC,UAAU;IAE/E,OAAO;QAAC4B;QAAOd;QAAcf;IAAI;AACnC;AASA,OAAO,eAAesE,mBAAmB/B,MAGxC;IACC,MAAMgC,qBAAqBpH,gBAAgBoF,OAAOxB,YAAY;IAC9D,MAAMyD,UAAU,MAAM/G,uBAAuB;QAAC8D,aAAagD;IAAkB;IAC7E,IAAI,CAACC,SAAS;QACZ3E,qBAAqB;QACrB,MAAM,IAAIhB,kBAAkB;IAC9B;IAEA,MAAMmB,OAAO,MAAMhC,aAAa;QAACsC,IAAIkE,QAAQnE,MAAM;IAAA;IACnD,IAAIL,MAAM4C,WAAW,UAAU;QAC7B,MAAMlF,qBAAqB;YAAC4D,WAAWkD,QAAQlD,SAAS;YAAEjB,QAAQmE,QAAQnE,MAAM;QAAA;QAChFR,qBAAqB;QACrB,MAAM,IAAIhB,kBAAkB;IAC9B;IAEA,6EAA6E;IAC7E,8EAA8E;IAC9E,IAAI2F,QAAQxD,SAAS,EAAE;QACrB,IAAIF,sBAAsB0D,UAAU;YAClC,MAAM3C,QAAQ,MAAM9B,gBAAgBC,MAAMuC,OAAOtC,UAAU,EAAEuE,QAAQlD,SAAS;YAC9EzB,qBAAqB;YACrB,OAAO;gBAACgC;gBAAOd,cAAcsD;gBAAWrE;YAAI;QAC9C;QACA,MAAMrC,2BAA2B;YAAC0C,QAAQL,KAAKM,EAAE;QAAA;QACjDT,qBAAqB;QACrB,MAAM,IAAIhB,kBAAkB;IAC9B;IAEA,2EAA2E;IAC3E,oEAAoE;IACpE,MAAM4F,mBAAmBvH,oBAAoB;IAC7C,MAAMwH,UAAU,MAAM9G,mBAAmB;QACvC0C,IAAIkE,QAAQlE,EAAE;QACdiE;QACAI,iBAAiBxH,gBAAgBsH;QACjCjD,WAAW7B,YAAYvC,OAAOqE,kCAAkC;IAClE;IACA,IAAI,CAACiD,SAAS;QACZ,MAAME,SAAS,MAAMnH,uBAAuB;YAAC8D,aAAagD;QAAkB;QAC5E,IAAI,CAACK,UAAU,CAAC9D,sBAAsB8D,SAAS;YAC7C/E,qBAAqB;YACrB,MAAM,IAAIhB,kBAAkB;QAC9B;QACA,MAAMgD,QAAQ,MAAM9B,gBAAgBC,MAAMuC,OAAOtC,UAAU,EAAE2E,OAAOtD,SAAS;QAC7EzB,qBAAqB;QACrB,OAAO;YAACgC;YAAOd,cAAcsD;YAAWrE;QAAI;IAC9C;IAEA,MAAM6B,QAAQ,MAAM9B,gBAAgBC,MAAMuC,OAAOtC,UAAU,EAAEuE,QAAQlD,SAAS;IAC9EzB,qBAAqB;IACrB,OAAO;QAACgC;QAAOd,cAAc0D;QAAkBzE;IAAI;AACrD;AAYA,OAAO,eAAe6E,yBAAyBtC,MAK9C;IACC,MAAMvC,OAAO,MAAMjC,gBAAgB;QAAC0C,OAAO/D,YAAY8F,KAAK,CAACD,OAAO9B,KAAK;IAAC;IAC1E,IAAI,CAACT,MAAM,MAAM,IAAInB,kBAAkB;IACvC,MAAMlC,sBAAsB;QAAC2D,IAAIiC,OAAOuC,WAAW;QAAEzC,MAAME,OAAOF,IAAI;QAAEa,cAAclD,KAAKM,EAAE;IAAA;IAC7F,MAAM1D,2BAA2B;QAC/B0D,IAAIiC,OAAOuC,WAAW;QACtB7B,SAAS9D;QACT+D,cAAclD,KAAKM,EAAE;IACvB;IAEA,MAAMyE,eAAe,MAAM9G,kBAAkB;QAACoC,QAAQL,KAAKM,EAAE;IAAA;IAC7D,IAAIyE,cAAcnC,WAAW,UAAU;QACrC,MAAM,IAAI/D,kBAAkB;IAC9B;IAEA,MAAM,EAACgD,KAAK,EAAEd,YAAY,EAAC,GAAG,MAAMW,oBAAoBqD,cAAcxC,OAAOtC,UAAU;IAEvF,OAAO;QAAC4B;QAAOd;QAAcf,MAAM+E;IAAY;AACjD;AAEA,OAAO,eAAeC,wBAAwBzC,MAI7C;IACC,MAAMvC,OAAO,MAAMjC,gBAAgB;QAAC0C,OAAO/D,YAAY8F,KAAK,CAACD,OAAO9B,KAAK;IAAC;IAC1E,IAAI,CAACT,QAAQA,KAAK6C,eAAe,IAAI7C,KAAK4C,MAAM,KAAK,UAAU;QAC7D,OAAO;YAACqC,uBAAuB,IAAIxF;QAAM;IAC3C;IACA,MAAMmE,SAAS,MAAM9G,qBAAqB;QACxCwD,IAAIiC,OAAOuC,WAAW;QACtB5B,cAAclD,KAAKM,EAAE;QACrB8C,UAAUb,OAAOa,QAAQ;IAC3B;IAEA,OAAO;QAAC6B,uBAAuBrB,OAAOqB,qBAAqB;IAAA;AAC7D;AAEA,OAAO,eAAeC,qBAAqB3C,MAAuB;IAChE,MAAMvC,OAAO,MAAMjC,gBAAgB;QAAC0C,OAAO8B,OAAO9B,KAAK;IAAA;IACvD,IAAIT,MAAM4C,WAAW,YAAY5C,KAAK8C,cAAc,KAAK,MAAM;QAC7D;IACF;IAEA,MAAMf,WAAW7E,oBAAoB;IACrC,MAAMI,oBAAoB;QACxB+C,QAAQL,KAAKM,EAAE;QACfiB,aAAapE,gBAAgB4E;QAC7BP,WAAWjC,aAAaL;QACxBiG,WAAW;YACT1E,OAAOT,KAAKS,KAAK;YACjB2E,WAAWtD,kBAAkBC;YAC7BsD,gBAAgBnG;QAClB;IACF;AACF;AAQA,OAAO,eAAeoG,qBAAqB/C,MAI1C;IACC,MAAMgD,WAAW,MAAMlI,qBAAqB;QAACkE,aAAapE,gBAAgBoF,OAAOV,KAAK;IAAC;IACvF,IAAI,CAAC0D,UAAU;QACb,MAAM,IAAI1G,kBAAkB;IAC9B;IAEA,MAAM2G,eAAe,MAAMxH,aAAa;QAACsC,IAAIiF,SAASlF,MAAM;IAAA;IAC5D,IAAImF,cAAc5C,WAAW,YAAY4C,aAAa1C,cAAc,KAAK,MAAM;QAC7E,MAAM,IAAIjE,kBAAkB;IAC9B;IAEA,MAAMiE,iBAAiB,MAAM9D,aAAa;QAACM,UAAUiD,OAAOkD,WAAW;IAAA;IACvE,MAAMzF,OAAO,MAAM5B,mBAAmB;QAACiC,QAAQkF,SAASlF,MAAM;QAAEyC;IAAc;IAC9E,IAAI9C,MAAM4C,WAAW,UAAU;QAC7B,MAAM,IAAI/D,kBAAkB;IAC9B;IAEA,MAAMlB,2BAA2B;QAAC0C,QAAQkF,SAASlF,MAAM;IAAA;IAEzD,MAAM,EAACwB,KAAK,EAAEd,YAAY,EAAC,GAAG,MAAMW,oBAAoB1B,MAAMuC,OAAOtC,UAAU;IAE/E,OAAO;QAAC4B;QAAOd;QAAcf;IAAI;AACnC;AAEA,OAAO,eAAe0F,eAAenD,MAKpC;IACC,MAAMvC,OAAO,MAAMhC,aAAa;QAACsC,IAAIiC,OAAOlC,MAAM;IAAA;IAClD,IAAI,CAACL,MAAM;QACT,MAAM,IAAIlB,kBAAkByD,OAAOlC,MAAM;IAC3C;IAEA,MAAM6D,cAAclE,KAAK8C,cAAc,KAAK;IAC5C,MAAMqB,KAAK,MAAMlF,eAAe;QAC9BK,UAAUiD,OAAOoD,eAAe;QAChC5C,MAAM/C,KAAK8C,cAAc,IAAK,MAAMzD;IACtC;IACA,IAAI,CAAC6E,eAAe,CAACC,IAAI;QACvB,MAAM,IAAI1F;IACZ;IAEA,MAAMqE,iBAAiB,MAAM9D,aAAa;QAACM,UAAUiD,OAAOkD,WAAW;IAAA;IACvE,MAAMG,wBAAwBrD,OAAOxB,YAAY,GAC7C,MAAMvD,6BAA6B;QAAC+D,aAAapE,gBAAgBoF,OAAOxB,YAAY;IAAC,KACrFsD;IACJ,MAAMwB,uBACJD,uBAAuBvF,WAAWL,KAAKM,EAAE,GAAGsF,sBAAsBtF,EAAE,GAAG+D;IAEzE,MAAMjG,mBAAmB;QAACiC,QAAQL,KAAKM,EAAE;QAAEwC;IAAc;IACzD,MAAMnF,2BAA2B;QAAC0C,QAAQL,KAAKM,EAAE;QAAEuF;IAAoB;AACzE;AAEA,OAAO,eAAeC,OAAOvD,MAA2C;IACtE,IAAI,CAACA,OAAOxB,YAAY,EAAE;IAC1B,MAAMA,eAAe,MAAMtD,uBAAuB;QAChD8D,aAAapE,gBAAgBoF,OAAOxB,YAAY;IAClD;IACA,IAAI,CAACA,cAAc;IACnB,MAAMrD,qBAAqB;QAAC4D,WAAWP,aAAaO,SAAS;QAAEjB,QAAQU,aAAaV,MAAM;IAAA;AAC5F;AAMA,OAAO,eAAe0F,eAAexD,MAAwB;IAC3D,MAAMvC,OAAO,MAAMhC,aAAa;QAACsC,IAAIiC,OAAOlC,MAAM;IAAA;IAClD,IAAI,CAACL,MAAM;QACT,MAAM,IAAIlB,kBAAkByD,OAAOlC,MAAM;IAC3C;IAEA,OAAO;QAACL;IAAI;AACd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../../src/core/entities/refresh-token.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB"}
|
|
1
|
+
{"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../../src/core/entities/refresh-token.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/entities/refresh-token.ts"],"sourcesContent":["export interface RefreshToken {\n id: string;\n userId: string;\n hashedToken: string;\n expiresAt: Date;\n revokedAt: Date | null;\n rotatedAt: Date | null;\n lastUsedAt: Date | null;\n createdAt: Date;\n updatedAt: Date;\n}\n"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"sources":["../../../src/core/entities/refresh-token.ts"],"sourcesContent":["export interface RefreshToken {\n id: string;\n sessionId: string;\n userId: string;\n hashedToken: string;\n expiresAt: Date;\n revokedAt: Date | null;\n rotatedAt: Date | null;\n lastUsedAt: Date | null;\n createdAt: Date;\n updatedAt: Date;\n}\n"],"names":[],"mappings":"AAAA,WAWC"}
|
package/dist/core/jwt.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare const tokenMembershipSchema: z.ZodObject<{
|
|
|
8
8
|
export type TokenMembership = z.infer<typeof tokenMembershipSchema>;
|
|
9
9
|
export declare const userTokenClaimsSchema: z.ZodObject<{
|
|
10
10
|
sub: z.ZodString;
|
|
11
|
+
refreshSessionId: z.ZodOptional<z.ZodString>;
|
|
11
12
|
email: z.ZodString;
|
|
12
13
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
13
14
|
memberships: z.ZodArray<z.ZodObject<{
|
|
@@ -21,6 +22,7 @@ export declare const userTokenClaimsSchema: z.ZodObject<{
|
|
|
21
22
|
}, z.core.$strip>;
|
|
22
23
|
export type UserTokenClaims = z.infer<typeof userTokenClaimsSchema>;
|
|
23
24
|
export interface SignUserTokenParams {
|
|
25
|
+
refreshSessionId?: string | undefined;
|
|
24
26
|
userId: string;
|
|
25
27
|
email: string;
|
|
26
28
|
name?: string | null | undefined;
|
package/dist/core/jwt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/core/jwt.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,eAAO,MAAM,qBAAqB;;;;;iBAGhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,qBAAqB
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/core/jwt.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,eAAO,MAAM,qBAAqB;;;;;iBAGhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;iBAQhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACjC,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC;CAC7B;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAchF;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAa7F"}
|
package/dist/core/jwt.js
CHANGED
|
@@ -8,6 +8,7 @@ export const tokenMembershipSchema = z.object({
|
|
|
8
8
|
});
|
|
9
9
|
export const userTokenClaimsSchema = z.object({
|
|
10
10
|
sub: z.string().uuid(),
|
|
11
|
+
refreshSessionId: z.string().uuid().optional(),
|
|
11
12
|
email: z.string().email(),
|
|
12
13
|
name: z.string().nullable().optional(),
|
|
13
14
|
memberships: z.array(tokenMembershipSchema),
|
|
@@ -19,7 +20,8 @@ export async function signUserToken(params) {
|
|
|
19
20
|
payload: {
|
|
20
21
|
email: params.email,
|
|
21
22
|
name: params.name ?? null,
|
|
22
|
-
memberships: params.memberships
|
|
23
|
+
memberships: params.memberships,
|
|
24
|
+
refreshSessionId: params.refreshSessionId
|
|
23
25
|
},
|
|
24
26
|
secret: params.secret,
|
|
25
27
|
expiresIn: params.expiresIn,
|
package/dist/core/jwt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/jwt.ts"],"sourcesContent":["import {workspaceRoleSchema} from '@shipfox/api-workspaces-dto';\nimport {signHs256, verifyHs256} from '@shipfox/node-jwt';\nimport {z} from 'zod';\nimport {recordTokenIssued, recordTokenVerified} from '#metrics/index.js';\n\nexport const tokenMembershipSchema = z.object({\n workspaceId: z.string().uuid(),\n role: workspaceRoleSchema,\n});\n\nexport type TokenMembership = z.infer<typeof tokenMembershipSchema>;\n\nexport const userTokenClaimsSchema = z.object({\n sub: z.string().uuid(),\n email: z.string().email(),\n name: z.string().nullable().optional(),\n memberships: z.array(tokenMembershipSchema),\n iat: z.number().int(),\n exp: z.number().int(),\n});\n\nexport type UserTokenClaims = z.infer<typeof userTokenClaimsSchema>;\n\nexport interface SignUserTokenParams {\n userId: string;\n email: string;\n name?: string | null | undefined;\n memberships: TokenMembership[];\n secret: string | Uint8Array;\n expiresIn: string;\n}\n\nexport interface VerifyUserTokenParams {\n token: string;\n secret: string | Uint8Array;\n}\n\nexport async function signUserToken(params: SignUserTokenParams): Promise<string> {\n const token = await signHs256({\n payload: {\n email: params.email,\n name: params.name ?? null,\n memberships: params.memberships,\n },\n secret: params.secret,\n expiresIn: params.expiresIn,\n subject: params.userId,\n });\n recordTokenIssued('session');\n return token;\n}\n\nexport async function verifyUserToken(params: VerifyUserTokenParams): Promise<UserTokenClaims> {\n try {\n const claims = await verifyHs256({\n token: params.token,\n secret: params.secret,\n schema: userTokenClaimsSchema,\n });\n recordTokenVerified('session', 'ok');\n return claims;\n } catch (error) {\n recordTokenVerified('session', 'rejected');\n throw error;\n }\n}\n"],"names":["workspaceRoleSchema","signHs256","verifyHs256","z","recordTokenIssued","recordTokenVerified","tokenMembershipSchema","object","workspaceId","string","uuid","role","userTokenClaimsSchema","sub","email","name","nullable","
|
|
1
|
+
{"version":3,"sources":["../../src/core/jwt.ts"],"sourcesContent":["import {workspaceRoleSchema} from '@shipfox/api-workspaces-dto';\nimport {signHs256, verifyHs256} from '@shipfox/node-jwt';\nimport {z} from 'zod';\nimport {recordTokenIssued, recordTokenVerified} from '#metrics/index.js';\n\nexport const tokenMembershipSchema = z.object({\n workspaceId: z.string().uuid(),\n role: workspaceRoleSchema,\n});\n\nexport type TokenMembership = z.infer<typeof tokenMembershipSchema>;\n\nexport const userTokenClaimsSchema = z.object({\n sub: z.string().uuid(),\n refreshSessionId: z.string().uuid().optional(),\n email: z.string().email(),\n name: z.string().nullable().optional(),\n memberships: z.array(tokenMembershipSchema),\n iat: z.number().int(),\n exp: z.number().int(),\n});\n\nexport type UserTokenClaims = z.infer<typeof userTokenClaimsSchema>;\n\nexport interface SignUserTokenParams {\n refreshSessionId?: string | undefined;\n userId: string;\n email: string;\n name?: string | null | undefined;\n memberships: TokenMembership[];\n secret: string | Uint8Array;\n expiresIn: string;\n}\n\nexport interface VerifyUserTokenParams {\n token: string;\n secret: string | Uint8Array;\n}\n\nexport async function signUserToken(params: SignUserTokenParams): Promise<string> {\n const token = await signHs256({\n payload: {\n email: params.email,\n name: params.name ?? null,\n memberships: params.memberships,\n refreshSessionId: params.refreshSessionId,\n },\n secret: params.secret,\n expiresIn: params.expiresIn,\n subject: params.userId,\n });\n recordTokenIssued('session');\n return token;\n}\n\nexport async function verifyUserToken(params: VerifyUserTokenParams): Promise<UserTokenClaims> {\n try {\n const claims = await verifyHs256({\n token: params.token,\n secret: params.secret,\n schema: userTokenClaimsSchema,\n });\n recordTokenVerified('session', 'ok');\n return claims;\n } catch (error) {\n recordTokenVerified('session', 'rejected');\n throw error;\n }\n}\n"],"names":["workspaceRoleSchema","signHs256","verifyHs256","z","recordTokenIssued","recordTokenVerified","tokenMembershipSchema","object","workspaceId","string","uuid","role","userTokenClaimsSchema","sub","refreshSessionId","optional","email","name","nullable","memberships","array","iat","number","int","exp","signUserToken","params","token","payload","secret","expiresIn","subject","userId","verifyUserToken","claims","schema","error"],"mappings":"AAAA,SAAQA,mBAAmB,QAAO,8BAA8B;AAChE,SAAQC,SAAS,EAAEC,WAAW,QAAO,oBAAoB;AACzD,SAAQC,CAAC,QAAO,MAAM;AACtB,SAAQC,iBAAiB,EAAEC,mBAAmB,QAAO,oBAAoB;AAEzE,OAAO,MAAMC,wBAAwBH,EAAEI,MAAM,CAAC;IAC5CC,aAAaL,EAAEM,MAAM,GAAGC,IAAI;IAC5BC,MAAMX;AACR,GAAG;AAIH,OAAO,MAAMY,wBAAwBT,EAAEI,MAAM,CAAC;IAC5CM,KAAKV,EAAEM,MAAM,GAAGC,IAAI;IACpBI,kBAAkBX,EAAEM,MAAM,GAAGC,IAAI,GAAGK,QAAQ;IAC5CC,OAAOb,EAAEM,MAAM,GAAGO,KAAK;IACvBC,MAAMd,EAAEM,MAAM,GAAGS,QAAQ,GAAGH,QAAQ;IACpCI,aAAahB,EAAEiB,KAAK,CAACd;IACrBe,KAAKlB,EAAEmB,MAAM,GAAGC,GAAG;IACnBC,KAAKrB,EAAEmB,MAAM,GAAGC,GAAG;AACrB,GAAG;AAmBH,OAAO,eAAeE,cAAcC,MAA2B;IAC7D,MAAMC,QAAQ,MAAM1B,UAAU;QAC5B2B,SAAS;YACPZ,OAAOU,OAAOV,KAAK;YACnBC,MAAMS,OAAOT,IAAI,IAAI;YACrBE,aAAaO,OAAOP,WAAW;YAC/BL,kBAAkBY,OAAOZ,gBAAgB;QAC3C;QACAe,QAAQH,OAAOG,MAAM;QACrBC,WAAWJ,OAAOI,SAAS;QAC3BC,SAASL,OAAOM,MAAM;IACxB;IACA5B,kBAAkB;IAClB,OAAOuB;AACT;AAEA,OAAO,eAAeM,gBAAgBP,MAA6B;IACjE,IAAI;QACF,MAAMQ,SAAS,MAAMhC,YAAY;YAC/ByB,OAAOD,OAAOC,KAAK;YACnBE,QAAQH,OAAOG,MAAM;YACrBM,QAAQvB;QACV;QACAP,oBAAoB,WAAW;QAC/B,OAAO6B;IACT,EAAE,OAAOE,OAAO;QACd/B,oBAAoB,WAAW;QAC/B,MAAM+B;IACR;AACF"}
|