@voltro/plugin-auth-social 0.85.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.
package/dist/plugin.js ADDED
@@ -0,0 +1,741 @@
1
+ import { SocialAuthError as e, SocialConfigError as t } from "./errors.js";
2
+ import { isSocialProviderId as n, socialProvider as r } from "./providers.js";
3
+ import { socialBeginLogin as i } from "./login.js";
4
+ import { socialCompleteLogin as a } from "./callback.js";
5
+ import { completeSocialSignIn as o } from "./signIn.js";
6
+ import { dataStoreSocialIdentityStore as s, socialAuthTables as c } from "./identityStore.js";
7
+ import { completeGuestSocialUpgrade as l } from "./guestUpgrade.js";
8
+ import { decodeGuestUpgradeHandoff as u, encodeGuestUpgradeHandoff as d, guestIdentityOf as f } from "./guestHandoff.js";
9
+ import { isNativeVerifier as p, nativeUpgradeRequestOf as m, openNativeUpgradeCode as h, sealNativeUpgradeCode as g, signNativeUpgradeTicket as ee, verifierMatches as te, verifyNativeUpgradeTicket as ne } from "./nativeGuestUpgrade.js";
10
+ import { verifyPlatformTicket as _ } from "./platform.js";
11
+ import { Effect as v, Result as y } from "effect";
12
+ import { and as b, eq as x } from "@voltro/database";
13
+ import { definePlugin as re } from "@voltro/protocol";
14
+ import { SESSION_HEADER_NAME as S, buildSetCookie as C, readCookie as w } from "@voltro/protocol/session";
15
+ import { CSRF_COOKIE_NAME as T, CSRF_HEADER_NAME as ie, configSessionSecrets as E, dataStoreGuestAuth as ae, dataStoreSessionRegistry as oe, issueCsrfToken as se, parseGuestCredential as D, readSessionFromHeaders as O, signAuthClaims as ce, verifyAuthClaims as k, verifyCsrf as le } from "@voltro/plugin-auth";
16
+ //#region src/plugin.ts
17
+ var ue = "/auth/social", de = 600, fe = "voltro:oauth", A = {
18
+ google: [{
19
+ name: "VOLTRO_GOOGLE_CLIENT_ID",
20
+ secret: !1,
21
+ description: "Google OAuth client id (…apps.googleusercontent.com)."
22
+ }, {
23
+ name: "VOLTRO_GOOGLE_CLIENT_SECRET",
24
+ secret: !0,
25
+ description: "Google OAuth client secret."
26
+ }],
27
+ github: [{
28
+ name: "VOLTRO_GITHUB_CLIENT_ID",
29
+ secret: !1,
30
+ description: "GitHub OAuth app client id."
31
+ }, {
32
+ name: "VOLTRO_GITHUB_CLIENT_SECRET",
33
+ secret: !0,
34
+ description: "GitHub OAuth app client secret."
35
+ }],
36
+ apple: [
37
+ {
38
+ name: "VOLTRO_APPLE_CLIENT_ID",
39
+ secret: !1,
40
+ description: "Apple Services ID, e.g. com.example.web."
41
+ },
42
+ {
43
+ name: "VOLTRO_APPLE_TEAM_ID",
44
+ secret: !1,
45
+ description: "Apple Developer Team ID (10 chars)."
46
+ },
47
+ {
48
+ name: "VOLTRO_APPLE_KEY_ID",
49
+ secret: !1,
50
+ description: "Key ID of the Sign-in-with-Apple .p8 key."
51
+ },
52
+ {
53
+ name: "VOLTRO_APPLE_PRIVATE_KEY",
54
+ secret: !0,
55
+ description: "Contents of the .p8 file (PKCS#8 PEM). Apple signs no static secret — this key mints one per exchange."
56
+ }
57
+ ],
58
+ discord: [{
59
+ name: "VOLTRO_DISCORD_CLIENT_ID",
60
+ secret: !1,
61
+ description: "Discord OAuth application client id."
62
+ }, {
63
+ name: "VOLTRO_DISCORD_CLIENT_SECRET",
64
+ secret: !0,
65
+ description: "Discord OAuth application client secret."
66
+ }],
67
+ twitch: [{
68
+ name: "VOLTRO_TWITCH_CLIENT_ID",
69
+ secret: !1,
70
+ description: "Twitch confidential application client id."
71
+ }, {
72
+ name: "VOLTRO_TWITCH_CLIENT_SECRET",
73
+ secret: !0,
74
+ description: "Twitch confidential application client secret."
75
+ }],
76
+ microsoft: [{
77
+ name: "VOLTRO_MICROSOFT_CLIENT_ID",
78
+ secret: !1,
79
+ description: "Microsoft application client id for personal accounts."
80
+ }, {
81
+ name: "VOLTRO_MICROSOFT_CLIENT_SECRET",
82
+ secret: !0,
83
+ description: "Microsoft application client secret."
84
+ }],
85
+ epic: [{
86
+ name: "VOLTRO_EPIC_CLIENT_ID",
87
+ secret: !1,
88
+ description: "Epic Account Services OAuth client id."
89
+ }, {
90
+ name: "VOLTRO_EPIC_CLIENT_SECRET",
91
+ secret: !0,
92
+ description: "Epic Account Services OAuth client secret."
93
+ }]
94
+ }, j = (e, t) => ({
95
+ status: e,
96
+ body: JSON.stringify(t),
97
+ contentType: "application/json; charset=utf-8",
98
+ headers: { "cache-control": "no-store" }
99
+ }), M = (e, t) => e + (e.includes("?") ? "&" : "?") + new URLSearchParams(t).toString(), N = (e, t) => ({
100
+ status: 302,
101
+ body: "",
102
+ contentType: "text/plain; charset=utf-8",
103
+ headers: {
104
+ "cache-control": "no-store",
105
+ "referrer-policy": "no-referrer",
106
+ location: e,
107
+ ...t === void 0 ? {} : { "set-cookie": t }
108
+ }
109
+ }), pe = (e, t) => ce({ ...e }, t.current.secret, "social-login"), me = (e, t) => {
110
+ if (!e) return null;
111
+ try {
112
+ let n = [t.current, t.previous].filter((e) => e !== void 0).map((t) => k(e, t.secret, "social-login")).find((e) => e !== null);
113
+ if (!n) return null;
114
+ let r = n;
115
+ if (typeof r.p != "string" || typeof r.s != "string" || typeof r.v != "string" || typeof r.n != "string" || typeof r.exp != "number" || typeof r.audience != "string" || typeof r.redirectUri != "string") return null;
116
+ let i = r.guest === void 0 ? void 0 : f(r.guest);
117
+ if (i === null) return null;
118
+ let a = r.native, o = a === void 0 ? void 0 : m(a, typeof a?.r == "string" ? [a.r] : []);
119
+ return o === null || o !== void 0 && i === void 0 ? null : {
120
+ p: r.p,
121
+ s: r.s,
122
+ v: r.v,
123
+ n: r.n,
124
+ exp: r.exp,
125
+ audience: r.audience,
126
+ redirectUri: r.redirectUri,
127
+ ...i === void 0 ? {} : { guest: i },
128
+ ...o === void 0 ? {} : { native: o }
129
+ };
130
+ } catch {
131
+ return null;
132
+ }
133
+ }, he = (e) => e.method === "POST" && (e.headers["content-type"] ?? "").includes("application/x-www-form-urlencoded") ? new URLSearchParams(Buffer.from(e.rawBody).toString("utf8")) : new URLSearchParams(e.query), P = (k) => {
134
+ let P = k.prefix ?? ue, F = k.stateTtlSeconds ?? de, I = k.stateCookieName ?? fe, L = k.linkPolicy ?? "never", R = k.auth.cookieSecure ?? !0, z = k.providers ?? {}, ge = Object.keys(z);
135
+ if (ge.find((e) => !n(e)) !== void 0) throw new t("socialAuthPlugin: unknown provider configuration");
136
+ let B = ge.filter(n), V = /* @__PURE__ */ new Map();
137
+ for (let e of k.platforms ?? []) {
138
+ if (V.has(e.id)) throw new t(`socialAuthPlugin: platform ${e.id} is configured twice`);
139
+ V.set(e.id, e);
140
+ }
141
+ if (B.length === 0 && V.size === 0) throw new t("socialAuthPlugin: `providers` and `platforms` are empty — enable at least one provider or platform");
142
+ if (!Number.isSafeInteger(F) || F <= 0 || F > 3600) throw new t("socialAuthPlugin: stateTtlSeconds must be an integer from 1 to 3600");
143
+ let H = (k.appBaseUrl ?? k.auth.appBaseUrl ?? "").replace(/\/+$/, ""), U = I + ":upgrade", W = k.guestUpgrade, G = JSON.stringify([
144
+ H,
145
+ P,
146
+ k.name ?? "",
147
+ k.auth.defaultTenantId
148
+ ]);
149
+ if (W) {
150
+ if (W.guest.tenantId !== k.auth.defaultTenantId || k.auth.ensureActor !== void 0 && W.configForTransaction === void 0) throw new t("Guest upgrade requires matching server tenants and transaction-bound actor hooks");
151
+ if (k.identities !== void 0) throw new t("Guest upgrade requires transaction-bound SQL social identities");
152
+ let e = !1;
153
+ try {
154
+ let t = new URL(H), n = new URL(W.confirmationRedirect, t);
155
+ e = /^https?:$/.test(t.protocol) && n.origin === t.origin && !n.username && !n.password && !W.confirmationRedirect.startsWith("//") && !W.confirmationRedirect.includes("\\");
156
+ } catch {}
157
+ if (!e) throw new t("Guest upgrade confirmation must be a same-origin application page");
158
+ for (let e of W.native?.redirectUris ?? ["acme://valid"]) {
159
+ let n = null;
160
+ try {
161
+ n = new URL(e);
162
+ } catch {}
163
+ if (!n || e.includes("*") || n.hash || n.username || n.password || /^(javascript|data|file):$/.test(n.protocol) || n.protocol === "http:" && ![
164
+ "127.0.0.1",
165
+ "[::1]",
166
+ "localhost"
167
+ ].includes(n.hostname)) throw new t("Native guest upgrade redirect URIs must be exact app URIs (custom scheme, https or loopback http)");
168
+ }
169
+ if (W.native && W.native.redirectUris.length === 0) throw new t("Native guest upgrade needs at least one redirect URI");
170
+ }
171
+ let K = (e) => {
172
+ if (!W) throw new t("Guest upgrade is not configured");
173
+ return W.service?.(e) ?? ae(e, W.guest);
174
+ }, q = (e) => C(e, "", {
175
+ maxAgeSeconds: 0,
176
+ path: P,
177
+ secure: R,
178
+ httpOnly: !0,
179
+ sameSite: "lax",
180
+ ...k.auth.cookieDomain ? { domain: k.auth.cookieDomain } : {}
181
+ }), J = (e) => {
182
+ let n = z[e] ?? {}, r = process.env, i = n.clientId ?? r[A[e][0].name] ?? "";
183
+ if (!i) throw new t(`socialAuthPlugin: ${e} has no client id — pass \`providers.${e}.clientId\` or set ${A[e][0].name}`);
184
+ let a = n.redirectUri ?? `${H}${P}/${e}/callback`, o = !1;
185
+ try {
186
+ let e = new URL(a);
187
+ o = (e.protocol === "https:" || e.protocol === "http:") && e.username === "" && e.password === "" && e.hash === "";
188
+ } catch {}
189
+ if (!o) throw new t(`socialAuthPlugin: ${e} needs an absolute redirect URI — set \`appBaseUrl\` (or \`providers.${e}.redirectUri\`)`);
190
+ let s = {
191
+ id: e,
192
+ clientId: i,
193
+ redirectUri: a,
194
+ endpoints: n.endpoints ?? {},
195
+ ...n.scopes === void 0 ? {} : { scopes: n.scopes }
196
+ };
197
+ if (e === "apple") {
198
+ let e = n.appleKey ?? (r.VOLTRO_APPLE_TEAM_ID && r.VOLTRO_APPLE_KEY_ID && r.VOLTRO_APPLE_PRIVATE_KEY ? {
199
+ teamId: r.VOLTRO_APPLE_TEAM_ID,
200
+ keyId: r.VOLTRO_APPLE_KEY_ID,
201
+ privateKey: r.VOLTRO_APPLE_PRIVATE_KEY.replace(/\\n/g, "\n")
202
+ } : void 0);
203
+ if (!e) throw new t("socialAuthPlugin: apple needs `appleKey` (teamId + keyId + .p8) or VOLTRO_APPLE_TEAM_ID / VOLTRO_APPLE_KEY_ID / VOLTRO_APPLE_PRIVATE_KEY");
204
+ return {
205
+ ...s,
206
+ appleKey: e
207
+ };
208
+ }
209
+ let c = n.clientSecret ?? r[A[e][1].name] ?? "";
210
+ if (!c) throw new t(`socialAuthPlugin: ${e} has no client secret — pass \`providers.${e}.clientSecret\` or set ${A[e][1].name}`);
211
+ return {
212
+ ...s,
213
+ clientSecret: c
214
+ };
215
+ }, Y = k.identities ?? null, _e = (e) => {
216
+ if (Y) return Y;
217
+ let n = e.store;
218
+ if (!n) throw new t("socialAuthPlugin: no identity store — pass `identities`, or run on an app with a DataStore");
219
+ return Y = s(n), Y;
220
+ }, ve = (e) => {
221
+ if (!e.startsWith(`${P}/`)) return null;
222
+ let [t, ...r] = e.slice(P.length + 1).split("/");
223
+ return !t || !n(t) || !B.includes(t) ? null : r.length === 0 ? {
224
+ id: t,
225
+ isCallback: !1
226
+ } : r.length === 1 && r[0] === "callback" ? {
227
+ id: t,
228
+ isCallback: !0
229
+ } : null;
230
+ }, X = (e, t, n) => {
231
+ let r = [q(I), ...W ? [q(U)] : []];
232
+ if (!k.failureRedirect) return {
233
+ ...j(e, {
234
+ error: t,
235
+ message: n
236
+ }),
237
+ headers: {
238
+ "cache-control": "no-store",
239
+ "set-cookie": r
240
+ }
241
+ };
242
+ let i = k.failureRedirect.includes("?") ? "&" : "?", a = N(`${k.failureRedirect}${i}social_error=${encodeURIComponent(t)}`);
243
+ return {
244
+ ...a,
245
+ headers: {
246
+ ...a.headers,
247
+ "set-cookie": r
248
+ }
249
+ };
250
+ }, Z = (e, t, n, a) => {
251
+ let o = J(t), s = r(t, o.endpoints), c = i({
252
+ provider: t,
253
+ clientId: o.clientId,
254
+ redirectUri: o.redirectUri,
255
+ ...o.scopes === void 0 ? {} : { scopes: o.scopes },
256
+ endpoints: o.endpoints,
257
+ ...new URLSearchParams(e.query).get("login_hint") ? { loginHint: new URLSearchParams(e.query).get("login_hint") } : {}
258
+ }), l = C(I, pe({
259
+ p: t,
260
+ s: c.state,
261
+ v: c.codeVerifier,
262
+ n: c.nonce,
263
+ exp: Math.floor(Date.now() / 1e3) + F,
264
+ audience: G,
265
+ redirectUri: o.redirectUri,
266
+ ...n === void 0 ? {} : { guest: n },
267
+ ...n !== void 0 && a !== void 0 ? { native: a } : {}
268
+ }, E(k.auth)), {
269
+ maxAgeSeconds: F,
270
+ httpOnly: !0,
271
+ sameSite: s.responseMode === "form_post" ? "none" : "lax",
272
+ secure: s.responseMode === "form_post" || R,
273
+ ...k.auth.cookieDomain ? { domain: k.auth.cookieDomain } : {},
274
+ path: P
275
+ }), u = N(c.url, l);
276
+ return W ? {
277
+ ...u,
278
+ headers: {
279
+ ...u.headers,
280
+ "set-cookie": [l, q(U)]
281
+ }
282
+ } : u;
283
+ }, ye = async (e, t) => {
284
+ let n = he(e), r = me(w(e.headers.cookie, I), E(k.auth)), i = J(t), s = r && r.p === t && r.audience === G && r.redirectUri === i.redirectUri ? r : null, c = W?.native && s?.guest && s.native ? s.native : null, l = (e) => {
285
+ let t = N(e);
286
+ return {
287
+ ...t,
288
+ headers: {
289
+ ...t.headers,
290
+ "set-cookie": [q(I)]
291
+ }
292
+ };
293
+ }, u = (e, t, n) => c ? l(M(c.r, {
294
+ error: t,
295
+ state: c.ns
296
+ })) : X(e, t, n);
297
+ if (n.get("error")) return u(400, "exchange_failed", `${t} refused the authorization`);
298
+ if (s?.guest) {
299
+ if (!W || !e.store || s.guest.tenantId !== W.guest.tenantId) return u(403, "credential_rejected", "Guest upgrade was refused.");
300
+ let t = await v.runPromise(v.result(K(e.store).isLive(s.guest)));
301
+ if (y.isFailure(t)) return u(503, "store_unavailable", "Guest upgrade storage is unavailable.");
302
+ if (!t.success) return u(403, "credential_rejected", "Guest upgrade was refused.");
303
+ }
304
+ let f = await a({
305
+ provider: t,
306
+ clientId: i.clientId,
307
+ ...i.clientSecret === void 0 ? {} : { clientSecret: i.clientSecret },
308
+ ...i.appleKey === void 0 ? {} : { appleKey: i.appleKey },
309
+ redirectUri: i.redirectUri,
310
+ code: n.get("code") ?? "",
311
+ state: n.get("state") ?? "",
312
+ expectedState: s?.s ?? "",
313
+ codeVerifier: s?.v ?? "",
314
+ expectedNonce: s?.n ?? "",
315
+ ...n.get("user") ? { appleUserPayload: n.get("user") } : {},
316
+ endpoints: i.endpoints
317
+ });
318
+ if (s?.guest && W) {
319
+ let e = Math.min(s.exp, Math.floor(Date.now() / 1e3) + F), t = d({
320
+ guest: s.guest,
321
+ profile: f,
322
+ exp: e,
323
+ audience: G
324
+ }, E(k.auth));
325
+ if (c) {
326
+ let n = g({
327
+ handoff: t,
328
+ ch: c.ch,
329
+ guestId: s.guest.id,
330
+ exp: Math.min(e, Math.floor(Date.now() / 1e3) + 120)
331
+ }, E(k.auth), G);
332
+ return l(M(c.r, {
333
+ code: n,
334
+ state: c.ns
335
+ }));
336
+ }
337
+ let n = N(W.confirmationRedirect);
338
+ return {
339
+ ...n,
340
+ headers: {
341
+ ...n.headers,
342
+ "set-cookie": [q(I), C(U, t, {
343
+ maxAgeSeconds: Math.max(0, e - Math.floor(Date.now() / 1e3)),
344
+ path: P,
345
+ httpOnly: !0,
346
+ sameSite: "lax",
347
+ secure: R,
348
+ ...k.auth.cookieDomain ? { domain: k.auth.cookieDomain } : {}
349
+ })]
350
+ }
351
+ };
352
+ }
353
+ let p = await v.runPromise(o({
354
+ profile: f,
355
+ identities: _e(e),
356
+ users: k.users,
357
+ config: k.auth,
358
+ policy: L,
359
+ ipAddress: e.remoteAddr ?? null,
360
+ userAgent: e.headers["user-agent"] ?? null
361
+ }));
362
+ if (p.kind === "refused") return X(p.status, p.code, p.message);
363
+ let m = C(I, "", {
364
+ maxAgeSeconds: 0,
365
+ path: P,
366
+ secure: R
367
+ });
368
+ return {
369
+ status: 302,
370
+ body: "",
371
+ contentType: "text/plain; charset=utf-8",
372
+ headers: {
373
+ "cache-control": "no-store",
374
+ "referrer-policy": "no-referrer",
375
+ location: k.successRedirect ?? k.auth.successRedirect ?? "/",
376
+ "set-cookie": [m, p.setCookie]
377
+ }
378
+ };
379
+ }, Q = (e, t) => ({
380
+ ...j(e, t),
381
+ headers: { "cache-control": "no-store" }
382
+ }), be = async (e, t, r) => {
383
+ let i = W?.native;
384
+ if (!W || !i) return j(404, { error: "not_found" });
385
+ let a = async (t) => {
386
+ if (!e.store) return Q(503, { error: "store_unavailable" });
387
+ let n = await v.runPromise(v.result(K(e.store).isLive(t)));
388
+ return y.isFailure(n) ? Q(503, { error: "store_unavailable" }) : n.success ? null : Q(403, { error: "credential_rejected" });
389
+ };
390
+ if (t === "begin") {
391
+ if (e.method !== "GET") return j(405, { error: "method_not_allowed" });
392
+ let t = ne(new URLSearchParams(e.query).get("t"), r, G, i.redirectUris);
393
+ return !t || !n(t.provider) || !B.includes(t.provider) || t.guest.tenantId !== W.guest.tenantId ? Q(403, { error: "upgrade_ticket_rejected" }) : await a(t.guest) ?? Z(e, t.provider, t.guest, t.native);
394
+ }
395
+ if (e.method !== "POST") return j(405, { error: "method_not_allowed" });
396
+ if ((e.headers["content-type"] ?? "").split(";")[0]?.trim() !== "application/json") return j(415, { error: "json_required" });
397
+ if (e.rawBody.byteLength > 8192) return j(413, { error: "body_too_large" });
398
+ let o;
399
+ try {
400
+ o = JSON.parse(new TextDecoder().decode(e.rawBody));
401
+ } catch {
402
+ return Q(400, { error: "invalid_input" });
403
+ }
404
+ if (!o || typeof o != "object" || Array.isArray(o)) return Q(400, { error: "invalid_input" });
405
+ let s = o;
406
+ if (t.startsWith("start/")) {
407
+ let o = t.slice(6);
408
+ if (!n(o) || !B.includes(o)) return j(404, { error: "not_found" });
409
+ let c = e.headers[S], l = typeof W.guest.secret == "function" ? W.guest.secret() : W.guest.secret, u = typeof c == "string" && c ? f(O({ [S]: c }, l)?.subject) : null;
410
+ if (!u || u.tenantId !== W.guest.tenantId) return Q(403, { error: "credential_rejected" });
411
+ let d = m({
412
+ ch: s.codeChallenge,
413
+ r: s.redirectUri,
414
+ ns: s.state
415
+ }, i.redirectUris);
416
+ if (!d || Object.keys(s).some((e) => e !== "codeChallenge" && e !== "redirectUri" && e !== "state")) return Q(400, { error: "invalid_input" });
417
+ let p = await a(u);
418
+ if (p) return p;
419
+ let h = ee({
420
+ guest: u,
421
+ provider: o,
422
+ native: d,
423
+ exp: Math.floor(Date.now() / 1e3) + F,
424
+ audience: G
425
+ }, r), g = new URL(P + "/upgrade/native/begin", J(o).redirectUri);
426
+ return g.search = new URLSearchParams({ t: h }).toString(), Q(200, { authorizationUrl: g.toString() });
427
+ }
428
+ if (t !== "complete") return j(404, { error: "not_found" });
429
+ if (Object.keys(s).some((e) => e !== "code" && e !== "codeVerifier" && e !== "credential" && e !== "confirmed") || s.confirmed !== !0 || typeof s.credential != "string" || !p(s.codeVerifier)) return Q(400, { error: "invalid_input" });
430
+ let c = h(s.code, r, G);
431
+ if (!c || !te(s.codeVerifier, c.ch)) return Q(400, { error: "invalid_code" });
432
+ let d = u(c.handoff, r, G), g = D(s.credential);
433
+ if (!d || d.guest.id !== c.guestId || d.guest.tenantId !== W.guest.tenantId || !g || g.guestId !== d.guest.id || g.deviceId !== d.guest.metadata?.guestDeviceId) return Q(403, { error: "upgrade_handoff_rejected" });
434
+ let _ = await a(d.guest);
435
+ if (_) return _;
436
+ let b = await v.runPromise(l({
437
+ profile: d.profile,
438
+ credential: s.credential,
439
+ store: e.store,
440
+ guest: W.guest,
441
+ service: K(e.store),
442
+ users: k.users,
443
+ config: k.auth,
444
+ ...W.configForTransaction ? { configForTransaction: W.configForTransaction } : {},
445
+ ipAddress: e.remoteAddr ?? null,
446
+ userAgent: e.headers["user-agent"] ?? null
447
+ }));
448
+ return b.kind === "refused" ? Q(b.status, {
449
+ error: b.code,
450
+ message: b.message
451
+ }) : Q(200, {
452
+ ok: !0,
453
+ upgraded: !0,
454
+ subject: b.subject,
455
+ session: b.session.value,
456
+ expiresAt: b.session.expiresAt
457
+ });
458
+ }, $ = /* @__PURE__ */ new Map(), xe = async (e, t) => {
459
+ let n = ($.get(e) ?? Promise.resolve()).then(t, t), r = n.then(() => void 0, () => void 0);
460
+ $.set(e, r);
461
+ try {
462
+ return await n;
463
+ } finally {
464
+ $.get(e) === r && $.delete(e);
465
+ }
466
+ }, Se = async (t) => {
467
+ let [n, r, ...i] = t.path.slice((P + "/platform/").length).split("/"), a = n === void 0 ? void 0 : V.get(n);
468
+ if (!a || i.length !== 0 || r !== "sign-in" && r !== "link" || r === "link" && !W) return j(404, { error: "not_found" });
469
+ if (t.method !== "POST") return j(405, { error: "method_not_allowed" });
470
+ if ((t.headers["content-type"] ?? "").split(";")[0]?.trim() !== "application/json") return j(415, { error: "json_required" });
471
+ if (t.rawBody.byteLength > 16384) return j(413, { error: "body_too_large" });
472
+ let s;
473
+ try {
474
+ s = JSON.parse(new TextDecoder().decode(t.rawBody));
475
+ } catch {
476
+ return Q(400, { error: "invalid_input" });
477
+ }
478
+ if (!s || typeof s != "object" || Array.isArray(s)) return Q(400, { error: "invalid_input" });
479
+ let c = s, u = r === "link" ? ["ticket", "credential"] : ["ticket"];
480
+ if (Object.keys(c).some((e) => !u.includes(e)) || typeof c.ticket != "string" || c.ticket.length === 0 || r === "link" && (typeof c.credential != "string" || !D(c.credential))) return Q(400, { error: "invalid_input" });
481
+ if (!t.store && (r === "link" || !k.identities)) return Q(503, { error: "store_unavailable" });
482
+ let d;
483
+ try {
484
+ d = await _(a, c.ticket);
485
+ } catch (t) {
486
+ if (t instanceof e) return Q(t.status, {
487
+ error: t.code,
488
+ message: t.message
489
+ });
490
+ throw t;
491
+ }
492
+ let f = {
493
+ provider: a.id,
494
+ providerAccountId: d.accountId,
495
+ email: null,
496
+ emailVerified: !1,
497
+ emailIsPrivateRelay: !1,
498
+ name: null,
499
+ firstName: null,
500
+ lastName: null,
501
+ avatarUrl: null,
502
+ nameIsFirstAuthorizationOnly: !1,
503
+ raw: {}
504
+ }, p = {
505
+ ipAddress: t.remoteAddr ?? null,
506
+ userAgent: t.headers["user-agent"] ?? null
507
+ }, m = await xe(a.id + "\0" + d.accountId, () => r === "sign-in" ? v.runPromise(o({
508
+ profile: f,
509
+ identities: _e(t),
510
+ users: k.users,
511
+ config: k.auth,
512
+ policy: L,
513
+ ...p
514
+ })) : v.runPromise(l({
515
+ profile: f,
516
+ credential: c.credential,
517
+ store: t.store,
518
+ guest: W.guest,
519
+ service: K(t.store),
520
+ users: k.users,
521
+ config: k.auth,
522
+ ...W.configForTransaction ? { configForTransaction: W.configForTransaction } : {},
523
+ ...p
524
+ })));
525
+ return m.kind === "refused" ? Q(m.status, {
526
+ error: m.code,
527
+ message: m.message
528
+ }) : Q(200, {
529
+ ok: !0,
530
+ subject: m.subject,
531
+ session: m.session.value,
532
+ expiresAt: m.session.expiresAt,
533
+ created: m.created
534
+ });
535
+ }, Ce = async (e) => {
536
+ if (!W) return j(404, { error: "not_found" });
537
+ let t = e.path.slice((P + "/upgrade/").length), r = E(k.auth);
538
+ if (t.startsWith("native/")) return be(e, t.slice(7), r);
539
+ if (t === "csrf") {
540
+ if (e.method !== "GET") return j(405, { error: "method_not_allowed" });
541
+ let t = se(r.current.secret);
542
+ return {
543
+ ...j(200, { token: t }),
544
+ headers: {
545
+ "cache-control": "no-store",
546
+ "set-cookie": C(T, t, {
547
+ path: "/",
548
+ httpOnly: !1,
549
+ secure: R,
550
+ sameSite: "lax",
551
+ maxAgeSeconds: F,
552
+ ...k.auth.cookieDomain ? { domain: k.auth.cookieDomain } : {}
553
+ })
554
+ }
555
+ };
556
+ }
557
+ let i = () => u(w(e.headers.cookie, U), r, G);
558
+ if (t === "receipt") {
559
+ if (e.method !== "GET") return j(405, { error: "method_not_allowed" });
560
+ let t = O(e.headers, r)?.subject;
561
+ if (!e.store || t?.type !== "user" || t.kind === "guest" || !/^guest_[A-Za-z0-9_-]+$/.test(t.id) || t.tenantId !== W.guest.tenantId || typeof t.metadata?.sessionId != "string") return j(403, { error: "credential_rejected" });
562
+ try {
563
+ let n = await v.runPromise(oe(e.store).findSession(t.metadata.sessionId));
564
+ if (!n || n.userId !== t.id || n.tenantId !== t.tenantId || !(n.expiresAt.getTime() > Date.now())) return j(403, { error: "credential_rejected" });
565
+ let r = {
566
+ predicate: b(x("id", t.id), x("tenantId", t.tenantId)),
567
+ order: [],
568
+ take: void 0,
569
+ skip: void 0,
570
+ projection: void 0
571
+ }, i = (await e.store.query({
572
+ table: "users",
573
+ ...r
574
+ }))[0], a = (await e.store.query({
575
+ table: "authGuests",
576
+ ...r
577
+ }))[0];
578
+ return !i || i.id !== t.id || i.tenantId !== t.tenantId || a || i.deactivatedAt !== null && i.deactivatedAt !== void 0 ? j(403, { error: "credential_rejected" }) : j(200, {
579
+ upgraded: !0,
580
+ subject: {
581
+ type: "user",
582
+ id: t.id,
583
+ tenantId: t.tenantId
584
+ }
585
+ });
586
+ } catch {
587
+ return j(503, { error: "store_unavailable" });
588
+ }
589
+ }
590
+ if (t === "pending") {
591
+ if (e.method !== "GET") return j(405, { error: "method_not_allowed" });
592
+ let t = i();
593
+ if (!t || t.guest.tenantId !== W.guest.tenantId || !e.store) return j(403, { error: "upgrade_handoff_rejected" });
594
+ let n = await v.runPromise(v.result(K(e.store).isLive(t.guest)));
595
+ return y.isFailure(n) ? j(503, { error: "store_unavailable" }) : n.success ? j(200, {
596
+ guestId: t.guest.id,
597
+ provider: t.profile.provider,
598
+ email: t.profile.email,
599
+ emailVerified: t.profile.emailVerified,
600
+ expiresAt: t.exp * 1e3
601
+ }) : j(403, { error: "credential_rejected" });
602
+ }
603
+ let a = n(t) && B.includes(t);
604
+ if (!a && t !== "complete") return j(404, { error: "not_found" });
605
+ if (e.method !== "POST") return j(405, { error: "method_not_allowed" });
606
+ if ((e.headers["content-type"] ?? "").split(";")[0]?.trim() !== "application/json") return j(415, { error: "json_required" });
607
+ if (e.rawBody.byteLength > 1024) return j(413, { error: "body_too_large" });
608
+ if (![r.current, r.previous].filter((e) => e !== void 0).some((t) => le(e.headers[ie], w(e.headers.cookie, T), t.secret))) return j(403, { error: "csrf_rejected" });
609
+ let o;
610
+ try {
611
+ o = JSON.parse(new TextDecoder().decode(e.rawBody));
612
+ } catch {
613
+ return j(400, { error: "invalid_input" });
614
+ }
615
+ if (!o || typeof o != "object" || Array.isArray(o)) return j(400, { error: "invalid_input" });
616
+ let s = o;
617
+ if (!e.store) return j(503, { error: "store_unavailable" });
618
+ let c = K(e.store);
619
+ if (a && n(t)) {
620
+ if (Object.keys(s).length !== 0) return j(400, { error: "invalid_input" });
621
+ let n = typeof W.guest.secret == "function" ? W.guest.secret() : W.guest.secret, r = f(O(e.headers, n)?.subject);
622
+ if (!r || r.tenantId !== W.guest.tenantId) return j(403, { error: "credential_rejected" });
623
+ let i = await v.runPromise(v.result(c.isLive(r)));
624
+ if (y.isFailure(i)) return j(503, { error: "store_unavailable" });
625
+ if (!i.success) return j(403, { error: "credential_rejected" });
626
+ let a = Z(e, t, r);
627
+ return {
628
+ ...j(200, { authorizationUrl: a.headers?.location }),
629
+ headers: {
630
+ "cache-control": "no-store",
631
+ "set-cookie": a.headers?.["set-cookie"] ?? []
632
+ }
633
+ };
634
+ }
635
+ if (Object.keys(s).some((e) => e !== "credential" && e !== "confirmed") || typeof s.credential != "string" || s.confirmed !== !0) return j(400, { error: "invalid_input" });
636
+ let d = i(), p = D(s.credential);
637
+ if (!d || !p || d.guest.tenantId !== W.guest.tenantId || p.guestId !== d.guest.id || p.deviceId !== d.guest.metadata?.guestDeviceId) return j(403, { error: "upgrade_handoff_rejected" });
638
+ let m = await v.runPromise(v.result(c.isLive(d.guest)));
639
+ if (y.isFailure(m)) return j(503, { error: "store_unavailable" });
640
+ if (!m.success) return j(403, { error: "credential_rejected" });
641
+ let h = await v.runPromise(l({
642
+ profile: d.profile,
643
+ credential: s.credential,
644
+ store: e.store,
645
+ guest: W.guest,
646
+ service: c,
647
+ users: k.users,
648
+ config: k.auth,
649
+ ...W.configForTransaction ? { configForTransaction: W.configForTransaction } : {},
650
+ ipAddress: e.remoteAddr ?? null,
651
+ userAgent: e.headers["user-agent"] ?? null
652
+ }));
653
+ return h.kind === "refused" ? j(h.status, {
654
+ error: h.code,
655
+ message: h.message
656
+ }) : {
657
+ ...j(200, {
658
+ upgraded: !0,
659
+ subject: h.subject
660
+ }),
661
+ headers: {
662
+ "cache-control": "no-store",
663
+ "set-cookie": [q(U), h.setCookie]
664
+ }
665
+ };
666
+ }, we = {
667
+ method: "*",
668
+ path: P,
669
+ handle: async (t) => {
670
+ if (t.path.startsWith(P + "/platform/")) return Se(t);
671
+ if (t.path.startsWith(P + "/upgrade/")) try {
672
+ return await Ce(t);
673
+ } catch (t) {
674
+ if (t instanceof e) return X(t.status, t.code, t.message);
675
+ throw t;
676
+ }
677
+ let n = ve(t.path);
678
+ if (!n) return j(404, { error: "not_found" });
679
+ try {
680
+ return n.isCallback ? t.method !== "GET" && t.method !== "POST" ? j(405, { error: "method_not_allowed" }) : await ye(t, n.id) : t.method === "GET" ? Z(t, n.id) : j(405, { error: "method_not_allowed" });
681
+ } catch (t) {
682
+ if (t instanceof e) return X(t.status, t.code, t.message);
683
+ throw t;
684
+ }
685
+ }
686
+ }, Te = [
687
+ "store:write",
688
+ "network:outbound:oauth2.googleapis.com",
689
+ "network:outbound:www.googleapis.com",
690
+ "network:outbound:github.com",
691
+ "network:outbound:api.github.com",
692
+ "network:outbound:appleid.apple.com",
693
+ "network:outbound:discord.com",
694
+ "network:outbound:id.twitch.tv",
695
+ "network:outbound:login.microsoftonline.com",
696
+ "network:outbound:api.epicgames.dev",
697
+ ...[...V.values()].flatMap((e) => e.permissions)
698
+ ], Ee = (e, t) => {
699
+ let n = z[e];
700
+ return t === 0 ? n?.clientId !== void 0 : e === "apple" ? t === 1 ? n?.appleKey?.teamId !== void 0 : t === 2 ? n?.appleKey?.keyId !== void 0 : n?.appleKey?.privateKey !== void 0 : n?.clientSecret !== void 0;
701
+ }, De = B.flatMap((e) => A[e].map((t, n) => ({
702
+ name: t.name,
703
+ required: !Ee(e, n),
704
+ secret: t.secret,
705
+ description: t.description
706
+ }))), Oe = k.auth.secret === void 0 && k.auth.secrets === void 0 ? [{
707
+ name: "VOLTRO_SESSION_SECRET",
708
+ required: !0,
709
+ secret: !0,
710
+ generate: "base64url",
711
+ description: "Project key for signed login handoffs and ordinary sessions."
712
+ }] : [];
713
+ return re({
714
+ name: k.name ? `@voltro/plugin-auth-social#${k.name}` : "@voltro/plugin-auth-social",
715
+ description: "Social login — Google / GitHub / Apple / Discord / Twitch / Microsoft / Epic, and game sign-in with Steam / EOS / console tickets. Mandatory S256 + state, JWKS-verified ID tokens, a deliberate account-linking policy, and sessions issued through @voltro/plugin-auth.",
716
+ permissions: Te,
717
+ declaredEnv: [
718
+ ...De,
719
+ ...[...V.values()].flatMap((e) => e.declaredEnv),
720
+ ...Oe
721
+ ],
722
+ extendSchema: { tables: c },
723
+ httpRoutes: [we],
724
+ onActivate: (e) => v.sync(() => {
725
+ for (let e of B) J(e);
726
+ for (let e of V.values()) e.check();
727
+ let n = E(k.auth);
728
+ if (ce({ exp: Math.floor(Date.now() / 1e3) + F }, n.current.secret, "social-login"), n.previous && n.previous.secret.length < 32) throw new t("socialAuthPlugin: previous project signing key is too short");
729
+ e.logger.info("social auth active", {
730
+ prefix: P,
731
+ providers: B.join(","),
732
+ platforms: [...V.keys()].join(","),
733
+ linkPolicy: L,
734
+ stateTtlSeconds: F
735
+ });
736
+ }),
737
+ onDeactivate: () => v.void
738
+ });
739
+ };
740
+ //#endregion
741
+ export { P as socialAuthPlugin };