@voltro/plugin-auth 0.86.0 → 0.87.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,10 @@
1
+ import { sessionSecretsOf as e } from "./session.js";
2
+ import { assertProductionSessionSecret as t } from "@voltro/protocol/session";
3
+ //#region 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
+ //#endregion
10
+ export { n as validateGuestSecrets };
@@ -0,0 +1,224 @@
1
+ import { newAuthId as e } from "./userStore.js";
2
+ import { dataStoreSessionRegistry as t, issueRegisteredSession as n } from "./sessionRegistry.js";
3
+ import { validateGuestSecrets as r } from "./guestSecrets.js";
4
+ import { parseGuestCredential as i } from "./guestCredential.js";
5
+ import { createHash as a, randomBytes as o, timingSafeEqual as s } from "node:crypto";
6
+ import { Data as c, Effect as l } from "effect";
7
+ import { noteSigningLifetime as u } from "@voltro/protocol/session";
8
+ import { and as d, eq as f } from "@voltro/database";
9
+ //#region src/guestService.ts
10
+ var p = class extends c.TaggedError("GuestAuthError") {}, m = "authGuests", h = "authGuestDevices", g = 86400, _ = () => new p({ code: "credential_rejected" }), v = (e) => a("sha256").update(e).digest("hex"), y = (e) => e instanceof Date ? e.getTime() : typeof e == "string" ? Date.parse(e) : NaN, b = (e, t) => ({
11
+ table: e,
12
+ predicate: t,
13
+ order: [],
14
+ take: void 0,
15
+ skip: void 0,
16
+ projection: void 0
17
+ }), x = (e, t, n) => d(f(e, t), f("tenantId", n)), S = (e, t) => typeof e != "string" || !/^[a-f0-9]{64}$/.test(e) ? !1 : s(Buffer.from(e, "hex"), Buffer.from(v(t), "hex")), C = (e) => {
18
+ let t = i(e);
19
+ if (!t) throw _();
20
+ return t;
21
+ }, w = (e) => l.tryPromise({
22
+ try: e,
23
+ catch: (e) => e instanceof p ? e : new p({ code: "store_unavailable" })
24
+ }), T = (i, a) => {
25
+ if (!a.tenantId.trim()) throw Error("Guest authentication requires a server-owned tenant");
26
+ let s = a.ttlSeconds ?? 365 * g, c = a.sessionTtlSeconds ?? 7 * g;
27
+ if (![s, c].every((e) => Number.isSafeInteger(e) && e > 0 && e <= 3650 * g)) throw Error("Guest authentication lifetimes must be positive bounded whole seconds");
28
+ u("session", c * 1e3);
29
+ let l = a.now ?? Date.now, d = (e, t, n, r) => e.query(b(t, x(n, r, a.tenantId))), f = async (e, t, n) => {
30
+ let r = await e.update(m, t, { lastSeenAt: new Date(n) });
31
+ if (!r || r.tenantId !== a.tenantId) throw _();
32
+ return r;
33
+ }, T = async (e, t, n) => {
34
+ let r = C(t), i = await f(e, r.guestId, n), a = (await d(e, h, "id", r.deviceId))[0];
35
+ if (!a || a.guestId !== r.guestId || !S(a.secretHash, r.secret) || !(y(i.expiresAt) > n) || !(y(a.expiresAt) > n) || !Number.isSafeInteger(a.generation) || Number(a.generation) < 0) throw _();
36
+ return {
37
+ guest: i,
38
+ device: a,
39
+ parsed: r
40
+ };
41
+ }, E = async (e, i, o, s) => {
42
+ let l = Math.floor((Math.min(y(i.expiresAt), y(o.expiresAt)) - s) / 1e3);
43
+ if (l < 1) throw _();
44
+ let u = {
45
+ type: "user",
46
+ kind: "guest",
47
+ id: String(i.id),
48
+ tenantId: a.tenantId,
49
+ metadata: {
50
+ guestDeviceId: o.id,
51
+ guestDeviceGeneration: o.generation
52
+ }
53
+ }, d = await n(t(e), u, r(typeof a.secret == "function" ? a.secret() : a.secret), {
54
+ ttlSeconds: Math.min(c, l),
55
+ now: () => s,
56
+ ...a.secure === void 0 ? {} : { secure: a.secure }
57
+ });
58
+ return {
59
+ ...d,
60
+ subject: {
61
+ ...u,
62
+ metadata: {
63
+ ...u.metadata,
64
+ sessionId: d.sessionId
65
+ }
66
+ }
67
+ };
68
+ }, D = async (e, t) => {
69
+ for (let n of await d(e, "sessions", "userId", t)) await e.delete("sessions", String(n.id));
70
+ };
71
+ return {
72
+ create: () => w(async () => {
73
+ let t = l(), n = e("guest"), r = e("gdev"), c = o(32).toString("base64url"), u = {
74
+ id: n,
75
+ tenantId: a.tenantId,
76
+ upgradeable: a.upgradeable ?? !0,
77
+ expiresAt: new Date(t + s * 1e3),
78
+ createdAt: new Date(t),
79
+ lastSeenAt: new Date(t)
80
+ }, d = {
81
+ id: r,
82
+ guestId: n,
83
+ tenantId: a.tenantId,
84
+ secretHash: v(c),
85
+ generation: 0,
86
+ expiresAt: u.expiresAt,
87
+ createdAt: new Date(t)
88
+ };
89
+ return i.transactional(async (e) => (await e.insert(m, u), await e.insert(h, d), {
90
+ ...await E(e, u, d, t),
91
+ deviceCredential: `${n}.${r}.${c}`
92
+ }));
93
+ }),
94
+ resume: (e) => w(async () => {
95
+ let t = l();
96
+ return i.transactional(async (n) => {
97
+ let { guest: r, device: i } = await T(n, e, t);
98
+ return E(n, r, i, t);
99
+ });
100
+ }),
101
+ prepareRotation: (e) => w(async () => {
102
+ r(typeof a.secret == "function" ? a.secret() : a.secret);
103
+ let t = l(), n = o(32).toString("base64url");
104
+ return i.transactional(async (r) => {
105
+ let { parsed: i } = await T(r, e, t);
106
+ return { deviceCredential: `${i.guestId}.${i.deviceId}.${n}` };
107
+ });
108
+ }),
109
+ rotate: (e, t) => w(async () => {
110
+ let n = l(), r = C(t);
111
+ return i.transactional(async (i) => {
112
+ let { guest: a, device: o, parsed: s } = await T(i, e, n);
113
+ if (r.guestId !== s.guestId || r.deviceId !== s.deviceId || r.secret === s.secret) throw _();
114
+ let c = Number(o.generation) + 1;
115
+ if (!Number.isSafeInteger(c)) throw _();
116
+ let l = await i.update(h, s.deviceId, {
117
+ secretHash: v(r.secret),
118
+ generation: c
119
+ });
120
+ if (!l) throw _();
121
+ return await D(i, s.guestId), {
122
+ ...await E(i, a, l, n),
123
+ deviceCredential: t
124
+ };
125
+ });
126
+ }),
127
+ revoke: (e) => w(async () => {
128
+ let t = l();
129
+ await i.transactional(async (n) => {
130
+ let { parsed: r } = await T(n, e, t);
131
+ await n.delete(h, r.deviceId), await D(n, r.guestId);
132
+ });
133
+ }),
134
+ upgrade: (e, t) => w(async () => {
135
+ r(typeof a.secret == "function" ? a.secret() : a.secret);
136
+ let n = l();
137
+ return i.transactional(async (r) => {
138
+ let { guest: i, parsed: o } = await T(r, e, n);
139
+ if (i.upgradeable !== !0 && i.upgradeable !== 1) throw _();
140
+ if ((await d(r, "users", "id", o.guestId)).length !== 0) throw new p({ code: "upgrade_conflict" });
141
+ await D(r, o.guestId);
142
+ let s = await t({
143
+ store: r,
144
+ subjectId: o.guestId,
145
+ tenantId: a.tenantId,
146
+ createdAt: new Date(y(i.createdAt))
147
+ }), c = (await d(r, "users", "id", o.guestId))[0];
148
+ if (!c || c.id !== o.guestId || c.tenantId !== a.tenantId) throw new p({ code: "upgrade_conflict" });
149
+ for (let e of await d(r, h, "guestId", o.guestId)) await r.delete(h, String(e.id));
150
+ if (!await r.delete(m, o.guestId)) throw _();
151
+ return s;
152
+ });
153
+ }),
154
+ erase: (e) => w(async () => i.transactional(async (t) => {
155
+ let n = await t.update(m, e, { lastSeenAt: new Date(l()) });
156
+ if (!n) return !1;
157
+ if (n.tenantId !== a.tenantId) throw _();
158
+ for (let n of await d(t, h, "guestId", e)) await t.delete(h, String(n.id));
159
+ return await D(t, e), t.delete(m, e);
160
+ })),
161
+ mergeInto: (e, t, n) => w(async () => {
162
+ r(typeof a.secret == "function" ? a.secret() : a.secret);
163
+ let o = l();
164
+ if (typeof t != "string" || !t.trim()) throw _();
165
+ return i.transactional(async (r) => {
166
+ let { guest: i, parsed: s } = await T(r, e, o);
167
+ if (i.upgradeable !== !0 && i.upgradeable !== 1) throw _();
168
+ if (t === s.guestId) throw new p({ code: "upgrade_conflict" });
169
+ let c = await r.update("users", t, { updatedAt: new Date(o) });
170
+ if (!c || c.id !== t || c.tenantId !== a.tenantId || c.deactivatedAt !== null && c.deactivatedAt !== void 0 || (await d(r, m, "id", t)).length !== 0 || (await d(r, "users", "id", s.guestId)).length !== 0) throw _();
171
+ await D(r, s.guestId);
172
+ let l = await n({
173
+ store: r,
174
+ subjectId: s.guestId,
175
+ targetId: t,
176
+ tenantId: a.tenantId,
177
+ createdAt: new Date(y(i.createdAt))
178
+ }), u = (await d(r, "users", "id", t))[0];
179
+ if (!u || u.id !== t || u.tenantId !== a.tenantId || (await d(r, "users", "id", s.guestId)).length !== 0) throw new p({ code: "upgrade_conflict" });
180
+ for (let e of await d(r, h, "guestId", s.guestId)) await r.delete(h, String(e.id));
181
+ if (!await r.delete(m, s.guestId)) throw _();
182
+ return l;
183
+ });
184
+ }),
185
+ export: (e) => w(async () => {
186
+ let t = (await d(i, m, "id", e))[0];
187
+ if (!t) return null;
188
+ let n = await d(i, h, "guestId", e), r = await d(i, "sessions", "userId", e);
189
+ return {
190
+ subject: {
191
+ id: e,
192
+ tenantId: a.tenantId,
193
+ kind: "guest",
194
+ upgradeable: t.upgradeable === !0 || t.upgradeable === 1,
195
+ createdAt: y(t.createdAt),
196
+ expiresAt: y(t.expiresAt)
197
+ },
198
+ devices: n.map((e) => ({
199
+ id: String(e.id),
200
+ generation: Number(e.generation),
201
+ createdAt: y(e.createdAt),
202
+ expiresAt: y(e.expiresAt)
203
+ })),
204
+ sessions: r.map((e) => ({
205
+ id: String(e.id),
206
+ expiresAt: y(e.expiresAt)
207
+ }))
208
+ };
209
+ }),
210
+ isLive: (e) => w(async () => {
211
+ if (e.type !== "user" || e.kind !== "guest" || e.tenantId !== a.tenantId) return !1;
212
+ let t = e.metadata?.sessionId, n = e.metadata?.guestDeviceId, r = e.metadata?.guestDeviceGeneration;
213
+ if (typeof t != "string" || typeof n != "string" || !Number.isSafeInteger(r)) return !1;
214
+ let o = l(), s = (await d(i, m, "id", e.id))[0];
215
+ if (!s || !(y(s.expiresAt) > o)) return !1;
216
+ let c = (await d(i, h, "id", n))[0];
217
+ if (!c || c.guestId !== e.id || c.generation !== r || !(y(c.expiresAt) > o)) return !1;
218
+ let u = (await d(i, "sessions", "id", t))[0];
219
+ return u?.userId === e.id && y(u.expiresAt) > o;
220
+ })
221
+ };
222
+ };
223
+ //#endregion
224
+ export { p as GuestAuthError, T as dataStoreGuestAuth };