@voltro/plugin-auth 0.86.0 → 0.88.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +5484 -0
  2. package/README.md +83 -0
  3. package/THIRD-PARTY-NOTICES.md +3 -227
  4. package/dist/admission.js +1 -2
  5. package/dist/appUserStore.js +1 -2
  6. package/dist/authExtensions.js +8 -0
  7. package/dist/csrf.js +1 -2
  8. package/dist/emailVerification.js +14 -13
  9. package/dist/guest.d.ts +99 -0
  10. package/dist/guest.js +3 -0
  11. package/dist/guestClient.js +236 -0
  12. package/dist/guestCredential.js +11 -0
  13. package/dist/guestPasswordUpgrade.js +84 -0
  14. package/dist/guestPlugin.js +165 -0
  15. package/dist/guestSchema.js +19 -0
  16. package/dist/guestSecrets.js +9 -0
  17. package/dist/guestService.js +223 -0
  18. package/dist/handlers.js +114 -115
  19. package/dist/impersonation.js +80 -80
  20. package/dist/index.d.ts +265 -15
  21. package/dist/index.js +27 -19
  22. package/dist/invitations.js +24 -25
  23. package/dist/mail.js +1 -2
  24. package/dist/memberships.js +1 -2
  25. package/dist/native/guestStorage.js +40 -0
  26. package/dist/native.d.ts +110 -0
  27. package/dist/native.js +46 -45
  28. package/dist/nativeRoutes.js +62 -74
  29. package/dist/overlayPlugin.js +166 -0
  30. package/dist/passkeyHandlers.js +1 -2
  31. package/dist/password.js +1 -2
  32. package/dist/plugin.d.ts +119 -10
  33. package/dist/plugin.js +100 -80
  34. package/dist/postgresUserStore.js +20 -9
  35. package/dist/revocation.js +41 -25
  36. package/dist/schema.d.ts +41 -1
  37. package/dist/schema.js +100 -100
  38. package/dist/session.js +1 -2
  39. package/dist/sessionFamily.js +14 -0
  40. package/dist/sessionRegistry.js +94 -80
  41. package/dist/signedClaims.js +23 -0
  42. package/dist/strategy.d.ts +13 -9
  43. package/dist/strategy.js +27 -24
  44. package/dist/subjectGuard.js +1 -2
  45. package/dist/tokens.js +1 -2
  46. package/dist/totp.js +1 -2
  47. package/dist/userStore.js +146 -140
  48. package/dist/web/guestStorage.js +46 -0
  49. package/dist/web/passkeyClient.js +1 -2
  50. package/dist/web/requireAuth.js +1 -2
  51. package/dist/web/subjectContext.js +1 -2
  52. package/dist/web.d.ts +94 -0
  53. package/dist/web.js +4 -2
  54. package/dist/webauthn.js +1 -2
  55. package/package.json +9 -4
@@ -0,0 +1,236 @@
1
+ import { parseGuestCredential as e } from "./guestCredential.js";
2
+ // src/guestClient.ts
3
+ var t = class extends Error {
4
+ code;
5
+ status;
6
+ constructor(e, t) {
7
+ super("Guest authentication: " + e), this.code = e, this.status = t, this.name = "GuestClientError";
8
+ }
9
+ }, n = (e) => {
10
+ if (typeof e != "object" || !e) throw new t("invalid_response");
11
+ let n = e, r = n.subject;
12
+ if (typeof n.session != "string" || !n.session || !r || r.type !== "user" || r.kind !== "guest" || typeof r.id != "string" || !r.id.trim() || typeof r.tenantId != "string" || !r.tenantId.trim()) throw new t("invalid_response");
13
+ return {
14
+ session: n.session,
15
+ subject: r
16
+ };
17
+ }, r = (r, i) => {
18
+ let a = n(r), o = e(i);
19
+ if (!o || a.subject.id !== o.guestId) throw new t("invalid_response");
20
+ return a;
21
+ }, i = (t, n) => {
22
+ let r = e(t), i = e(n);
23
+ return r !== null && i !== null && r.guestId === i.guestId && r.deviceId === i.deviceId && r.secret !== i.secret;
24
+ }, a = (e) => {
25
+ if (e === null || !e.startsWith("{")) return e;
26
+ let n;
27
+ try {
28
+ n = JSON.parse(e);
29
+ } catch {
30
+ throw new t("storage_unavailable");
31
+ }
32
+ if (!n || typeof n != "object" || Array.isArray(n)) throw new t("storage_unavailable");
33
+ let r = n;
34
+ if (r.kind !== "guest-rotation" || typeof r.current != "string" || typeof r.next != "string" || Object.keys(r).sort().join(",") !== "current,kind,next" || !i(r.current, r.next)) throw new t("storage_unavailable");
35
+ return {
36
+ kind: "guest-rotation",
37
+ current: r.current,
38
+ next: r.next
39
+ };
40
+ }, o = (e) => {
41
+ let n = e.fetch ?? globalThis.fetch, o = e.apiBaseUrl.replace(/\/+$/, "") + (e.prefix ?? "/auth/guest"), s = Promise.resolve(), c = (n) => {
42
+ let r = async () => {
43
+ try {
44
+ return await e.storage.exclusive(n);
45
+ } catch (e) {
46
+ throw e instanceof t ? e : new t("storage_unavailable");
47
+ }
48
+ }, i = s.then(r, r);
49
+ return s = i.then(() => void 0, () => void 0), i;
50
+ }, l = async (e) => {
51
+ try {
52
+ return await e();
53
+ } catch {
54
+ throw new t("storage_unavailable");
55
+ }
56
+ }, u = async (r, i) => {
57
+ let a = { "content-type": "application/json" }, s = e.cookies ? "include" : "omit";
58
+ try {
59
+ if (e.cookies) {
60
+ let e = await n(o + "/csrf", {
61
+ credentials: s,
62
+ cache: "no-store"
63
+ });
64
+ if (!e.ok) throw new t("transport_failed", e.status);
65
+ let r = await e.json();
66
+ if (!r || typeof r != "object" || !("token" in r) || typeof r.token != "string") throw new t("invalid_response");
67
+ a["x-csrf-token"] = r.token;
68
+ }
69
+ let c = await n(o + r, {
70
+ method: "POST",
71
+ credentials: s,
72
+ headers: a,
73
+ body: JSON.stringify(i),
74
+ cache: "no-store"
75
+ });
76
+ if (!c.ok) throw new t(c.status === 401 ? "credential_rejected" : "transport_failed", c.status);
77
+ let l = await c.json();
78
+ if (!l || typeof l != "object" || Array.isArray(l)) throw new t("invalid_response");
79
+ return l;
80
+ } catch (e) {
81
+ throw e instanceof t ? e : new t("transport_failed");
82
+ }
83
+ }, d = e.apiBaseUrl.replace(/\/+$/, "") + (e.accountPrefix ?? "/auth"), f = async (e, r, i, a = "json") => {
84
+ try {
85
+ let o = await n(d + "/csrf", {
86
+ credentials: "include",
87
+ cache: "no-store"
88
+ });
89
+ if (!o.ok) throw new t("transport_failed", o.status);
90
+ let s = await o.json();
91
+ if (!s || typeof s != "object" || !("csrfToken" in s) || typeof s.csrfToken != "string") throw new t("invalid_response");
92
+ let c = await n(d + e, {
93
+ method: "POST",
94
+ credentials: "include",
95
+ cache: "no-store",
96
+ ...a === "redirect" ? { redirect: "manual" } : {},
97
+ headers: {
98
+ "content-type": "application/json",
99
+ "x-csrf-token": s.csrfToken
100
+ },
101
+ body: JSON.stringify(r)
102
+ });
103
+ if (a === "redirect" && (c.type === "opaqueredirect" || c.status >= 200 && c.status < 400)) return {};
104
+ if (!c.ok) throw new t(i(c.status), c.status);
105
+ let l = await c.json();
106
+ if (!l || typeof l != "object" || Array.isArray(l)) throw new t("invalid_response");
107
+ return l;
108
+ } catch (e) {
109
+ throw e instanceof t ? e : new t("transport_failed");
110
+ }
111
+ }, p = async () => {
112
+ if (!e.cookies) return null;
113
+ let r;
114
+ try {
115
+ r = await n(d + "/session", {
116
+ credentials: "include",
117
+ cache: "no-store"
118
+ });
119
+ } catch {
120
+ throw new t("transport_failed");
121
+ }
122
+ if (r.status === 401 || r.status === 404) return null;
123
+ if (!r.ok) throw new t("transport_failed", r.status);
124
+ let i;
125
+ try {
126
+ i = await r.json();
127
+ } catch {
128
+ throw new t("invalid_response");
129
+ }
130
+ let a = i && typeof i == "object" ? i.subject : void 0;
131
+ if (!a || a.type !== "user" || typeof a.id != "string" || !a.id.trim() || typeof a.tenantId != "string") throw new t("invalid_response");
132
+ return a.kind === "guest" ? null : { subject: a };
133
+ }, m = async (n, i) => {
134
+ let a = r(n, i.current);
135
+ if (n.deviceCredential !== void 0 && n.deviceCredential !== i.next) throw new t("invalid_response");
136
+ return await l(() => e.storage.write(i.next)), a;
137
+ }, h = async (e) => {
138
+ let n;
139
+ try {
140
+ n = await u("/resume", { credential: e.next });
141
+ } catch (r) {
142
+ if (!(r instanceof t) || r.code !== "credential_rejected") throw r;
143
+ n = await u("/rotate", {
144
+ credential: e.current,
145
+ nextCredential: e.next
146
+ });
147
+ }
148
+ return m(n, e);
149
+ }, g = () => c(async () => {
150
+ let n = a(await l(() => e.storage.read()));
151
+ if (typeof n == "object" && n) return h(n);
152
+ if (n !== null) return r(await u("/resume", { credential: n }), n);
153
+ if (await p()) throw new t("account_signed_in");
154
+ let i = await u("/create", {});
155
+ if (typeof i.deviceCredential != "string" || !i.deviceCredential) throw new t("invalid_response");
156
+ let o = r(i, i.deviceCredential);
157
+ try {
158
+ await l(() => e.storage.write(String(i.deviceCredential)));
159
+ } catch (e) {
160
+ try {
161
+ await u("/revoke", { credential: i.deviceCredential });
162
+ } catch {}
163
+ throw e;
164
+ }
165
+ return o;
166
+ });
167
+ return {
168
+ connect: g,
169
+ rotate: () => c(async () => {
170
+ let n = a(await l(() => e.storage.read()));
171
+ if (n === null) throw new t("credential_rejected");
172
+ if (typeof n == "object") return h(n);
173
+ let r = await u("/prepare-rotation", { credential: n });
174
+ if (typeof r.deviceCredential != "string" || !i(n, r.deviceCredential)) throw new t("invalid_response");
175
+ let o = {
176
+ kind: "guest-rotation",
177
+ current: n,
178
+ next: r.deviceCredential
179
+ };
180
+ return await l(() => e.storage.write(JSON.stringify(o))), m(await u("/rotate", {
181
+ credential: n,
182
+ nextCredential: o.next
183
+ }), o);
184
+ }),
185
+ revoke: () => c(async () => {
186
+ let n = a(await l(() => e.storage.read()));
187
+ if (n !== null) {
188
+ for (let e of typeof n == "string" ? [n] : [n.next, n.current]) try {
189
+ await u("/revoke", { credential: e });
190
+ break;
191
+ } catch (e) {
192
+ if (!(e instanceof t) || e.code !== "credential_rejected") throw e;
193
+ }
194
+ await l(() => e.storage.remove());
195
+ }
196
+ }),
197
+ upgrade: (n) => c(async () => {
198
+ let r = a(await l(() => e.storage.read()));
199
+ if (typeof r != "string") throw new t("credential_rejected");
200
+ let i = await f("/upgrade-guest", {
201
+ credential: r,
202
+ email: n.email,
203
+ password: n.password
204
+ }, (e) => e === 401 ? "credential_rejected" : e === 409 ? "upgrade_conflict" : "transport_failed");
205
+ if (i.ok !== !0) throw new t("invalid_response");
206
+ return await l(() => e.storage.remove()), { subject: i.subject };
207
+ }),
208
+ account: () => p(),
209
+ start: async () => {
210
+ let e = await p();
211
+ if (e) return {
212
+ kind: "account",
213
+ subject: e.subject
214
+ };
215
+ let t = await g();
216
+ return {
217
+ kind: "guest",
218
+ subject: t.subject,
219
+ session: t.session
220
+ };
221
+ },
222
+ signIn: async (e) => {
223
+ await f("/sign-in", {
224
+ email: e.email,
225
+ password: e.password
226
+ }, (e) => e === 400 || e === 401 || e === 429 ? "credential_rejected" : "transport_failed");
227
+ let n = await p();
228
+ if (!n) throw new t("credential_rejected");
229
+ return n;
230
+ },
231
+ signOut: async () => {
232
+ await f("/sign-out", {}, () => "transport_failed", "redirect");
233
+ }
234
+ };
235
+ };
236
+ export { t as GuestClientError, o as createGuestClient };
@@ -0,0 +1,11 @@
1
+ // src/guestCredential.ts
2
+ var e = (e) => {
3
+ if (typeof e != "string" || e.length > 512) return null;
4
+ let [t, n, r, i] = e.split(".");
5
+ return !t || !/^guest_[a-zA-Z0-9_-]+$/.test(t) || !n || !/^gdev_[a-zA-Z0-9_-]+$/.test(n) || !r || !/^[a-zA-Z0-9_-]{43}$/.test(r) || i !== void 0 ? null : {
6
+ guestId: t,
7
+ deviceId: n,
8
+ secret: r
9
+ };
10
+ };
11
+ export { e as parseGuestCredential };
@@ -0,0 +1,84 @@
1
+ import { hashPassword as e } from "./password.js";
2
+ import { newAuthId as t } from "./userStore.js";
3
+ import { dataStoreSessionRegistry as n } from "./sessionRegistry.js";
4
+ import { GuestAuthError as r } from "./guestService.js";
5
+ import { decideAdmission as i } from "./admission.js";
6
+ import { runSubjectGuards as a } from "./subjectGuard.js";
7
+ import { issueUserSession as o } from "./handlers.js";
8
+ import { Effect as s } from "effect";
9
+ import { and as c, eq as l } from "@voltro/database";
10
+ // src/guestPasswordUpgrade.ts
11
+ var u = (e, t, n) => ({
12
+ table: e,
13
+ predicate: l(t, n),
14
+ order: [],
15
+ take: void 0,
16
+ skip: void 0,
17
+ projection: void 0
18
+ }), d = (e, t, n) => ({
19
+ kind: "refused",
20
+ status: e,
21
+ code: t,
22
+ message: n
23
+ }), f = (f) => s.gen(function* () {
24
+ let { email: p, password: m, config: h } = f;
25
+ if (!p || !m) return d(400, "invalid", "email and password required");
26
+ if (m.length < 8) return d(400, "invalid", "password must be at least 8 characters");
27
+ if (yield* f.users.findByEmail(p)) return d(409, "email_taken", "email already registered");
28
+ let g = h.defaultTenantId, _ = yield* i(h.admit, {
29
+ email: p,
30
+ tenantId: g,
31
+ via: "sign-up"
32
+ });
33
+ if (_) return d(403, "not_admitted", _.message ?? _.reason);
34
+ let v = yield* e(m);
35
+ return yield* f.service.upgrade(f.credential, async (e) => {
36
+ let i = e.store;
37
+ if (e.tenantId !== g) throw new r({ code: "credential_rejected" });
38
+ if ((await i.query(u("users", "email", p))).length !== 0) throw new r({ code: "upgrade_conflict" });
39
+ let d = {
40
+ id: e.subjectId,
41
+ tenantId: g,
42
+ email: p,
43
+ passwordHash: v,
44
+ createdAt: e.createdAt,
45
+ mfaSecret: null,
46
+ mfaEnrolledAt: null,
47
+ emailVerifiedAt: null
48
+ };
49
+ if (!(await s.runPromise(a(h.subjectGuards ?? [], d))).ok) throw new r({ code: "credential_rejected" });
50
+ await i.insert("users", {
51
+ ...d,
52
+ updatedAt: /* @__PURE__ */ new Date()
53
+ }), await i.insert("memberships", {
54
+ id: t("mem"),
55
+ userId: d.id,
56
+ tenantId: g,
57
+ role: "owner",
58
+ joinedAt: /* @__PURE__ */ new Date()
59
+ });
60
+ let m = {
61
+ ...n(i),
62
+ listMemberships: (e) => s.promise(async () => (await i.query({
63
+ ...u("memberships", "userId", e),
64
+ predicate: c(l("userId", e), l("tenantId", g))
65
+ })).map((t) => ({
66
+ userId: e,
67
+ tenantId: g,
68
+ role: String(t.role),
69
+ joinedAt: t.joinedAt instanceof Date ? t.joinedAt : new Date(String(t.joinedAt))
70
+ })))
71
+ }, _ = new Proxy(f.users, { get: (e, t) => typeof t == "string" && t in m ? m[t] : Reflect.get(e, t) }), y = await s.runPromise(o(d, _, h, {
72
+ clearLoginFailures: !1,
73
+ ipAddress: f.ipAddress ?? null,
74
+ userAgent: f.userAgent ?? null
75
+ }));
76
+ return {
77
+ kind: "ok",
78
+ userId: d.id,
79
+ subject: y.subject,
80
+ setCookie: y.setCookie
81
+ };
82
+ }).pipe(s.catch((e) => s.succeed(e.code === "upgrade_conflict" ? d(409, "email_taken", "email already registered") : e.code === "credential_rejected" ? d(401, "guest_rejected", "guest credential rejected") : d(503, "unavailable", "upgrade unavailable"))));
83
+ }).pipe(s.catch(() => s.succeed(d(500, "upgrade_failed", "upgrade failed"))));
84
+ export { f as completeGuestPasswordUpgrade };
@@ -0,0 +1,165 @@
1
+ import { clearSessionCookie as e, resolveSessionSecret as t, sessionSecretsOf as n } from "./session.js";
2
+ import { CSRF_COOKIE_NAME as r, issueCsrfToken as i, verifyCsrf as a } from "./csrf.js";
3
+ import { validateGuestSecrets as o } from "./guestSecrets.js";
4
+ import { GuestAuthError as s, dataStoreGuestAuth as c } from "./guestService.js";
5
+ import { guestTables as l } from "./guestSchema.js";
6
+ import { sessionsTable as u } from "./schema.js";
7
+ import { Duration as d, Effect as f, Result as p } from "effect";
8
+ import { buildSetCookie as m, noteSigningLifetime as h, readCookie as g, readSessionValue as _, verifySessionKeyed as v } from "@voltro/protocol/session";
9
+ import { definePlugin as y } from "@voltro/protocol";
10
+ // src/guestPlugin.ts
11
+ var b = (e, t, n = {}) => ({
12
+ status: e,
13
+ body: JSON.stringify(t),
14
+ contentType: "application/json",
15
+ headers: {
16
+ "cache-control": "no-store",
17
+ ...n
18
+ }
19
+ }), x = (x) => {
20
+ let S = x.prefix ?? "/auth/guest";
21
+ if (!/^\/[a-zA-Z0-9/_-]+$/.test(S) || S.endsWith("/")) throw Error("Guest route prefix must be an absolute path without a trailing slash");
22
+ if (!x.tenantId.trim()) throw Error("Guest authentication requires a server-owned tenant");
23
+ let C = d.toSeconds(x.ttl ?? "365 days"), w = d.toSeconds(x.sessionTtl ?? "7 days");
24
+ if (![C, w].every((e) => Number.isSafeInteger(e) && e > 0 && e <= 31536e4)) throw Error("Guest authentication lifetimes must be positive bounded whole seconds");
25
+ h("session", w * 1e3);
26
+ let T = () => o(x.secret ?? t()), E, D = /* @__PURE__ */ new WeakMap(), O = (e) => {
27
+ let t = D.get(e);
28
+ if (t) return t;
29
+ let n = x.service?.(e) ?? c(e, {
30
+ tenantId: x.tenantId,
31
+ secret: T,
32
+ ttlSeconds: C,
33
+ sessionTtlSeconds: w,
34
+ upgradeable: x.upgradeable ?? !0,
35
+ ...x.secure === void 0 ? {} : { secure: x.secure }
36
+ });
37
+ return D.set(e, n), n;
38
+ }, k = (e) => f.gen(function* () {
39
+ return e.tenantId !== x.tenantId || e.kind !== "guest" ? yield* f.fail(new s({ code: "credential_rejected" })) : E ? O(E) : yield* f.fail(new s({ code: "store_unavailable" }));
40
+ }), A = async (t) => {
41
+ let n = T(), o = typeof n == "string" ? n : n.current.secret;
42
+ if (t.method === "GET" && t.path === S + "/csrf") {
43
+ let e = i(o);
44
+ return b(200, { token: e }, { "set-cookie": m(r, e, {
45
+ path: "/",
46
+ sameSite: "lax",
47
+ secure: x.secure ?? !0,
48
+ httpOnly: !1,
49
+ maxAgeSeconds: 3600
50
+ }) });
51
+ }
52
+ if (![
53
+ "/create",
54
+ "/resume",
55
+ "/prepare-rotation",
56
+ "/rotate",
57
+ "/revoke"
58
+ ].some((e) => t.path === S + e)) return b(404, { error: "not_found" });
59
+ if (t.method !== "POST") return b(405, { error: "method_not_allowed" }, { allow: "POST" });
60
+ if ((t.headers["content-type"] ?? "").split(";")[0]?.trim() !== "application/json") return b(415, { error: "json_required" });
61
+ if (t.rawBody.byteLength > 2048) return b(413, { error: "body_too_large" });
62
+ if (t.headers.cookie && !a(t.headers["x-csrf-token"], g(t.headers.cookie, "voltro:csrf"), o)) return b(403, { error: "csrf_rejected" });
63
+ let s = t.store ?? E;
64
+ if (!s) return b(503, { error: "store_unavailable" });
65
+ let c;
66
+ try {
67
+ c = JSON.parse(new TextDecoder().decode(t.rawBody));
68
+ } catch {
69
+ return b(400, { error: "invalid_input" });
70
+ }
71
+ if (typeof c != "object" || !c || Array.isArray(c)) return b(400, { error: "invalid_input" });
72
+ let l = c, u = t.path === S + "/create", d = t.path === S + "/rotate";
73
+ if (Object.keys(l).some((e) => u || e !== "credential" && !(d && e === "nextCredential")) || !u && typeof l.credential != "string" || d && typeof l.nextCredential != "string") return b(400, { error: "invalid_input" });
74
+ let h = O(s);
75
+ if (u && _(t.headers)) return b(409, { error: "session_already_present" });
76
+ let v = typeof l.credential == "string" ? l.credential : "";
77
+ if (t.path === S + "/prepare-rotation") {
78
+ let e = await f.runPromise(f.result(h.prepareRotation(v)));
79
+ return p.isFailure(e) ? b(e.failure.code === "credential_rejected" ? 401 : 503, { error: e.failure.code }) : b(200, e.success);
80
+ }
81
+ let y = await f.runPromise(f.result(u ? h.create() : t.path === S + "/resume" ? h.resume(v) : d ? h.rotate(v, String(l.nextCredential)) : h.revoke(v).pipe(f.as(null))));
82
+ if (p.isFailure(y)) return b(y.failure.code === "credential_rejected" ? 401 : 503, { error: y.failure.code });
83
+ if (y.success === null) return b(200, { revoked: !0 }, { "set-cookie": e({ secure: x.secure ?? !0 }) });
84
+ let { subject: C, value: w, setCookie: D, deviceCredential: k } = y.success;
85
+ return b(200, {
86
+ subject: C,
87
+ session: w,
88
+ ...k === void 0 ? {} : { deviceCredential: k }
89
+ }, { "set-cookie": D });
90
+ };
91
+ return {
92
+ ...y({
93
+ name: "@voltro/plugin-auth#guest",
94
+ baseName: "@voltro/plugin-auth",
95
+ permissions: ["store:write"],
96
+ extendSchema: { tables: [...l, u] },
97
+ declaredEnv: x.secret === void 0 ? [{
98
+ name: "VOLTRO_SESSION_SECRET",
99
+ required: !0,
100
+ secret: !0,
101
+ generate: "base64url",
102
+ description: "Signs this project's ordinary and guest sessions"
103
+ }] : [],
104
+ privacy: { collects: [{
105
+ category: "identifiers",
106
+ linked: !0,
107
+ tracking: !1,
108
+ purposes: ["app-functionality"]
109
+ }] },
110
+ subjectPrivacy: {
111
+ tables: [...l, u].map((e) => e.tableName),
112
+ kinds: ["guest"],
113
+ erasePhase: "identity"
114
+ },
115
+ onErase: (e, t) => k(t).pipe(f.flatMap((t) => t.erase(e)), f.asVoid),
116
+ onExport: (e, t) => k(t).pipe(f.flatMap((t) => t.export(e))),
117
+ httpRoutes: [{
118
+ method: "*",
119
+ path: S,
120
+ handle: A
121
+ }],
122
+ bindDataStore: (e) => {
123
+ E = e;
124
+ },
125
+ onActivate: () => f.sync(() => {
126
+ T();
127
+ }),
128
+ onDeactivate: () => f.sync(() => {
129
+ E = void 0;
130
+ })
131
+ }),
132
+ guest: { service: () => {
133
+ if (!E) throw Error("Guest lifecycle requires the bound app DataStore");
134
+ return O(E);
135
+ } },
136
+ auth: { sessionStrategy: {
137
+ id: "voltro-guest",
138
+ resolve: async (e) => {
139
+ let t = _(e.headers);
140
+ if (!t) return { kind: "skip" };
141
+ let r = v(t, n(T()));
142
+ if (!r) return {
143
+ kind: "failed",
144
+ reason: "voltro session cookie failed verification"
145
+ };
146
+ if (r.subject.type !== "user" || r.subject.kind !== "guest") return { kind: "skip" };
147
+ let i = e.store ?? E;
148
+ if (!i) return {
149
+ kind: "failed",
150
+ reason: "guest session registry unavailable"
151
+ };
152
+ let a = await f.runPromise(f.result(O(i).isLive(r.subject)));
153
+ return p.isSuccess(a) && a.success ? {
154
+ kind: "matched",
155
+ subject: r.subject,
156
+ credentialExpiresAt: r.exp * 1e3
157
+ } : {
158
+ kind: "failed",
159
+ reason: "guest session revoked or expired"
160
+ };
161
+ }
162
+ } }
163
+ };
164
+ };
165
+ export { x as guestAuthPlugin };
@@ -0,0 +1,19 @@
1
+ import { boolean as e, id as t, integer as n, table as r, text as i, timestamp as a } from "@voltro/database";
2
+ // src/guestSchema.ts
3
+ var o = r("authGuests", {
4
+ id: t(),
5
+ tenantId: i(),
6
+ upgradeable: e(),
7
+ expiresAt: a(),
8
+ createdAt: a(),
9
+ lastSeenAt: a()
10
+ }).index(["tenantId"]), s = r("authGuestDevices", {
11
+ id: t(),
12
+ guestId: i(),
13
+ tenantId: i(),
14
+ secretHash: i().serverOnly(),
15
+ generation: n(),
16
+ expiresAt: a(),
17
+ createdAt: a()
18
+ }).index(["guestId"]).index(["tenantId"]), c = [o, s];
19
+ export { s as authGuestDevicesTable, o as authGuestsTable, c as guestTables };
@@ -0,0 +1,9 @@
1
+ import { sessionSecretsOf as e } from "./session.js";
2
+ import { assertProductionSessionSecret as t } from "@voltro/protocol/session";
3
+ // src/guestSecrets.ts
4
+ var n = (n) => {
5
+ let r = e(n);
6
+ for (let e of [r.current, ...r.previous ? [r.previous] : []]) t({ VOLTRO_SESSION_SECRET: e.secret });
7
+ return n;
8
+ };
9
+ export { n as validateGuestSecrets };