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