@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.
@@ -0,0 +1,23 @@
1
+ import { SocialAccountLinkError as e } from "./errors.js";
2
+ //#region src/linking.ts
3
+ var t = (t) => {
4
+ let { profile: n, linkedUserId: r, existingUserIdByEmail: i, policy: a } = t;
5
+ return r === null ? n.email === null || i === null ? { kind: "create-user" } : a === "never" ? {
6
+ kind: "refuse",
7
+ error: new e("account_exists_unlinked", `An account already exists for ${n.email}. Sign in with your existing method, then connect ${n.provider} from your account settings.`)
8
+ } : n.emailVerified ? n.emailIsPrivateRelay ? {
9
+ kind: "refuse",
10
+ error: new e("private_relay_email", "This is an Apple private-relay address, which is unique to this app and cannot identify an existing account. Sign in with your existing method, then connect Apple from your account settings.")
11
+ } : {
12
+ kind: "link",
13
+ userId: i
14
+ } : {
15
+ kind: "refuse",
16
+ error: new e("email_not_verified", `${n.provider} did not verify ownership of ${n.email}, so it cannot be attached to the existing account with that address.`)
17
+ } : {
18
+ kind: "sign-in",
19
+ userId: r
20
+ };
21
+ };
22
+ //#endregion
23
+ export { t as decideSocialLink };
package/dist/login.js ADDED
@@ -0,0 +1,32 @@
1
+ import { randomToken as e, s256 as t } from "./pkce.js";
2
+ import { SocialConfigError as n } from "./errors.js";
3
+ import { socialProvider as r } from "./providers.js";
4
+ //#region src/login.ts
5
+ var i = (i) => {
6
+ if (!i.clientId) throw new n("socialBeginLogin: `clientId` is required");
7
+ if (!i.redirectUri) throw new n("socialBeginLogin: `redirectUri` is required");
8
+ let a = r(i.provider, i.endpoints ?? {}), o = i.randomness?.state ?? e(), s = i.randomness?.codeVerifier ?? e(), c = a.kind === "oidc" ? i.randomness?.nonce ?? e() : "", l = i.scopes ?? a.defaultScopes, u = new URLSearchParams({
9
+ client_id: i.clientId,
10
+ redirect_uri: i.redirectUri,
11
+ response_type: "code",
12
+ scope: l.join(" "),
13
+ state: o,
14
+ code_challenge: t(s),
15
+ code_challenge_method: "S256"
16
+ });
17
+ return c && u.set("nonce", c), a.id === "twitch" && u.set("claims", JSON.stringify({ id_token: {
18
+ email: null,
19
+ email_verified: null,
20
+ preferred_username: null,
21
+ picture: null
22
+ } })), a.responseMode === "form_post" && u.set("response_mode", "form_post"), i.prompt && u.set("prompt", i.prompt), i.loginHint && u.set(a.id === "github" ? "login" : "login_hint", i.loginHint), {
23
+ provider: a.id,
24
+ url: `${a.authorizeUrl}?${u.toString()}`,
25
+ state: o,
26
+ codeVerifier: s,
27
+ nonce: c,
28
+ responseMode: a.responseMode
29
+ };
30
+ };
31
+ //#endregion
32
+ export { i as socialBeginLogin };
@@ -0,0 +1,66 @@
1
+ import { guestIdentityOf as e } from "./guestHandoff.js";
2
+ import { signAuthClaims as t, verifyAuthClaims as n } from "@voltro/plugin-auth";
3
+ import { createCipheriv as r, createDecipheriv as i, createHash as a, hkdfSync as o, randomBytes as s, timingSafeEqual as c } from "node:crypto";
4
+ //#region src/nativeGuestUpgrade.ts
5
+ var l = /^[A-Za-z0-9_-]{43}$/, u = /^[A-Za-z0-9._~-]{43,128}$/, d = /^[A-Za-z0-9._~-]{1,256}$/, f = (e, t) => {
6
+ if (!e || typeof e != "object" || Array.isArray(e)) return null;
7
+ let n = e;
8
+ return Object.keys(n).some((e) => e !== "ch" && e !== "r" && e !== "ns") || typeof n.ch != "string" || !l.test(n.ch) || typeof n.r != "string" || !t.includes(n.r) || typeof n.ns != "string" || !d.test(n.ns) ? null : {
9
+ ch: n.ch,
10
+ r: n.r,
11
+ ns: n.ns
12
+ };
13
+ }, p = (e) => typeof e == "string" && u.test(e), m = (e, t) => {
14
+ let n = Buffer.from(a("sha256").update(e, "ascii").digest("base64url")), r = Buffer.from(t);
15
+ return n.length === r.length && c(n, r);
16
+ }, h = (e) => [e.current, e.previous].filter((e) => e !== void 0).map((e) => e.secret), g = (e, n) => t({
17
+ kind: "ticket",
18
+ g: e.guest,
19
+ p: e.provider,
20
+ ...e.native,
21
+ exp: e.exp,
22
+ audience: e.audience
23
+ }, n.current.secret, "social-native-upgrade"), _ = (t, r, i, a) => {
24
+ if (!t || t.length > 3e3) return null;
25
+ let o = h(r).map((e) => n(t, e, "social-native-upgrade")).find((e) => e !== null);
26
+ if (!o || o.kind !== "ticket" || o.audience !== i || typeof o.exp != "number" || o.exp <= Math.floor(Date.now() / 1e3) || typeof o.p != "string") return null;
27
+ let s = e(o.g), c = f({
28
+ ch: o.ch,
29
+ r: o.r,
30
+ ns: o.ns
31
+ }, a);
32
+ return s && c ? {
33
+ guest: s,
34
+ provider: o.p,
35
+ native: c
36
+ } : null;
37
+ }, v = (e, t) => Buffer.from(o("sha256", e, "voltro:social-native-upgrade:v1", t, 32)), y = (e, t, n) => {
38
+ let i = s(12), a = r("aes-256-gcm", v(t.current.secret, n), i), o = Buffer.concat([a.update(JSON.stringify({
39
+ h: e.handoff,
40
+ ch: e.ch,
41
+ g: e.guestId,
42
+ exp: e.exp
43
+ }), "utf8"), a.final()]);
44
+ return Buffer.concat([
45
+ i,
46
+ a.getAuthTag(),
47
+ o
48
+ ]).toString("base64url");
49
+ }, b = (e, t, n) => {
50
+ if (typeof e != "string" || e.length < 40 || e.length > 6e3 || !/^[A-Za-z0-9_-]+$/.test(e)) return null;
51
+ let r = Buffer.from(e, "base64url");
52
+ if (r.length < 29) return null;
53
+ for (let e of h(t)) try {
54
+ let t = i("aes-256-gcm", v(e, n), r.subarray(0, 12));
55
+ t.setAuthTag(r.subarray(12, 28));
56
+ let a = JSON.parse(Buffer.concat([t.update(r.subarray(28)), t.final()]).toString("utf8"));
57
+ return typeof a.h != "string" || typeof a.ch != "string" || typeof a.g != "string" || typeof a.exp != "number" || a.exp <= Math.floor(Date.now() / 1e3) ? null : {
58
+ handoff: a.h,
59
+ ch: a.ch,
60
+ guestId: a.g
61
+ };
62
+ } catch {}
63
+ return null;
64
+ };
65
+ //#endregion
66
+ export { p as isNativeVerifier, f as nativeUpgradeRequestOf, b as openNativeUpgradeCode, y as sealNativeUpgradeCode, g as signNativeUpgradeTicket, m as verifierMatches, _ as verifyNativeUpgradeTicket };
package/dist/pkce.js ADDED
@@ -0,0 +1,5 @@
1
+ import { createHash as e, randomBytes as t } from "node:crypto";
2
+ //#region src/pkce.ts
3
+ var n = () => t(32).toString("base64url"), r = (t) => e("sha256").update(t, "ascii").digest().toString("base64url");
4
+ //#endregion
5
+ export { n as randomToken, r as s256 };
@@ -0,0 +1,115 @@
1
+ import { PlatformTicketError as e, SocialAuthError as t, SocialConfigError as n, SocialExchangeError as r } from "./errors.js";
2
+ import { isSocialProviderId as i } from "./providers.js";
3
+ import { verifySteamWebApiTicket as a } from "./steam.js";
4
+ import { JwtVerifyError as o, verifyJwt as s } from "@voltro/protocol/jwt";
5
+ //#region src/platform.ts
6
+ var c = /^[a-z][a-z0-9-]{1,31}$/, l = /^[\x21-\x7e]{1,255}$/, u = (e) => {
7
+ if (!c.test(e.id) || i(e.id)) throw new n("definePlatformLogin: id must be 2–32 lowercase letters, digits or hyphens and not a social provider id");
8
+ let t = e.verify;
9
+ return {
10
+ id: e.id,
11
+ declaredEnv: e.declaredEnv ?? [],
12
+ permissions: e.permissions ?? [],
13
+ check: e.check ?? (() => void 0),
14
+ verify: async (n) => {
15
+ let i = await t(n);
16
+ if (typeof i?.accountId != "string" || !l.test(i.accountId)) throw new r("malformed_token_response", `${e.id}: the verifier returned no usable account id`, 503);
17
+ return { accountId: i.accountId };
18
+ }
19
+ };
20
+ }, d = (e = {}) => {
21
+ let t = e.identity ?? "voltro";
22
+ if (!t || t.length > 255) throw new n("steamPlatformLogin: identity must be 1–255 characters");
23
+ let r = () => {
24
+ let t = e.appId ?? Number(process.env.VOLTRO_STEAM_APP_ID ?? ""), r = e.publisherKey ?? process.env.VOLTRO_STEAM_PUBLISHER_KEY ?? "";
25
+ if (!Number.isSafeInteger(t) || t < 1 || t > 4294967295) throw new n("steamPlatformLogin: no app id — pass `appId` or set VOLTRO_STEAM_APP_ID");
26
+ if (!r) throw new n("steamPlatformLogin: no publisher key — pass `publisherKey` or set VOLTRO_STEAM_PUBLISHER_KEY");
27
+ return {
28
+ appId: t,
29
+ publisherKey: r
30
+ };
31
+ };
32
+ return {
33
+ id: "steam",
34
+ declaredEnv: [{
35
+ name: "VOLTRO_STEAM_APP_ID",
36
+ required: e.appId === void 0,
37
+ secret: !1,
38
+ description: "Steam App ID the game runs as."
39
+ }, {
40
+ name: "VOLTRO_STEAM_PUBLISHER_KEY",
41
+ required: e.publisherKey === void 0,
42
+ secret: !0,
43
+ description: "Steamworks Web API publisher key; verifies game tickets on the server."
44
+ }],
45
+ permissions: ["network:outbound:partner.steam-api.com"],
46
+ check: () => {
47
+ r();
48
+ },
49
+ verify: async (n) => {
50
+ let { appId: i, publisherKey: o } = r();
51
+ return { accountId: (await a({
52
+ ticket: n,
53
+ appId: i,
54
+ identity: t,
55
+ publisherKey: o,
56
+ ...e.timeoutMs === void 0 ? {} : { timeoutMs: e.timeoutMs },
57
+ ...e.allowPublisherBanned === void 0 ? {} : { allowPublisherBanned: e.allowPublisherBanned },
58
+ ...e.verificationUrl === void 0 ? {} : { verificationUrl: e.verificationUrl },
59
+ ...e.fetchImpl === void 0 ? {} : { fetchImpl: e.fetchImpl }
60
+ })).providerAccountId };
61
+ }
62
+ };
63
+ }, f = "https://api.epicgames.dev/auth/v1/oauth/jwks", p = "https://api.epicgames.dev", m = /^[0-9a-fA-F]{32}$/, h = (t = {}) => {
64
+ let i = () => {
65
+ let e = t.clientId ?? process.env.VOLTRO_EOS_CLIENT_ID ?? "", r = t.deploymentId ?? process.env.VOLTRO_EOS_DEPLOYMENT_ID ?? "";
66
+ if (!e) throw new n("eosPlatformLogin: no client id — pass `clientId` or set VOLTRO_EOS_CLIENT_ID");
67
+ if (!r) throw new n("eosPlatformLogin: no deployment id — pass `deploymentId` or set VOLTRO_EOS_DEPLOYMENT_ID");
68
+ return {
69
+ clientId: e,
70
+ deploymentId: r
71
+ };
72
+ };
73
+ return {
74
+ id: "eos",
75
+ declaredEnv: [{
76
+ name: "VOLTRO_EOS_CLIENT_ID",
77
+ required: t.clientId === void 0,
78
+ secret: !1,
79
+ description: "EOS client id the game signs in with."
80
+ }, {
81
+ name: "VOLTRO_EOS_DEPLOYMENT_ID",
82
+ required: t.deploymentId === void 0,
83
+ secret: !1,
84
+ description: "EOS deployment id the game runs in."
85
+ }],
86
+ permissions: ["network:outbound:api.epicgames.dev"],
87
+ check: () => {
88
+ i();
89
+ },
90
+ verify: async (n) => {
91
+ let { clientId: a, deploymentId: c } = i();
92
+ if (n.length > 8192 || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(n)) throw new e("eos: malformed ID token");
93
+ let l;
94
+ try {
95
+ l = await s(n, t.jwksUrl ?? f, {
96
+ audience: a,
97
+ algorithms: ["RS256"]
98
+ });
99
+ } catch (t) {
100
+ throw t instanceof o && t.code === "jwks_unreachable" ? new r("exchange_failed", "eos: signing keys unavailable", 503) : new e("eos: ID token failed verification");
101
+ }
102
+ let u = l.iss, d = l.iat;
103
+ if (typeof u != "string" || !(u === p || u.startsWith(p + "/")) || typeof d != "number" || d * 1e3 > Date.now() + 6e4 || l.pfdid !== c || typeof l.sub != "string" || !m.test(l.sub)) throw new e("eos: ID token is for another issuer, deployment or player");
104
+ return { accountId: l.sub };
105
+ }
106
+ };
107
+ }, g = async (e, n) => {
108
+ try {
109
+ return await e.verify(n);
110
+ } catch (n) {
111
+ throw n instanceof t ? n : new r("exchange_failed", `${e.id}: ticket verification failed`, 503);
112
+ }
113
+ };
114
+ //#endregion
115
+ export { u as definePlatformLogin, h as eosPlatformLogin, d as steamPlatformLogin, g as verifyPlatformTicket };