@shipfox/api-auth 5.0.0 → 6.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +52 -0
- package/README.md +71 -37
- package/dist/config.d.ts +0 -12
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -62
- package/dist/config.js.map +1 -1
- package/dist/core/auth.d.ts +21 -2
- package/dist/core/auth.d.ts.map +1 -1
- package/dist/core/auth.js +109 -77
- package/dist/core/auth.js.map +1 -1
- package/dist/core/email-owner.d.ts +12 -0
- package/dist/core/email-owner.d.ts.map +1 -0
- package/dist/core/email-owner.js +20 -0
- package/dist/core/email-owner.js.map +1 -0
- package/dist/core/job-lease-token.d.ts.map +1 -1
- package/dist/core/job-lease-token.js +3 -2
- package/dist/core/job-lease-token.js.map +1 -1
- package/dist/core/jwt.d.ts +2 -2
- package/dist/core/jwt.d.ts.map +1 -1
- package/dist/core/jwt.js.map +1 -1
- package/dist/core/rate-limit.d.ts.map +1 -1
- package/dist/core/rate-limit.js +2 -7
- package/dist/core/rate-limit.js.map +1 -1
- package/dist/core/runner-session-token.d.ts.map +1 -1
- package/dist/core/runner-session-token.js +3 -2
- package/dist/core/runner-session-token.js.map +1 -1
- package/dist/db/db.d.ts +0 -218
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +0 -2
- package/dist/db/db.js.map +1 -1
- package/dist/db/users.d.ts +3 -0
- package/dist/db/users.d.ts.map +1 -1
- package/dist/db/users.js +28 -10
- package/dist/db/users.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -34
- package/dist/index.js.map +1 -1
- package/dist/presentation/auth/jwt-auth.d.ts.map +1 -1
- package/dist/presentation/auth/jwt-auth.js +2 -2
- package/dist/presentation/auth/jwt-auth.js.map +1 -1
- package/dist/presentation/e2eRoutes/create-session.d.ts +2 -1
- package/dist/presentation/e2eRoutes/create-session.d.ts.map +1 -1
- package/dist/presentation/e2eRoutes/create-session.js +42 -39
- package/dist/presentation/e2eRoutes/create-session.js.map +1 -1
- package/dist/presentation/e2eRoutes/index.d.ts +2 -1
- package/dist/presentation/e2eRoutes/index.d.ts.map +1 -1
- package/dist/presentation/e2eRoutes/index.js +12 -10
- package/dist/presentation/e2eRoutes/index.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +4 -0
- package/dist/presentation/inter-module.d.ts.map +1 -0
- package/dist/presentation/inter-module.js +16 -0
- package/dist/presentation/inter-module.js.map +1 -0
- package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts +2 -1
- package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts.map +1 -1
- package/dist/presentation/routes/email-verification/verify-email-confirm.js +36 -25
- package/dist/presentation/routes/email-verification/verify-email-confirm.js.map +1 -1
- package/dist/presentation/routes/email-verification/verify-email-resend.d.ts.map +1 -1
- package/dist/presentation/routes/email-verification/verify-email-resend.js +20 -4
- package/dist/presentation/routes/email-verification/verify-email-resend.js.map +1 -1
- package/dist/presentation/routes/index.d.ts +2 -2
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +11 -13
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/password/password-reset-confirm.d.ts +2 -1
- package/dist/presentation/routes/password/password-reset-confirm.d.ts.map +1 -1
- package/dist/presentation/routes/password/password-reset-confirm.js +29 -26
- package/dist/presentation/routes/password/password-reset-confirm.js.map +1 -1
- package/dist/presentation/routes/registration/signup.d.ts +2 -1
- package/dist/presentation/routes/registration/signup.d.ts.map +1 -1
- package/dist/presentation/routes/registration/signup.js +86 -68
- package/dist/presentation/routes/registration/signup.js.map +1 -1
- package/dist/presentation/routes/session/login.d.ts +2 -1
- package/dist/presentation/routes/session/login.d.ts.map +1 -1
- package/dist/presentation/routes/session/login.js +40 -37
- package/dist/presentation/routes/session/login.js.map +1 -1
- package/dist/presentation/routes/session/refresh.d.ts +2 -1
- package/dist/presentation/routes/session/refresh.d.ts.map +1 -1
- package/dist/presentation/routes/session/refresh.js +42 -39
- package/dist/presentation/routes/session/refresh.js.map +1 -1
- package/dist/presentation/subscribers/index.d.ts +0 -1
- package/dist/presentation/subscribers/index.d.ts.map +1 -1
- package/dist/presentation/subscribers/index.js +0 -1
- package/dist/presentation/subscribers/index.js.map +1 -1
- package/dist/presentation/subscribers/on-password-reset-send-requested.js +1 -1
- package/dist/presentation/subscribers/on-password-reset-send-requested.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0000_initial.sql +1 -13
- package/drizzle/meta/0000_snapshot.json +0 -92
- package/package.json +17 -13
- package/src/config.ts +0 -56
- package/src/core/auth.test.ts +106 -187
- package/src/core/auth.ts +131 -87
- package/src/core/email-owner.test.ts +86 -0
- package/src/core/email-owner.ts +23 -0
- package/src/core/job-lease-token.test.ts +2 -1
- package/src/core/job-lease-token.ts +3 -2
- package/src/core/jwt.ts +2 -2
- package/src/core/rate-limit.test.ts +11 -28
- package/src/core/rate-limit.ts +3 -11
- package/src/core/runner-session-token.test.ts +2 -1
- package/src/core/runner-session-token.ts +3 -2
- package/src/db/db.ts +0 -2
- package/src/db/users.ts +32 -14
- package/src/index.test.ts +15 -11
- package/src/index.ts +23 -20
- package/src/presentation/auth/jwt-auth.test.ts +2 -1
- package/src/presentation/auth/jwt-auth.ts +2 -2
- package/src/presentation/e2eRoutes/create-session.ts +34 -30
- package/src/presentation/e2eRoutes/index.test.ts +9 -6
- package/src/presentation/e2eRoutes/index.ts +8 -5
- package/src/presentation/inter-module.ts +13 -0
- package/src/presentation/routes/email-verification/verify-email-confirm.ts +42 -24
- package/src/presentation/routes/email-verification/verify-email-resend.ts +18 -4
- package/src/presentation/routes/index.test.ts +10 -3
- package/src/presentation/routes/index.ts +22 -12
- package/src/presentation/routes/password/password-reset-confirm.ts +25 -22
- package/src/presentation/routes/password/password-reset-request.test.ts +1 -0
- package/src/presentation/routes/rate-limit.test.ts +2 -2
- package/src/presentation/routes/registration/signup.test.ts +39 -74
- package/src/presentation/routes/registration/signup.ts +89 -70
- package/src/presentation/routes/session/login.ts +38 -35
- package/src/presentation/routes/session/refresh.ts +43 -40
- package/src/presentation/subscribers/email-send-requested.test.ts +2 -30
- package/src/presentation/subscribers/index.ts +0 -1
- package/src/presentation/subscribers/on-password-reset-send-requested.ts +1 -1
- package/test/env.ts +1 -3
- package/test/globalSetup.ts +10 -1
- package/test/routes.ts +51 -84
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/core/entities/email-verification.d.ts +0 -9
- package/dist/core/entities/email-verification.d.ts.map +0 -1
- package/dist/core/entities/email-verification.js +0 -3
- package/dist/core/entities/email-verification.js.map +0 -1
- package/dist/db/email-verifications.d.ts +0 -39
- package/dist/db/email-verifications.d.ts.map +0 -1
- package/dist/db/email-verifications.js +0 -95
- package/dist/db/email-verifications.js.map +0 -1
- package/dist/db/schema/email-verifications.d.ts +0 -114
- package/dist/db/schema/email-verifications.d.ts.map +0 -1
- package/dist/db/schema/email-verifications.js +0 -35
- package/dist/db/schema/email-verifications.js.map +0 -1
- package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts +0 -3
- package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts.map +0 -1
- package/dist/presentation/subscribers/on-email-verification-send-requested.js +0 -13
- package/dist/presentation/subscribers/on-email-verification-send-requested.js.map +0 -1
- package/src/core/entities/email-verification.ts +0 -8
- package/src/db/email-verifications.test.ts +0 -61
- package/src/db/email-verifications.ts +0 -160
- package/src/db/schema/email-verifications.ts +0 -37
- package/src/presentation/routes/email-verification/verify-email-confirm.test.ts +0 -66
- package/src/presentation/routes/email-verification/verify-email-resend.test.ts +0 -100
- package/src/presentation/subscribers/on-email-verification-send-requested.ts +0 -10
package/dist/core/auth.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { emailSchema } from '@shipfox/api-auth-dto';
|
|
2
|
+
import { confirmEmailChallenge, consumeEmailChallengeProof, createEmailChallenge, resendEmailChallenge } from '@shipfox/api-email-challenges';
|
|
3
|
+
import { workspacesInterModuleContract } from '@shipfox/api-workspaces-dto/inter-module';
|
|
4
|
+
import { isInterModuleKnownError } from '@shipfox/inter-module';
|
|
5
|
+
import { userAccessTokenKey } from '@shipfox/node-auth-root-key';
|
|
3
6
|
import { generateOpaqueToken, hashOpaqueToken } from '@shipfox/node-tokens';
|
|
4
7
|
import { config } from '#config.js';
|
|
5
|
-
import { consumeEmailVerification, createEmailVerification, createResendEmailVerification } from '#db/email-verifications.js';
|
|
6
8
|
import { consumePasswordReset, createPasswordReset } from '#db/password-resets.js';
|
|
7
9
|
import { createRefreshToken, findActiveRefreshTokenByHash, findRefreshTokenByHash, revokeRefreshTokenByHash, revokeRefreshTokensForUser, rotateRefreshToken } from '#db/refresh-tokens.js';
|
|
8
10
|
import { createUser as createDbUser, findUserByEmail, findUserById, markEmailVerified, provisionUser as provisionDbUser, updateUserPassword } from '#db/users.js';
|
|
9
11
|
import { recordTokenRefreshed } from '#metrics/index.js';
|
|
10
|
-
import { AuthDependencyUnavailableError, EmailNotVerifiedError, EmailTakenError, InvalidCredentialsError, TokenInvalidError, UserNotFoundError } from './errors.js';
|
|
12
|
+
import { AuthDependencyUnavailableError, EmailNotVerifiedError, EmailTakenError, InvalidCredentialsError, InvitationEmailMismatchError, TokenAlreadyUsedError, TokenExpiredError, TokenInvalidError, UserNotFoundError } from './errors.js';
|
|
11
13
|
import { signUserToken } from './jwt.js';
|
|
12
14
|
import { hashPassword, verifyPassword } from './password.js';
|
|
13
|
-
const VERIFICATION_TTL_HOURS = 24;
|
|
14
15
|
const RESET_TTL_HOURS = 1;
|
|
16
|
+
const PASSWORD_VERIFICATION_PURPOSE = 'password-verification';
|
|
15
17
|
let dummyHashCache;
|
|
16
18
|
async function getDummyHash() {
|
|
17
19
|
if (!dummyHashCache) {
|
|
@@ -30,8 +32,8 @@ function daysFromNow(days) {
|
|
|
30
32
|
function recordRefreshOutcome(outcome) {
|
|
31
33
|
recordTokenRefreshed(outcome);
|
|
32
34
|
}
|
|
33
|
-
async function signAccessToken(user) {
|
|
34
|
-
const memberships = await
|
|
35
|
+
async function signAccessToken(user, workspaces) {
|
|
36
|
+
const memberships = await workspaces.listMembershipsForTokenClaims({
|
|
35
37
|
userId: user.id
|
|
36
38
|
}).catch((error)=>{
|
|
37
39
|
throw new AuthDependencyUnavailableError('workspaces', error);
|
|
@@ -40,11 +42,8 @@ async function signAccessToken(user) {
|
|
|
40
42
|
userId: user.id,
|
|
41
43
|
email: user.email,
|
|
42
44
|
name: user.name,
|
|
43
|
-
memberships: memberships.
|
|
44
|
-
|
|
45
|
-
role: 'admin'
|
|
46
|
-
})),
|
|
47
|
-
secret: config.AUTH_JWT_SECRET,
|
|
45
|
+
memberships: memberships.memberships,
|
|
46
|
+
secret: userAccessTokenKey(),
|
|
48
47
|
expiresIn: config.AUTH_JWT_EXPIRES_IN
|
|
49
48
|
});
|
|
50
49
|
}
|
|
@@ -62,23 +61,22 @@ async function createRefreshSession(user) {
|
|
|
62
61
|
});
|
|
63
62
|
return refreshToken;
|
|
64
63
|
}
|
|
65
|
-
function verificationLink(rawToken) {
|
|
66
|
-
return `${config.CLIENT_BASE_URL}/auth/verify-email?token=${rawToken}`;
|
|
67
|
-
}
|
|
68
64
|
function passwordResetLink(rawToken) {
|
|
69
65
|
return `${config.CLIENT_BASE_URL}/auth/reset?token=${rawToken}`;
|
|
70
66
|
}
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
67
|
+
function invitationErrorFromKnownError(error, method) {
|
|
68
|
+
const contractMethod = workspacesInterModuleContract.methods[method];
|
|
69
|
+
if (!isInterModuleKnownError(contractMethod, error)) return error;
|
|
70
|
+
if (error.code === 'invitation-token-invalid') return new TokenInvalidError('Invitation token is invalid');
|
|
71
|
+
if (error.code === 'invitation-token-used') return new TokenAlreadyUsedError();
|
|
72
|
+
if (error.code === 'invitation-token-expired') return new TokenExpiredError();
|
|
73
|
+
return new InvitationEmailMismatchError();
|
|
74
|
+
}
|
|
75
|
+
function invitationErrorMessage(code) {
|
|
76
|
+
if (code === 'invitation-token-invalid') return 'Invitation token is invalid';
|
|
77
|
+
if (code === 'invitation-token-used') return 'Invitation has already been accepted';
|
|
78
|
+
if (code === 'invitation-token-expired') return 'Invitation has expired';
|
|
79
|
+
return 'Signup email does not match the invitation';
|
|
82
80
|
}
|
|
83
81
|
/**
|
|
84
82
|
* Creates a verified, password-less user for an external identity provider.
|
|
@@ -94,6 +92,22 @@ export async function signup(params) {
|
|
|
94
92
|
email: params.email
|
|
95
93
|
});
|
|
96
94
|
if (existing) {
|
|
95
|
+
const canResumeVerification = existing.status === 'active' && existing.emailVerifiedAt === null && existing.hashedPassword !== null && await verifyPassword({
|
|
96
|
+
password: params.password,
|
|
97
|
+
hash: existing.hashedPassword
|
|
98
|
+
});
|
|
99
|
+
if (canResumeVerification) {
|
|
100
|
+
const emailChallenge = await createEmailChallenge({
|
|
101
|
+
email: existing.email,
|
|
102
|
+
purpose: PASSWORD_VERIFICATION_PURPOSE,
|
|
103
|
+
continuation: existing.id,
|
|
104
|
+
sourceIp: params.sourceIp ?? '0.0.0.0'
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
...existing,
|
|
108
|
+
emailChallenge
|
|
109
|
+
};
|
|
110
|
+
}
|
|
97
111
|
throw new EmailTakenError(params.email);
|
|
98
112
|
}
|
|
99
113
|
const hashedPassword = await hashPassword({
|
|
@@ -102,29 +116,33 @@ export async function signup(params) {
|
|
|
102
116
|
const user = await createDbUser({
|
|
103
117
|
email: params.email,
|
|
104
118
|
hashedPassword,
|
|
105
|
-
name: params.name ?? null
|
|
119
|
+
name: params.name ?? null,
|
|
120
|
+
signedUp: {
|
|
121
|
+
viaInvitation: false
|
|
122
|
+
}
|
|
106
123
|
});
|
|
107
|
-
await
|
|
108
|
-
|
|
124
|
+
const emailChallenge = await createEmailChallenge({
|
|
125
|
+
email: user.email,
|
|
126
|
+
purpose: PASSWORD_VERIFICATION_PURPOSE,
|
|
127
|
+
continuation: user.id,
|
|
128
|
+
sourceIp: params.sourceIp ?? '0.0.0.0'
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
...user,
|
|
132
|
+
emailChallenge
|
|
133
|
+
};
|
|
109
134
|
}
|
|
110
135
|
export async function signupWithInvitation(params) {
|
|
111
136
|
// Step 1: Pre-validate the invitation BEFORE any user write so an invalid
|
|
112
137
|
// token never produces an orphan user. Re-validation happens again inside
|
|
113
138
|
// acceptWorkspaceInvitation (race-safe) after the user is created.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
throw new TokenAlreadyUsedError();
|
|
122
|
-
}
|
|
123
|
-
if (invitation.expiresAt.getTime() <= Date.now()) {
|
|
124
|
-
throw new TokenExpiredError();
|
|
125
|
-
}
|
|
126
|
-
if (invitation.email !== params.email) {
|
|
127
|
-
throw new InvitationEmailMismatchError();
|
|
139
|
+
try {
|
|
140
|
+
await params.workspaces.preflightInvitationAcceptance({
|
|
141
|
+
token: params.invitationToken,
|
|
142
|
+
email: params.email
|
|
143
|
+
});
|
|
144
|
+
} catch (error) {
|
|
145
|
+
throw invitationErrorFromKnownError(error, 'preflightInvitationAcceptance');
|
|
128
146
|
}
|
|
129
147
|
// Step 2: Create the user as verified. The invitation email is the proof of
|
|
130
148
|
// ownership, and this avoids a second auth-table write just to mark verified.
|
|
@@ -141,7 +159,10 @@ export async function signupWithInvitation(params) {
|
|
|
141
159
|
email: params.email,
|
|
142
160
|
hashedPassword,
|
|
143
161
|
name: params.name ?? null,
|
|
144
|
-
emailVerifiedAt: new Date()
|
|
162
|
+
emailVerifiedAt: new Date(),
|
|
163
|
+
signedUp: {
|
|
164
|
+
viaInvitation: true
|
|
165
|
+
}
|
|
145
166
|
});
|
|
146
167
|
// Step 3: Accept the invitation through the workspaces module boundary.
|
|
147
168
|
// This is intentionally not one database transaction with auth; treat it like
|
|
@@ -150,7 +171,7 @@ export async function signupWithInvitation(params) {
|
|
|
150
171
|
let membership = null;
|
|
151
172
|
let acceptError;
|
|
152
173
|
try {
|
|
153
|
-
const result = await
|
|
174
|
+
const result = await params.workspaces.acceptInvitation({
|
|
154
175
|
token: params.invitationToken,
|
|
155
176
|
userId: user.id,
|
|
156
177
|
email: user.email,
|
|
@@ -162,10 +183,10 @@ export async function signupWithInvitation(params) {
|
|
|
162
183
|
workspaceId: result.membership.workspaceId
|
|
163
184
|
};
|
|
164
185
|
} catch (error) {
|
|
165
|
-
if (
|
|
186
|
+
if (isInterModuleKnownError(workspacesInterModuleContract.methods.acceptInvitation, error)) {
|
|
166
187
|
acceptError = {
|
|
167
|
-
code: error.
|
|
168
|
-
message: error.
|
|
188
|
+
code: error.code,
|
|
189
|
+
message: invitationErrorMessage(error.code)
|
|
169
190
|
};
|
|
170
191
|
} else {
|
|
171
192
|
acceptError = {
|
|
@@ -176,7 +197,7 @@ export async function signupWithInvitation(params) {
|
|
|
176
197
|
}
|
|
177
198
|
// Step 4: Issue session. signAccessToken reads memberships through the
|
|
178
199
|
// workspaces module API, so a successful accept is reflected in the JWT.
|
|
179
|
-
const token = await signAccessToken(user);
|
|
200
|
+
const token = await signAccessToken(user, params.workspaces);
|
|
180
201
|
const refreshToken = await createRefreshSession(user);
|
|
181
202
|
if (acceptError) {
|
|
182
203
|
return {
|
|
@@ -234,7 +255,7 @@ export async function login(params) {
|
|
|
234
255
|
if (user.emailVerifiedAt === null) {
|
|
235
256
|
throw new EmailNotVerifiedError();
|
|
236
257
|
}
|
|
237
|
-
const token = await signAccessToken(user);
|
|
258
|
+
const token = await signAccessToken(user, params.workspaces);
|
|
238
259
|
const refreshToken = await createRefreshSession(user);
|
|
239
260
|
return {
|
|
240
261
|
token,
|
|
@@ -246,7 +267,7 @@ export async function createSessionForUser(params) {
|
|
|
246
267
|
const user = params.userId ? await findUserById({
|
|
247
268
|
id: params.userId
|
|
248
269
|
}) : params.email ? await findUserByEmail({
|
|
249
|
-
email: params.email
|
|
270
|
+
email: emailSchema.parse(params.email)
|
|
250
271
|
}) : undefined;
|
|
251
272
|
if (!user) {
|
|
252
273
|
throw new UserNotFoundError(params.userId ?? params.email ?? 'unknown');
|
|
@@ -257,7 +278,7 @@ export async function createSessionForUser(params) {
|
|
|
257
278
|
if (user.status !== 'active') {
|
|
258
279
|
throw new InvalidCredentialsError();
|
|
259
280
|
}
|
|
260
|
-
const token = await signAccessToken(user);
|
|
281
|
+
const token = await signAccessToken(user, params.workspaces);
|
|
261
282
|
const refreshToken = await createRefreshSession(user);
|
|
262
283
|
return {
|
|
263
284
|
token,
|
|
@@ -288,7 +309,7 @@ export async function refreshAccessToken(params) {
|
|
|
288
309
|
// second tab); past it, reuse of a retired token means a compromised session.
|
|
289
310
|
if (current.rotatedAt) {
|
|
290
311
|
if (isWithinRotationGrace(current)) {
|
|
291
|
-
const token = await signAccessToken(user);
|
|
312
|
+
const token = await signAccessToken(user, params.workspaces);
|
|
292
313
|
recordRefreshOutcome('grace');
|
|
293
314
|
return {
|
|
294
315
|
token,
|
|
@@ -319,7 +340,7 @@ export async function refreshAccessToken(params) {
|
|
|
319
340
|
recordRefreshOutcome('rejected');
|
|
320
341
|
throw new TokenInvalidError('Refresh token is invalid or expired');
|
|
321
342
|
}
|
|
322
|
-
const token = await signAccessToken(user);
|
|
343
|
+
const token = await signAccessToken(user, params.workspaces);
|
|
323
344
|
recordRefreshOutcome('grace');
|
|
324
345
|
return {
|
|
325
346
|
token,
|
|
@@ -327,7 +348,7 @@ export async function refreshAccessToken(params) {
|
|
|
327
348
|
user
|
|
328
349
|
};
|
|
329
350
|
}
|
|
330
|
-
const token = await signAccessToken(user);
|
|
351
|
+
const token = await signAccessToken(user, params.workspaces);
|
|
331
352
|
recordRefreshOutcome('rotated');
|
|
332
353
|
return {
|
|
333
354
|
token,
|
|
@@ -336,36 +357,47 @@ export async function refreshAccessToken(params) {
|
|
|
336
357
|
};
|
|
337
358
|
}
|
|
338
359
|
export async function confirmEmailVerification(params) {
|
|
339
|
-
const
|
|
340
|
-
|
|
360
|
+
const user = await findUserByEmail({
|
|
361
|
+
email: emailSchema.parse(params.email)
|
|
341
362
|
});
|
|
342
|
-
if (!
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
363
|
+
if (!user) throw new TokenInvalidError('Verification code is invalid or expired');
|
|
364
|
+
await confirmEmailChallenge({
|
|
365
|
+
id: params.challengeId,
|
|
366
|
+
code: params.code,
|
|
367
|
+
continuation: user.id
|
|
347
368
|
});
|
|
348
|
-
|
|
349
|
-
|
|
369
|
+
await consumeEmailChallengeProof({
|
|
370
|
+
id: params.challengeId,
|
|
371
|
+
purpose: PASSWORD_VERIFICATION_PURPOSE,
|
|
372
|
+
continuation: user.id
|
|
373
|
+
});
|
|
374
|
+
const verifiedUser = await markEmailVerified({
|
|
375
|
+
userId: user.id
|
|
376
|
+
});
|
|
377
|
+
if (verifiedUser?.status !== 'active') {
|
|
378
|
+
throw new TokenInvalidError('Verification code is invalid or expired');
|
|
350
379
|
}
|
|
351
|
-
const token = await signAccessToken(
|
|
352
|
-
const refreshToken = await createRefreshSession(
|
|
380
|
+
const token = await signAccessToken(verifiedUser, params.workspaces);
|
|
381
|
+
const refreshToken = await createRefreshSession(verifiedUser);
|
|
353
382
|
return {
|
|
354
383
|
token,
|
|
355
384
|
refreshToken,
|
|
356
|
-
user
|
|
385
|
+
user: verifiedUser
|
|
357
386
|
};
|
|
358
387
|
}
|
|
359
388
|
export async function resendEmailVerification(params) {
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
389
|
+
const user = await findUserByEmail({
|
|
390
|
+
email: emailSchema.parse(params.email)
|
|
391
|
+
});
|
|
392
|
+
if (!user || user.emailVerifiedAt || user.status !== 'active') {
|
|
393
|
+
return {
|
|
394
|
+
nextResendAvailableAt: new Date()
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
const result = await resendEmailChallenge({
|
|
398
|
+
id: params.challengeId,
|
|
399
|
+
continuation: user.id,
|
|
400
|
+
sourceIp: params.sourceIp
|
|
369
401
|
});
|
|
370
402
|
return {
|
|
371
403
|
nextResendAvailableAt: result.nextResendAvailableAt
|
|
@@ -416,7 +448,7 @@ export async function confirmPasswordReset(params) {
|
|
|
416
448
|
await revokeRefreshTokensForUser({
|
|
417
449
|
userId: consumed.userId
|
|
418
450
|
});
|
|
419
|
-
const token = await signAccessToken(user);
|
|
451
|
+
const token = await signAccessToken(user, params.workspaces);
|
|
420
452
|
const refreshToken = await createRefreshSession(user);
|
|
421
453
|
return {
|
|
422
454
|
token,
|
package/dist/core/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/auth.ts"],"sourcesContent":["import {EMAIL_VERIFICATION_RESEND_COOLDOWN_SECONDS, emailSchema} from '@shipfox/api-auth-dto';\nimport {\n acceptWorkspaceInvitation,\n InvitationEmailMismatchError,\n listMembershipsByUser,\n peekInvitationByRawToken,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError as WorkspacesTokenInvalidError,\n} from '@shipfox/api-workspaces';\nimport {generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';\nimport {config} from '#config.js';\nimport {\n consumeEmailVerification,\n createEmailVerification,\n createResendEmailVerification,\n} from '#db/email-verifications.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 TokenInvalidError,\n UserNotFoundError,\n} from './errors.js';\nimport {signUserToken} from './jwt.js';\nimport {hashPassword, verifyPassword} from './password.js';\n\nconst VERIFICATION_TTL_HOURS = 24;\nconst RESET_TTL_HOURS = 1;\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(user: User): Promise<string> {\n const memberships = await listMembershipsByUser({userId: user.id}).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.map((m) => ({workspaceId: m.workspaceId, role: 'admin' as const})),\n secret: config.AUTH_JWT_SECRET,\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 verificationLink(rawToken: string): string {\n return `${config.CLIENT_BASE_URL}/auth/verify-email?token=${rawToken}`;\n}\n\nfunction passwordResetLink(rawToken: string): string {\n return `${config.CLIENT_BASE_URL}/auth/reset?token=${rawToken}`;\n}\n\nasync function createAndQueueEmailVerification(user: User): Promise<void> {\n const rawToken = generateOpaqueToken('emailVerification');\n await createEmailVerification({\n userId: user.id,\n hashedToken: hashOpaqueToken(rawToken),\n expiresAt: hoursFromNow(VERIFICATION_TTL_HOURS),\n sendEmail: {\n email: user.email,\n verifyLink: verificationLink(rawToken),\n },\n });\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 async function signup(params: SignupParams): 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 });\n\n await createAndQueueEmailVerification(user);\n\n return user;\n}\n\nexport interface SignupWithInvitationParams extends SignupParams {\n invitationToken: string;\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 const invitation = await peekInvitationByRawToken({token: params.invitationToken});\n if (!invitation) {\n throw new WorkspacesTokenInvalidError('Invitation token is invalid');\n }\n if (invitation.acceptedAt !== null) {\n throw new TokenAlreadyUsedError();\n }\n if (invitation.expiresAt.getTime() <= Date.now()) {\n throw new TokenExpiredError();\n }\n if (invitation.email !== params.email) {\n throw new InvitationEmailMismatchError();\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 });\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 acceptWorkspaceInvitation({\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 (\n error instanceof WorkspacesTokenInvalidError ||\n error instanceof TokenAlreadyUsedError ||\n error instanceof TokenExpiredError ||\n error instanceof InvitationEmailMismatchError\n ) {\n acceptError = {code: error.name, message: error.message};\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);\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}\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);\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}\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: 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);\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}): 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);\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);\n recordRefreshOutcome('grace');\n return {token, refreshToken: undefined, user};\n }\n\n const token = await signAccessToken(user);\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 token: string;\n}): Promise<ConfirmEmailVerificationResult> {\n const consumed = await consumeEmailVerification({hashedToken: hashOpaqueToken(params.token)});\n if (!consumed) {\n throw new TokenInvalidError('Verification token is invalid or expired');\n }\n\n const user = await markEmailVerified({userId: consumed.userId});\n if (user?.status !== 'active') {\n throw new TokenInvalidError('Verification token is invalid or expired');\n }\n\n const token = await signAccessToken(user);\n const refreshToken = await createRefreshSession(user);\n\n return {token, refreshToken, user};\n}\n\nexport async function resendEmailVerification(params: {\n email: string;\n}): Promise<ResendEmailVerificationResult> {\n const rawToken = generateOpaqueToken('emailVerification');\n const result = await createResendEmailVerification({\n email: params.email,\n hashedToken: hashOpaqueToken(rawToken),\n expiresAt: hoursFromNow(VERIFICATION_TTL_HOURS),\n cooldownSeconds: EMAIL_VERIFICATION_RESEND_COOLDOWN_SECONDS,\n sendEmail: {\n verifyLink: verificationLink(rawToken),\n },\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}): 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);\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":["EMAIL_VERIFICATION_RESEND_COOLDOWN_SECONDS","emailSchema","acceptWorkspaceInvitation","InvitationEmailMismatchError","listMembershipsByUser","peekInvitationByRawToken","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","WorkspacesTokenInvalidError","generateOpaqueToken","hashOpaqueToken","config","consumeEmailVerification","createEmailVerification","createResendEmailVerification","consumePasswordReset","createPasswordReset","createRefreshToken","findActiveRefreshTokenByHash","findRefreshTokenByHash","revokeRefreshTokenByHash","revokeRefreshTokensForUser","rotateRefreshToken","createUser","createDbUser","findUserByEmail","findUserById","markEmailVerified","provisionUser","provisionDbUser","updateUserPassword","recordTokenRefreshed","AuthDependencyUnavailableError","EmailNotVerifiedError","EmailTakenError","InvalidCredentialsError","UserNotFoundError","signUserToken","hashPassword","verifyPassword","VERIFICATION_TTL_HOURS","RESET_TTL_HOURS","dummyHashCache","getDummyHash","password","hoursFromNow","hours","Date","now","daysFromNow","days","recordRefreshOutcome","outcome","signAccessToken","user","memberships","userId","id","catch","error","email","name","map","m","workspaceId","role","secret","AUTH_JWT_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","verificationLink","rawToken","CLIENT_BASE_URL","passwordResetLink","createAndQueueEmailVerification","sendEmail","verifyLink","params","parse","signup","existing","hashedPassword","signupWithInvitation","invitation","token","invitationToken","acceptedAt","emailVerifiedAt","membership","acceptError","result","code","message","verified","login","hash","hasPassword","ok","status","createSessionForUser","undefined","refreshAccessToken","currentHashedToken","current","nextRefreshToken","rotated","nextHashedToken","latest","confirmEmailVerification","consumed","resendEmailVerification","cooldownSeconds","nextResendAvailableAt","requestPasswordReset","resetLink","expiresInHours","confirmPasswordReset","existingUser","newPassword","changePassword","currentPassword","currentRefreshSession","exceptRefreshTokenId","logout","getCurrentUser"],"mappings":"AAAA,SAAQA,0CAA0C,EAAEC,WAAW,QAAO,wBAAwB;AAC9F,SACEC,yBAAyB,EACzBC,4BAA4B,EAC5BC,qBAAqB,EACrBC,wBAAwB,EACxBC,qBAAqB,EACrBC,iBAAiB,EACjBC,qBAAqBC,2BAA2B,QAC3C,0BAA0B;AACjC,SAAQC,mBAAmB,EAAEC,eAAe,QAAO,uBAAuB;AAC1E,SAAQC,MAAM,QAAO,aAAa;AAClC,SACEC,wBAAwB,EACxBC,uBAAuB,EACvBC,6BAA6B,QACxB,6BAA6B;AACpC,SAAQC,oBAAoB,EAAEC,mBAAmB,QAAO,yBAAyB;AACjF,SACEC,kBAAkB,EAClBC,4BAA4B,EAC5BC,sBAAsB,EACtBC,wBAAwB,EACxBC,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,EACvB5B,iBAAiB,EACjB6B,iBAAiB,QACZ,cAAc;AACrB,SAAQC,aAAa,QAAO,WAAW;AACvC,SAAQC,YAAY,EAAEC,cAAc,QAAO,gBAAgB;AAE3D,MAAMC,yBAAyB;AAC/B,MAAMC,kBAAkB;AAExB,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;IAC5DrB,qBAAqBqB;AACvB;AAEA,eAAeC,gBAAgBC,IAAU;IACvC,MAAMC,cAAc,MAAMpD,sBAAsB;QAACqD,QAAQF,KAAKG,EAAE;IAAA,GAAGC,KAAK,CAAC,CAACC;QACxE,MAAM,IAAI3B,+BAA+B,cAAc2B;IACzD;IACA,OAAO,MAAMtB,cAAc;QACzBmB,QAAQF,KAAKG,EAAE;QACfG,OAAON,KAAKM,KAAK;QACjBC,MAAMP,KAAKO,IAAI;QACfN,aAAaA,YAAYO,GAAG,CAAC,CAACC,IAAO,CAAA;gBAACC,aAAaD,EAAEC,WAAW;gBAAEC,MAAM;YAAgB,CAAA;QACxFC,QAAQvD,OAAOwD,eAAe;QAC9BC,WAAWzD,OAAO0D,mBAAmB;IACvC;AACF;AAEA,SAASC,sBAAsBC,YAA0B;IACvD,IAAI,CAACA,aAAaC,SAAS,EAAE,OAAO;IACpC,MAAMC,UAAU9D,OAAO+D,mCAAmC,GAAG;IAC7D,OAAO3B,KAAKC,GAAG,KAAKuB,aAAaC,SAAS,CAACG,OAAO,MAAMF;AAC1D;AAEA,eAAeG,qBAAqBtB,IAAU;IAC5C,MAAMiB,eAAe9D,oBAAoB;IACzC,MAAMQ,mBAAmB;QACvBuC,QAAQF,KAAKG,EAAE;QACfoB,aAAanE,gBAAgB6D;QAC7BO,WAAW7B,YAAYtC,OAAOoE,kCAAkC;IAClE;IACA,OAAOR;AACT;AAEA,SAASS,iBAAiBC,QAAgB;IACxC,OAAO,GAAGtE,OAAOuE,eAAe,CAAC,yBAAyB,EAAED,UAAU;AACxE;AAEA,SAASE,kBAAkBF,QAAgB;IACzC,OAAO,GAAGtE,OAAOuE,eAAe,CAAC,kBAAkB,EAAED,UAAU;AACjE;AAEA,eAAeG,gCAAgC9B,IAAU;IACvD,MAAM2B,WAAWxE,oBAAoB;IACrC,MAAMI,wBAAwB;QAC5B2C,QAAQF,KAAKG,EAAE;QACfoB,aAAanE,gBAAgBuE;QAC7BH,WAAWjC,aAAaL;QACxB6C,WAAW;YACTzB,OAAON,KAAKM,KAAK;YACjB0B,YAAYN,iBAAiBC;QAC/B;IACF;AACF;AAaA;;;CAGC,GACD,OAAO,eAAerD,cAAc2D,MAA2B;IAC7D,OAAO,MAAM1D,gBAAgB;QAC3B+B,OAAO5D,YAAYwF,KAAK,CAACD,OAAO3B,KAAK;QACrCC,MAAM0B,OAAO1B,IAAI,IAAI;IACvB;AACF;AAEA,OAAO,eAAe4B,OAAOF,MAAoB;IAC/C,MAAMG,WAAW,MAAMjE,gBAAgB;QAACmC,OAAO2B,OAAO3B,KAAK;IAAA;IAC3D,IAAI8B,UAAU;QACZ,MAAM,IAAIxD,gBAAgBqD,OAAO3B,KAAK;IACxC;IAEA,MAAM+B,iBAAiB,MAAMrD,aAAa;QAACM,UAAU2C,OAAO3C,QAAQ;IAAA;IACpE,MAAMU,OAAO,MAAM9B,aAAa;QAC9BoC,OAAO2B,OAAO3B,KAAK;QACnB+B;QACA9B,MAAM0B,OAAO1B,IAAI,IAAI;IACvB;IAEA,MAAMuB,gCAAgC9B;IAEtC,OAAOA;AACT;AAWA,OAAO,eAAesC,qBACpBL,MAAkC;IAElC,0EAA0E;IAC1E,0EAA0E;IAC1E,mEAAmE;IACnE,MAAMM,aAAa,MAAMzF,yBAAyB;QAAC0F,OAAOP,OAAOQ,eAAe;IAAA;IAChF,IAAI,CAACF,YAAY;QACf,MAAM,IAAIrF,4BAA4B;IACxC;IACA,IAAIqF,WAAWG,UAAU,KAAK,MAAM;QAClC,MAAM,IAAI3F;IACZ;IACA,IAAIwF,WAAWf,SAAS,CAACH,OAAO,MAAM5B,KAAKC,GAAG,IAAI;QAChD,MAAM,IAAI1C;IACZ;IACA,IAAIuF,WAAWjC,KAAK,KAAK2B,OAAO3B,KAAK,EAAE;QACrC,MAAM,IAAI1D;IACZ;IAEA,4EAA4E;IAC5E,8EAA8E;IAC9E,MAAMwF,WAAW,MAAMjE,gBAAgB;QAACmC,OAAO2B,OAAO3B,KAAK;IAAA;IAC3D,IAAI8B,UAAU;QACZ,MAAM,IAAIxD,gBAAgBqD,OAAO3B,KAAK;IACxC;IACA,MAAM+B,iBAAiB,MAAMrD,aAAa;QAACM,UAAU2C,OAAO3C,QAAQ;IAAA;IACpE,MAAMU,OAAO,MAAM9B,aAAa;QAC9BoC,OAAO2B,OAAO3B,KAAK;QACnB+B;QACA9B,MAAM0B,OAAO1B,IAAI,IAAI;QACrBoC,iBAAiB,IAAIlD;IACvB;IAEA,wEAAwE;IACxE,8EAA8E;IAC9E,8EAA8E;IAC9E,yDAAyD;IACzD,IAAImD,aAAuD;IAC3D,IAAIC;IACJ,IAAI;QACF,MAAMC,SAAS,MAAMnG,0BAA0B;YAC7C6F,OAAOP,OAAOQ,eAAe;YAC7BvC,QAAQF,KAAKG,EAAE;YACfG,OAAON,KAAKM,KAAK;YACjBC,MAAMP,KAAKO,IAAI;QACjB;QACAqC,aAAa;YACXzC,IAAI2C,OAAOF,UAAU,CAACzC,EAAE;YACxBD,QAAQ4C,OAAOF,UAAU,CAAC1C,MAAM;YAChCQ,aAAaoC,OAAOF,UAAU,CAAClC,WAAW;QAC5C;IACF,EAAE,OAAOL,OAAO;QACd,IACEA,iBAAiBnD,+BACjBmD,iBAAiBtD,yBACjBsD,iBAAiBrD,qBACjBqD,iBAAiBzD,8BACjB;YACAiG,cAAc;gBAACE,MAAM1C,MAAME,IAAI;gBAAEyC,SAAS3C,MAAM2C,OAAO;YAAA;QACzD,OAAO;YACLH,cAAc;gBACZE,MAAM;gBACNC,SAAS;YACX;QACF;IACF;IAEA,uEAAuE;IACvE,yEAAyE;IACzE,MAAMR,QAAQ,MAAMzC,gBAAgBC;IACpC,MAAMiB,eAAe,MAAMK,qBAAqBtB;IAEhD,IAAI6C,aAAa;QACf,OAAO;YAACL;YAAOvB;YAAcjB;YAAM4C;YAAYC;QAAW;IAC5D;IACA,OAAO;QAACL;QAAOvB;QAAcjB;QAAM4C;IAAU;AAC/C;AAMA,OAAO,eAAe3E,WAAWgE,MAAwB;IACvD,MAAMG,WAAW,MAAMjE,gBAAgB;QAACmC,OAAO2B,OAAO3B,KAAK;IAAA;IAC3D,IAAI8B,UAAU;QACZ,MAAM,IAAIxD,gBAAgBqD,OAAO3B,KAAK;IACxC;IAEA,MAAM+B,iBAAiB,MAAMrD,aAAa;QAACM,UAAU2C,OAAO3C,QAAQ;IAAA;IACpE,MAAMU,OAAO,MAAM9B,aAAa;QAC9BoC,OAAO2B,OAAO3B,KAAK;QACnB+B;QACA9B,MAAM0B,OAAO1B,IAAI,IAAI;QACrBoC,iBAAiBV,OAAOgB,QAAQ,GAAG,IAAIxD,SAAS;IAClD;IAEA,OAAOO;AACT;AAaA,OAAO,eAAekD,MAAMjB,MAAmB;IAC7C,MAAMjC,OAAO,MAAM7B,gBAAgB;QAACmC,OAAO2B,OAAO3B,KAAK;IAAA;IAEvD,IAAI,CAACN,MAAM;QACT,MAAMf,eAAe;YAACK,UAAU2C,OAAO3C,QAAQ;YAAE6D,MAAM,MAAM9D;QAAc;QAC3E,MAAM,IAAIR;IACZ;IAEA,MAAMuE,cAAcpD,KAAKqC,cAAc,KAAK;IAC5C,MAAMgB,KAAK,MAAMpE,eAAe;QAC9BK,UAAU2C,OAAO3C,QAAQ;QACzB6D,MAAMnD,KAAKqC,cAAc,IAAK,MAAMhD;IACtC;IACA,IAAI,CAAC+D,eAAe,CAACC,MAAMrD,KAAKsD,MAAM,KAAK,UAAU;QACnD,MAAM,IAAIzE;IACZ;IAEA,IAAImB,KAAK2C,eAAe,KAAK,MAAM;QACjC,MAAM,IAAIhE;IACZ;IAEA,MAAM6D,QAAQ,MAAMzC,gBAAgBC;IACpC,MAAMiB,eAAe,MAAMK,qBAAqBtB;IAEhD,OAAO;QAACwC;QAAOvB;QAAcjB;IAAI;AACnC;AAmBA,OAAO,eAAeuD,qBACpBtB,MAAkC;IAElC,MAAMjC,OAAOiC,OAAO/B,MAAM,GACtB,MAAM9B,aAAa;QAAC+B,IAAI8B,OAAO/B,MAAM;IAAA,KACrC+B,OAAO3B,KAAK,GACV,MAAMnC,gBAAgB;QAACmC,OAAO2B,OAAO3B,KAAK;IAAA,KAC1CkD;IAEN,IAAI,CAACxD,MAAM;QACT,MAAM,IAAIlB,kBAAkBmD,OAAO/B,MAAM,IAAI+B,OAAO3B,KAAK,IAAI;IAC/D;IACA,IAAIN,KAAK2C,eAAe,KAAK,MAAM;QACjC,MAAM,IAAIhE;IACZ;IACA,IAAIqB,KAAKsD,MAAM,KAAK,UAAU;QAC5B,MAAM,IAAIzE;IACZ;IAEA,MAAM2D,QAAQ,MAAMzC,gBAAgBC;IACpC,MAAMiB,eAAe,MAAMK,qBAAqBtB;IAEhD,OAAO;QAACwC;QAAOvB;QAAcjB;IAAI;AACnC;AASA,OAAO,eAAeyD,mBAAmBxB,MAExC;IACC,MAAMyB,qBAAqBtG,gBAAgB6E,OAAOhB,YAAY;IAC9D,MAAM0C,UAAU,MAAM9F,uBAAuB;QAAC0D,aAAamC;IAAkB;IAC7E,IAAI,CAACC,SAAS;QACZ9D,qBAAqB;QACrB,MAAM,IAAI5C,kBAAkB;IAC9B;IAEA,MAAM+C,OAAO,MAAM5B,aAAa;QAAC+B,IAAIwD,QAAQzD,MAAM;IAAA;IACnD,IAAIF,MAAMsD,WAAW,UAAU;QAC7B,MAAMxF,yBAAyB;YAACyD,aAAamC;QAAkB;QAC/D7D,qBAAqB;QACrB,MAAM,IAAI5C,kBAAkB;IAC9B;IAEA,6EAA6E;IAC7E,8EAA8E;IAC9E,IAAI0G,QAAQzC,SAAS,EAAE;QACrB,IAAIF,sBAAsB2C,UAAU;YAClC,MAAMnB,QAAQ,MAAMzC,gBAAgBC;YACpCH,qBAAqB;YACrB,OAAO;gBAAC2C;gBAAOvB,cAAcuC;gBAAWxD;YAAI;QAC9C;QACA,MAAMjC,2BAA2B;YAACmC,QAAQF,KAAKG,EAAE;QAAA;QACjDN,qBAAqB;QACrB,MAAM,IAAI5C,kBAAkB;IAC9B;IAEA,2EAA2E;IAC3E,oEAAoE;IACpE,MAAM2G,mBAAmBzG,oBAAoB;IAC7C,MAAM0G,UAAU,MAAM7F,mBAAmB;QACvCmC,IAAIwD,QAAQxD,EAAE;QACduD;QACAI,iBAAiB1G,gBAAgBwG;QACjCpC,WAAW7B,YAAYtC,OAAOoE,kCAAkC;IAClE;IACA,IAAI,CAACoC,SAAS;QACZ,MAAME,SAAS,MAAMlG,uBAAuB;YAAC0D,aAAamC;QAAkB;QAC5E,IAAI,CAACK,UAAU,CAAC/C,sBAAsB+C,SAAS;YAC7ClE,qBAAqB;YACrB,MAAM,IAAI5C,kBAAkB;QAC9B;QACA,MAAMuF,QAAQ,MAAMzC,gBAAgBC;QACpCH,qBAAqB;QACrB,OAAO;YAAC2C;YAAOvB,cAAcuC;YAAWxD;QAAI;IAC9C;IAEA,MAAMwC,QAAQ,MAAMzC,gBAAgBC;IACpCH,qBAAqB;IACrB,OAAO;QAAC2C;QAAOvB,cAAc2C;QAAkB5D;IAAI;AACrD;AAYA,OAAO,eAAegE,yBAAyB/B,MAE9C;IACC,MAAMgC,WAAW,MAAM3G,yBAAyB;QAACiE,aAAanE,gBAAgB6E,OAAOO,KAAK;IAAC;IAC3F,IAAI,CAACyB,UAAU;QACb,MAAM,IAAIhH,kBAAkB;IAC9B;IAEA,MAAM+C,OAAO,MAAM3B,kBAAkB;QAAC6B,QAAQ+D,SAAS/D,MAAM;IAAA;IAC7D,IAAIF,MAAMsD,WAAW,UAAU;QAC7B,MAAM,IAAIrG,kBAAkB;IAC9B;IAEA,MAAMuF,QAAQ,MAAMzC,gBAAgBC;IACpC,MAAMiB,eAAe,MAAMK,qBAAqBtB;IAEhD,OAAO;QAACwC;QAAOvB;QAAcjB;IAAI;AACnC;AAEA,OAAO,eAAekE,wBAAwBjC,MAE7C;IACC,MAAMN,WAAWxE,oBAAoB;IACrC,MAAM2F,SAAS,MAAMtF,8BAA8B;QACjD8C,OAAO2B,OAAO3B,KAAK;QACnBiB,aAAanE,gBAAgBuE;QAC7BH,WAAWjC,aAAaL;QACxBiF,iBAAiB1H;QACjBsF,WAAW;YACTC,YAAYN,iBAAiBC;QAC/B;IACF;IAEA,OAAO;QAACyC,uBAAuBtB,OAAOsB,qBAAqB;IAAA;AAC7D;AAEA,OAAO,eAAeC,qBAAqBpC,MAAuB;IAChE,MAAMjC,OAAO,MAAM7B,gBAAgB;QAACmC,OAAO2B,OAAO3B,KAAK;IAAA;IACvD,IAAIN,MAAMsD,WAAW,YAAYtD,KAAKqC,cAAc,KAAK,MAAM;QAC7D;IACF;IAEA,MAAMV,WAAWxE,oBAAoB;IACrC,MAAMO,oBAAoB;QACxBwC,QAAQF,KAAKG,EAAE;QACfoB,aAAanE,gBAAgBuE;QAC7BH,WAAWjC,aAAaJ;QACxB4C,WAAW;YACTzB,OAAON,KAAKM,KAAK;YACjBgE,WAAWzC,kBAAkBF;YAC7B4C,gBAAgBpF;QAClB;IACF;AACF;AAQA,OAAO,eAAeqF,qBAAqBvC,MAG1C;IACC,MAAMgC,WAAW,MAAMxG,qBAAqB;QAAC8D,aAAanE,gBAAgB6E,OAAOO,KAAK;IAAC;IACvF,IAAI,CAACyB,UAAU;QACb,MAAM,IAAIhH,kBAAkB;IAC9B;IAEA,MAAMwH,eAAe,MAAMrG,aAAa;QAAC+B,IAAI8D,SAAS/D,MAAM;IAAA;IAC5D,IAAIuE,cAAcnB,WAAW,YAAYmB,aAAapC,cAAc,KAAK,MAAM;QAC7E,MAAM,IAAIpF,kBAAkB;IAC9B;IAEA,MAAMoF,iBAAiB,MAAMrD,aAAa;QAACM,UAAU2C,OAAOyC,WAAW;IAAA;IACvE,MAAM1E,OAAO,MAAMxB,mBAAmB;QAAC0B,QAAQ+D,SAAS/D,MAAM;QAAEmC;IAAc;IAC9E,IAAIrC,MAAMsD,WAAW,UAAU;QAC7B,MAAM,IAAIrG,kBAAkB;IAC9B;IAEA,MAAMc,2BAA2B;QAACmC,QAAQ+D,SAAS/D,MAAM;IAAA;IAEzD,MAAMsC,QAAQ,MAAMzC,gBAAgBC;IACpC,MAAMiB,eAAe,MAAMK,qBAAqBtB;IAEhD,OAAO;QAACwC;QAAOvB;QAAcjB;IAAI;AACnC;AAEA,OAAO,eAAe2E,eAAe1C,MAKpC;IACC,MAAMjC,OAAO,MAAM5B,aAAa;QAAC+B,IAAI8B,OAAO/B,MAAM;IAAA;IAClD,IAAI,CAACF,MAAM;QACT,MAAM,IAAIlB,kBAAkBmD,OAAO/B,MAAM;IAC3C;IAEA,MAAMkD,cAAcpD,KAAKqC,cAAc,KAAK;IAC5C,MAAMgB,KAAK,MAAMpE,eAAe;QAC9BK,UAAU2C,OAAO2C,eAAe;QAChCzB,MAAMnD,KAAKqC,cAAc,IAAK,MAAMhD;IACtC;IACA,IAAI,CAAC+D,eAAe,CAACC,IAAI;QACvB,MAAM,IAAIxE;IACZ;IAEA,MAAMwD,iBAAiB,MAAMrD,aAAa;QAACM,UAAU2C,OAAOyC,WAAW;IAAA;IACvE,MAAMG,wBAAwB5C,OAAOhB,YAAY,GAC7C,MAAMrD,6BAA6B;QAAC2D,aAAanE,gBAAgB6E,OAAOhB,YAAY;IAAC,KACrFuC;IACJ,MAAMsB,uBACJD,uBAAuB3E,WAAWF,KAAKG,EAAE,GAAG0E,sBAAsB1E,EAAE,GAAGqD;IAEzE,MAAMhF,mBAAmB;QAAC0B,QAAQF,KAAKG,EAAE;QAAEkC;IAAc;IACzD,MAAMtE,2BAA2B;QAACmC,QAAQF,KAAKG,EAAE;QAAE2E;IAAoB;AACzE;AAEA,OAAO,eAAeC,OAAO9C,MAA2C;IACtE,IAAI,CAACA,OAAOhB,YAAY,EAAE;IAC1B,MAAMnD,yBAAyB;QAACyD,aAAanE,gBAAgB6E,OAAOhB,YAAY;IAAC;AACnF;AAMA,OAAO,eAAe+D,eAAe/C,MAAwB;IAC3D,MAAMjC,OAAO,MAAM5B,aAAa;QAAC+B,IAAI8B,OAAO/B,MAAM;IAAA;IAClD,IAAI,CAACF,MAAM;QACT,MAAM,IAAIlB,kBAAkBmD,OAAO/B,MAAM;IAC3C;IAEA,OAAO;QAACF;IAAI;AACd"}
|
|
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":"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,wBAAwB,EACxBC,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;IAEvC,MAAMC,cAAc,MAAMD,WACvBE,6BAA6B,CAAC;QAACC,QAAQJ,KAAKK,EAAE;IAAA,GAC9CC,KAAK,CAAC,CAACC;QACN,MAAM,IAAIjC,+BAA+B,cAAciC;IACzD;IACF,OAAO,MAAMxB,cAAc;QACzBqB,QAAQJ,KAAKK,EAAE;QACfG,OAAOR,KAAKQ,KAAK;QACjBC,MAAMT,KAAKS,IAAI;QACfP,aAAaA,YAAYA,WAAW;QACpCQ,QAAQzD;QACR0D,WAAWvD,OAAOwD,mBAAmB;IACvC;AACF;AAEA,SAASC,sBAAsBC,YAA0B;IACvD,IAAI,CAACA,aAAaC,SAAS,EAAE,OAAO;IACpC,MAAMC,UAAU5D,OAAO6D,mCAAmC,GAAG;IAC7D,OAAOxB,KAAKC,GAAG,KAAKoB,aAAaC,SAAS,CAACG,OAAO,MAAMF;AAC1D;AAEA,eAAeG,qBAAqBnB,IAAU;IAC5C,MAAMc,eAAe5D,oBAAoB;IACzC,MAAMK,mBAAmB;QACvB6C,QAAQJ,KAAKK,EAAE;QACfe,aAAajE,gBAAgB2D;QAC7BO,WAAW1B,YAAYvC,OAAOkE,kCAAkC;IAClE;IACA,OAAOR;AACT;AAEA,SAASS,kBAAkBC,QAAgB;IACzC,OAAO,GAAGpE,OAAOqE,eAAe,CAAC,kBAAkB,EAAED,UAAU;AACjE;AAEA,SAASE,8BACPnB,KAAc,EACdoB,MAA4D;IAE5D,MAAMC,iBAAiB7E,8BAA8B8E,OAAO,CAACF,OAAO;IACpE,IAAI,CAAC3E,wBAAwB4E,gBAAgBrB,QAAQ,OAAOA;IAE5D,IAAIA,MAAMuB,IAAI,KAAK,4BACjB,OAAO,IAAIjD,kBAAkB;IAC/B,IAAI0B,MAAMuB,IAAI,KAAK,yBAAyB,OAAO,IAAInD;IACvD,IAAI4B,MAAMuB,IAAI,KAAK,4BAA4B,OAAO,IAAIlD;IAC1D,OAAO,IAAIF;AACb;AAEA,SAASqD,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,eAAe5D,cAAc8D,MAA2B;IAC7D,OAAO,MAAM7D,gBAAgB;QAC3BqC,OAAO9D,YAAYuF,KAAK,CAACD,OAAOxB,KAAK;QACrCC,MAAMuB,OAAOvB,IAAI,IAAI;IACvB;AACF;AAMA,OAAO,eAAeyB,OAAOF,MAA0C;IACrE,MAAMG,WAAW,MAAMpE,gBAAgB;QAACyC,OAAOwB,OAAOxB,KAAK;IAAA;IAC3D,IAAI2B,UAAU;QACZ,MAAMC,wBACJD,SAASE,MAAM,KAAK,YACpBF,SAASG,eAAe,KAAK,QAC7BH,SAASI,cAAc,KAAK,QAC3B,MAAMtD,eAAe;YAACK,UAAU0C,OAAO1C,QAAQ;YAAEkD,MAAML,SAASI,cAAc;QAAA;QACjF,IAAIH,uBAAuB;YACzB,MAAMK,iBAAiB,MAAM5F,qBAAqB;gBAChD2D,OAAO2B,SAAS3B,KAAK;gBACrBkC,SAASvD;gBACTwD,cAAcR,SAAS9B,EAAE;gBACzBuC,UAAUZ,OAAOY,QAAQ,IAAI;YAC/B;YACA,OAAO;gBAAC,GAAGT,QAAQ;gBAAEM;YAAc;QACrC;QACA,MAAM,IAAIjE,gBAAgBwD,OAAOxB,KAAK;IACxC;IAEA,MAAM+B,iBAAiB,MAAMvD,aAAa;QAACM,UAAU0C,OAAO1C,QAAQ;IAAA;IACpE,MAAMU,OAAO,MAAMlC,aAAa;QAC9B0C,OAAOwB,OAAOxB,KAAK;QACnB+B;QACA9B,MAAMuB,OAAOvB,IAAI,IAAI;QACrBoC,UAAU;YAACC,eAAe;QAAK;IACjC;IAEA,MAAML,iBAAiB,MAAM5F,qBAAqB;QAChD2D,OAAOR,KAAKQ,KAAK;QACjBkC,SAASvD;QACTwD,cAAc3C,KAAKK,EAAE;QACrBuC,UAAUZ,OAAOY,QAAQ,IAAI;IAC/B;IAEA,OAAO;QAAC,GAAG5C,IAAI;QAAEyC;IAAc;AACjC;AAYA,OAAO,eAAeM,qBACpBf,MAAkC;IAElC,0EAA0E;IAC1E,0EAA0E;IAC1E,mEAAmE;IACnE,IAAI;QACF,MAAMA,OAAO/B,UAAU,CAAC+C,6BAA6B,CAAC;YACpDC,OAAOjB,OAAOkB,eAAe;YAC7B1C,OAAOwB,OAAOxB,KAAK;QACrB;IACF,EAAE,OAAOD,OAAO;QACd,MAAMmB,8BAA8BnB,OAAO;IAC7C;IAEA,4EAA4E;IAC5E,8EAA8E;IAC9E,MAAM4B,WAAW,MAAMpE,gBAAgB;QAACyC,OAAOwB,OAAOxB,KAAK;IAAA;IAC3D,IAAI2B,UAAU;QACZ,MAAM,IAAI3D,gBAAgBwD,OAAOxB,KAAK;IACxC;IACA,MAAM+B,iBAAiB,MAAMvD,aAAa;QAACM,UAAU0C,OAAO1C,QAAQ;IAAA;IACpE,MAAMU,OAAO,MAAMlC,aAAa;QAC9B0C,OAAOwB,OAAOxB,KAAK;QACnB+B;QACA9B,MAAMuB,OAAOvB,IAAI,IAAI;QACrB6B,iBAAiB,IAAI7C;QACrBoD,UAAU;YAACC,eAAe;QAAI;IAChC;IAEA,wEAAwE;IACxE,8EAA8E;IAC9E,8EAA8E;IAC9E,yDAAyD;IACzD,IAAIK,aAAuD;IAC3D,IAAIC;IACJ,IAAI;QACF,MAAMC,SAAS,MAAMrB,OAAO/B,UAAU,CAACqD,gBAAgB,CAAC;YACtDL,OAAOjB,OAAOkB,eAAe;YAC7B9C,QAAQJ,KAAKK,EAAE;YACfG,OAAOR,KAAKQ,KAAK;YACjBC,MAAMT,KAAKS,IAAI;QACjB;QACA0C,aAAa;YACX9C,IAAIgD,OAAOF,UAAU,CAAC9C,EAAE;YACxBD,QAAQiD,OAAOF,UAAU,CAAC/C,MAAM;YAChCmD,aAAaF,OAAOF,UAAU,CAACI,WAAW;QAC5C;IACF,EAAE,OAAOhD,OAAO;QACd,IAAIvD,wBAAwBD,8BAA8B8E,OAAO,CAACyB,gBAAgB,EAAE/C,QAAQ;YAC1F6C,cAAc;gBAACtB,MAAMvB,MAAMuB,IAAI;gBAAE0B,SAASzB,uBAAuBxB,MAAMuB,IAAI;YAAC;QAC9E,OAAO;YACLsB,cAAc;gBACZtB,MAAM;gBACN0B,SAAS;YACX;QACF;IACF;IAEA,uEAAuE;IACvE,yEAAyE;IACzE,MAAMP,QAAQ,MAAMlD,gBAAgBC,MAAMgC,OAAO/B,UAAU;IAC3D,MAAMa,eAAe,MAAMK,qBAAqBnB;IAEhD,IAAIoD,aAAa;QACf,OAAO;YAACH;YAAOnC;YAAcd;YAAMmD;YAAYC;QAAW;IAC5D;IACA,OAAO;QAACH;QAAOnC;QAAcd;QAAMmD;IAAU;AAC/C;AAMA,OAAO,eAAetF,WAAWmE,MAAwB;IACvD,MAAMG,WAAW,MAAMpE,gBAAgB;QAACyC,OAAOwB,OAAOxB,KAAK;IAAA;IAC3D,IAAI2B,UAAU;QACZ,MAAM,IAAI3D,gBAAgBwD,OAAOxB,KAAK;IACxC;IAEA,MAAM+B,iBAAiB,MAAMvD,aAAa;QAACM,UAAU0C,OAAO1C,QAAQ;IAAA;IACpE,MAAMU,OAAO,MAAMlC,aAAa;QAC9B0C,OAAOwB,OAAOxB,KAAK;QACnB+B;QACA9B,MAAMuB,OAAOvB,IAAI,IAAI;QACrB6B,iBAAiBN,OAAOyB,QAAQ,GAAG,IAAIhE,SAAS;IAClD;IAEA,OAAOO;AACT;AAcA,OAAO,eAAe0D,MAAM1B,MAAmB;IAC7C,MAAMhC,OAAO,MAAMjC,gBAAgB;QAACyC,OAAOwB,OAAOxB,KAAK;IAAA;IAEvD,IAAI,CAACR,MAAM;QACT,MAAMf,eAAe;YAACK,UAAU0C,OAAO1C,QAAQ;YAAEkD,MAAM,MAAMnD;QAAc;QAC3E,MAAM,IAAIZ;IACZ;IAEA,MAAMkF,cAAc3D,KAAKuC,cAAc,KAAK;IAC5C,MAAMqB,KAAK,MAAM3E,eAAe;QAC9BK,UAAU0C,OAAO1C,QAAQ;QACzBkD,MAAMxC,KAAKuC,cAAc,IAAK,MAAMlD;IACtC;IACA,IAAI,CAACsE,eAAe,CAACC,MAAM5D,KAAKqC,MAAM,KAAK,UAAU;QACnD,MAAM,IAAI5D;IACZ;IAEA,IAAIuB,KAAKsC,eAAe,KAAK,MAAM;QACjC,MAAM,IAAI/D;IACZ;IAEA,MAAM0E,QAAQ,MAAMlD,gBAAgBC,MAAMgC,OAAO/B,UAAU;IAC3D,MAAMa,eAAe,MAAMK,qBAAqBnB;IAEhD,OAAO;QAACiD;QAAOnC;QAAcd;IAAI;AACnC;AAoBA,OAAO,eAAe6D,qBACpB7B,MAAkC;IAElC,MAAMhC,OAAOgC,OAAO5B,MAAM,GACtB,MAAMpC,aAAa;QAACqC,IAAI2B,OAAO5B,MAAM;IAAA,KACrC4B,OAAOxB,KAAK,GACV,MAAMzC,gBAAgB;QAACyC,OAAO9D,YAAYuF,KAAK,CAACD,OAAOxB,KAAK;IAAC,KAC7DsD;IAEN,IAAI,CAAC9D,MAAM;QACT,MAAM,IAAIlB,kBAAkBkD,OAAO5B,MAAM,IAAI4B,OAAOxB,KAAK,IAAI;IAC/D;IACA,IAAIR,KAAKsC,eAAe,KAAK,MAAM;QACjC,MAAM,IAAI/D;IACZ;IACA,IAAIyB,KAAKqC,MAAM,KAAK,UAAU;QAC5B,MAAM,IAAI5D;IACZ;IAEA,MAAMwE,QAAQ,MAAMlD,gBAAgBC,MAAMgC,OAAO/B,UAAU;IAC3D,MAAMa,eAAe,MAAMK,qBAAqBnB;IAEhD,OAAO;QAACiD;QAAOnC;QAAcd;IAAI;AACnC;AASA,OAAO,eAAe+D,mBAAmB/B,MAGxC;IACC,MAAMgC,qBAAqB7G,gBAAgB6E,OAAOlB,YAAY;IAC9D,MAAMmD,UAAU,MAAMxG,uBAAuB;QAAC2D,aAAa4C;IAAkB;IAC7E,IAAI,CAACC,SAAS;QACZpE,qBAAqB;QACrB,MAAM,IAAIhB,kBAAkB;IAC9B;IAEA,MAAMmB,OAAO,MAAMhC,aAAa;QAACqC,IAAI4D,QAAQ7D,MAAM;IAAA;IACnD,IAAIJ,MAAMqC,WAAW,UAAU;QAC7B,MAAM3E,yBAAyB;YAAC0D,aAAa4C;QAAkB;QAC/DnE,qBAAqB;QACrB,MAAM,IAAIhB,kBAAkB;IAC9B;IAEA,6EAA6E;IAC7E,8EAA8E;IAC9E,IAAIoF,QAAQlD,SAAS,EAAE;QACrB,IAAIF,sBAAsBoD,UAAU;YAClC,MAAMhB,QAAQ,MAAMlD,gBAAgBC,MAAMgC,OAAO/B,UAAU;YAC3DJ,qBAAqB;YACrB,OAAO;gBAACoD;gBAAOnC,cAAcgD;gBAAW9D;YAAI;QAC9C;QACA,MAAMrC,2BAA2B;YAACyC,QAAQJ,KAAKK,EAAE;QAAA;QACjDR,qBAAqB;QACrB,MAAM,IAAIhB,kBAAkB;IAC9B;IAEA,2EAA2E;IAC3E,oEAAoE;IACpE,MAAMqF,mBAAmBhH,oBAAoB;IAC7C,MAAMiH,UAAU,MAAMvG,mBAAmB;QACvCyC,IAAI4D,QAAQ5D,EAAE;QACd2D;QACAI,iBAAiBjH,gBAAgB+G;QACjC7C,WAAW1B,YAAYvC,OAAOkE,kCAAkC;IAClE;IACA,IAAI,CAAC6C,SAAS;QACZ,MAAME,SAAS,MAAM5G,uBAAuB;YAAC2D,aAAa4C;QAAkB;QAC5E,IAAI,CAACK,UAAU,CAACxD,sBAAsBwD,SAAS;YAC7CxE,qBAAqB;YACrB,MAAM,IAAIhB,kBAAkB;QAC9B;QACA,MAAMoE,QAAQ,MAAMlD,gBAAgBC,MAAMgC,OAAO/B,UAAU;QAC3DJ,qBAAqB;QACrB,OAAO;YAACoD;YAAOnC,cAAcgD;YAAW9D;QAAI;IAC9C;IAEA,MAAMiD,QAAQ,MAAMlD,gBAAgBC,MAAMgC,OAAO/B,UAAU;IAC3DJ,qBAAqB;IACrB,OAAO;QAACoD;QAAOnC,cAAcoD;QAAkBlE;IAAI;AACrD;AAYA,OAAO,eAAesE,yBAAyBtC,MAK9C;IACC,MAAMhC,OAAO,MAAMjC,gBAAgB;QAACyC,OAAO9D,YAAYuF,KAAK,CAACD,OAAOxB,KAAK;IAAC;IAC1E,IAAI,CAACR,MAAM,MAAM,IAAInB,kBAAkB;IACvC,MAAMlC,sBAAsB;QAAC0D,IAAI2B,OAAOuC,WAAW;QAAEzC,MAAME,OAAOF,IAAI;QAAEa,cAAc3C,KAAKK,EAAE;IAAA;IAC7F,MAAMzD,2BAA2B;QAC/ByD,IAAI2B,OAAOuC,WAAW;QACtB7B,SAASvD;QACTwD,cAAc3C,KAAKK,EAAE;IACvB;IAEA,MAAMmE,eAAe,MAAMvG,kBAAkB;QAACmC,QAAQJ,KAAKK,EAAE;IAAA;IAC7D,IAAImE,cAAcnC,WAAW,UAAU;QACrC,MAAM,IAAIxD,kBAAkB;IAC9B;IAEA,MAAMoE,QAAQ,MAAMlD,gBAAgByE,cAAcxC,OAAO/B,UAAU;IACnE,MAAMa,eAAe,MAAMK,qBAAqBqD;IAEhD,OAAO;QAACvB;QAAOnC;QAAcd,MAAMwE;IAAY;AACjD;AAEA,OAAO,eAAeC,wBAAwBzC,MAI7C;IACC,MAAMhC,OAAO,MAAMjC,gBAAgB;QAACyC,OAAO9D,YAAYuF,KAAK,CAACD,OAAOxB,KAAK;IAAC;IAC1E,IAAI,CAACR,QAAQA,KAAKsC,eAAe,IAAItC,KAAKqC,MAAM,KAAK,UAAU;QAC7D,OAAO;YAACqC,uBAAuB,IAAIjF;QAAM;IAC3C;IACA,MAAM4D,SAAS,MAAMvG,qBAAqB;QACxCuD,IAAI2B,OAAOuC,WAAW;QACtB5B,cAAc3C,KAAKK,EAAE;QACrBuC,UAAUZ,OAAOY,QAAQ;IAC3B;IAEA,OAAO;QAAC8B,uBAAuBrB,OAAOqB,qBAAqB;IAAA;AAC7D;AAEA,OAAO,eAAeC,qBAAqB3C,MAAuB;IAChE,MAAMhC,OAAO,MAAMjC,gBAAgB;QAACyC,OAAOwB,OAAOxB,KAAK;IAAA;IACvD,IAAIR,MAAMqC,WAAW,YAAYrC,KAAKuC,cAAc,KAAK,MAAM;QAC7D;IACF;IAEA,MAAMf,WAAWtE,oBAAoB;IACrC,MAAMI,oBAAoB;QACxB8C,QAAQJ,KAAKK,EAAE;QACfe,aAAajE,gBAAgBqE;QAC7BH,WAAW9B,aAAaL;QACxB0F,WAAW;YACTpE,OAAOR,KAAKQ,KAAK;YACjBqE,WAAWtD,kBAAkBC;YAC7BsD,gBAAgB5F;QAClB;IACF;AACF;AAQA,OAAO,eAAe6F,qBAAqB/C,MAI1C;IACC,MAAMgD,WAAW,MAAM3H,qBAAqB;QAAC+D,aAAajE,gBAAgB6E,OAAOiB,KAAK;IAAC;IACvF,IAAI,CAAC+B,UAAU;QACb,MAAM,IAAInG,kBAAkB;IAC9B;IAEA,MAAMoG,eAAe,MAAMjH,aAAa;QAACqC,IAAI2E,SAAS5E,MAAM;IAAA;IAC5D,IAAI6E,cAAc5C,WAAW,YAAY4C,aAAa1C,cAAc,KAAK,MAAM;QAC7E,MAAM,IAAI1D,kBAAkB;IAC9B;IAEA,MAAM0D,iBAAiB,MAAMvD,aAAa;QAACM,UAAU0C,OAAOkD,WAAW;IAAA;IACvE,MAAMlF,OAAO,MAAM5B,mBAAmB;QAACgC,QAAQ4E,SAAS5E,MAAM;QAAEmC;IAAc;IAC9E,IAAIvC,MAAMqC,WAAW,UAAU;QAC7B,MAAM,IAAIxD,kBAAkB;IAC9B;IAEA,MAAMlB,2BAA2B;QAACyC,QAAQ4E,SAAS5E,MAAM;IAAA;IAEzD,MAAM6C,QAAQ,MAAMlD,gBAAgBC,MAAMgC,OAAO/B,UAAU;IAC3D,MAAMa,eAAe,MAAMK,qBAAqBnB;IAEhD,OAAO;QAACiD;QAAOnC;QAAcd;IAAI;AACnC;AAEA,OAAO,eAAemF,eAAenD,MAKpC;IACC,MAAMhC,OAAO,MAAMhC,aAAa;QAACqC,IAAI2B,OAAO5B,MAAM;IAAA;IAClD,IAAI,CAACJ,MAAM;QACT,MAAM,IAAIlB,kBAAkBkD,OAAO5B,MAAM;IAC3C;IAEA,MAAMuD,cAAc3D,KAAKuC,cAAc,KAAK;IAC5C,MAAMqB,KAAK,MAAM3E,eAAe;QAC9BK,UAAU0C,OAAOoD,eAAe;QAChC5C,MAAMxC,KAAKuC,cAAc,IAAK,MAAMlD;IACtC;IACA,IAAI,CAACsE,eAAe,CAACC,IAAI;QACvB,MAAM,IAAInF;IACZ;IAEA,MAAM8D,iBAAiB,MAAMvD,aAAa;QAACM,UAAU0C,OAAOkD,WAAW;IAAA;IACvE,MAAMG,wBAAwBrD,OAAOlB,YAAY,GAC7C,MAAMtD,6BAA6B;QAAC4D,aAAajE,gBAAgB6E,OAAOlB,YAAY;IAAC,KACrFgD;IACJ,MAAMwB,uBACJD,uBAAuBjF,WAAWJ,KAAKK,EAAE,GAAGgF,sBAAsBhF,EAAE,GAAGyD;IAEzE,MAAM1F,mBAAmB;QAACgC,QAAQJ,KAAKK,EAAE;QAAEkC;IAAc;IACzD,MAAM5E,2BAA2B;QAACyC,QAAQJ,KAAKK,EAAE;QAAEiF;IAAoB;AACzE;AAEA,OAAO,eAAeC,OAAOvD,MAA2C;IACtE,IAAI,CAACA,OAAOlB,YAAY,EAAE;IAC1B,MAAMpD,yBAAyB;QAAC0D,aAAajE,gBAAgB6E,OAAOlB,YAAY;IAAC;AACnF;AAMA,OAAO,eAAe0E,eAAexD,MAAwB;IAC3D,MAAMhC,OAAO,MAAMhC,aAAa;QAACqC,IAAI2B,OAAO5B,MAAM;IAAA;IAClD,IAAI,CAACJ,MAAM;QACT,MAAM,IAAIlB,kBAAkBkD,OAAO5B,MAAM;IAC3C;IAEA,OAAO;QAACJ;IAAI;AACd"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { User } from './entities/user.js';
|
|
2
|
+
export type EmailOwner = Pick<User, 'id' | 'email' | 'status'>;
|
|
3
|
+
export interface FindUserByEmailParams {
|
|
4
|
+
email: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Read-only lookup of the current owner of a normalized email. Performs no
|
|
8
|
+
* user, session, or verification writes; returns undefined when no user owns
|
|
9
|
+
* the address.
|
|
10
|
+
*/
|
|
11
|
+
export declare function findUserByEmail(params: FindUserByEmailParams): Promise<EmailOwner | undefined>;
|
|
12
|
+
//# sourceMappingURL=email-owner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-owner.d.ts","sourceRoot":"","sources":["../../src/core/email-owner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,oBAAoB,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,GAAG,QAAQ,CAAC,CAAC;AAE/D,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAKjC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { emailSchema } from '@shipfox/api-auth-dto';
|
|
2
|
+
import { findUserByEmail as findUserByEmailInDb } from '#db/users.js';
|
|
3
|
+
/**
|
|
4
|
+
* Read-only lookup of the current owner of a normalized email. Performs no
|
|
5
|
+
* user, session, or verification writes; returns undefined when no user owns
|
|
6
|
+
* the address.
|
|
7
|
+
*/ export async function findUserByEmail(params) {
|
|
8
|
+
const email = emailSchema.parse(params.email);
|
|
9
|
+
const user = await findUserByEmailInDb({
|
|
10
|
+
email
|
|
11
|
+
});
|
|
12
|
+
if (!user) return undefined;
|
|
13
|
+
return {
|
|
14
|
+
id: user.id,
|
|
15
|
+
email: user.email,
|
|
16
|
+
status: user.status
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=email-owner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/email-owner.ts"],"sourcesContent":["import {emailSchema} from '@shipfox/api-auth-dto';\nimport {findUserByEmail as findUserByEmailInDb} from '#db/users.js';\nimport type {User} from './entities/user.js';\n\nexport type EmailOwner = Pick<User, 'id' | 'email' | 'status'>;\n\nexport interface FindUserByEmailParams {\n email: string;\n}\n\n/**\n * Read-only lookup of the current owner of a normalized email. Performs no\n * user, session, or verification writes; returns undefined when no user owns\n * the address.\n */\nexport async function findUserByEmail(\n params: FindUserByEmailParams,\n): Promise<EmailOwner | undefined> {\n const email = emailSchema.parse(params.email);\n const user = await findUserByEmailInDb({email});\n if (!user) return undefined;\n return {id: user.id, email: user.email, status: user.status};\n}\n"],"names":["emailSchema","findUserByEmail","findUserByEmailInDb","params","email","parse","user","undefined","id","status"],"mappings":"AAAA,SAAQA,WAAW,QAAO,wBAAwB;AAClD,SAAQC,mBAAmBC,mBAAmB,QAAO,eAAe;AASpE;;;;CAIC,GACD,OAAO,eAAeD,gBACpBE,MAA6B;IAE7B,MAAMC,QAAQJ,YAAYK,KAAK,CAACF,OAAOC,KAAK;IAC5C,MAAME,OAAO,MAAMJ,oBAAoB;QAACE;IAAK;IAC7C,IAAI,CAACE,MAAM,OAAOC;IAClB,OAAO;QAACC,IAAIF,KAAKE,EAAE;QAAEJ,OAAOE,KAAKF,KAAK;QAAEK,QAAQH,KAAKG,MAAM;IAAA;AAC7D"}
|
|
@@ -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;
|
|
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,4 +1,5 @@
|
|
|
1
1
|
import { JOB_LEASE_TOKEN_AUDIENCE, jobLeaseTokenClaimsSchema } from '@shipfox/api-auth-dto';
|
|
2
|
+
import { jobLeaseTokenKey } from '@shipfox/node-auth-root-key';
|
|
2
3
|
import { signHs256, verifyHs256 } from '@shipfox/node-jwt';
|
|
3
4
|
import { config } from '#config.js';
|
|
4
5
|
import { recordTokenIssued, recordTokenVerified } from '#metrics/index.js';
|
|
@@ -32,7 +33,7 @@ export async function issueJobLeaseToken(claims) {
|
|
|
32
33
|
currentStepAttempt: claims.currentStepAttempt
|
|
33
34
|
} : {}
|
|
34
35
|
},
|
|
35
|
-
secret:
|
|
36
|
+
secret: jobLeaseTokenKey(),
|
|
36
37
|
expiresIn: config.AUTH_JOB_LEASE_TOKEN_EXPIRES_IN,
|
|
37
38
|
audience: JOB_LEASE_TOKEN_AUDIENCE
|
|
38
39
|
});
|
|
@@ -43,7 +44,7 @@ export async function issueJobLeaseToken(claims) {
|
|
|
43
44
|
try {
|
|
44
45
|
const claims = await verifyHs256({
|
|
45
46
|
token,
|
|
46
|
-
secret:
|
|
47
|
+
secret: jobLeaseTokenKey(),
|
|
47
48
|
schema: jobLeaseTokenClaimsSchema,
|
|
48
49
|
audience: JOB_LEASE_TOKEN_AUDIENCE
|
|
49
50
|
});
|
|
@@ -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 {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:
|
|
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"}
|
package/dist/core/jwt.d.ts
CHANGED
|
@@ -25,12 +25,12 @@ export interface SignUserTokenParams {
|
|
|
25
25
|
email: string;
|
|
26
26
|
name?: string | null | undefined;
|
|
27
27
|
memberships: TokenMembership[];
|
|
28
|
-
secret: string;
|
|
28
|
+
secret: string | Uint8Array;
|
|
29
29
|
expiresIn: string;
|
|
30
30
|
}
|
|
31
31
|
export interface VerifyUserTokenParams {
|
|
32
32
|
token: string;
|
|
33
|
-
secret: string;
|
|
33
|
+
secret: string | Uint8Array;
|
|
34
34
|
}
|
|
35
35
|
export declare function signUserToken(params: SignUserTokenParams): Promise<string>;
|
|
36
36
|
export declare function verifyUserToken(params: VerifyUserTokenParams): Promise<UserTokenClaims>;
|
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;;;;;;;;;;;;iBAOhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,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,CAAC;
|
|
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;;;;;;;;;;;;iBAOhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,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,CAahF;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAa7F"}
|