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