@voltro/plugin-auth 0.33.0 → 0.34.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.
@@ -1,820 +0,0 @@
1
- import { hashPassword as e, verifyPassword as t, verifyPasswordWithRehash as n } from "./password.js";
2
- import { clearSessionCookie as r, issueSession as i, readSessionKeyed as a, resolveSessionSecret as o, resolveSessionSecrets as s, sessionSecretsOf as c } from "./session.js";
3
- import { a as l, l as u, o as d, s as f, t as p } from "./strategy-Ce4FlGi4.js";
4
- import { hashToken as m, mintToken as h } from "./tokens.js";
5
- import { CSRF_COOKIE_NAME as g, CSRF_HEADER_NAME as _, issueCsrfToken as v, verifyCsrf as ee } from "./csrf.js";
6
- import { generateChallenge as y, verifyAssertion as b, verifyRegistration as te } from "./webauthn.js";
7
- import { createHmac as ne, randomBytes as x, timingSafeEqual as re } from "node:crypto";
8
- import { Data as ie, Effect as S } from "effect";
9
- import { buildSetCookie as ae, readCookie as oe } from "@voltro/protocol/session";
10
- import { definePlugin as se } from "@voltro/protocol";
11
- //#region src/subjectGuard.ts
12
- var ce = { ok: !0 }, C = (e, t) => S.gen(function* () {
13
- for (let n of e) {
14
- let e = yield* n(t);
15
- if (!e.ok) return e;
16
- }
17
- return ce;
18
- }), w = 30, T = 6, le = 20, ue = "sha1", de = 1, E = class extends ie.TaggedError("TotpVerifyError") {}, D = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", fe = (e) => {
19
- let t = 0, n = 0, r = "";
20
- for (let i of e) for (n = n << 8 | i, t += 8; t >= 5;) r += D[n >>> t - 5 & 31], t -= 5;
21
- return t > 0 && (r += D[n << 5 - t & 31]), r;
22
- }, pe = (e) => {
23
- let t = e.toUpperCase().replace(/[\s=]+/g, "");
24
- if (!/^[A-Z2-7]+$/.test(t)) return null;
25
- let n = new Uint8Array(Math.floor(t.length * 5 / 8)), r = 0, i = 0, a = 0;
26
- for (let e of t) {
27
- let t = D.indexOf(e);
28
- if (t === -1) return null;
29
- i = i << 5 | t, r += 5, r >= 8 && (n[a++] = i >>> r - 8 & 255, r -= 8);
30
- }
31
- return n.subarray(0, a);
32
- }, me = (e) => {
33
- let t = Buffer.alloc(8);
34
- return t.writeBigUInt64BE(BigInt(e), 0), t;
35
- }, he = (e, t) => {
36
- let n = ne(ue, Buffer.from(e));
37
- n.update(me(t));
38
- let r = n.digest(), i = r[r.length - 1] & 15, a = (r[i] & 127) << 24 | (r[i + 1] & 255) << 16 | (r[i + 2] & 255) << 8 | r[i + 3] & 255;
39
- return String(a % 10 ** T).padStart(T, "0");
40
- }, O = () => fe(x(le)), k = (e) => `otpauth://totp/${encodeURIComponent(`${e.issuer}:${e.accountName}`)}?${new URLSearchParams({
41
- secret: e.secret,
42
- issuer: e.issuer,
43
- algorithm: "SHA1",
44
- digits: String(T),
45
- period: String(w)
46
- }).toString()}`, ge = (e, t = Date.now()) => {
47
- let n = pe(e);
48
- return n ? he(n, Math.floor(t / 1e3 / w)) : "000000";
49
- }, A = (e, t, n = Date.now()) => S.gen(function* () {
50
- let r = pe(e);
51
- if (!r) return yield* S.fail(new E({ reason: "malformedSecret" }));
52
- let i = t.replace(/\s+/g, "");
53
- if (!/^[0-9]{6}$/.test(i)) return yield* S.fail(new E({ reason: "invalidCode" }));
54
- let a = Math.floor(n / 1e3 / w), o = Buffer.from(i);
55
- for (let e = -1; e <= de; e++) {
56
- let t = he(r, a + e), n = Buffer.from(t);
57
- if (n.length === o.length && re(n, o)) return !0;
58
- }
59
- return yield* S.fail(new E({ reason: "invalidCode" }));
60
- }), j = (e = 10) => {
61
- let t = [];
62
- for (let n = 0; n < e; n++) {
63
- let e = fe(x(16)).slice(0, 25).match(/.{1,5}/g) ?? [];
64
- t.push(e.join("-"));
65
- }
66
- return t;
67
- }, _e = 5, ve = 900, ye = 900, M = (e) => ({
68
- maxAttempts: e.lockout?.maxAttempts ?? _e,
69
- windowMs: (e.lockout?.windowSeconds ?? ve) * 1e3,
70
- lockMs: (e.lockout?.lockSeconds ?? ye) * 1e3
71
- }), N = (e) => e.secrets ?? (e.secret === void 0 ? s() : c(e.secret)), P = (e) => e.secret ?? o(), F = (e, t, n = {}) => ({
72
- status: e,
73
- body: JSON.stringify(t),
74
- contentType: "application/json; charset=utf-8",
75
- ...n
76
- }), I = (e, t = {}) => ({
77
- status: 302,
78
- body: "",
79
- contentType: "text/plain",
80
- location: e,
81
- ...t
82
- }), L = 3600 * 24 * 7, R = (e, t) => e.type === "user" ? {
83
- ...e,
84
- metadata: {
85
- ...e.metadata ?? {},
86
- sessionId: t
87
- }
88
- } : e, z = (e, t, n, r = {}) => S.gen(function* () {
89
- yield* t.clearLoginFailures(e.email);
90
- let a = yield* t.listMemberships(e.id), o = l("sess");
91
- yield* t.insertSession({
92
- id: o,
93
- userId: e.id,
94
- tenantId: e.tenantId,
95
- expiresAt: new Date(Date.now() + L * 1e3),
96
- ipAddress: r.ipAddress ?? null,
97
- userAgent: r.userAgent ?? null
98
- }).pipe(S.ignore);
99
- let s = R(f(e, { memberships: a }), o);
100
- return {
101
- setCookie: i(s, N(n), {
102
- ttlSeconds: L,
103
- ...n.cookieDomain ? { domain: n.cookieDomain } : {},
104
- secure: n.cookieSecure ?? !0
105
- }).setCookie,
106
- subject: s
107
- };
108
- }), B = (e, t) => S.gen(function* () {
109
- let n = t.subjectGuards ?? [];
110
- if (n.length === 0) return null;
111
- let r = yield* C(n, e);
112
- return r.ok ? null : F(403, {
113
- error: r.code,
114
- message: r.message
115
- });
116
- }), be = 300, V = (r, i, a) => S.gen(function* () {
117
- if (!r.email || !r.password) return F(400, { error: "email and password required" });
118
- let o = yield* i.findByEmail(r.email), s = (a.now ?? Date.now)(), c = yield* i.isLockedOut(r.email, s);
119
- if (c.locked) return F(429, {
120
- error: "account_locked",
121
- retryAfterSeconds: c.retryAfterSeconds
122
- });
123
- let u = o?.passwordHash, d = typeof u == "string" && u.length > 0 ? yield* n(r.password, u) : yield* S.gen(function* () {
124
- let n = yield* e("decoy-not-a-real-password").pipe(S.orElseSucceed(() => ""));
125
- return yield* t(r.password, n), { valid: !1 };
126
- });
127
- if (!o || !d.valid) return yield* i.recordLoginFailure(r.email, s, M(a)), F(401, { error: "invalid credentials" });
128
- let f = yield* B(o, a);
129
- if (f) return f;
130
- if (d.rehash && (yield* i.updatePassword(o.id, d.rehash).pipe(S.ignore)), o.mfaEnrolledAt) {
131
- let e = h("mfa-pending", 300);
132
- return yield* i.insertToken({
133
- id: l("tok"),
134
- tokenHash: e.tokenHash,
135
- userId: o.id,
136
- purpose: "mfa-pending",
137
- expiresAt: e.expiresAt
138
- }).pipe(S.ignore), F(200, {
139
- ok: !0,
140
- mfaRequired: !0,
141
- pendingToken: e.token,
142
- expiresInSeconds: 300
143
- });
144
- }
145
- let { setCookie: p, subject: m } = yield* z(o, i, a, {
146
- ipAddress: r.ipAddress ?? null,
147
- userAgent: r.userAgent ?? null
148
- });
149
- return r.redirectAfter ? I(a.successRedirect ?? "/", { setCookie: p }) : F(200, {
150
- ok: !0,
151
- subject: m
152
- }, { setCookie: p });
153
- }), H = (e, t, n) => S.gen(function* () {
154
- if (!e.pendingToken) return F(400, { error: "pending token required" });
155
- if (!e.code && !e.recoveryCode) return F(400, { error: "code or recoveryCode required" });
156
- let r = yield* t.consumeToken(m(e.pendingToken), "mfa-pending");
157
- if (!r) return F(401, { error: "invalid or expired challenge" });
158
- let i = yield* t.findById(r.userId);
159
- if (!i || !i.mfaEnrolledAt || !i.mfaSecret) return F(401, { error: "mfa not enrolled" });
160
- let a = (n.now ?? Date.now)(), o = yield* t.isLockedOut(i.email, a);
161
- if (o.locked) return F(429, {
162
- error: "account_locked",
163
- retryAfterSeconds: o.retryAfterSeconds
164
- });
165
- let s = yield* B(i, n);
166
- if (s) return s;
167
- if (!(e.recoveryCode ? yield* t.consumeRecoveryCode(i.id, m(e.recoveryCode.trim())) : yield* A(i.mfaSecret, e.code ?? "").pipe(S.map(() => !0), S.catchTag("TotpVerifyError", () => S.succeed(!1))))) return yield* t.recordLoginFailure(i.email, a, M(n)), F(401, { error: "invalid_code" });
168
- let { setCookie: c, subject: l } = yield* z(i, t, n, {
169
- ipAddress: e.ipAddress ?? null,
170
- userAgent: e.userAgent ?? null
171
- });
172
- return e.redirectAfter ? I(n.successRedirect ?? "/", { setCookie: c }) : F(200, {
173
- ok: !0,
174
- subject: l
175
- }, { setCookie: c });
176
- }), U = (t, n, r) => S.gen(function* () {
177
- if (!t.email || !t.password) return F(400, { error: "email and password required" });
178
- if (t.password.length < 8) return F(400, { error: "password must be at least 8 characters" });
179
- if (yield* n.findByEmail(t.email)) return F(409, { error: "email already registered" });
180
- let a = t.tenantId ?? r.defaultTenantId, o = yield* e(t.password), s = yield* n.insert({
181
- id: l("user"),
182
- email: t.email,
183
- passwordHash: o,
184
- tenantId: a
185
- });
186
- yield* n.addMembership({
187
- userId: s.id,
188
- tenantId: a,
189
- role: "owner"
190
- }).pipe(S.ignore);
191
- let c = yield* n.listMemberships(s.id), u = l("sess");
192
- yield* n.insertSession({
193
- id: u,
194
- userId: s.id,
195
- tenantId: a,
196
- expiresAt: new Date(Date.now() + L * 1e3),
197
- ipAddress: null,
198
- userAgent: null
199
- }).pipe(S.ignore);
200
- let d = R(f(s, { memberships: c }), u), p = i(d, N(r), {
201
- ttlSeconds: L,
202
- ...r.cookieDomain ? { domain: r.cookieDomain } : {},
203
- secure: r.cookieSecure ?? !0
204
- });
205
- return t.redirectAfter ? I(r.successRedirect ?? "/", { setCookie: p.setCookie }) : F(200, {
206
- ok: !0,
207
- subject: d
208
- }, { setCookie: p.setCookie });
209
- }).pipe(S.catchAll((e) => S.succeed(F(500, {
210
- error: "signup_failed",
211
- cause: String(e)
212
- })))), W = (e) => ({
213
- status: 302,
214
- body: "",
215
- location: e.successRedirect ?? "/",
216
- setCookie: r({
217
- ...e.cookieDomain ? { domain: e.cookieDomain } : {},
218
- secure: e.cookieSecure ?? !0
219
- })
220
- }), G = (e, t) => S.gen(function* () {
221
- let n = yield* t.findById(e.userId);
222
- if (!n) return F(404, { error: "user_not_found" });
223
- let r = O();
224
- return yield* t.setMfaSecret(n.id, r).pipe(S.catchAll(() => S.succeed(n))), F(200, {
225
- ok: !0,
226
- secret: r,
227
- otpauthUrl: k({
228
- issuer: e.issuer,
229
- accountName: e.accountName,
230
- secret: r
231
- })
232
- });
233
- }), K = (e, t) => S.gen(function* () {
234
- let n = yield* t.findById(e.userId);
235
- if (!n) return F(404, { error: "user_not_found" });
236
- if (!n.mfaSecret) return F(400, { error: "no_enrollment_in_progress" });
237
- if (n.mfaEnrolledAt) return F(409, { error: "already_enrolled" });
238
- if (!(yield* A(n.mfaSecret, e.code).pipe(S.map(() => !0), S.catchTag("TotpVerifyError", () => S.succeed(!1))))) return F(401, { error: "invalid_code" });
239
- yield* t.markMfaEnrolled(n.id).pipe(S.catchAll(() => S.succeed(n)));
240
- let r = j();
241
- return yield* t.replaceRecoveryCodes(n.id, r.map((e) => m(e))).pipe(S.ignore), F(200, {
242
- ok: !0,
243
- enrolled: !0,
244
- recoveryCodes: r
245
- });
246
- }), q = (e, t) => S.gen(function* () {
247
- let n = yield* t.findById(e.userId);
248
- if (!n) return F(404, { error: "user_not_found" });
249
- if (!n.mfaEnrolledAt) return F(400, { error: "mfa_not_enrolled" });
250
- let r = j();
251
- return yield* t.replaceRecoveryCodes(n.id, r.map((e) => m(e))).pipe(S.ignore), F(200, {
252
- ok: !0,
253
- recoveryCodes: r
254
- });
255
- }), xe = (e, t) => S.gen(function* () {
256
- let n = yield* t.findById(e.userId);
257
- return n ? (yield* t.clearMfa(n.id).pipe(S.catchAll(() => S.succeed(n))), yield* t.replaceRecoveryCodes(n.id, []).pipe(S.ignore), F(200, {
258
- ok: !0,
259
- enrolled: !1
260
- })) : F(404, { error: "user_not_found" });
261
- }), Se = (e) => {
262
- let t = v(P(e)), n = ae(g, t, {
263
- httpOnly: !1,
264
- sameSite: "lax",
265
- secure: e.cookieSecure ?? !0,
266
- ...e.cookieDomain ? { domain: e.cookieDomain } : {},
267
- path: "/"
268
- });
269
- return F(200, { csrfToken: t }, { setCookie: n });
270
- }, Ce = (e, t, n) => `${e}${t}${t.includes("?") ? "&" : "?"}token=${encodeURIComponent(n)}`, we = (e, t, n) => S.gen(function* () {
271
- if (!e.email) return F(400, { error: "email required" });
272
- let r = yield* t.findByEmail(e.email);
273
- if (r) {
274
- let i = h("magic-link");
275
- yield* t.insertToken({
276
- id: l("tok"),
277
- tokenHash: i.tokenHash,
278
- userId: r.id,
279
- purpose: "magic-link",
280
- expiresAt: i.expiresAt
281
- }).pipe(S.ignore);
282
- let a = Ce(n.appBaseUrl ?? "", e.callbackPath ?? "/auth/magic-link/callback", i.token);
283
- n.sendEmail && (yield* S.tryPromise(() => n.sendEmail({
284
- to: r.email,
285
- kind: "magic-link",
286
- actionUrl: a,
287
- subject: "Your sign-in link",
288
- text: `Sign in: ${a}`,
289
- html: `<p><a href="${a}">Sign in</a> — this link expires shortly.</p>`
290
- })).pipe(S.ignore));
291
- }
292
- return F(202, { ok: !0 });
293
- }), Te = (e, t, n) => S.gen(function* () {
294
- if (!e.token) return F(400, { error: "token required" });
295
- let r = yield* t.consumeToken(m(e.token), "magic-link");
296
- if (!r) return F(401, { error: "invalid or expired token" });
297
- let a = yield* t.findById(r.userId);
298
- if (!a) return F(401, { error: "invalid or expired token" });
299
- let o = yield* B(a, n);
300
- if (o) return o;
301
- let s = yield* t.listMemberships(a.id), c = l("sess");
302
- yield* t.insertSession({
303
- id: c,
304
- userId: a.id,
305
- tenantId: a.tenantId,
306
- expiresAt: new Date(Date.now() + L * 1e3),
307
- ipAddress: null,
308
- userAgent: null
309
- }).pipe(S.ignore);
310
- let u = R(f(a, { memberships: s }), c), d = i(u, N(n), {
311
- ttlSeconds: L,
312
- ...n.cookieDomain ? { domain: n.cookieDomain } : {},
313
- secure: n.cookieSecure ?? !0
314
- });
315
- return e.redirectAfter ? I(n.successRedirect ?? "/", { setCookie: d.setCookie }) : F(200, {
316
- ok: !0,
317
- subject: u
318
- }, { setCookie: d.setCookie });
319
- }), Ee = (e, t, n) => S.gen(function* () {
320
- if (!e.email) return F(400, { error: "email required" });
321
- let r = yield* t.findByEmail(e.email);
322
- if (r) {
323
- let i = h("password-reset");
324
- yield* t.insertToken({
325
- id: l("tok"),
326
- tokenHash: i.tokenHash,
327
- userId: r.id,
328
- purpose: "password-reset",
329
- expiresAt: i.expiresAt
330
- }).pipe(S.ignore);
331
- let a = Ce(n.appBaseUrl ?? "", e.callbackPath ?? "/auth/password-reset/callback", i.token);
332
- n.sendEmail && (yield* S.tryPromise(() => n.sendEmail({
333
- to: r.email,
334
- kind: "password-reset",
335
- actionUrl: a,
336
- subject: "Reset your password",
337
- text: `Reset your password: ${a}`,
338
- html: `<p><a href="${a}">Reset your password</a> — this link expires shortly.</p>`
339
- })).pipe(S.ignore));
340
- }
341
- return F(202, { ok: !0 });
342
- }), De = (t, n) => S.gen(function* () {
343
- if (!t.token || !t.newPassword) return F(400, { error: "token and newPassword required" });
344
- if (t.newPassword.length < 8) return F(400, { error: "password must be at least 8 characters" });
345
- let r = yield* n.consumeToken(m(t.token), "password-reset");
346
- if (!r) return F(401, { error: "invalid or expired token" });
347
- let i = yield* e(t.newPassword).pipe(S.orElseSucceed(() => ""));
348
- return i ? (yield* n.updatePassword(r.userId, i).pipe(S.ignore), yield* n.revokeAllOtherSessions(r.userId, "__none__").pipe(S.ignore), F(200, { ok: !0 })) : F(500, { error: "hash_failed" });
349
- }), Oe = (e, t) => S.gen(function* () {
350
- return F(200, { sessions: (yield* t.listSessions(e.userId)).map((e) => ({
351
- id: e.id,
352
- tenantId: e.tenantId,
353
- ipAddress: e.ipAddress ?? null,
354
- userAgent: e.userAgent ?? null,
355
- createdAt: e.createdAt.toISOString(),
356
- lastSeenAt: e.lastSeenAt.toISOString(),
357
- expiresAt: e.expiresAt.toISOString()
358
- })) });
359
- }), ke = (e, t) => S.gen(function* () {
360
- return (yield* t.revokeSession(e.userId, e.sessionId)) ? F(200, {
361
- ok: !0,
362
- revoked: 1
363
- }) : F(404, { error: "session_not_found" });
364
- }), Ae = (e, t) => S.gen(function* () {
365
- return F(200, {
366
- ok: !0,
367
- revoked: yield* t.revokeAllOtherSessions(e.userId, e.keepSessionId)
368
- });
369
- }), je = (e, t, n, r) => S.gen(function* () {
370
- let a = yield* t.findById(e.userId);
371
- if (!a) return F(404, { error: "user_not_found" });
372
- let o = yield* t.membershipRole(e.userId, e.targetTenantId);
373
- if (o === null) return F(403, { error: "not a member of the target tenant" });
374
- let s = yield* t.listMemberships(a.id), c = e.metadata === void 0 ? void 0 : Object.fromEntries(Object.entries(e.metadata).filter(([e]) => e !== "memberships")), l = f(a, {
375
- tenantId: e.targetTenantId,
376
- memberships: s,
377
- ...c === void 0 ? {} : { metadata: c }
378
- }), u = e.sessionId ? R(l, e.sessionId) : l;
379
- e.clientId !== void 0 && r && r(e.clientId, u);
380
- let d = i(u, N(n), {
381
- ttlSeconds: L,
382
- ...n.cookieDomain ? { domain: n.cookieDomain } : {},
383
- secure: n.cookieSecure ?? !0
384
- });
385
- return F(200, {
386
- ok: !0,
387
- tenantId: e.targetTenantId,
388
- role: o,
389
- subject: u
390
- }, { setCookie: d.setCookie });
391
- }), Me = (e, t) => S.gen(function* () {
392
- return F(200, { memberships: (yield* t.listMemberships(e.userId)).map((e) => ({
393
- tenantId: e.tenantId,
394
- role: e.role,
395
- joinedAt: e.joinedAt.toISOString()
396
- })) });
397
- }), J = (e, t, n = {}) => ({
398
- status: e,
399
- body: JSON.stringify(t),
400
- contentType: "application/json; charset=utf-8",
401
- ...n
402
- }), Ne = 3600 * 24 * 7, Pe = () => {
403
- let e = /* @__PURE__ */ new Map();
404
- return {
405
- put: (t, n, r) => S.sync(() => {
406
- e.set(t, {
407
- challenge: n,
408
- expiresAt: Date.now() + r * 1e3
409
- });
410
- }),
411
- take: (t) => S.sync(() => {
412
- let n = e.get(t);
413
- return e.delete(t), !n || n.expiresAt < Date.now() ? null : n.challenge;
414
- })
415
- };
416
- }, Fe = (e) => ({
417
- put: (t, n, r) => S.gen(function* () {
418
- let i = /* @__PURE__ */ new Date(), a = new Date(i.getTime() + r * 1e3);
419
- (yield* e`
420
- UPDATE "passkeyChallenges"
421
- SET challenge = ${n}, "expiresAt" = ${a}, "createdAt" = ${i}
422
- WHERE key = ${t}
423
- RETURNING key
424
- `).length === 0 && (yield* e`
425
- INSERT INTO "passkeyChallenges" (id, key, challenge, "expiresAt", "createdAt")
426
- VALUES (${l("pkc")}, ${t}, ${n}, ${a}, ${i})
427
- `);
428
- }).pipe(S.asVoid, S.orElseSucceed(() => void 0)),
429
- take: (t) => S.gen(function* () {
430
- let n = (yield* e`
431
- DELETE FROM "passkeyChallenges" WHERE key = ${t}
432
- RETURNING challenge, "expiresAt"
433
- `)[0];
434
- return n && n.expiresAt.getTime() > Date.now() ? n.challenge : null;
435
- }).pipe(S.orElseSucceed(() => null))
436
- }), Y = 300, Ie = (e, t, n, r) => S.gen(function* () {
437
- if (!(yield* t.findById(e.userId))) return J(404, { error: "user_not_found" });
438
- let i = y();
439
- yield* n.put(`${e.userId}:register`, i, Y);
440
- let a = yield* t.listPasskeys(e.userId);
441
- return J(200, {
442
- challenge: i,
443
- rp: {
444
- id: r.rpId,
445
- name: r.rpName
446
- },
447
- user: {
448
- id: e.userId,
449
- name: e.userName,
450
- displayName: e.userName
451
- },
452
- pubKeyCredParams: [{
453
- type: "public-key",
454
- alg: -7
455
- }, {
456
- type: "public-key",
457
- alg: -257
458
- }],
459
- excludeCredentials: a.map((e) => ({
460
- type: "public-key",
461
- id: e.credentialId
462
- })),
463
- authenticatorSelection: {
464
- userVerification: "preferred",
465
- residentKey: "preferred"
466
- },
467
- timeout: Y * 1e3
468
- });
469
- }), Le = (e, t, n, r) => S.gen(function* () {
470
- if (!(yield* t.findById(e.userId))) return J(404, { error: "user_not_found" });
471
- let i = yield* n.take(`${e.userId}:register`);
472
- if (!i) return J(400, { error: "no_pending_challenge" });
473
- let a = te({
474
- clientDataJSON: e.clientDataJSON,
475
- authenticatorData: e.authenticatorData,
476
- expectedChallenge: i,
477
- expectedOrigin: r.origin,
478
- rpId: r.rpId
479
- });
480
- return a.ok ? (yield* t.insertPasskey({
481
- id: l("pk"),
482
- credentialId: a.result.credentialId,
483
- userId: e.userId,
484
- publicKey: a.result.publicKey,
485
- counter: a.result.counter,
486
- transports: e.transports ?? null
487
- }).pipe(S.ignore), J(200, {
488
- ok: !0,
489
- credentialId: a.result.credentialId
490
- })) : J(400, { error: a.error });
491
- }), Re = (e, t, n, r) => S.gen(function* () {
492
- let i = y(), a = e.userId ? `${e.userId}:assert` : "*:assert";
493
- yield* n.put(a, i, Y);
494
- let o = e.userId ? (yield* t.listPasskeys(e.userId)).map((e) => ({
495
- type: "public-key",
496
- id: e.credentialId
497
- })) : [];
498
- return J(200, {
499
- challenge: i,
500
- rpId: r.rpId,
501
- allowCredentials: o,
502
- userVerification: "preferred",
503
- timeout: Y * 1e3
504
- });
505
- }), ze = (e, t, n, r, a) => S.gen(function* () {
506
- let o = yield* t.findPasskey(e.credentialId);
507
- if (!o) return J(401, { error: "unknown_credential" });
508
- let s = e.userId ? `${e.userId}:assert` : "*:assert", c = yield* n.take(s);
509
- if (!c) return J(400, { error: "no_pending_challenge" });
510
- let u = b({
511
- clientDataJSON: e.clientDataJSON,
512
- authenticatorData: e.authenticatorData,
513
- signature: e.signature,
514
- expectedChallenge: c,
515
- expectedOrigin: r.origin,
516
- rpId: r.rpId,
517
- storedPublicKey: o.publicKey,
518
- storedCounter: o.counter
519
- });
520
- if (!u.ok) return J(401, { error: u.error });
521
- if (!(yield* t.advancePasskeyCounter(e.credentialId, u.result.newCounter)) && u.result.newCounter !== 0) return J(401, { error: "counter_regressed" });
522
- let d = yield* t.findById(o.userId);
523
- if (!d) return J(401, { error: "unknown_credential" });
524
- let p = yield* B(d, a);
525
- if (p) return p;
526
- let m = yield* t.listMemberships(d.id), h = l("sess");
527
- yield* t.insertSession({
528
- id: h,
529
- userId: d.id,
530
- tenantId: d.tenantId,
531
- expiresAt: new Date(Date.now() + Ne * 1e3),
532
- ipAddress: null,
533
- userAgent: null
534
- }).pipe(S.ignore);
535
- let g = f(d, { memberships: m }), _ = g.type === "user" ? {
536
- ...g,
537
- metadata: {
538
- ...g.metadata ?? {},
539
- sessionId: h
540
- }
541
- } : g, v = i(_, N(a), {
542
- ttlSeconds: Ne,
543
- ...a.cookieDomain ? { domain: a.cookieDomain } : {},
544
- secure: a.cookieSecure ?? !0
545
- });
546
- return J(200, {
547
- ok: !0,
548
- subject: _
549
- }, { setCookie: v.setCookie });
550
- }), Be = (e) => {
551
- for (let t of e) if (Ve(t)) return t.auth.sessionStrategy;
552
- return null;
553
- }, Ve = (e) => typeof e.auth == "object" && e.auth !== null && typeof e.auth.sessionStrategy == "object", He = "/auth", Ue = (e) => {
554
- let t = Buffer.from(e.rawBody).toString("utf8");
555
- if (!t) return {};
556
- if ((e.headers["content-type"] ?? "").includes("application/x-www-form-urlencoded")) {
557
- let e = new URLSearchParams(t), n = {};
558
- for (let [t, r] of e) n[t] = r;
559
- return n;
560
- }
561
- try {
562
- let e = JSON.parse(t);
563
- return typeof e == "object" && e ? e : {};
564
- } catch {
565
- return {};
566
- }
567
- }, X = (e) => ({
568
- status: e.status,
569
- ...e.body === void 0 ? {} : { body: e.body },
570
- ...e.contentType === void 0 ? {} : { contentType: e.contentType },
571
- headers: {
572
- ...e.setCookie === void 0 ? {} : { "set-cookie": e.setCookie },
573
- ...e.location === void 0 ? {} : { location: e.location }
574
- }
575
- }), Z = (e, t) => ({
576
- status: e,
577
- body: JSON.stringify(t),
578
- contentType: "application/json; charset=utf-8"
579
- }), Q = (e) => typeof e == "string" ? e : void 0, $ = (e, t) => typeof t == "string" && t.length > 0 ? { [e]: t } : {}, We = (e) => {
580
- let t = e.prefix ?? He, n = e.store, r = e.challengeStore ?? Pe(), o = {
581
- ...e.secret === void 0 ? {} : { secret: e.secret },
582
- ...e.secrets === void 0 ? {} : { secrets: e.secrets },
583
- defaultTenantId: e.defaultTenantId,
584
- ...e.cookieDomain === void 0 ? {} : { cookieDomain: e.cookieDomain },
585
- ...e.cookieSecure === void 0 ? {} : { cookieSecure: e.cookieSecure },
586
- ...e.successRedirect === void 0 ? {} : { successRedirect: e.successRedirect },
587
- ...e.sendEmail === void 0 ? {} : { sendEmail: e.sendEmail },
588
- ...e.appBaseUrl === void 0 ? {} : { appBaseUrl: e.appBaseUrl },
589
- ...e.subjectGuards === void 0 ? {} : { subjectGuards: e.subjectGuards },
590
- ...e.lockout === void 0 ? {} : { lockout: e.lockout },
591
- ...e.now === void 0 ? {} : { now: e.now }
592
- }, s = u(n, e.sessionRevocation ?? {}), c = async (e) => {
593
- let t = N(o), r = a(e.headers.cookie, t);
594
- if (!r) return null;
595
- let c = d(r.subject);
596
- if (c && !await S.runPromise(s.isLive(c))) return null;
597
- if (r.renew || r.kid !== t.current.kid) {
598
- let e = Math.max(r.exp - r.iat, 60), a = i(r.subject, t, {
599
- ttlSeconds: e,
600
- ...o.cookieDomain ? { domain: o.cookieDomain } : {},
601
- secure: o.cookieSecure ?? !0
602
- });
603
- return c && await S.runPromise(n.touchSession(c, new Date(Date.now() + e * 1e3))), {
604
- subject: r.subject,
605
- sessionId: c,
606
- renewSetCookie: a.setCookie
607
- };
608
- }
609
- return {
610
- subject: r.subject,
611
- sessionId: c
612
- };
613
- }, l = (e, t) => {
614
- if (!t) return e;
615
- let n = e.headers ?? {};
616
- return n["set-cookie"] ? e : {
617
- ...e,
618
- headers: {
619
- ...n,
620
- "set-cookie": t
621
- }
622
- };
623
- }, f = (t) => {
624
- let n = t.headers[_];
625
- return ee(n, oe(t.headers.cookie, g), P(e));
626
- }, m = (e, n) => e.path === `${t}${n}`, h = (e) => S.runPromise(e).then(X), v = {
627
- method: "*",
628
- path: t,
629
- handle: async (i) => {
630
- let a = i.method === "GET" ? {} : Ue(i);
631
- if (i.method === "GET" && m(i, "/csrf")) return X(Se(o));
632
- if (i.method === "POST" && m(i, "/sign-in")) return h(V({
633
- email: Q(a.email) ?? "",
634
- password: Q(a.password) ?? "",
635
- ...a.redirectAfter === void 0 ? {} : { redirectAfter: !!a.redirectAfter },
636
- ...i.headers["x-forwarded-for"] ? { ipAddress: i.headers["x-forwarded-for"] } : {},
637
- ...i.headers["user-agent"] ? { userAgent: i.headers["user-agent"] } : {}
638
- }, n, o));
639
- if (i.method === "POST" && m(i, "/sign-up")) return h(U({
640
- email: Q(a.email) ?? "",
641
- password: Q(a.password) ?? "",
642
- ...$("tenantId", a.tenantId),
643
- ...a.redirectAfter === void 0 ? {} : { redirectAfter: !!a.redirectAfter }
644
- }, n, o));
645
- if (i.method === "POST" && m(i, "/sign-out")) {
646
- let e = await c(i);
647
- return e && e.subject.type === "user" && e.sessionId && (await S.runPromise(n.revokeSession(e.subject.id, e.sessionId)), s.invalidate(e.sessionId)), X(W(o));
648
- }
649
- if (i.method === "POST" && m(i, "/magic-link")) return h(we({
650
- email: Q(a.email) ?? "",
651
- ...$("callbackPath", a.callbackPath)
652
- }, n, o));
653
- if (i.method === "POST" && m(i, "/magic-link/callback")) return h(Te({
654
- token: Q(a.token) ?? "",
655
- ...a.redirectAfter === void 0 ? {} : { redirectAfter: !!a.redirectAfter }
656
- }, n, o));
657
- if (i.method === "POST" && m(i, "/password-reset")) return h(Ee({
658
- email: Q(a.email) ?? "",
659
- ...$("callbackPath", a.callbackPath)
660
- }, n, o));
661
- if (i.method === "POST" && m(i, "/password-reset/confirm")) return h(De({
662
- token: Q(a.token) ?? "",
663
- newPassword: Q(a.newPassword) ?? ""
664
- }, n));
665
- if (i.method === "POST" && m(i, "/mfa/verify")) return h(H({
666
- pendingToken: Q(a.pendingToken) ?? "",
667
- ...$("code", a.code),
668
- ...$("recoveryCode", a.recoveryCode),
669
- ...a.redirectAfter === void 0 ? {} : { redirectAfter: !!a.redirectAfter },
670
- ...i.headers["x-forwarded-for"] ? { ipAddress: i.headers["x-forwarded-for"] } : {},
671
- ...i.headers["user-agent"] ? { userAgent: i.headers["user-agent"] } : {}
672
- }, n, o));
673
- if (e.passkey && i.method === "POST" && m(i, "/passkey/assert/options")) return h(Re({ ...$("userId", a.userId) }, n, r, e.passkey));
674
- if (e.passkey && i.method === "POST" && m(i, "/passkey/assert/verify")) return h(ze({
675
- credentialId: Q(a.credentialId) ?? "",
676
- clientDataJSON: Q(a.clientDataJSON) ?? "",
677
- authenticatorData: Q(a.authenticatorData) ?? "",
678
- signature: Q(a.signature) ?? "",
679
- ...$("userId", a.userId)
680
- }, n, r, e.passkey, o));
681
- let u = await c(i), d = u?.subject ?? null, p = () => !d || d.type !== "user" ? Z(401, { error: "unauthenticated" }) : { userId: d.id }, g = () => f(i) ? null : Z(403, { error: "csrf" }), _ = (e) => h(e).then((e) => l(e, u?.renewSetCookie));
682
- if (i.method === "GET" && m(i, "/session")) {
683
- let e = p();
684
- return "status" in e ? e : l(Z(200, {
685
- ok: !0,
686
- subject: d,
687
- renewed: u?.renewSetCookie !== void 0
688
- }), u?.renewSetCookie);
689
- }
690
- if (i.method === "GET" && m(i, "/sessions")) {
691
- let e = p();
692
- return "status" in e ? e : _(Oe({ userId: e.userId }, n));
693
- }
694
- if (i.method === "GET" && m(i, "/memberships")) {
695
- let e = p();
696
- return "status" in e ? e : _(Me({ userId: e.userId }, n));
697
- }
698
- if (i.method === "POST" && m(i, "/sessions/revoke")) {
699
- let e = p();
700
- if ("status" in e) return e;
701
- let t = g();
702
- if (t) return t;
703
- let r = Q(a.sessionId) ?? "";
704
- return _(ke({
705
- userId: e.userId,
706
- sessionId: r
707
- }, n)).then((e) => (r && s.invalidate(r), e));
708
- }
709
- if (i.method === "POST" && m(i, "/sessions/revoke-others")) {
710
- let e = p();
711
- return "status" in e ? e : g() || _(Ae({
712
- userId: e.userId,
713
- keepSessionId: u?.sessionId ?? "__none__"
714
- }, n));
715
- }
716
- if (i.method === "POST" && m(i, "/switch-tenant")) {
717
- let t = p();
718
- return "status" in t ? t : g() || h(je({
719
- userId: t.userId,
720
- targetTenantId: Q(a.targetTenantId) ?? "",
721
- ...typeof a.clientId == "number" ? { clientId: a.clientId } : {},
722
- ...u?.sessionId ? { sessionId: u.sessionId } : {},
723
- ...d?.type === "user" && d.metadata ? { metadata: d.metadata } : {}
724
- }, n, o, e.rebind));
725
- }
726
- if (e.mfa && i.method === "POST" && m(i, "/mfa/enroll/start")) {
727
- let t = p();
728
- if ("status" in t) return t;
729
- let r = g();
730
- if (r) return r;
731
- let i = await S.runPromise(n.findById(t.userId));
732
- return _(G({
733
- userId: t.userId,
734
- issuer: e.mfa.issuer,
735
- accountName: i?.email ?? t.userId
736
- }, n));
737
- }
738
- if (e.mfa && i.method === "POST" && m(i, "/mfa/enroll/verify")) {
739
- let e = p();
740
- return "status" in e ? e : g() || _(K({
741
- userId: e.userId,
742
- code: Q(a.code) ?? ""
743
- }, n));
744
- }
745
- if (e.mfa && i.method === "POST" && m(i, "/mfa/unenroll")) {
746
- let e = p();
747
- return "status" in e ? e : g() || _(xe({ userId: e.userId }, n));
748
- }
749
- if (e.mfa && i.method === "POST" && m(i, "/mfa/recovery-codes/regenerate")) {
750
- let e = p();
751
- return "status" in e ? e : g() || _(q({ userId: e.userId }, n));
752
- }
753
- if (e.passkey && i.method === "POST" && m(i, "/passkey/register/options")) {
754
- let t = p();
755
- return "status" in t ? t : g() || _(Ie({
756
- userId: t.userId,
757
- userName: Q(a.userName) ?? t.userId
758
- }, n, r, e.passkey));
759
- }
760
- if (e.passkey && i.method === "POST" && m(i, "/passkey/register/verify")) {
761
- let t = p();
762
- return "status" in t ? t : g() || _(Le({
763
- userId: t.userId,
764
- credentialId: Q(a.credentialId) ?? "",
765
- clientDataJSON: Q(a.clientDataJSON) ?? "",
766
- authenticatorData: Q(a.authenticatorData) ?? "",
767
- ...$("transports", a.transports)
768
- }, n, r, e.passkey));
769
- }
770
- return !e.passkey && i.path.startsWith(`${t}/passkey/`) ? Z(501, { error: "passkey_not_configured" }) : Z(404, { error: "not_found" });
771
- }
772
- }, y = se({
773
- name: e.name ? `@voltro/plugin-auth#${e.name}` : "@voltro/plugin-auth",
774
- description: "Auth routes — mounts the full auth surface (password, magic-link, password-reset, CSRF, session probe/list/revoke, memberships, switch-tenant, passkey ceremonies) under /auth on the framework listener, with keyed secret rotation + request-time revocation.",
775
- permissions: [],
776
- declaredEnv: Ge,
777
- httpRoutes: [v],
778
- onActivate: (n) => S.sync(() => {
779
- n.logger.info("auth routes active", {
780
- prefix: t,
781
- passkey: e.passkey ? e.passkey.rpId : "disabled",
782
- email: e.sendEmail ? "wired" : "unwired",
783
- revocationWindowMs: s.ttlMs
784
- });
785
- }),
786
- onDeactivate: () => S.void
787
- }), b = e.secrets ?? e.secret;
788
- return {
789
- ...y,
790
- auth: { sessionStrategy: p({
791
- ...b === void 0 ? {} : { secret: b },
792
- store: n,
793
- revocation: s
794
- }) }
795
- };
796
- }, Ge = [
797
- {
798
- name: "VOLTRO_SESSION_SECRET_PREVIOUS",
799
- required: !1,
800
- secret: !0,
801
- description: "The session secret being rotated AWAY from. While set, cookies signed with it still verify (and get re-issued under the current secret on the next authenticated /auth response). Set the new secret as VOLTRO_SESSION_SECRET, move the old one here for one session lifetime, then drop it.",
802
- example: "the-previous-signing-secret"
803
- },
804
- {
805
- name: "VOLTRO_SESSION_KID",
806
- required: !1,
807
- secret: !1,
808
- description: "Non-secret key-id label stamped into cookies signed with the current secret (default 'k0').",
809
- example: "2026-07"
810
- },
811
- {
812
- name: "VOLTRO_SESSION_KID_PREVIOUS",
813
- required: !1,
814
- secret: !1,
815
- description: "Non-secret key-id label for the previous secret (default 'k-previous').",
816
- example: "2026-01"
817
- }
818
- ];
819
- //#endregion
820
- export { z as A, Ee as C, W as D, V as E, k as F, A as I, C as L, j as M, ge as N, U as O, O as P, De as S, ke as T, G as _, ze as a, xe as b, Pe as c, B as d, Se as f, we as g, Te as h, Re as i, E as j, je as k, be as l, Oe as m, Be as n, Ie as o, Me as p, Fe as r, Le as s, We as t, N as u, K as v, Ae as w, H as x, q as y };