@puzzle-ui/auth 0.0.0-stage → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +54 -2
  2. package/dist/LICENSE +21 -0
  3. package/dist/auth.lifecycle.manager.cjs +835 -0
  4. package/dist/auth.lifecycle.manager.cjs.map +1 -0
  5. package/dist/auth.lifecycle.manager.d.cts +115 -0
  6. package/dist/auth.lifecycle.manager.d.ts +115 -0
  7. package/dist/auth.lifecycle.manager.js +835 -0
  8. package/dist/auth.lifecycle.manager.js.map +1 -0
  9. package/dist/auth.manager.cjs +454 -0
  10. package/dist/auth.manager.cjs.map +1 -0
  11. package/dist/auth.manager.d.cts +54 -0
  12. package/dist/auth.manager.d.ts +54 -0
  13. package/dist/auth.manager.js +454 -0
  14. package/dist/auth.manager.js.map +1 -0
  15. package/dist/auth.mfa.manager.cjs +1488 -0
  16. package/dist/auth.mfa.manager.cjs.map +1 -0
  17. package/dist/auth.mfa.manager.d.cts +100 -0
  18. package/dist/auth.mfa.manager.d.ts +100 -0
  19. package/dist/auth.mfa.manager.js +1488 -0
  20. package/dist/auth.mfa.manager.js.map +1 -0
  21. package/dist/auth.rate-limit.cjs +78 -0
  22. package/dist/auth.rate-limit.cjs.map +1 -0
  23. package/dist/auth.rate-limit.d.cts +24 -0
  24. package/dist/auth.rate-limit.d.ts +24 -0
  25. package/dist/auth.rate-limit.js +78 -0
  26. package/dist/auth.rate-limit.js.map +1 -0
  27. package/dist/auth.step-up.manager.cjs +269 -0
  28. package/dist/auth.step-up.manager.cjs.map +1 -0
  29. package/dist/auth.step-up.manager.d.cts +74 -0
  30. package/dist/auth.step-up.manager.d.ts +74 -0
  31. package/dist/auth.step-up.manager.js +268 -0
  32. package/dist/auth.step-up.manager.js.map +1 -0
  33. package/dist/auth.storage.cjs +164 -0
  34. package/dist/auth.storage.cjs.map +1 -0
  35. package/dist/auth.storage.d.cts +29 -0
  36. package/dist/auth.storage.d.ts +29 -0
  37. package/dist/auth.storage.js +148 -0
  38. package/dist/auth.storage.js.map +1 -0
  39. package/dist/errors/auth.errors.cjs +101 -0
  40. package/dist/errors/auth.errors.cjs.map +1 -0
  41. package/dist/errors/auth.errors.d.cts +62 -0
  42. package/dist/errors/auth.errors.d.ts +62 -0
  43. package/dist/errors/auth.errors.js +90 -0
  44. package/dist/errors/auth.errors.js.map +1 -0
  45. package/dist/index.cjs +54 -0
  46. package/dist/index.d.cts +21 -0
  47. package/dist/index.d.ts +21 -0
  48. package/dist/index.js +13 -0
  49. package/dist/providers/mfa-provider.types.d.cts +66 -0
  50. package/dist/providers/mfa-provider.types.d.ts +66 -0
  51. package/dist/providers/provider.types.d.cts +34 -0
  52. package/dist/providers/provider.types.d.ts +34 -0
  53. package/dist/providers/simplewebauthn-provider.cjs +185 -0
  54. package/dist/providers/simplewebauthn-provider.cjs.map +1 -0
  55. package/dist/providers/simplewebauthn-provider.d.cts +35 -0
  56. package/dist/providers/simplewebauthn-provider.d.ts +35 -0
  57. package/dist/providers/simplewebauthn-provider.js +185 -0
  58. package/dist/providers/simplewebauthn-provider.js.map +1 -0
  59. package/dist/providers/webcrypto-secret-protector.cjs +118 -0
  60. package/dist/providers/webcrypto-secret-protector.cjs.map +1 -0
  61. package/dist/providers/webcrypto-secret-protector.d.cts +14 -0
  62. package/dist/providers/webcrypto-secret-protector.d.ts +14 -0
  63. package/dist/providers/webcrypto-secret-protector.js +118 -0
  64. package/dist/providers/webcrypto-secret-protector.js.map +1 -0
  65. package/dist/types/auth-lifecycle.types.d.cts +53 -0
  66. package/dist/types/auth-lifecycle.types.d.ts +53 -0
  67. package/dist/types/auth-manager.types.d.cts +106 -0
  68. package/dist/types/auth-manager.types.d.ts +106 -0
  69. package/dist/types/credential.types.d.cts +9 -0
  70. package/dist/types/credential.types.d.ts +9 -0
  71. package/dist/types/mfa.types.d.cts +244 -0
  72. package/dist/types/mfa.types.d.ts +244 -0
  73. package/dist/types/state.types.d.cts +33 -0
  74. package/dist/types/state.types.d.ts +33 -0
  75. package/dist/types/user.types.d.cts +9 -0
  76. package/dist/types/user.types.d.ts +9 -0
  77. package/dist/utils/auth-crypto.utils.cjs +38 -0
  78. package/dist/utils/auth-crypto.utils.cjs.map +1 -0
  79. package/dist/utils/auth-crypto.utils.d.cts +4 -0
  80. package/dist/utils/auth-crypto.utils.d.ts +4 -0
  81. package/dist/utils/auth-crypto.utils.js +35 -0
  82. package/dist/utils/auth-crypto.utils.js.map +1 -0
  83. package/dist/utils/email.utils.cjs +20 -0
  84. package/dist/utils/email.utils.cjs.map +1 -0
  85. package/dist/utils/email.utils.d.cts +2 -0
  86. package/dist/utils/email.utils.d.ts +2 -0
  87. package/dist/utils/email.utils.js +19 -0
  88. package/dist/utils/email.utils.js.map +1 -0
  89. package/dist/utils/totp.utils.cjs +145 -0
  90. package/dist/utils/totp.utils.cjs.map +1 -0
  91. package/dist/utils/totp.utils.d.cts +26 -0
  92. package/dist/utils/totp.utils.d.ts +26 -0
  93. package/dist/utils/totp.utils.js +140 -0
  94. package/dist/utils/totp.utils.js.map +1 -0
  95. package/package.json +50 -4
@@ -0,0 +1,269 @@
1
+ const require_auth_errors = require("./errors/auth.errors.cjs");
2
+ const require_auth_crypto_utils = require("./utils/auth-crypto.utils.cjs");
3
+ let _puzzle_ui_storage = require("@puzzle-ui/storage");
4
+ // src/auth.step-up.manager.ts
5
+ const AUTH_MFA_STEP_UP_COLLECTION = "auth_mfa_step_up_challenges";
6
+ const stepUpCollection = {
7
+ name: AUTH_MFA_STEP_UP_COLLECTION,
8
+ indexes: [
9
+ {
10
+ name: "step_up_token_hash_unique",
11
+ fields: ["tokenHash"],
12
+ unique: true
13
+ },
14
+ {
15
+ name: "step_up_session_expiration",
16
+ fields: ["sessionId", "expiresAt"]
17
+ },
18
+ {
19
+ name: "step_up_user_expiration",
20
+ fields: ["userId", "expiresAt"]
21
+ }
22
+ ]
23
+ };
24
+ const toPublicSession = (session) => {
25
+ if (session.kind !== "authenticated" || !session.userId) throw new require_auth_errors.AuthSessionError("A step-up session must be authenticated.");
26
+ return {
27
+ id: session.id,
28
+ userId: session.userId,
29
+ kind: "authenticated",
30
+ createdAt: session.createdAt,
31
+ lastSeenAt: session.lastSeenAt,
32
+ expiresAt: session.expiresAt,
33
+ revokedAt: session.revokedAt
34
+ };
35
+ };
36
+ var StorageAuthStepUpManager = class {
37
+ storage;
38
+ sessions;
39
+ mfa;
40
+ collectionName;
41
+ clock;
42
+ initialized = false;
43
+ initializationPromise = null;
44
+ constructor(dependencies, options = {}) {
45
+ this.storage = dependencies.storage;
46
+ this.sessions = dependencies.sessions;
47
+ this.mfa = dependencies.mfa;
48
+ this.collectionName = options.collectionName ?? "auth_mfa_step_up_challenges";
49
+ this.clock = options.clock ?? (() => /* @__PURE__ */ new Date());
50
+ if (!this.collectionName || this.collectionName.trim().length === 0) throw new require_auth_errors.AuthConfigurationError("A valid MFA step-up collection name is required.");
51
+ }
52
+ async initialize() {
53
+ if (!this.initializationPromise) {
54
+ const initialization = (async () => {
55
+ if (!this.storage.capabilities.uniqueConstraints || !this.storage.capabilities.conditionalWrites || !this.storage.capabilities.atomicBatch) throw new require_auth_errors.AuthConfigurationError("MFA step-up requires unique constraints, conditional writes and atomic batches.");
56
+ await this.storage.registerCollection({
57
+ ...stepUpCollection,
58
+ name: this.collectionName
59
+ });
60
+ this.initialized = true;
61
+ })();
62
+ this.initializationPromise = initialization.catch((error) => {
63
+ this.initializationPromise = null;
64
+ this.initialized = false;
65
+ throw error;
66
+ });
67
+ }
68
+ return this.initializationPromise;
69
+ }
70
+ assertInitialized() {
71
+ if (!this.initialized) throw new require_auth_errors.AuthConfigurationError("The MFA step-up manager must be initialized before use.");
72
+ }
73
+ now() {
74
+ const value = this.clock();
75
+ if (!(value instanceof Date) || !Number.isFinite(value.getTime())) throw new require_auth_errors.AuthConfigurationError("The configured step-up clock returned an invalid date.");
76
+ return value;
77
+ }
78
+ async getAuthenticatedSession(sessionToken) {
79
+ if (typeof sessionToken !== "string" || sessionToken.length === 0) throw new require_auth_errors.AuthSessionError("An authenticated session is required.");
80
+ let session;
81
+ try {
82
+ session = await this.sessions.validate(sessionToken);
83
+ } catch (error) {
84
+ throw new require_auth_errors.AuthSessionError("Could not validate the step-up session.", { cause: error });
85
+ }
86
+ if (!session || session.kind !== "authenticated" || !session.userId || session.revokedAt !== null || session.expiresAt.getTime() <= this.now().getTime()) throw new require_auth_errors.AuthSessionError("An authenticated session is required.");
87
+ return session;
88
+ }
89
+ async findChallenge(challengeToken) {
90
+ if (typeof challengeToken !== "string" || !/^[a-f0-9]{64}$/i.test(challengeToken)) return null;
91
+ const tokenHash = await require_auth_crypto_utils.hashAuthToken(challengeToken);
92
+ return this.storage.findOne(this.collectionName, { where: { tokenHash: {
93
+ operator: "eq",
94
+ value: tokenHash
95
+ } } });
96
+ }
97
+ async getPendingContext(challengeToken, sessionToken) {
98
+ const challenge = await this.findChallenge(challengeToken);
99
+ if (!challenge || challenge.consumedAt !== null || challenge.expiresAt.getTime() <= this.now().getTime()) throw new require_auth_errors.AuthMfaChallengeError();
100
+ const session = await this.getAuthenticatedSession(sessionToken);
101
+ if (session.id !== challenge.sessionId || session.userId !== challenge.userId) throw new require_auth_errors.AuthMfaChallengeError();
102
+ return {
103
+ challenge,
104
+ session
105
+ };
106
+ }
107
+ async beginStepUp(input) {
108
+ this.assertInitialized();
109
+ const session = await this.getAuthenticatedSession(input.sessionToken);
110
+ const challenge = await this.mfa.createSignInChallenge({
111
+ userId: session.userId,
112
+ ipAddress: input.ipAddress
113
+ });
114
+ if (!challenge) throw new require_auth_errors.AuthMfaFactorError("An active MFA factor is required for step-up authentication.");
115
+ const now = this.now();
116
+ const record = {
117
+ id: require_auth_crypto_utils.generateAuthId("mfa-step-up"),
118
+ challengeId: challenge.challengeId,
119
+ tokenHash: await require_auth_crypto_utils.hashAuthToken(challenge.challengeToken),
120
+ userId: session.userId,
121
+ sessionId: session.id,
122
+ expiresAt: challenge.expiresAt,
123
+ consumedAt: null,
124
+ createdAt: now,
125
+ updatedAt: now,
126
+ revision: 1
127
+ };
128
+ try {
129
+ await this.storage.insert(this.collectionName, record);
130
+ } catch (error) {
131
+ throw new require_auth_errors.AuthProviderError("Could not persist the step-up challenge.", { cause: error });
132
+ }
133
+ return challenge;
134
+ }
135
+ async getPendingAuthState(challengeToken, sessionToken) {
136
+ this.assertInitialized();
137
+ const challenge = await this.findChallenge(challengeToken);
138
+ if (!challenge || challenge.consumedAt !== null || challenge.expiresAt.getTime() <= this.now().getTime()) return null;
139
+ let session;
140
+ try {
141
+ session = await this.getAuthenticatedSession(sessionToken);
142
+ } catch {
143
+ return null;
144
+ }
145
+ if (session.id !== challenge.sessionId || session.userId !== challenge.userId) return null;
146
+ const mfaState = await this.mfa.getPendingAuthState(challengeToken);
147
+ if (!mfaState || mfaState.status !== "mfa-pending" || mfaState.challengeId !== challenge.challengeId) return null;
148
+ return {
149
+ status: "step-up-pending",
150
+ challengeId: challenge.challengeId,
151
+ expiresAt: challenge.expiresAt
152
+ };
153
+ }
154
+ async beginPasskey(input) {
155
+ this.assertInitialized();
156
+ await this.getPendingContext(input.challengeToken, input.sessionToken);
157
+ return this.mfa.beginPasskeyMfa(input.challengeToken, input.ipAddress);
158
+ }
159
+ async consumeChallenge(challenge) {
160
+ const now = this.now();
161
+ try {
162
+ if (!await this.storage.update(this.collectionName, challenge.id, {
163
+ consumedAt: now,
164
+ updatedAt: now
165
+ }, { expectedRevision: challenge.revision })) throw new _puzzle_ui_storage.StorageConflictError("The step-up challenge was already consumed.");
166
+ } catch (error) {
167
+ throw new require_auth_errors.AuthMfaChallengeError({ cause: error });
168
+ }
169
+ }
170
+ async revokeTemporarySession(sessionId) {
171
+ try {
172
+ await this.sessions.revoke(sessionId);
173
+ } catch (error) {
174
+ throw new require_auth_errors.AuthSessionError("Could not revoke the temporary MFA verification session.", { cause: error });
175
+ }
176
+ }
177
+ async completeStepUp(input, verify) {
178
+ this.assertInitialized();
179
+ const context = await this.getPendingContext(input.challengeToken, input.sessionToken);
180
+ const verified = await verify(input.challengeToken);
181
+ if (verified.user.id !== context.challenge.userId) {
182
+ await this.revokeTemporarySession(verified.session.id);
183
+ throw new require_auth_errors.AuthMfaChallengeError();
184
+ }
185
+ try {
186
+ await this.consumeChallenge(context.challenge);
187
+ } catch (error) {
188
+ try {
189
+ await this.revokeTemporarySession(verified.session.id);
190
+ } catch (revokeError) {
191
+ throw new require_auth_errors.AuthSessionError("The step-up challenge failed and its temporary session could not be revoked.", { cause: revokeError });
192
+ }
193
+ throw error;
194
+ }
195
+ const now = this.now();
196
+ let replacement;
197
+ try {
198
+ replacement = await this.sessions.rotate({
199
+ sessionId: context.session.id,
200
+ metadata: {
201
+ ...context.session.metadata ?? {},
202
+ authenticationMethod: input.method,
203
+ mfaVerifiedAt: now.toISOString(),
204
+ stepUpVerifiedAt: now.toISOString(),
205
+ stepUpOfSessionId: context.session.id
206
+ }
207
+ });
208
+ } catch (error) {
209
+ try {
210
+ await this.revokeTemporarySession(verified.session.id);
211
+ } catch (revokeError) {
212
+ throw new require_auth_errors.AuthSessionError("Session rotation failed and the temporary MFA session could not be revoked.", { cause: revokeError });
213
+ }
214
+ throw new require_auth_errors.AuthSessionError("Could not atomically rotate the session after step-up verification.", { cause: error });
215
+ }
216
+ try {
217
+ await this.revokeTemporarySession(verified.session.id);
218
+ } catch (error) {
219
+ try {
220
+ await this.sessions.revoke(replacement.session.id);
221
+ } catch (revokeError) {
222
+ throw new require_auth_errors.AuthSessionError("Could not revoke the replacement session after temporary-session cleanup failed.", { cause: revokeError });
223
+ }
224
+ throw error;
225
+ }
226
+ return {
227
+ user: verified.user,
228
+ session: toPublicSession(replacement.session),
229
+ token: replacement.token
230
+ };
231
+ }
232
+ async verifyTotp(input) {
233
+ return this.completeStepUp({
234
+ ...input,
235
+ method: "totp"
236
+ }, (challengeToken) => this.mfa.verifySignInTotp({
237
+ challengeToken,
238
+ code: input.code,
239
+ ipAddress: input.ipAddress
240
+ }));
241
+ }
242
+ async verifyRecoveryCode(input) {
243
+ return this.completeStepUp({
244
+ ...input,
245
+ method: "recovery-code"
246
+ }, (challengeToken) => this.mfa.verifySignInRecoveryCode({
247
+ challengeToken,
248
+ code: input.code,
249
+ ipAddress: input.ipAddress
250
+ }));
251
+ }
252
+ async finishPasskey(input) {
253
+ await this.getPendingContext(input.challengeToken, input.sessionToken);
254
+ return this.completeStepUp({
255
+ challengeToken: input.challengeToken,
256
+ sessionToken: input.sessionToken,
257
+ ipAddress: input.ipAddress,
258
+ method: "passkey"
259
+ }, (challengeToken) => this.mfa.finishPasskeyMfa({
260
+ challengeToken,
261
+ response: input.response,
262
+ ipAddress: input.ipAddress
263
+ }));
264
+ }
265
+ };
266
+ exports.AUTH_MFA_STEP_UP_COLLECTION = AUTH_MFA_STEP_UP_COLLECTION;
267
+ exports.StorageAuthStepUpManager = StorageAuthStepUpManager;
268
+
269
+ //# sourceMappingURL=auth.step-up.manager.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.step-up.manager.cjs","names":["AuthSessionError","AuthConfigurationError","hashAuthToken","AuthMfaChallengeError","AuthMfaFactorError","generateAuthId","AuthProviderError","StorageConflictError"],"sources":["../src/auth.step-up.manager.ts"],"sourcesContent":["import {\n StorageConflictError,\n type StorageAdapter,\n type StorageCollection,\n type StorageRecord,\n} from \"@puzzle-ui/storage\";\n\nimport type { SessionManager, SessionRecord } from \"@puzzle-ui/session\";\n\nimport {\n AuthConfigurationError,\n AuthMfaChallengeError,\n AuthMfaFactorError,\n AuthProviderError,\n AuthSessionError,\n} from \"./errors/auth.errors\";\n\nimport type { AuthResult, AuthSession } from \"./types/auth-manager.types\";\n\nimport type {\n AuthMfaPendingResult,\n PasskeyMfaStarted,\n} from \"./types/mfa.types\";\n\nimport { generateAuthId, hashAuthToken } from \"./utils/auth-crypto.utils\";\n\nimport type { StorageAuthMfaManager } from \"./auth.mfa.manager\";\n\nexport const AUTH_MFA_STEP_UP_COLLECTION = \"auth_mfa_step_up_challenges\";\n\nexport type AuthStepUpChallengeStorageRecord = StorageRecord & {\n readonly challengeId: string;\n readonly tokenHash: string;\n readonly userId: string;\n readonly sessionId: string;\n readonly expiresAt: Date;\n readonly consumedAt: Date | null;\n};\n\nexport type AuthStepUpManagerOptions = {\n readonly collectionName?: string;\n readonly clock?: () => Date;\n};\n\nexport type StorageAuthStepUpManagerDependencies = {\n readonly storage: StorageAdapter;\n readonly sessions: SessionManager;\n readonly mfa: StorageAuthMfaManager;\n};\n\nexport type BeginStepUpInput = {\n readonly sessionToken: string;\n readonly ipAddress: string;\n};\n\nexport type CompleteStepUpInput = {\n readonly challengeToken: string;\n readonly sessionToken: string;\n readonly code: string;\n readonly ipAddress: string;\n};\n\nexport type BeginStepUpPasskeyInput = {\n readonly challengeToken: string;\n readonly sessionToken: string;\n readonly ipAddress: string;\n};\n\nexport type FinishStepUpPasskeyInput = {\n readonly challengeToken: string;\n readonly sessionToken: string;\n readonly response: unknown;\n readonly ipAddress: string;\n};\n\nexport type AuthStepUpPendingState = {\n readonly status: \"step-up-pending\";\n readonly challengeId: string;\n readonly expiresAt: Date;\n};\n\ntype PendingContext = {\n readonly challenge: AuthStepUpChallengeStorageRecord;\n readonly session: SessionRecord;\n};\n\ntype MfaVerification = (challengeToken: string) => Promise<AuthResult>;\n\nconst stepUpCollection: StorageCollection<AuthStepUpChallengeStorageRecord> = {\n name: AUTH_MFA_STEP_UP_COLLECTION,\n indexes: [\n {\n name: \"step_up_token_hash_unique\",\n fields: [\"tokenHash\"],\n unique: true,\n },\n {\n name: \"step_up_session_expiration\",\n fields: [\"sessionId\", \"expiresAt\"],\n },\n {\n name: \"step_up_user_expiration\",\n fields: [\"userId\", \"expiresAt\"],\n },\n ],\n};\n\nconst toPublicSession = (session: SessionRecord): AuthSession => {\n if (session.kind !== \"authenticated\" || !session.userId) {\n throw new AuthSessionError(\"A step-up session must be authenticated.\");\n }\n\n return {\n id: session.id,\n userId: session.userId,\n kind: \"authenticated\",\n createdAt: session.createdAt,\n lastSeenAt: session.lastSeenAt,\n expiresAt: session.expiresAt,\n revokedAt: session.revokedAt,\n };\n};\n\nexport class StorageAuthStepUpManager {\n private readonly storage: StorageAdapter;\n private readonly sessions: SessionManager;\n private readonly mfa: StorageAuthMfaManager;\n\n private readonly collectionName: string;\n private readonly clock: () => Date;\n\n private initialized = false;\n private initializationPromise: Promise<void> | null = null;\n\n constructor(\n dependencies: StorageAuthStepUpManagerDependencies,\n options: AuthStepUpManagerOptions = {},\n ) {\n this.storage = dependencies.storage;\n this.sessions = dependencies.sessions;\n this.mfa = dependencies.mfa;\n\n this.collectionName = options.collectionName ?? AUTH_MFA_STEP_UP_COLLECTION;\n\n this.clock = options.clock ?? (() => new Date());\n\n if (!this.collectionName || this.collectionName.trim().length === 0) {\n throw new AuthConfigurationError(\n \"A valid MFA step-up collection name is required.\",\n );\n }\n }\n\n async initialize(): Promise<void> {\n if (!this.initializationPromise) {\n const initialization = (async () => {\n if (\n !this.storage.capabilities.uniqueConstraints ||\n !this.storage.capabilities.conditionalWrites ||\n !this.storage.capabilities.atomicBatch\n ) {\n throw new AuthConfigurationError(\n \"MFA step-up requires unique constraints, conditional writes and atomic batches.\",\n );\n }\n\n await this.storage.registerCollection({\n ...stepUpCollection,\n name: this.collectionName,\n });\n\n this.initialized = true;\n })();\n\n this.initializationPromise = initialization.catch((error) => {\n this.initializationPromise = null;\n this.initialized = false;\n throw error;\n });\n }\n\n return this.initializationPromise;\n }\n\n private assertInitialized(): void {\n if (!this.initialized) {\n throw new AuthConfigurationError(\n \"The MFA step-up manager must be initialized before use.\",\n );\n }\n }\n\n private now(): Date {\n const value = this.clock();\n\n if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {\n throw new AuthConfigurationError(\n \"The configured step-up clock returned an invalid date.\",\n );\n }\n\n return value;\n }\n\n private async getAuthenticatedSession(\n sessionToken: string,\n ): Promise<SessionRecord> {\n if (typeof sessionToken !== \"string\" || sessionToken.length === 0) {\n throw new AuthSessionError(\"An authenticated session is required.\");\n }\n\n let session: SessionRecord | null;\n\n try {\n session = await this.sessions.validate(sessionToken);\n } catch (error) {\n throw new AuthSessionError(\"Could not validate the step-up session.\", {\n cause: error,\n });\n }\n\n if (\n !session ||\n session.kind !== \"authenticated\" ||\n !session.userId ||\n session.revokedAt !== null ||\n session.expiresAt.getTime() <= this.now().getTime()\n ) {\n throw new AuthSessionError(\"An authenticated session is required.\");\n }\n\n return session;\n }\n\n private async findChallenge(\n challengeToken: string,\n ): Promise<AuthStepUpChallengeStorageRecord | null> {\n if (\n typeof challengeToken !== \"string\" ||\n !/^[a-f0-9]{64}$/i.test(challengeToken)\n ) {\n return null;\n }\n\n const tokenHash = await hashAuthToken(challengeToken);\n\n return this.storage.findOne<AuthStepUpChallengeStorageRecord>(\n this.collectionName,\n {\n where: {\n tokenHash: {\n operator: \"eq\",\n value: tokenHash,\n },\n },\n },\n );\n }\n\n private async getPendingContext(\n challengeToken: string,\n sessionToken: string,\n ): Promise<PendingContext> {\n const challenge = await this.findChallenge(challengeToken);\n\n if (\n !challenge ||\n challenge.consumedAt !== null ||\n challenge.expiresAt.getTime() <= this.now().getTime()\n ) {\n throw new AuthMfaChallengeError();\n }\n\n const session = await this.getAuthenticatedSession(sessionToken);\n\n if (\n session.id !== challenge.sessionId ||\n session.userId !== challenge.userId\n ) {\n throw new AuthMfaChallengeError();\n }\n\n return {\n challenge,\n session,\n };\n }\n\n async beginStepUp(input: BeginStepUpInput): Promise<AuthMfaPendingResult> {\n this.assertInitialized();\n\n const session = await this.getAuthenticatedSession(input.sessionToken);\n\n const challenge = await this.mfa.createSignInChallenge({\n userId: session.userId!,\n ipAddress: input.ipAddress,\n });\n\n if (!challenge) {\n throw new AuthMfaFactorError(\n \"An active MFA factor is required for step-up authentication.\",\n );\n }\n\n const now = this.now();\n\n const record: AuthStepUpChallengeStorageRecord = {\n id: generateAuthId(\"mfa-step-up\"),\n challengeId: challenge.challengeId,\n tokenHash: await hashAuthToken(challenge.challengeToken),\n userId: session.userId!,\n sessionId: session.id,\n expiresAt: challenge.expiresAt,\n consumedAt: null,\n createdAt: now,\n updatedAt: now,\n revision: 1,\n };\n\n try {\n await this.storage.insert<AuthStepUpChallengeStorageRecord>(\n this.collectionName,\n record,\n );\n } catch (error) {\n throw new AuthProviderError(\"Could not persist the step-up challenge.\", {\n cause: error,\n });\n }\n\n return challenge;\n }\n\n async getPendingAuthState(\n challengeToken: string,\n sessionToken: string,\n ): Promise<AuthStepUpPendingState | null> {\n this.assertInitialized();\n\n const challenge = await this.findChallenge(challengeToken);\n\n if (\n !challenge ||\n challenge.consumedAt !== null ||\n challenge.expiresAt.getTime() <= this.now().getTime()\n ) {\n return null;\n }\n\n let session: SessionRecord;\n\n try {\n session = await this.getAuthenticatedSession(sessionToken);\n } catch {\n return null;\n }\n\n if (\n session.id !== challenge.sessionId ||\n session.userId !== challenge.userId\n ) {\n return null;\n }\n\n const mfaState = await this.mfa.getPendingAuthState(challengeToken);\n\n if (\n !mfaState ||\n mfaState.status !== \"mfa-pending\" ||\n mfaState.challengeId !== challenge.challengeId\n ) {\n return null;\n }\n\n return {\n status: \"step-up-pending\",\n challengeId: challenge.challengeId,\n expiresAt: challenge.expiresAt,\n };\n }\n\n async beginPasskey(\n input: BeginStepUpPasskeyInput,\n ): Promise<PasskeyMfaStarted> {\n this.assertInitialized();\n\n await this.getPendingContext(input.challengeToken, input.sessionToken);\n\n return this.mfa.beginPasskeyMfa(input.challengeToken, input.ipAddress);\n }\n\n private async consumeChallenge(\n challenge: AuthStepUpChallengeStorageRecord,\n ): Promise<void> {\n const now = this.now();\n\n try {\n const consumed =\n await this.storage.update<AuthStepUpChallengeStorageRecord>(\n this.collectionName,\n challenge.id,\n {\n consumedAt: now,\n updatedAt: now,\n },\n {\n expectedRevision: challenge.revision,\n },\n );\n\n if (!consumed) {\n throw new StorageConflictError(\n \"The step-up challenge was already consumed.\",\n );\n }\n } catch (error) {\n throw new AuthMfaChallengeError({ cause: error });\n }\n }\n\n private async revokeTemporarySession(sessionId: string): Promise<void> {\n try {\n await this.sessions.revoke(sessionId);\n } catch (error) {\n throw new AuthSessionError(\n \"Could not revoke the temporary MFA verification session.\",\n { cause: error },\n );\n }\n }\n\n private async completeStepUp(\n input: {\n readonly challengeToken: string;\n readonly sessionToken: string;\n readonly method: string;\n readonly ipAddress: string;\n },\n verify: MfaVerification,\n ): Promise<AuthResult> {\n this.assertInitialized();\n\n const context = await this.getPendingContext(\n input.challengeToken,\n input.sessionToken,\n );\n\n // The MFA manager consumes its own challenge and creates a short-lived\n // internal session after successful factor verification. That token is\n // never returned to the caller.\n const verified = await verify(input.challengeToken);\n\n if (verified.user.id !== context.challenge.userId) {\n await this.revokeTemporarySession(verified.session.id);\n throw new AuthMfaChallengeError();\n }\n\n // Consume the step-up wrapper challenge before rotating the original\n // session. Conditional revision checking ensures that concurrent\n // requests cannot both proceed to session rotation.\n try {\n await this.consumeChallenge(context.challenge);\n } catch (error) {\n try {\n await this.revokeTemporarySession(verified.session.id);\n } catch (revokeError) {\n throw new AuthSessionError(\n \"The step-up challenge failed and its temporary session could not be revoked.\",\n { cause: revokeError },\n );\n }\n\n throw error;\n }\n\n const now = this.now();\n\n let replacement: Awaited<ReturnType<SessionManager[\"rotate\"]>>;\n\n try {\n // SessionManager.rotate() atomically inserts the replacement session\n // and revokes the original session in a single storage batch.\n replacement = await this.sessions.rotate({\n sessionId: context.session.id,\n metadata: {\n ...(context.session.metadata ?? {}),\n authenticationMethod: input.method,\n mfaVerifiedAt: now.toISOString(),\n stepUpVerifiedAt: now.toISOString(),\n stepUpOfSessionId: context.session.id,\n },\n });\n } catch (error) {\n try {\n await this.revokeTemporarySession(verified.session.id);\n } catch (revokeError) {\n throw new AuthSessionError(\n \"Session rotation failed and the temporary MFA session could not be revoked.\",\n { cause: revokeError },\n );\n }\n\n throw new AuthSessionError(\n \"Could not atomically rotate the session after step-up verification.\",\n { cause: error },\n );\n }\n\n // The temporary session created by the MFA manager is not the session\n // returned to the application. Revoke it after the original session has\n // been atomically rotated.\n try {\n await this.revokeTemporarySession(verified.session.id);\n } catch (error) {\n try {\n await this.sessions.revoke(replacement.session.id);\n } catch (revokeError) {\n throw new AuthSessionError(\n \"Could not revoke the replacement session after temporary-session cleanup failed.\",\n { cause: revokeError },\n );\n }\n\n throw error;\n }\n\n return {\n user: verified.user,\n session: toPublicSession(replacement.session),\n token: replacement.token,\n };\n }\n\n async verifyTotp(input: CompleteStepUpInput): Promise<AuthResult> {\n return this.completeStepUp(\n {\n ...input,\n method: \"totp\",\n },\n (challengeToken) =>\n this.mfa.verifySignInTotp({\n challengeToken,\n code: input.code,\n ipAddress: input.ipAddress,\n }),\n );\n }\n\n async verifyRecoveryCode(input: CompleteStepUpInput): Promise<AuthResult> {\n return this.completeStepUp(\n {\n ...input,\n method: \"recovery-code\",\n },\n (challengeToken) =>\n this.mfa.verifySignInRecoveryCode({\n challengeToken,\n code: input.code,\n ipAddress: input.ipAddress,\n }),\n );\n }\n\n async finishPasskey(input: FinishStepUpPasskeyInput): Promise<AuthResult> {\n await this.getPendingContext(input.challengeToken, input.sessionToken);\n\n return this.completeStepUp(\n {\n challengeToken: input.challengeToken,\n sessionToken: input.sessionToken,\n ipAddress: input.ipAddress,\n method: \"passkey\",\n },\n (challengeToken) =>\n this.mfa.finishPasskeyMfa({\n challengeToken,\n response: input.response,\n ipAddress: input.ipAddress,\n }),\n );\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import { AuthResult } from "./types/auth-manager.types.cjs";
2
+ import { AuthMfaPendingResult, PasskeyMfaStarted } from "./types/mfa.types.cjs";
3
+ import { StorageAuthMfaManager } from "./auth.mfa.manager.cjs";
4
+ import { StorageAdapter, StorageRecord } from "@puzzle-ui/storage";
5
+ import { SessionManager } from "@puzzle-ui/session";
6
+ export declare const AUTH_MFA_STEP_UP_COLLECTION = "auth_mfa_step_up_challenges";
7
+ export type AuthStepUpChallengeStorageRecord = StorageRecord & {
8
+ readonly challengeId: string;
9
+ readonly tokenHash: string;
10
+ readonly userId: string;
11
+ readonly sessionId: string;
12
+ readonly expiresAt: Date;
13
+ readonly consumedAt: Date | null;
14
+ };
15
+ export type AuthStepUpManagerOptions = {
16
+ readonly collectionName?: string;
17
+ readonly clock?: () => Date;
18
+ };
19
+ export type StorageAuthStepUpManagerDependencies = {
20
+ readonly storage: StorageAdapter;
21
+ readonly sessions: SessionManager;
22
+ readonly mfa: StorageAuthMfaManager;
23
+ };
24
+ export type BeginStepUpInput = {
25
+ readonly sessionToken: string;
26
+ readonly ipAddress: string;
27
+ };
28
+ export type CompleteStepUpInput = {
29
+ readonly challengeToken: string;
30
+ readonly sessionToken: string;
31
+ readonly code: string;
32
+ readonly ipAddress: string;
33
+ };
34
+ export type BeginStepUpPasskeyInput = {
35
+ readonly challengeToken: string;
36
+ readonly sessionToken: string;
37
+ readonly ipAddress: string;
38
+ };
39
+ export type FinishStepUpPasskeyInput = {
40
+ readonly challengeToken: string;
41
+ readonly sessionToken: string;
42
+ readonly response: unknown;
43
+ readonly ipAddress: string;
44
+ };
45
+ export type AuthStepUpPendingState = {
46
+ readonly status: "step-up-pending";
47
+ readonly challengeId: string;
48
+ readonly expiresAt: Date;
49
+ };
50
+ export declare class StorageAuthStepUpManager {
51
+ private readonly storage;
52
+ private readonly sessions;
53
+ private readonly mfa;
54
+ private readonly collectionName;
55
+ private readonly clock;
56
+ private initialized;
57
+ private initializationPromise;
58
+ constructor(dependencies: StorageAuthStepUpManagerDependencies, options?: AuthStepUpManagerOptions);
59
+ initialize(): Promise<void>;
60
+ private assertInitialized;
61
+ private now;
62
+ private getAuthenticatedSession;
63
+ private findChallenge;
64
+ private getPendingContext;
65
+ beginStepUp(input: BeginStepUpInput): Promise<AuthMfaPendingResult>;
66
+ getPendingAuthState(challengeToken: string, sessionToken: string): Promise<AuthStepUpPendingState | null>;
67
+ beginPasskey(input: BeginStepUpPasskeyInput): Promise<PasskeyMfaStarted>;
68
+ private consumeChallenge;
69
+ private revokeTemporarySession;
70
+ private completeStepUp;
71
+ verifyTotp(input: CompleteStepUpInput): Promise<AuthResult>;
72
+ verifyRecoveryCode(input: CompleteStepUpInput): Promise<AuthResult>;
73
+ finishPasskey(input: FinishStepUpPasskeyInput): Promise<AuthResult>;
74
+ }
@@ -0,0 +1,74 @@
1
+ import { AuthResult } from "./types/auth-manager.types.js";
2
+ import { AuthMfaPendingResult, PasskeyMfaStarted } from "./types/mfa.types.js";
3
+ import { StorageAuthMfaManager } from "./auth.mfa.manager.js";
4
+ import { StorageAdapter, StorageRecord } from "@puzzle-ui/storage";
5
+ import { SessionManager } from "@puzzle-ui/session";
6
+ export declare const AUTH_MFA_STEP_UP_COLLECTION = "auth_mfa_step_up_challenges";
7
+ export type AuthStepUpChallengeStorageRecord = StorageRecord & {
8
+ readonly challengeId: string;
9
+ readonly tokenHash: string;
10
+ readonly userId: string;
11
+ readonly sessionId: string;
12
+ readonly expiresAt: Date;
13
+ readonly consumedAt: Date | null;
14
+ };
15
+ export type AuthStepUpManagerOptions = {
16
+ readonly collectionName?: string;
17
+ readonly clock?: () => Date;
18
+ };
19
+ export type StorageAuthStepUpManagerDependencies = {
20
+ readonly storage: StorageAdapter;
21
+ readonly sessions: SessionManager;
22
+ readonly mfa: StorageAuthMfaManager;
23
+ };
24
+ export type BeginStepUpInput = {
25
+ readonly sessionToken: string;
26
+ readonly ipAddress: string;
27
+ };
28
+ export type CompleteStepUpInput = {
29
+ readonly challengeToken: string;
30
+ readonly sessionToken: string;
31
+ readonly code: string;
32
+ readonly ipAddress: string;
33
+ };
34
+ export type BeginStepUpPasskeyInput = {
35
+ readonly challengeToken: string;
36
+ readonly sessionToken: string;
37
+ readonly ipAddress: string;
38
+ };
39
+ export type FinishStepUpPasskeyInput = {
40
+ readonly challengeToken: string;
41
+ readonly sessionToken: string;
42
+ readonly response: unknown;
43
+ readonly ipAddress: string;
44
+ };
45
+ export type AuthStepUpPendingState = {
46
+ readonly status: "step-up-pending";
47
+ readonly challengeId: string;
48
+ readonly expiresAt: Date;
49
+ };
50
+ export declare class StorageAuthStepUpManager {
51
+ private readonly storage;
52
+ private readonly sessions;
53
+ private readonly mfa;
54
+ private readonly collectionName;
55
+ private readonly clock;
56
+ private initialized;
57
+ private initializationPromise;
58
+ constructor(dependencies: StorageAuthStepUpManagerDependencies, options?: AuthStepUpManagerOptions);
59
+ initialize(): Promise<void>;
60
+ private assertInitialized;
61
+ private now;
62
+ private getAuthenticatedSession;
63
+ private findChallenge;
64
+ private getPendingContext;
65
+ beginStepUp(input: BeginStepUpInput): Promise<AuthMfaPendingResult>;
66
+ getPendingAuthState(challengeToken: string, sessionToken: string): Promise<AuthStepUpPendingState | null>;
67
+ beginPasskey(input: BeginStepUpPasskeyInput): Promise<PasskeyMfaStarted>;
68
+ private consumeChallenge;
69
+ private revokeTemporarySession;
70
+ private completeStepUp;
71
+ verifyTotp(input: CompleteStepUpInput): Promise<AuthResult>;
72
+ verifyRecoveryCode(input: CompleteStepUpInput): Promise<AuthResult>;
73
+ finishPasskey(input: FinishStepUpPasskeyInput): Promise<AuthResult>;
74
+ }