@puzzle-ui/auth 0.0.0-stage → 0.5.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/README.md +301 -2
- package/dist/LICENSE +21 -0
- package/dist/auth.lifecycle.manager.cjs +835 -0
- package/dist/auth.lifecycle.manager.cjs.map +1 -0
- package/dist/auth.lifecycle.manager.d.cts +115 -0
- package/dist/auth.lifecycle.manager.d.ts +115 -0
- package/dist/auth.lifecycle.manager.js +835 -0
- package/dist/auth.lifecycle.manager.js.map +1 -0
- package/dist/auth.manager.cjs +454 -0
- package/dist/auth.manager.cjs.map +1 -0
- package/dist/auth.manager.d.cts +54 -0
- package/dist/auth.manager.d.ts +54 -0
- package/dist/auth.manager.js +454 -0
- package/dist/auth.manager.js.map +1 -0
- package/dist/auth.mfa.manager.cjs +1488 -0
- package/dist/auth.mfa.manager.cjs.map +1 -0
- package/dist/auth.mfa.manager.d.cts +100 -0
- package/dist/auth.mfa.manager.d.ts +100 -0
- package/dist/auth.mfa.manager.js +1488 -0
- package/dist/auth.mfa.manager.js.map +1 -0
- package/dist/auth.rate-limit.cjs +78 -0
- package/dist/auth.rate-limit.cjs.map +1 -0
- package/dist/auth.rate-limit.d.cts +24 -0
- package/dist/auth.rate-limit.d.ts +24 -0
- package/dist/auth.rate-limit.js +78 -0
- package/dist/auth.rate-limit.js.map +1 -0
- package/dist/auth.step-up.manager.cjs +269 -0
- package/dist/auth.step-up.manager.cjs.map +1 -0
- package/dist/auth.step-up.manager.d.cts +74 -0
- package/dist/auth.step-up.manager.d.ts +74 -0
- package/dist/auth.step-up.manager.js +268 -0
- package/dist/auth.step-up.manager.js.map +1 -0
- package/dist/auth.storage.cjs +164 -0
- package/dist/auth.storage.cjs.map +1 -0
- package/dist/auth.storage.d.cts +29 -0
- package/dist/auth.storage.d.ts +29 -0
- package/dist/auth.storage.js +148 -0
- package/dist/auth.storage.js.map +1 -0
- package/dist/errors/auth.errors.cjs +101 -0
- package/dist/errors/auth.errors.cjs.map +1 -0
- package/dist/errors/auth.errors.d.cts +62 -0
- package/dist/errors/auth.errors.d.ts +62 -0
- package/dist/errors/auth.errors.js +90 -0
- package/dist/errors/auth.errors.js.map +1 -0
- package/dist/index.cjs +54 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +13 -0
- package/dist/providers/mfa-provider.types.d.cts +66 -0
- package/dist/providers/mfa-provider.types.d.ts +66 -0
- package/dist/providers/provider.types.d.cts +34 -0
- package/dist/providers/provider.types.d.ts +34 -0
- package/dist/providers/simplewebauthn-provider.cjs +185 -0
- package/dist/providers/simplewebauthn-provider.cjs.map +1 -0
- package/dist/providers/simplewebauthn-provider.d.cts +35 -0
- package/dist/providers/simplewebauthn-provider.d.ts +35 -0
- package/dist/providers/simplewebauthn-provider.js +185 -0
- package/dist/providers/simplewebauthn-provider.js.map +1 -0
- package/dist/providers/webcrypto-secret-protector.cjs +118 -0
- package/dist/providers/webcrypto-secret-protector.cjs.map +1 -0
- package/dist/providers/webcrypto-secret-protector.d.cts +14 -0
- package/dist/providers/webcrypto-secret-protector.d.ts +14 -0
- package/dist/providers/webcrypto-secret-protector.js +118 -0
- package/dist/providers/webcrypto-secret-protector.js.map +1 -0
- package/dist/types/auth-lifecycle.types.d.cts +53 -0
- package/dist/types/auth-lifecycle.types.d.ts +53 -0
- package/dist/types/auth-manager.types.d.cts +106 -0
- package/dist/types/auth-manager.types.d.ts +106 -0
- package/dist/types/credential.types.d.cts +9 -0
- package/dist/types/credential.types.d.ts +9 -0
- package/dist/types/mfa.types.d.cts +244 -0
- package/dist/types/mfa.types.d.ts +244 -0
- package/dist/types/state.types.d.cts +33 -0
- package/dist/types/state.types.d.ts +33 -0
- package/dist/types/user.types.d.cts +9 -0
- package/dist/types/user.types.d.ts +9 -0
- package/dist/utils/auth-crypto.utils.cjs +38 -0
- package/dist/utils/auth-crypto.utils.cjs.map +1 -0
- package/dist/utils/auth-crypto.utils.d.cts +4 -0
- package/dist/utils/auth-crypto.utils.d.ts +4 -0
- package/dist/utils/auth-crypto.utils.js +35 -0
- package/dist/utils/auth-crypto.utils.js.map +1 -0
- package/dist/utils/email.utils.cjs +20 -0
- package/dist/utils/email.utils.cjs.map +1 -0
- package/dist/utils/email.utils.d.cts +2 -0
- package/dist/utils/email.utils.d.ts +2 -0
- package/dist/utils/email.utils.js +19 -0
- package/dist/utils/email.utils.js.map +1 -0
- package/dist/utils/totp.utils.cjs +145 -0
- package/dist/utils/totp.utils.cjs.map +1 -0
- package/dist/utils/totp.utils.d.cts +26 -0
- package/dist/utils/totp.utils.d.ts +26 -0
- package/dist/utils/totp.utils.js +140 -0
- package/dist/utils/totp.utils.js.map +1 -0
- package/package.json +45 -4
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { AuthConfigurationError, AuthMfaChallengeError, AuthMfaFactorError, AuthProviderError, AuthSessionError } from "./errors/auth.errors.js";
|
|
2
|
+
import { generateAuthId, hashAuthToken } from "./utils/auth-crypto.utils.js";
|
|
3
|
+
import { StorageConflictError } from "@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 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 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 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 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 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 AuthSessionError("An authenticated session is required.");
|
|
80
|
+
let session;
|
|
81
|
+
try {
|
|
82
|
+
session = await this.sessions.validate(sessionToken);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw new 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 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 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 AuthMfaChallengeError();
|
|
100
|
+
const session = await this.getAuthenticatedSession(sessionToken);
|
|
101
|
+
if (session.id !== challenge.sessionId || session.userId !== challenge.userId) throw new 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 AuthMfaFactorError("An active MFA factor is required for step-up authentication.");
|
|
115
|
+
const now = this.now();
|
|
116
|
+
const record = {
|
|
117
|
+
id: generateAuthId("mfa-step-up"),
|
|
118
|
+
challengeId: challenge.challengeId,
|
|
119
|
+
tokenHash: await 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 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 StorageConflictError("The step-up challenge was already consumed.");
|
|
166
|
+
} catch (error) {
|
|
167
|
+
throw new AuthMfaChallengeError({ cause: error });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async revokeTemporarySession(sessionId) {
|
|
171
|
+
try {
|
|
172
|
+
await this.sessions.revoke(sessionId);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
throw new 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 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 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 AuthSessionError("Session rotation failed and the temporary MFA session could not be revoked.", { cause: revokeError });
|
|
213
|
+
}
|
|
214
|
+
throw new 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 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
|
+
export { AUTH_MFA_STEP_UP_COLLECTION, StorageAuthStepUpManager };
|
|
267
|
+
|
|
268
|
+
//# sourceMappingURL=auth.step-up.manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.step-up.manager.js","names":[],"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,164 @@
|
|
|
1
|
+
// src/auth.storage.ts
|
|
2
|
+
const AUTH_USER_COLLECTION = "auth_users";
|
|
3
|
+
const AUTH_CREDENTIAL_COLLECTION = "auth_credentials";
|
|
4
|
+
const AUTH_ACTION_TOKEN_COLLECTION = "auth_action_tokens";
|
|
5
|
+
const AUTH_AUDIT_COLLECTION = "auth_audit_events";
|
|
6
|
+
const AUTH_RATE_LIMIT_COLLECTION = "auth_rate_limits";
|
|
7
|
+
const AUTH_MFA_FACTOR_COLLECTION = "auth_mfa_factors";
|
|
8
|
+
const AUTH_MFA_CHALLENGE_COLLECTION = "auth_mfa_challenges";
|
|
9
|
+
const AUTH_RECOVERY_CODE_COLLECTION = "auth_recovery_codes";
|
|
10
|
+
const authUserCollection = {
|
|
11
|
+
name: AUTH_USER_COLLECTION,
|
|
12
|
+
indexes: [{
|
|
13
|
+
name: "normalized_email_unique",
|
|
14
|
+
fields: ["normalizedEmail"],
|
|
15
|
+
unique: true
|
|
16
|
+
}]
|
|
17
|
+
};
|
|
18
|
+
const authCredentialCollection = {
|
|
19
|
+
name: AUTH_CREDENTIAL_COLLECTION,
|
|
20
|
+
indexes: [{
|
|
21
|
+
name: "user_kind_unique",
|
|
22
|
+
fields: ["userId", "kind"],
|
|
23
|
+
unique: true
|
|
24
|
+
}, {
|
|
25
|
+
name: "user_id",
|
|
26
|
+
fields: ["userId"]
|
|
27
|
+
}]
|
|
28
|
+
};
|
|
29
|
+
const authActionTokenCollection = {
|
|
30
|
+
name: AUTH_ACTION_TOKEN_COLLECTION,
|
|
31
|
+
indexes: [
|
|
32
|
+
{
|
|
33
|
+
name: "token_hash_unique",
|
|
34
|
+
fields: ["tokenHash"],
|
|
35
|
+
unique: true
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "user_purpose",
|
|
39
|
+
fields: ["userId", "purpose"]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "token_expiration",
|
|
43
|
+
fields: ["expiresAt"]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
};
|
|
47
|
+
const authAuditCollection = {
|
|
48
|
+
name: AUTH_AUDIT_COLLECTION,
|
|
49
|
+
indexes: [{
|
|
50
|
+
name: "user_created",
|
|
51
|
+
fields: ["userId", "createdAt"]
|
|
52
|
+
}, {
|
|
53
|
+
name: "action_created",
|
|
54
|
+
fields: ["action", "createdAt"]
|
|
55
|
+
}]
|
|
56
|
+
};
|
|
57
|
+
const authRateLimitCollection = {
|
|
58
|
+
name: AUTH_RATE_LIMIT_COLLECTION,
|
|
59
|
+
indexes: [{
|
|
60
|
+
name: "rate_limit_reset",
|
|
61
|
+
fields: ["resetAt"]
|
|
62
|
+
}]
|
|
63
|
+
};
|
|
64
|
+
const authMfaFactorCollection = {
|
|
65
|
+
name: AUTH_MFA_FACTOR_COLLECTION,
|
|
66
|
+
indexes: [
|
|
67
|
+
{
|
|
68
|
+
name: "factor_key_unique",
|
|
69
|
+
fields: ["factorKey"],
|
|
70
|
+
unique: true
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "factor_user_status",
|
|
74
|
+
fields: ["userId", "status"]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "factor_credential_id",
|
|
78
|
+
fields: ["credentialId"]
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
};
|
|
82
|
+
const authMfaChallengeCollection = {
|
|
83
|
+
name: AUTH_MFA_CHALLENGE_COLLECTION,
|
|
84
|
+
indexes: [
|
|
85
|
+
{
|
|
86
|
+
name: "mfa_challenge_token_unique",
|
|
87
|
+
fields: ["tokenHash"],
|
|
88
|
+
unique: true
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "mfa_challenge_user",
|
|
92
|
+
fields: ["userId", "purpose"]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "mfa_challenge_expiration",
|
|
96
|
+
fields: ["expiresAt"]
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
};
|
|
100
|
+
const authRecoveryCodeCollection = {
|
|
101
|
+
name: AUTH_RECOVERY_CODE_COLLECTION,
|
|
102
|
+
indexes: [{
|
|
103
|
+
name: "recovery_code_hash_unique",
|
|
104
|
+
fields: ["codeHash"],
|
|
105
|
+
unique: true
|
|
106
|
+
}, {
|
|
107
|
+
name: "recovery_code_user",
|
|
108
|
+
fields: ["userId", "consumedAt"]
|
|
109
|
+
}]
|
|
110
|
+
};
|
|
111
|
+
const initializeAuthStorage = async (storage, options = {}) => {
|
|
112
|
+
if (!storage.capabilities.uniqueConstraints || !storage.capabilities.atomicBatch) throw new Error("Authentication storage requires unique constraints and atomic batches.");
|
|
113
|
+
await storage.registerCollection({
|
|
114
|
+
...authUserCollection,
|
|
115
|
+
name: options.usersCollection ?? "auth_users"
|
|
116
|
+
});
|
|
117
|
+
await storage.registerCollection({
|
|
118
|
+
...authCredentialCollection,
|
|
119
|
+
name: options.credentialsCollection ?? "auth_credentials"
|
|
120
|
+
});
|
|
121
|
+
await storage.registerCollection({
|
|
122
|
+
...authActionTokenCollection,
|
|
123
|
+
name: options.actionTokensCollection ?? "auth_action_tokens"
|
|
124
|
+
});
|
|
125
|
+
await storage.registerCollection({
|
|
126
|
+
...authAuditCollection,
|
|
127
|
+
name: options.auditCollection ?? "auth_audit_events"
|
|
128
|
+
});
|
|
129
|
+
await storage.registerCollection({
|
|
130
|
+
...authRateLimitCollection,
|
|
131
|
+
name: options.rateLimitsCollection ?? "auth_rate_limits"
|
|
132
|
+
});
|
|
133
|
+
await storage.registerCollection({
|
|
134
|
+
...authMfaFactorCollection,
|
|
135
|
+
name: options.factorsCollection ?? "auth_mfa_factors"
|
|
136
|
+
});
|
|
137
|
+
await storage.registerCollection({
|
|
138
|
+
...authMfaChallengeCollection,
|
|
139
|
+
name: options.challengesCollection ?? "auth_mfa_challenges"
|
|
140
|
+
});
|
|
141
|
+
await storage.registerCollection({
|
|
142
|
+
...authRecoveryCodeCollection,
|
|
143
|
+
name: options.recoveryCodesCollection ?? "auth_recovery_codes"
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
exports.AUTH_ACTION_TOKEN_COLLECTION = AUTH_ACTION_TOKEN_COLLECTION;
|
|
147
|
+
exports.AUTH_AUDIT_COLLECTION = AUTH_AUDIT_COLLECTION;
|
|
148
|
+
exports.AUTH_CREDENTIAL_COLLECTION = AUTH_CREDENTIAL_COLLECTION;
|
|
149
|
+
exports.AUTH_MFA_CHALLENGE_COLLECTION = AUTH_MFA_CHALLENGE_COLLECTION;
|
|
150
|
+
exports.AUTH_MFA_FACTOR_COLLECTION = AUTH_MFA_FACTOR_COLLECTION;
|
|
151
|
+
exports.AUTH_RATE_LIMIT_COLLECTION = AUTH_RATE_LIMIT_COLLECTION;
|
|
152
|
+
exports.AUTH_RECOVERY_CODE_COLLECTION = AUTH_RECOVERY_CODE_COLLECTION;
|
|
153
|
+
exports.AUTH_USER_COLLECTION = AUTH_USER_COLLECTION;
|
|
154
|
+
exports.authActionTokenCollection = authActionTokenCollection;
|
|
155
|
+
exports.authAuditCollection = authAuditCollection;
|
|
156
|
+
exports.authCredentialCollection = authCredentialCollection;
|
|
157
|
+
exports.authMfaChallengeCollection = authMfaChallengeCollection;
|
|
158
|
+
exports.authMfaFactorCollection = authMfaFactorCollection;
|
|
159
|
+
exports.authRateLimitCollection = authRateLimitCollection;
|
|
160
|
+
exports.authRecoveryCodeCollection = authRecoveryCodeCollection;
|
|
161
|
+
exports.authUserCollection = authUserCollection;
|
|
162
|
+
exports.initializeAuthStorage = initializeAuthStorage;
|
|
163
|
+
|
|
164
|
+
//# sourceMappingURL=auth.storage.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.storage.cjs","names":[],"sources":["../src/auth.storage.ts"],"sourcesContent":["import type {\n StorageAdapter,\n StorageCollection,\n StorageRecord,\n} from \"@puzzle-ui/storage\";\n\nimport type { AuthUser } from \"./types/user.types\";\nimport type { PasswordCredentialStorageRecord } from \"./types/credential.types\";\n\nimport type {\n AuthActionTokenStorageRecord,\n AuthAuditEventStorageRecord,\n AuthRateLimitStorageRecord,\n} from \"./types/auth-lifecycle.types\";\n\nimport type {\n AuthMfaChallengeStorageRecord,\n AuthMfaFactorStorageRecord,\n AuthRecoveryCodeStorageRecord,\n} from \"./types/mfa.types\";\n\nexport const AUTH_USER_COLLECTION = \"auth_users\";\nexport const AUTH_CREDENTIAL_COLLECTION = \"auth_credentials\";\nexport const AUTH_ACTION_TOKEN_COLLECTION = \"auth_action_tokens\";\nexport const AUTH_AUDIT_COLLECTION = \"auth_audit_events\";\nexport const AUTH_RATE_LIMIT_COLLECTION = \"auth_rate_limits\";\n\nexport const AUTH_MFA_FACTOR_COLLECTION = \"auth_mfa_factors\";\nexport const AUTH_MFA_CHALLENGE_COLLECTION = \"auth_mfa_challenges\";\nexport const AUTH_RECOVERY_CODE_COLLECTION = \"auth_recovery_codes\";\n\nexport type AuthUserStorageRecord = AuthUser &\n StorageRecord & {\n readonly normalizedEmail: string;\n readonly deactivatedAt: Date | null;\n readonly sessionsInvalidatedAt: Date | null;\n };\n\nexport const authUserCollection: StorageCollection<AuthUserStorageRecord> = {\n name: AUTH_USER_COLLECTION,\n indexes: [\n {\n name: \"normalized_email_unique\",\n fields: [\"normalizedEmail\"],\n unique: true,\n },\n ],\n};\n\nexport const authCredentialCollection: StorageCollection<PasswordCredentialStorageRecord> =\n {\n name: AUTH_CREDENTIAL_COLLECTION,\n indexes: [\n {\n name: \"user_kind_unique\",\n fields: [\"userId\", \"kind\"],\n unique: true,\n },\n {\n name: \"user_id\",\n fields: [\"userId\"],\n },\n ],\n };\n\nexport const authActionTokenCollection: StorageCollection<AuthActionTokenStorageRecord> =\n {\n name: AUTH_ACTION_TOKEN_COLLECTION,\n indexes: [\n {\n name: \"token_hash_unique\",\n fields: [\"tokenHash\"],\n unique: true,\n },\n {\n name: \"user_purpose\",\n fields: [\"userId\", \"purpose\"],\n },\n {\n name: \"token_expiration\",\n fields: [\"expiresAt\"],\n },\n ],\n };\n\nexport const authAuditCollection: StorageCollection<AuthAuditEventStorageRecord> =\n {\n name: AUTH_AUDIT_COLLECTION,\n indexes: [\n {\n name: \"user_created\",\n fields: [\"userId\", \"createdAt\"],\n },\n {\n name: \"action_created\",\n fields: [\"action\", \"createdAt\"],\n },\n ],\n };\n\nexport const authRateLimitCollection: StorageCollection<AuthRateLimitStorageRecord> =\n {\n name: AUTH_RATE_LIMIT_COLLECTION,\n indexes: [\n {\n name: \"rate_limit_reset\",\n fields: [\"resetAt\"],\n },\n ],\n };\n\nexport const authMfaFactorCollection: StorageCollection<AuthMfaFactorStorageRecord> =\n {\n name: AUTH_MFA_FACTOR_COLLECTION,\n indexes: [\n {\n name: \"factor_key_unique\",\n fields: [\"factorKey\"],\n unique: true,\n },\n {\n name: \"factor_user_status\",\n fields: [\"userId\", \"status\"],\n },\n {\n name: \"factor_credential_id\",\n fields: [\"credentialId\"],\n },\n ],\n };\n\nexport const authMfaChallengeCollection: StorageCollection<AuthMfaChallengeStorageRecord> =\n {\n name: AUTH_MFA_CHALLENGE_COLLECTION,\n indexes: [\n {\n name: \"mfa_challenge_token_unique\",\n fields: [\"tokenHash\"],\n unique: true,\n },\n {\n name: \"mfa_challenge_user\",\n fields: [\"userId\", \"purpose\"],\n },\n {\n name: \"mfa_challenge_expiration\",\n fields: [\"expiresAt\"],\n },\n ],\n };\n\nexport const authRecoveryCodeCollection: StorageCollection<AuthRecoveryCodeStorageRecord> =\n {\n name: AUTH_RECOVERY_CODE_COLLECTION,\n indexes: [\n {\n name: \"recovery_code_hash_unique\",\n fields: [\"codeHash\"],\n unique: true,\n },\n {\n name: \"recovery_code_user\",\n fields: [\"userId\", \"consumedAt\"],\n },\n ],\n };\n\nexport type InitializeAuthStorageOptions = {\n readonly usersCollection?: string;\n readonly credentialsCollection?: string;\n readonly actionTokensCollection?: string;\n readonly auditCollection?: string;\n readonly rateLimitsCollection?: string;\n\n readonly factorsCollection?: string;\n readonly challengesCollection?: string;\n readonly recoveryCodesCollection?: string;\n};\n\nexport const initializeAuthStorage = async (\n storage: StorageAdapter,\n options: InitializeAuthStorageOptions = {},\n): Promise<void> => {\n if (\n !storage.capabilities.uniqueConstraints ||\n !storage.capabilities.atomicBatch\n ) {\n throw new Error(\n \"Authentication storage requires unique constraints and atomic batches.\",\n );\n }\n\n await storage.registerCollection({\n ...authUserCollection,\n name: options.usersCollection ?? AUTH_USER_COLLECTION,\n });\n\n await storage.registerCollection({\n ...authCredentialCollection,\n name: options.credentialsCollection ?? AUTH_CREDENTIAL_COLLECTION,\n });\n\n await storage.registerCollection({\n ...authActionTokenCollection,\n name: options.actionTokensCollection ?? AUTH_ACTION_TOKEN_COLLECTION,\n });\n\n await storage.registerCollection({\n ...authAuditCollection,\n name: options.auditCollection ?? AUTH_AUDIT_COLLECTION,\n });\n\n await storage.registerCollection({\n ...authRateLimitCollection,\n name: options.rateLimitsCollection ?? AUTH_RATE_LIMIT_COLLECTION,\n });\n\n await storage.registerCollection({\n ...authMfaFactorCollection,\n name: options.factorsCollection ?? AUTH_MFA_FACTOR_COLLECTION,\n });\n\n await storage.registerCollection({\n ...authMfaChallengeCollection,\n name: options.challengesCollection ?? AUTH_MFA_CHALLENGE_COLLECTION,\n });\n\n await storage.registerCollection({\n ...authRecoveryCodeCollection,\n name: options.recoveryCodesCollection ?? AUTH_RECOVERY_CODE_COLLECTION,\n });\n};\n"],"mappings":";AAqBA,MAAa,uBAAuB;AACpC,MAAa,6BAA6B;AAC1C,MAAa,+BAA+B;AAC5C,MAAa,wBAAwB;AACrC,MAAa,6BAA6B;AAE1C,MAAa,6BAA6B;AAC1C,MAAa,gCAAgC;AAC7C,MAAa,gCAAgC;AAS7C,MAAa,qBAA+D;CAC1E,MAAM;CACN,SAAS,CACP;EACE,MAAM;EACN,QAAQ,CAAC,iBAAiB;EAC1B,QAAQ;CACV,CACF;AACF;AAEA,MAAa,2BACX;CACE,MAAM;CACN,SAAS,CACP;EACE,MAAM;EACN,QAAQ,CAAC,UAAU,MAAM;EACzB,QAAQ;CACV,GACA;EACE,MAAM;EACN,QAAQ,CAAC,QAAQ;CACnB,CACF;AACF;AAEF,MAAa,4BACX;CACE,MAAM;CACN,SAAS;EACP;GACE,MAAM;GACN,QAAQ,CAAC,WAAW;GACpB,QAAQ;EACV;EACA;GACE,MAAM;GACN,QAAQ,CAAC,UAAU,SAAS;EAC9B;EACA;GACE,MAAM;GACN,QAAQ,CAAC,WAAW;EACtB;CACF;AACF;AAEF,MAAa,sBACX;CACE,MAAM;CACN,SAAS,CACP;EACE,MAAM;EACN,QAAQ,CAAC,UAAU,WAAW;CAChC,GACA;EACE,MAAM;EACN,QAAQ,CAAC,UAAU,WAAW;CAChC,CACF;AACF;AAEF,MAAa,0BACX;CACE,MAAM;CACN,SAAS,CACP;EACE,MAAM;EACN,QAAQ,CAAC,SAAS;CACpB,CACF;AACF;AAEF,MAAa,0BACX;CACE,MAAM;CACN,SAAS;EACP;GACE,MAAM;GACN,QAAQ,CAAC,WAAW;GACpB,QAAQ;EACV;EACA;GACE,MAAM;GACN,QAAQ,CAAC,UAAU,QAAQ;EAC7B;EACA;GACE,MAAM;GACN,QAAQ,CAAC,cAAc;EACzB;CACF;AACF;AAEF,MAAa,6BACX;CACE,MAAM;CACN,SAAS;EACP;GACE,MAAM;GACN,QAAQ,CAAC,WAAW;GACpB,QAAQ;EACV;EACA;GACE,MAAM;GACN,QAAQ,CAAC,UAAU,SAAS;EAC9B;EACA;GACE,MAAM;GACN,QAAQ,CAAC,WAAW;EACtB;CACF;AACF;AAEF,MAAa,6BACX;CACE,MAAM;CACN,SAAS,CACP;EACE,MAAM;EACN,QAAQ,CAAC,UAAU;EACnB,QAAQ;CACV,GACA;EACE,MAAM;EACN,QAAQ,CAAC,UAAU,YAAY;CACjC,CACF;AACF;AAcF,MAAa,wBAAwB,OACnC,SACA,UAAwC,CAAC,MACvB;CAClB,IACE,CAAC,QAAQ,aAAa,qBACtB,CAAC,QAAQ,aAAa,aAEtB,MAAM,IAAI,MACR,wEACF;CAGF,MAAM,QAAQ,mBAAmB;EAC/B,GAAG;EACH,MAAM,QAAQ,mBAAA;CAChB,CAAC;CAED,MAAM,QAAQ,mBAAmB;EAC/B,GAAG;EACH,MAAM,QAAQ,yBAAA;CAChB,CAAC;CAED,MAAM,QAAQ,mBAAmB;EAC/B,GAAG;EACH,MAAM,QAAQ,0BAAA;CAChB,CAAC;CAED,MAAM,QAAQ,mBAAmB;EAC/B,GAAG;EACH,MAAM,QAAQ,mBAAA;CAChB,CAAC;CAED,MAAM,QAAQ,mBAAmB;EAC/B,GAAG;EACH,MAAM,QAAQ,wBAAA;CAChB,CAAC;CAED,MAAM,QAAQ,mBAAmB;EAC/B,GAAG;EACH,MAAM,QAAQ,qBAAA;CAChB,CAAC;CAED,MAAM,QAAQ,mBAAmB;EAC/B,GAAG;EACH,MAAM,QAAQ,wBAAA;CAChB,CAAC;CAED,MAAM,QAAQ,mBAAmB;EAC/B,GAAG;EACH,MAAM,QAAQ,2BAAA;CAChB,CAAC;AACH"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AuthUser } from "./types/user.types.cjs";
|
|
2
|
+
import "./types/credential.types.cjs";
|
|
3
|
+
import "./types/auth-lifecycle.types.cjs";
|
|
4
|
+
import "./types/mfa.types.cjs";
|
|
5
|
+
import { StorageAdapter, StorageRecord } from "@puzzle-ui/storage";
|
|
6
|
+
export declare const AUTH_USER_COLLECTION = "auth_users";
|
|
7
|
+
export declare const AUTH_CREDENTIAL_COLLECTION = "auth_credentials";
|
|
8
|
+
export declare const AUTH_ACTION_TOKEN_COLLECTION = "auth_action_tokens";
|
|
9
|
+
export declare const AUTH_AUDIT_COLLECTION = "auth_audit_events";
|
|
10
|
+
export declare const AUTH_RATE_LIMIT_COLLECTION = "auth_rate_limits";
|
|
11
|
+
export declare const AUTH_MFA_FACTOR_COLLECTION = "auth_mfa_factors";
|
|
12
|
+
export declare const AUTH_MFA_CHALLENGE_COLLECTION = "auth_mfa_challenges";
|
|
13
|
+
export declare const AUTH_RECOVERY_CODE_COLLECTION = "auth_recovery_codes";
|
|
14
|
+
export type AuthUserStorageRecord = AuthUser & StorageRecord & {
|
|
15
|
+
readonly normalizedEmail: string;
|
|
16
|
+
readonly deactivatedAt: Date | null;
|
|
17
|
+
readonly sessionsInvalidatedAt: Date | null;
|
|
18
|
+
};
|
|
19
|
+
export type InitializeAuthStorageOptions = {
|
|
20
|
+
readonly usersCollection?: string;
|
|
21
|
+
readonly credentialsCollection?: string;
|
|
22
|
+
readonly actionTokensCollection?: string;
|
|
23
|
+
readonly auditCollection?: string;
|
|
24
|
+
readonly rateLimitsCollection?: string;
|
|
25
|
+
readonly factorsCollection?: string;
|
|
26
|
+
readonly challengesCollection?: string;
|
|
27
|
+
readonly recoveryCodesCollection?: string;
|
|
28
|
+
};
|
|
29
|
+
export declare const initializeAuthStorage: (storage: StorageAdapter, options?: InitializeAuthStorageOptions) => Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AuthUser } from "./types/user.types.js";
|
|
2
|
+
import "./types/credential.types.js";
|
|
3
|
+
import "./types/auth-lifecycle.types.js";
|
|
4
|
+
import "./types/mfa.types.js";
|
|
5
|
+
import { StorageAdapter, StorageRecord } from "@puzzle-ui/storage";
|
|
6
|
+
export declare const AUTH_USER_COLLECTION = "auth_users";
|
|
7
|
+
export declare const AUTH_CREDENTIAL_COLLECTION = "auth_credentials";
|
|
8
|
+
export declare const AUTH_ACTION_TOKEN_COLLECTION = "auth_action_tokens";
|
|
9
|
+
export declare const AUTH_AUDIT_COLLECTION = "auth_audit_events";
|
|
10
|
+
export declare const AUTH_RATE_LIMIT_COLLECTION = "auth_rate_limits";
|
|
11
|
+
export declare const AUTH_MFA_FACTOR_COLLECTION = "auth_mfa_factors";
|
|
12
|
+
export declare const AUTH_MFA_CHALLENGE_COLLECTION = "auth_mfa_challenges";
|
|
13
|
+
export declare const AUTH_RECOVERY_CODE_COLLECTION = "auth_recovery_codes";
|
|
14
|
+
export type AuthUserStorageRecord = AuthUser & StorageRecord & {
|
|
15
|
+
readonly normalizedEmail: string;
|
|
16
|
+
readonly deactivatedAt: Date | null;
|
|
17
|
+
readonly sessionsInvalidatedAt: Date | null;
|
|
18
|
+
};
|
|
19
|
+
export type InitializeAuthStorageOptions = {
|
|
20
|
+
readonly usersCollection?: string;
|
|
21
|
+
readonly credentialsCollection?: string;
|
|
22
|
+
readonly actionTokensCollection?: string;
|
|
23
|
+
readonly auditCollection?: string;
|
|
24
|
+
readonly rateLimitsCollection?: string;
|
|
25
|
+
readonly factorsCollection?: string;
|
|
26
|
+
readonly challengesCollection?: string;
|
|
27
|
+
readonly recoveryCodesCollection?: string;
|
|
28
|
+
};
|
|
29
|
+
export declare const initializeAuthStorage: (storage: StorageAdapter, options?: InitializeAuthStorageOptions) => Promise<void>;
|