@voltro/plugin-auth-social 0.84.0 → 0.86.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/CHANGELOG.md +1215 -0
- package/THIRD-PARTY-NOTICES.md +5 -89
- package/dist/apple.js +64 -0
- package/dist/callback.js +113 -0
- package/dist/errors.js +30 -0
- package/dist/github.js +34 -0
- package/dist/identityStore.js +104 -0
- package/dist/index.js +11 -721
- package/dist/linking.js +26 -0
- package/dist/login.js +27 -0
- package/dist/pkce.js +5 -0
- package/dist/plugin.js +244 -0
- package/dist/providers.js +70 -0
- package/dist/signIn.js +67 -0
- package/package.json +5 -5
package/dist/linking.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
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 ? {
|
|
6
|
+
kind: "refuse",
|
|
7
|
+
error: new e("no_email", `${n.provider} did not return an email address for this account. Add and verify an email with the provider, or sign in another way.`, 400)
|
|
8
|
+
} : i === null ? { kind: "create-user" } : a === "never" ? {
|
|
9
|
+
kind: "refuse",
|
|
10
|
+
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.`)
|
|
11
|
+
} : n.emailVerified ? n.emailIsPrivateRelay ? {
|
|
12
|
+
kind: "refuse",
|
|
13
|
+
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.")
|
|
14
|
+
} : {
|
|
15
|
+
kind: "link",
|
|
16
|
+
userId: i
|
|
17
|
+
} : {
|
|
18
|
+
kind: "refuse",
|
|
19
|
+
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.`)
|
|
20
|
+
} : {
|
|
21
|
+
kind: "sign-in",
|
|
22
|
+
userId: r
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { t as decideSocialLink };
|
package/dist/login.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
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.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), {
|
|
18
|
+
provider: a.id,
|
|
19
|
+
url: `${a.authorizeUrl}?${u.toString()}`,
|
|
20
|
+
state: o,
|
|
21
|
+
codeVerifier: s,
|
|
22
|
+
nonce: c,
|
|
23
|
+
responseMode: a.responseMode
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { i as socialBeginLogin };
|
package/dist/pkce.js
ADDED
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
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 { Effect as l } from "effect";
|
|
8
|
+
import { definePlugin as u } from "@voltro/protocol";
|
|
9
|
+
import { buildSetCookie as d, readCookie as f } from "@voltro/protocol/session";
|
|
10
|
+
//#region src/plugin.ts
|
|
11
|
+
var p = "/auth/social", m = 600, h = "voltro:oauth", g = {
|
|
12
|
+
google: [{
|
|
13
|
+
name: "VOLTRO_GOOGLE_CLIENT_ID",
|
|
14
|
+
secret: !1,
|
|
15
|
+
description: "Google OAuth client id (…apps.googleusercontent.com)."
|
|
16
|
+
}, {
|
|
17
|
+
name: "VOLTRO_GOOGLE_CLIENT_SECRET",
|
|
18
|
+
secret: !0,
|
|
19
|
+
description: "Google OAuth client secret."
|
|
20
|
+
}],
|
|
21
|
+
github: [{
|
|
22
|
+
name: "VOLTRO_GITHUB_CLIENT_ID",
|
|
23
|
+
secret: !1,
|
|
24
|
+
description: "GitHub OAuth app client id."
|
|
25
|
+
}, {
|
|
26
|
+
name: "VOLTRO_GITHUB_CLIENT_SECRET",
|
|
27
|
+
secret: !0,
|
|
28
|
+
description: "GitHub OAuth app client secret."
|
|
29
|
+
}],
|
|
30
|
+
apple: [
|
|
31
|
+
{
|
|
32
|
+
name: "VOLTRO_APPLE_CLIENT_ID",
|
|
33
|
+
secret: !1,
|
|
34
|
+
description: "Apple Services ID, e.g. com.example.web."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "VOLTRO_APPLE_TEAM_ID",
|
|
38
|
+
secret: !1,
|
|
39
|
+
description: "Apple Developer Team ID (10 chars)."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "VOLTRO_APPLE_KEY_ID",
|
|
43
|
+
secret: !1,
|
|
44
|
+
description: "Key ID of the Sign-in-with-Apple .p8 key."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "VOLTRO_APPLE_PRIVATE_KEY",
|
|
48
|
+
secret: !0,
|
|
49
|
+
description: "Contents of the .p8 file (PKCS#8 PEM). Apple signs no static secret — this key mints one per exchange."
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}, _ = (e, t) => ({
|
|
53
|
+
status: e,
|
|
54
|
+
body: JSON.stringify(t),
|
|
55
|
+
contentType: "application/json; charset=utf-8"
|
|
56
|
+
}), v = (e, t) => ({
|
|
57
|
+
status: 302,
|
|
58
|
+
body: "",
|
|
59
|
+
contentType: "text/plain; charset=utf-8",
|
|
60
|
+
headers: {
|
|
61
|
+
location: e,
|
|
62
|
+
...t === void 0 ? {} : { "set-cookie": t }
|
|
63
|
+
}
|
|
64
|
+
}), y = (e) => Buffer.from(JSON.stringify(e), "utf8").toString("base64url"), b = (e) => {
|
|
65
|
+
if (!e) return null;
|
|
66
|
+
try {
|
|
67
|
+
let t = JSON.parse(Buffer.from(e, "base64url").toString("utf8"));
|
|
68
|
+
return typeof t.p != "string" || typeof t.s != "string" || typeof t.v != "string" || typeof t.n != "string" ? null : {
|
|
69
|
+
p: t.p,
|
|
70
|
+
s: t.s,
|
|
71
|
+
v: t.v,
|
|
72
|
+
n: t.n
|
|
73
|
+
};
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}, x = (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), S = (S) => {
|
|
78
|
+
let C = S.prefix ?? p, w = S.stateTtlSeconds ?? m, T = S.stateCookieName ?? h, E = S.linkPolicy ?? "never", D = S.auth.cookieSecure ?? !0, O = Object.keys(S.providers).filter(n);
|
|
79
|
+
if (O.length === 0) throw new t("socialAuthPlugin: `providers` is empty — enable at least one of google / github / apple");
|
|
80
|
+
let k = (S.appBaseUrl ?? S.auth.appBaseUrl ?? "").replace(/\/+$/, ""), A = (e) => {
|
|
81
|
+
let n = S.providers[e] ?? {}, r = process.env, i = n.clientId ?? r[g[e][0].name] ?? "";
|
|
82
|
+
if (!i) throw new t(`socialAuthPlugin: ${e} has no client id — pass \`providers.${e}.clientId\` or set ${g[e][0].name}`);
|
|
83
|
+
let a = n.redirectUri ?? `${k}${C}/${e}/callback`;
|
|
84
|
+
if (!a.startsWith("http")) throw new t(`socialAuthPlugin: ${e} needs an absolute redirect URI — set \`appBaseUrl\` (or \`providers.${e}.redirectUri\`)`);
|
|
85
|
+
let o = {
|
|
86
|
+
id: e,
|
|
87
|
+
clientId: i,
|
|
88
|
+
redirectUri: a,
|
|
89
|
+
endpoints: n.endpoints ?? {},
|
|
90
|
+
...n.scopes === void 0 ? {} : { scopes: n.scopes }
|
|
91
|
+
};
|
|
92
|
+
if (e === "apple") {
|
|
93
|
+
let e = n.appleKey ?? (r.VOLTRO_APPLE_TEAM_ID && r.VOLTRO_APPLE_KEY_ID && r.VOLTRO_APPLE_PRIVATE_KEY ? {
|
|
94
|
+
teamId: r.VOLTRO_APPLE_TEAM_ID,
|
|
95
|
+
keyId: r.VOLTRO_APPLE_KEY_ID,
|
|
96
|
+
privateKey: r.VOLTRO_APPLE_PRIVATE_KEY.replace(/\\n/g, "\n")
|
|
97
|
+
} : void 0);
|
|
98
|
+
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");
|
|
99
|
+
return {
|
|
100
|
+
...o,
|
|
101
|
+
appleKey: e
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
let s = n.clientSecret ?? r[g[e][1].name] ?? "";
|
|
105
|
+
if (!s) throw new t(`socialAuthPlugin: ${e} has no client secret — pass \`providers.${e}.clientSecret\` or set ${g[e][1].name}`);
|
|
106
|
+
return {
|
|
107
|
+
...o,
|
|
108
|
+
clientSecret: s
|
|
109
|
+
};
|
|
110
|
+
}, j = S.identities ?? null, M = (e) => {
|
|
111
|
+
if (j) return j;
|
|
112
|
+
let n = e.store;
|
|
113
|
+
if (!n) throw new t("socialAuthPlugin: no identity store — pass `identities`, or run on an app with a DataStore");
|
|
114
|
+
return j = s(n), j;
|
|
115
|
+
}, N = (e) => {
|
|
116
|
+
if (!e.startsWith(`${C}/`)) return null;
|
|
117
|
+
let [t, ...r] = e.slice(C.length + 1).split("/");
|
|
118
|
+
return !t || !n(t) || !O.includes(t) ? null : r.length === 0 ? {
|
|
119
|
+
id: t,
|
|
120
|
+
isCallback: !1
|
|
121
|
+
} : r.length === 1 && r[0] === "callback" ? {
|
|
122
|
+
id: t,
|
|
123
|
+
isCallback: !0
|
|
124
|
+
} : null;
|
|
125
|
+
}, P = (e, t, n) => {
|
|
126
|
+
if (!S.failureRedirect) return _(e, {
|
|
127
|
+
error: t,
|
|
128
|
+
message: n
|
|
129
|
+
});
|
|
130
|
+
let r = S.failureRedirect.includes("?") ? "&" : "?";
|
|
131
|
+
return v(`${S.failureRedirect}${r}social_error=${encodeURIComponent(t)}`, d(T, "", {
|
|
132
|
+
maxAgeSeconds: 0,
|
|
133
|
+
path: C,
|
|
134
|
+
secure: D
|
|
135
|
+
}));
|
|
136
|
+
}, F = (e, t) => {
|
|
137
|
+
let n = A(t), a = r(t, n.endpoints), o = i({
|
|
138
|
+
provider: t,
|
|
139
|
+
clientId: n.clientId,
|
|
140
|
+
redirectUri: n.redirectUri,
|
|
141
|
+
...n.scopes === void 0 ? {} : { scopes: n.scopes },
|
|
142
|
+
endpoints: n.endpoints,
|
|
143
|
+
...new URLSearchParams(e.query).get("login_hint") ? { loginHint: new URLSearchParams(e.query).get("login_hint") } : {}
|
|
144
|
+
}), s = d(T, y({
|
|
145
|
+
p: t,
|
|
146
|
+
s: o.state,
|
|
147
|
+
v: o.codeVerifier,
|
|
148
|
+
n: o.nonce
|
|
149
|
+
}), {
|
|
150
|
+
maxAgeSeconds: w,
|
|
151
|
+
httpOnly: !0,
|
|
152
|
+
sameSite: a.responseMode === "form_post" ? "none" : "lax",
|
|
153
|
+
secure: a.responseMode === "form_post" || D,
|
|
154
|
+
...S.auth.cookieDomain ? { domain: S.auth.cookieDomain } : {},
|
|
155
|
+
path: C
|
|
156
|
+
});
|
|
157
|
+
return v(o.url, s);
|
|
158
|
+
}, I = async (e, t) => {
|
|
159
|
+
let n = x(e), r = n.get("error");
|
|
160
|
+
if (r) return P(400, "exchange_failed", `${t} refused the authorization: ${r}`);
|
|
161
|
+
let i = b(f(e.headers.cookie, T)), s = i && i.p === t ? i : null, c = A(t), u = await a({
|
|
162
|
+
provider: t,
|
|
163
|
+
clientId: c.clientId,
|
|
164
|
+
...c.clientSecret === void 0 ? {} : { clientSecret: c.clientSecret },
|
|
165
|
+
...c.appleKey === void 0 ? {} : { appleKey: c.appleKey },
|
|
166
|
+
redirectUri: c.redirectUri,
|
|
167
|
+
code: n.get("code") ?? "",
|
|
168
|
+
state: n.get("state") ?? "",
|
|
169
|
+
expectedState: s?.s ?? "",
|
|
170
|
+
codeVerifier: s?.v ?? "",
|
|
171
|
+
expectedNonce: s?.n ?? "",
|
|
172
|
+
...n.get("user") ? { appleUserPayload: n.get("user") } : {},
|
|
173
|
+
endpoints: c.endpoints
|
|
174
|
+
}), p = await l.runPromise(o({
|
|
175
|
+
profile: u,
|
|
176
|
+
identities: M(e),
|
|
177
|
+
users: S.users,
|
|
178
|
+
config: S.auth,
|
|
179
|
+
policy: E,
|
|
180
|
+
ipAddress: e.remoteAddr ?? null,
|
|
181
|
+
userAgent: e.headers["user-agent"] ?? null
|
|
182
|
+
}));
|
|
183
|
+
if (p.kind === "refused") return P(p.status, p.code, p.message);
|
|
184
|
+
let m = d(T, "", {
|
|
185
|
+
maxAgeSeconds: 0,
|
|
186
|
+
path: C,
|
|
187
|
+
secure: D
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
status: 302,
|
|
191
|
+
body: "",
|
|
192
|
+
contentType: "text/plain; charset=utf-8",
|
|
193
|
+
headers: {
|
|
194
|
+
location: S.successRedirect ?? S.auth.successRedirect ?? "/",
|
|
195
|
+
"set-cookie": [m, p.setCookie]
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}, L = {
|
|
199
|
+
method: "*",
|
|
200
|
+
path: C,
|
|
201
|
+
handle: async (t) => {
|
|
202
|
+
let n = N(t.path);
|
|
203
|
+
if (!n) return _(404, { error: "not_found" });
|
|
204
|
+
try {
|
|
205
|
+
return n.isCallback ? t.method !== "GET" && t.method !== "POST" ? _(405, { error: "method_not_allowed" }) : await I(t, n.id) : t.method === "GET" ? F(t, n.id) : _(405, { error: "method_not_allowed" });
|
|
206
|
+
} catch (t) {
|
|
207
|
+
if (t instanceof e) return P(t.status, t.code, t.message);
|
|
208
|
+
throw t;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}, R = [
|
|
212
|
+
"store:write",
|
|
213
|
+
"network:outbound:oauth2.googleapis.com",
|
|
214
|
+
"network:outbound:www.googleapis.com",
|
|
215
|
+
"network:outbound:github.com",
|
|
216
|
+
"network:outbound:api.github.com",
|
|
217
|
+
"network:outbound:appleid.apple.com"
|
|
218
|
+
], z = O.flatMap((e) => g[e].map((t) => ({
|
|
219
|
+
name: t.name,
|
|
220
|
+
required: S.providers[e]?.clientId === void 0,
|
|
221
|
+
secret: t.secret,
|
|
222
|
+
description: t.description
|
|
223
|
+
})));
|
|
224
|
+
return u({
|
|
225
|
+
name: S.name ? `@voltro/plugin-auth-social#${S.name}` : "@voltro/plugin-auth-social",
|
|
226
|
+
description: "Social login — Sign in with Google / GitHub / Apple. Mandatory PKCE (S256) + state, JWKS-verified ID tokens, a deliberate account-linking policy, and sessions issued through @voltro/plugin-auth.",
|
|
227
|
+
permissions: R,
|
|
228
|
+
declaredEnv: z,
|
|
229
|
+
extendSchema: { tables: c },
|
|
230
|
+
httpRoutes: [L],
|
|
231
|
+
onActivate: (e) => l.sync(() => {
|
|
232
|
+
for (let e of O) A(e);
|
|
233
|
+
e.logger.info("social auth active", {
|
|
234
|
+
prefix: C,
|
|
235
|
+
providers: O.join(","),
|
|
236
|
+
linkPolicy: E,
|
|
237
|
+
stateTtlSeconds: w
|
|
238
|
+
});
|
|
239
|
+
}),
|
|
240
|
+
onDeactivate: () => l.void
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
//#endregion
|
|
244
|
+
export { S as socialAuthPlugin };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { SocialConfigError as e } from "./errors.js";
|
|
2
|
+
//#region src/providers.ts
|
|
3
|
+
var t = {
|
|
4
|
+
id: "google",
|
|
5
|
+
label: "Google",
|
|
6
|
+
authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
7
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
8
|
+
jwksUrl: "https://www.googleapis.com/oauth2/v3/certs",
|
|
9
|
+
issuer: "https://accounts.google.com",
|
|
10
|
+
apiBase: null,
|
|
11
|
+
defaultScopes: [
|
|
12
|
+
"openid",
|
|
13
|
+
"email",
|
|
14
|
+
"profile"
|
|
15
|
+
],
|
|
16
|
+
kind: "oidc",
|
|
17
|
+
responseMode: "query",
|
|
18
|
+
assertsEmailVerification: !0,
|
|
19
|
+
pkceEnforced: !0
|
|
20
|
+
}, n = {
|
|
21
|
+
id: "github",
|
|
22
|
+
label: "GitHub",
|
|
23
|
+
authorizeUrl: "https://github.com/login/oauth/authorize",
|
|
24
|
+
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
25
|
+
jwksUrl: null,
|
|
26
|
+
issuer: null,
|
|
27
|
+
apiBase: "https://api.github.com",
|
|
28
|
+
defaultScopes: ["read:user", "user:email"],
|
|
29
|
+
kind: "oauth2",
|
|
30
|
+
responseMode: "query",
|
|
31
|
+
assertsEmailVerification: !0,
|
|
32
|
+
pkceEnforced: !1
|
|
33
|
+
}, r = {
|
|
34
|
+
id: "apple",
|
|
35
|
+
label: "Apple",
|
|
36
|
+
authorizeUrl: "https://appleid.apple.com/auth/authorize",
|
|
37
|
+
tokenUrl: "https://appleid.apple.com/auth/token",
|
|
38
|
+
jwksUrl: "https://appleid.apple.com/auth/keys",
|
|
39
|
+
issuer: "https://appleid.apple.com",
|
|
40
|
+
apiBase: null,
|
|
41
|
+
defaultScopes: ["name", "email"],
|
|
42
|
+
kind: "oidc",
|
|
43
|
+
responseMode: "form_post",
|
|
44
|
+
assertsEmailVerification: !0,
|
|
45
|
+
pkceEnforced: !0
|
|
46
|
+
}, i = {
|
|
47
|
+
google: t,
|
|
48
|
+
github: n,
|
|
49
|
+
apple: r
|
|
50
|
+
}, a = [
|
|
51
|
+
t,
|
|
52
|
+
n,
|
|
53
|
+
r
|
|
54
|
+
], o = [
|
|
55
|
+
"google",
|
|
56
|
+
"github",
|
|
57
|
+
"apple"
|
|
58
|
+
], s = (e) => e === "google" || e === "github" || e === "apple", c = (t, n = {}) => {
|
|
59
|
+
if (!s(t)) throw new e(`unknown social provider '${t}' — expected one of ${o.join(", ")}`);
|
|
60
|
+
return {
|
|
61
|
+
...i[t],
|
|
62
|
+
...n.authorizeUrl === void 0 ? {} : { authorizeUrl: n.authorizeUrl },
|
|
63
|
+
...n.tokenUrl === void 0 ? {} : { tokenUrl: n.tokenUrl },
|
|
64
|
+
...n.jwksUrl === void 0 ? {} : { jwksUrl: n.jwksUrl },
|
|
65
|
+
...n.issuer === void 0 ? {} : { issuer: n.issuer },
|
|
66
|
+
...n.apiBase === void 0 ? {} : { apiBase: n.apiBase }
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
//#endregion
|
|
70
|
+
export { a as SOCIAL_PROVIDERS, o as SOCIAL_PROVIDER_IDS, s as isSocialProviderId, c as socialProvider };
|
package/dist/signIn.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { SocialAccountLinkError as e } from "./errors.js";
|
|
2
|
+
import { decideSocialLink as t } from "./linking.js";
|
|
3
|
+
import { Effect as n } from "effect";
|
|
4
|
+
import { issueUserSession as r, newAuthId as i, runSubjectGuards as a } from "@voltro/plugin-auth";
|
|
5
|
+
//#region src/signIn.ts
|
|
6
|
+
var o = (e) => ({
|
|
7
|
+
kind: "refused",
|
|
8
|
+
status: e.status,
|
|
9
|
+
code: e.code,
|
|
10
|
+
message: e.message
|
|
11
|
+
}), s = (s) => n.gen(function* () {
|
|
12
|
+
let { profile: c, identities: l, users: u, config: d } = s, f = s.policy ?? "never", p = yield* l.findByProviderAccount(c.provider, c.providerAccountId), m = c.email === null ? null : yield* u.findByEmail(c.email), h = t({
|
|
13
|
+
profile: c,
|
|
14
|
+
linkedUserId: p?.userId ?? null,
|
|
15
|
+
existingUserIdByEmail: m?.id ?? null,
|
|
16
|
+
policy: f
|
|
17
|
+
});
|
|
18
|
+
if (h.kind === "refuse") return o(h.error);
|
|
19
|
+
let g = null, _ = !1, v = !1;
|
|
20
|
+
if (h.kind === "create-user") {
|
|
21
|
+
let t = c.email, r = yield* u.insert({
|
|
22
|
+
id: i("user"),
|
|
23
|
+
email: t,
|
|
24
|
+
passwordHash: null,
|
|
25
|
+
tenantId: d.defaultTenantId
|
|
26
|
+
}).pipe(n.catchTag("UserAlreadyExistsError", (t) => n.succeed(new e("account_exists_unlinked", `An account already exists for ${t.email}. Sign in with your existing method, then connect ${c.provider} from your account settings.`))));
|
|
27
|
+
if (r instanceof e) return o(r);
|
|
28
|
+
g = r, _ = !0;
|
|
29
|
+
} else {
|
|
30
|
+
if (v = h.kind === "link", g = yield* u.findById(h.userId), !g) return o(new e("account_exists_unlinked", "The account this login is linked to no longer exists.", 409));
|
|
31
|
+
let t = d.subjectGuards ?? [];
|
|
32
|
+
if (t.length > 0) {
|
|
33
|
+
let e = yield* a(t, g);
|
|
34
|
+
if (!e.ok) return {
|
|
35
|
+
kind: "refused",
|
|
36
|
+
status: 403,
|
|
37
|
+
code: e.code,
|
|
38
|
+
message: e.message
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
p === null ? yield* l.insert({
|
|
43
|
+
userId: g.id,
|
|
44
|
+
provider: c.provider,
|
|
45
|
+
providerAccountId: c.providerAccountId,
|
|
46
|
+
email: c.email,
|
|
47
|
+
emailVerified: c.emailVerified
|
|
48
|
+
}) : yield* l.touch(p.id, /* @__PURE__ */ new Date());
|
|
49
|
+
let y = yield* r(g, u, d, {
|
|
50
|
+
ipAddress: s.ipAddress ?? null,
|
|
51
|
+
userAgent: s.userAgent ?? null
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
kind: "ok",
|
|
55
|
+
userId: g.id,
|
|
56
|
+
subject: y.subject,
|
|
57
|
+
setCookie: y.setCookie,
|
|
58
|
+
created: _,
|
|
59
|
+
linked: v,
|
|
60
|
+
oneTimeName: c.nameIsFirstAuthorizationOnly ? {
|
|
61
|
+
firstName: c.firstName,
|
|
62
|
+
lastName: c.lastName
|
|
63
|
+
} : null
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
//#endregion
|
|
67
|
+
export { s as completeSocialSignIn };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-auth-social",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.86.0",
|
|
4
4
|
"description": "First-party social login for the Voltro framework — Sign in with Google, GitHub and Apple. Mandatory PKCE (S256) + state, JWKS-verified ID tokens, a deliberate account-linking policy, and sessions issued through @voltro/plugin-auth. No identity vendor required.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"node": ">=24.0.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@voltro/database": "0.
|
|
37
|
-
"@voltro/plugin-auth": "0.
|
|
38
|
-
"@voltro/protocol": "0.
|
|
36
|
+
"@voltro/database": "0.86.0",
|
|
37
|
+
"@voltro/plugin-auth": "0.86.0",
|
|
38
|
+
"@voltro/protocol": "0.86.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"effect": "
|
|
41
|
+
"effect": "4.0.1"
|
|
42
42
|
},
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|