@holo-js/auth-social 0.1.3 → 0.1.5
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/index.d.ts +47 -5
- package/dist/index.mjs +229 -34
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -36,11 +36,40 @@ interface SocialProviderRuntime {
|
|
|
36
36
|
readonly tokens: SocialProviderTokens;
|
|
37
37
|
}>;
|
|
38
38
|
}
|
|
39
|
+
type SocialRequestHeaders = Headers | ReadonlyArray<readonly [string, string]> | Record<string, string | readonly string[] | undefined> | {
|
|
40
|
+
readonly get?: (name: string) => string | null | undefined;
|
|
41
|
+
readonly forEach?: (callback: (value: string, key: string) => void) => void;
|
|
42
|
+
readonly entries?: () => Iterable<readonly [string, string]>;
|
|
43
|
+
};
|
|
44
|
+
type SocialRequestLike = {
|
|
45
|
+
readonly method?: string;
|
|
46
|
+
readonly path?: string;
|
|
47
|
+
readonly url?: string | URL;
|
|
48
|
+
readonly headers?: SocialRequestHeaders;
|
|
49
|
+
readonly request?: Request;
|
|
50
|
+
readonly req?: Request | {
|
|
51
|
+
readonly method?: string;
|
|
52
|
+
readonly url?: string;
|
|
53
|
+
readonly headers?: SocialRequestHeaders;
|
|
54
|
+
};
|
|
55
|
+
readonly node?: {
|
|
56
|
+
readonly req?: {
|
|
57
|
+
readonly method?: string;
|
|
58
|
+
readonly url?: string;
|
|
59
|
+
readonly headers?: SocialRequestHeaders;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
readonly web?: {
|
|
63
|
+
readonly request?: Request;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
type SocialRequestInput = Request | SocialRequestLike;
|
|
39
67
|
interface SocialPendingStateRecord {
|
|
40
68
|
readonly provider: string;
|
|
41
69
|
readonly state: string;
|
|
42
70
|
readonly codeVerifier: string;
|
|
43
71
|
readonly guard: string;
|
|
72
|
+
readonly browserBinding?: string;
|
|
44
73
|
readonly createdAt: Date;
|
|
45
74
|
}
|
|
46
75
|
interface SocialPendingStateStore {
|
|
@@ -72,8 +101,21 @@ interface SocialAuthBindings {
|
|
|
72
101
|
readonly encryptionKey?: string;
|
|
73
102
|
}
|
|
74
103
|
interface SocialAuthFacade {
|
|
75
|
-
redirect(provider: string, request:
|
|
76
|
-
callback(provider: string, request:
|
|
104
|
+
redirect(provider: string, request: SocialRequestInput): Promise<Response>;
|
|
105
|
+
callback(provider: string, request: SocialRequestInput): Promise<SocialCallbackResult>;
|
|
106
|
+
}
|
|
107
|
+
type SocialCallbackResult = SocialCallbackSuccess | SocialCallbackFailure;
|
|
108
|
+
interface SocialCallbackSuccess {
|
|
109
|
+
readonly ok: true;
|
|
110
|
+
readonly guard: string;
|
|
111
|
+
readonly authProvider: string;
|
|
112
|
+
readonly provider: string;
|
|
113
|
+
readonly user: AuthUserLike;
|
|
114
|
+
}
|
|
115
|
+
interface SocialCallbackFailure {
|
|
116
|
+
readonly ok: false;
|
|
117
|
+
readonly status: 400;
|
|
118
|
+
readonly message: string;
|
|
77
119
|
}
|
|
78
120
|
declare function getBindings(): SocialAuthBindings;
|
|
79
121
|
declare function createState(): string;
|
|
@@ -89,8 +131,8 @@ declare function resolveLinkedUser(provider: string, profile: SocialProviderProf
|
|
|
89
131
|
readonly authProvider: string;
|
|
90
132
|
readonly user: AuthUserLike;
|
|
91
133
|
}>;
|
|
92
|
-
declare function redirect(provider: string,
|
|
93
|
-
declare function callback(provider: string,
|
|
134
|
+
declare function redirect(provider: string, input: SocialRequestInput): Promise<Response>;
|
|
135
|
+
declare function callback(provider: string, input: SocialRequestInput): Promise<SocialCallbackResult>;
|
|
94
136
|
declare function configureSocialAuthRuntime(bindings?: SocialAuthBindings): void;
|
|
95
137
|
declare function resetSocialAuthRuntime(): void;
|
|
96
138
|
declare const socialAuth: Readonly<{
|
|
@@ -108,4 +150,4 @@ declare const socialAuthInternals: {
|
|
|
108
150
|
resolveLinkedUser: typeof resolveLinkedUser;
|
|
109
151
|
};
|
|
110
152
|
|
|
111
|
-
export { type SocialAuthBindings, type SocialAuthFacade, type SocialCallbackContext, type SocialIdentityRecord, type SocialIdentityStore, type SocialPendingStateRecord, type SocialPendingStateStore, type SocialProviderProfile, type SocialProviderRuntime, type SocialProviderTokens, type SocialRedirectContext, callback, configureSocialAuthRuntime, decryptTokens, redirect, resetSocialAuthRuntime, socialAuth, socialAuthInternals };
|
|
153
|
+
export { type SocialAuthBindings, type SocialAuthFacade, type SocialCallbackContext, type SocialCallbackFailure, type SocialCallbackResult, type SocialCallbackSuccess, type SocialIdentityRecord, type SocialIdentityStore, type SocialPendingStateRecord, type SocialPendingStateStore, type SocialProviderProfile, type SocialProviderRuntime, type SocialProviderTokens, type SocialRedirectContext, type SocialRequestHeaders, type SocialRequestInput, type SocialRequestLike, callback, configureSocialAuthRuntime, decryptTokens, redirect, resetSocialAuthRuntime, socialAuth, socialAuthInternals };
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,116 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "crypto";
|
|
2
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes, timingSafeEqual } from "crypto";
|
|
3
3
|
import { authRuntimeInternals } from "@holo-js/auth";
|
|
4
|
-
var
|
|
4
|
+
var SOCIAL_BINDINGS_KEY = "__holoAuthSocialBindings__";
|
|
5
5
|
var AUTH_PROVIDER_MARKER = /* @__PURE__ */ Symbol.for("holo-js.auth.provider");
|
|
6
|
+
var GET_ONLY_REQUEST_HEADER_NAMES = ["authorization", "cookie", "host", "x-forwarded-host", "x-forwarded-proto"];
|
|
7
|
+
function getSocialRuntimeGlobal() {
|
|
8
|
+
return globalThis;
|
|
9
|
+
}
|
|
10
|
+
function isPlainHeaderRecord(value) {
|
|
11
|
+
return Boolean(value) && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype;
|
|
12
|
+
}
|
|
13
|
+
function appendKnownHeaders(headers, input) {
|
|
14
|
+
for (const name of GET_ONLY_REQUEST_HEADER_NAMES) {
|
|
15
|
+
const value = input.get?.(name);
|
|
16
|
+
if (typeof value === "string" && value) {
|
|
17
|
+
headers.set(name, value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function hasHeaderForEach(input) {
|
|
22
|
+
return !Array.isArray(input) && "forEach" in input && typeof input.forEach === "function";
|
|
23
|
+
}
|
|
24
|
+
function hasHeaderEntries(input) {
|
|
25
|
+
return !Array.isArray(input) && "entries" in input && typeof input.entries === "function";
|
|
26
|
+
}
|
|
27
|
+
function hasHeaderGet(input) {
|
|
28
|
+
return !Array.isArray(input) && "get" in input && typeof input.get === "function";
|
|
29
|
+
}
|
|
30
|
+
function normalizeRequestHeaders(input) {
|
|
31
|
+
const headers = new Headers();
|
|
32
|
+
if (!input) {
|
|
33
|
+
return headers;
|
|
34
|
+
}
|
|
35
|
+
if (input instanceof Headers || Array.isArray(input)) {
|
|
36
|
+
new Headers(input).forEach((value, name) => headers.append(name, value));
|
|
37
|
+
return headers;
|
|
38
|
+
}
|
|
39
|
+
if (hasHeaderForEach(input)) {
|
|
40
|
+
input.forEach((value, name) => headers.append(name, value));
|
|
41
|
+
return headers;
|
|
42
|
+
}
|
|
43
|
+
if (hasHeaderEntries(input)) {
|
|
44
|
+
for (const [name, value] of input.entries()) {
|
|
45
|
+
headers.append(name, value);
|
|
46
|
+
}
|
|
47
|
+
return headers;
|
|
48
|
+
}
|
|
49
|
+
if (hasHeaderGet(input)) {
|
|
50
|
+
appendKnownHeaders(headers, input);
|
|
51
|
+
return headers;
|
|
52
|
+
}
|
|
53
|
+
if (isPlainHeaderRecord(input)) {
|
|
54
|
+
for (const [name, value] of Object.entries(input)) {
|
|
55
|
+
if (typeof value === "string") {
|
|
56
|
+
headers.append(name, value);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
const separator = name.toLowerCase() === "cookie" ? "; " : ",";
|
|
61
|
+
const joined = value.filter((entry) => typeof entry === "string").join(separator);
|
|
62
|
+
if (joined) {
|
|
63
|
+
headers.append(name, joined);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return headers;
|
|
69
|
+
}
|
|
70
|
+
function getRequestFromLikeInput(input) {
|
|
71
|
+
return input.request ?? input.web?.request ?? (input.req instanceof Request ? input.req : void 0);
|
|
72
|
+
}
|
|
73
|
+
function getRequestLikeHeaders(input) {
|
|
74
|
+
return input.headers ?? (typeof input.req === "object" && !(input.req instanceof Request) ? input.req.headers : void 0) ?? input.node?.req?.headers;
|
|
75
|
+
}
|
|
76
|
+
function getRequestLikeMethod(input) {
|
|
77
|
+
return input.method ?? (typeof input.req === "object" && !(input.req instanceof Request) ? input.req.method : void 0) ?? input.node?.req?.method ?? "GET";
|
|
78
|
+
}
|
|
79
|
+
function isProductionRuntime() {
|
|
80
|
+
return process.env.NODE_ENV === "production";
|
|
81
|
+
}
|
|
82
|
+
function createRelativeRequestBaseUrl(headers) {
|
|
83
|
+
const forwardedProtocol = headers.get("x-forwarded-proto");
|
|
84
|
+
const forwardedHost = headers.get("x-forwarded-host");
|
|
85
|
+
if (isProductionRuntime() && (!forwardedProtocol || !forwardedHost)) {
|
|
86
|
+
throw new Error("[@holo-js/auth-social] Relative request URLs require x-forwarded-proto and x-forwarded-host headers in production.");
|
|
87
|
+
}
|
|
88
|
+
const protocol = forwardedProtocol ?? "http";
|
|
89
|
+
const host = forwardedHost ?? headers.get("host") ?? "localhost";
|
|
90
|
+
return `${protocol}://${host}`;
|
|
91
|
+
}
|
|
92
|
+
function getRequestLikeUrl(input, headers) {
|
|
93
|
+
const url = (typeof input.url === "string" ? input.url : input.url?.toString()) ?? (typeof input.req === "object" && !(input.req instanceof Request) ? input.req.url : void 0) ?? input.node?.req?.url ?? input.path ?? "/";
|
|
94
|
+
try {
|
|
95
|
+
return new URL(url).toString();
|
|
96
|
+
} catch {
|
|
97
|
+
return new URL(url, createRelativeRequestBaseUrl(headers)).toString();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function normalizeSocialRequest(input) {
|
|
101
|
+
if (input instanceof Request) {
|
|
102
|
+
return input;
|
|
103
|
+
}
|
|
104
|
+
const request = getRequestFromLikeInput(input);
|
|
105
|
+
if (request) {
|
|
106
|
+
return request;
|
|
107
|
+
}
|
|
108
|
+
const headers = normalizeRequestHeaders(getRequestLikeHeaders(input));
|
|
109
|
+
return new Request(getRequestLikeUrl(input, headers), {
|
|
110
|
+
method: getRequestLikeMethod(input),
|
|
111
|
+
headers
|
|
112
|
+
});
|
|
113
|
+
}
|
|
6
114
|
function requireUserRecord(user, message) {
|
|
7
115
|
if (user == null) {
|
|
8
116
|
throw new Error(message);
|
|
@@ -37,6 +145,7 @@ function throwUnconfigured() {
|
|
|
37
145
|
throw new Error("[@holo-js/auth-social] Social auth runtime is not configured yet.");
|
|
38
146
|
}
|
|
39
147
|
function getBindings() {
|
|
148
|
+
const socialBindings = getSocialRuntimeGlobal()[SOCIAL_BINDINGS_KEY];
|
|
40
149
|
if (!socialBindings) {
|
|
41
150
|
throwUnconfigured();
|
|
42
151
|
}
|
|
@@ -45,12 +154,83 @@ function getBindings() {
|
|
|
45
154
|
function createState() {
|
|
46
155
|
return randomBytes(24).toString("base64url");
|
|
47
156
|
}
|
|
157
|
+
function createBrowserBindingNonce() {
|
|
158
|
+
return createState();
|
|
159
|
+
}
|
|
160
|
+
function hashBrowserBinding(nonce) {
|
|
161
|
+
return createHash("sha256").update(nonce).digest("base64url");
|
|
162
|
+
}
|
|
48
163
|
function createCodeVerifier() {
|
|
49
164
|
return randomBytes(32).toString("base64url");
|
|
50
165
|
}
|
|
51
166
|
function createCodeChallenge(verifier) {
|
|
52
167
|
return createHash("sha256").update(verifier).digest("base64url");
|
|
53
168
|
}
|
|
169
|
+
function getStateCookieName(provider) {
|
|
170
|
+
const suffix = provider.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
171
|
+
return `holo_oauth_state_${suffix}`;
|
|
172
|
+
}
|
|
173
|
+
function serializeStateCookie(provider, state, nonce, request) {
|
|
174
|
+
const secure = new URL(request.url).protocol === "https:";
|
|
175
|
+
const attributes = [
|
|
176
|
+
`${getStateCookieName(provider)}=${state}.${nonce}`,
|
|
177
|
+
"Path=/",
|
|
178
|
+
"HttpOnly",
|
|
179
|
+
"SameSite=Lax",
|
|
180
|
+
"Max-Age=600"
|
|
181
|
+
];
|
|
182
|
+
if (secure) {
|
|
183
|
+
attributes.push("Secure");
|
|
184
|
+
}
|
|
185
|
+
return attributes.join("; ");
|
|
186
|
+
}
|
|
187
|
+
function readCookie(request, name) {
|
|
188
|
+
const header = request.headers.get("cookie");
|
|
189
|
+
if (!header) {
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
192
|
+
for (const entry of header.split(";")) {
|
|
193
|
+
const separatorIndex = entry.indexOf("=");
|
|
194
|
+
if (separatorIndex < 0) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const cookieName = entry.slice(0, separatorIndex).trim();
|
|
198
|
+
if (cookieName !== name) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
return entry.slice(separatorIndex + 1).trim();
|
|
202
|
+
}
|
|
203
|
+
return void 0;
|
|
204
|
+
}
|
|
205
|
+
function readStateCookie(request, provider) {
|
|
206
|
+
const value = readCookie(request, getStateCookieName(provider));
|
|
207
|
+
if (!value) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
const separatorIndex = value.indexOf(".");
|
|
211
|
+
if (separatorIndex <= 0 || separatorIndex === value.length - 1) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
state: value.slice(0, separatorIndex),
|
|
216
|
+
nonce: value.slice(separatorIndex + 1)
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function timingSafeStringEqual(left, right) {
|
|
220
|
+
const leftBuffer = Buffer.from(left);
|
|
221
|
+
const rightBuffer = Buffer.from(right);
|
|
222
|
+
return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
|
|
223
|
+
}
|
|
224
|
+
function verifyBrowserBinding(provider, state, pending, request) {
|
|
225
|
+
if (!pending.browserBinding) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
const cookie = readStateCookie(request, provider);
|
|
229
|
+
if (!cookie || cookie.state !== state) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
return timingSafeStringEqual(hashBrowserBinding(cookie.nonce), pending.browserBinding);
|
|
233
|
+
}
|
|
54
234
|
function encryptTokens(value, encryptionKey) {
|
|
55
235
|
if (typeof encryptionKey !== "string" || !encryptionKey.trim()) {
|
|
56
236
|
throw new Error("[@holo-js/auth-social] encryptionKey is required when encryptTokens is enabled.");
|
|
@@ -113,9 +293,6 @@ function resolveGuardAndProvider(provider) {
|
|
|
113
293
|
if (!guard) {
|
|
114
294
|
throw new Error(`[@holo-js/auth-social] Guard "${guardName}" is not configured for social provider "${provider}".`);
|
|
115
295
|
}
|
|
116
|
-
if (guard.driver !== "session") {
|
|
117
|
-
throw new Error(`[@holo-js/auth-social] Social sign-in requires auth guard "${guardName}" to use the session driver.`);
|
|
118
|
-
}
|
|
119
296
|
const authProvider = providerConfig.mapToProvider ?? guard.provider;
|
|
120
297
|
const adapter = authBindings.providers[authProvider];
|
|
121
298
|
if (!adapter) {
|
|
@@ -161,19 +338,25 @@ function resolveEmailForCreation(provider, profile, options = {}) {
|
|
|
161
338
|
}
|
|
162
339
|
async function resolveLinkedUser(provider, profile, tokens) {
|
|
163
340
|
const bindings = getBindings();
|
|
164
|
-
const { guard, authProvider, adapter } = resolveGuardAndProvider(provider);
|
|
165
341
|
const existingIdentity = await bindings.identityStore.findByProviderUserId(provider, profile.id);
|
|
166
342
|
const authBindings = authRuntimeInternals.getRuntimeBindings();
|
|
167
343
|
const verificationRequired = authBindings.config.emailVerification.required === true;
|
|
168
344
|
if (existingIdentity) {
|
|
345
|
+
if (!authBindings.config.guards[existingIdentity.guard]) {
|
|
346
|
+
throw new Error(`[@holo-js/auth-social] Guard "${existingIdentity.guard}" is not configured for linked social identity "${provider}:${profile.id}".`);
|
|
347
|
+
}
|
|
348
|
+
const storedAdapter = authBindings.providers[existingIdentity.authProvider];
|
|
349
|
+
if (!storedAdapter) {
|
|
350
|
+
throw new Error(`[@holo-js/auth-social] Auth provider runtime "${existingIdentity.authProvider}" is not configured for linked social identity "${provider}:${profile.id}".`);
|
|
351
|
+
}
|
|
169
352
|
const linkedUser = resolveUserRecord(
|
|
170
|
-
await
|
|
353
|
+
await storedAdapter.findById(existingIdentity.userId),
|
|
171
354
|
`[@holo-js/auth-social] Linked social identity "${provider}:${profile.id}" references a missing local user.`
|
|
172
355
|
);
|
|
173
356
|
if (!linkedUser) {
|
|
174
357
|
throw new Error(`[@holo-js/auth-social] Linked social identity "${provider}:${profile.id}" references a missing local user.`);
|
|
175
358
|
}
|
|
176
|
-
const serialized2 = serializeLocalUser(
|
|
359
|
+
const serialized2 = serializeLocalUser(storedAdapter, linkedUser, existingIdentity.authProvider);
|
|
177
360
|
await bindings.identityStore.save({
|
|
178
361
|
...existingIdentity,
|
|
179
362
|
email: profile.email,
|
|
@@ -187,8 +370,13 @@ async function resolveLinkedUser(provider, profile, tokens) {
|
|
|
187
370
|
tokens: getConfiguredProviderConfig(provider).encryptTokens ? encryptTokens(tokens, bindings.encryptionKey) : tokens,
|
|
188
371
|
updatedAt: /* @__PURE__ */ new Date()
|
|
189
372
|
});
|
|
190
|
-
return {
|
|
373
|
+
return {
|
|
374
|
+
guard: existingIdentity.guard,
|
|
375
|
+
authProvider: existingIdentity.authProvider,
|
|
376
|
+
user: serialized2
|
|
377
|
+
};
|
|
191
378
|
}
|
|
379
|
+
const { guard, authProvider, adapter } = resolveGuardAndProvider(provider);
|
|
192
380
|
const hasVerifiedEmail = profile.emailVerified === true && typeof profile.email === "string" && profile.email.trim().length > 0;
|
|
193
381
|
if (!hasVerifiedEmail && verificationRequired) {
|
|
194
382
|
throw new Error(`[@holo-js/auth-social] Social sign-in with "${provider}" requires a verified email address.`);
|
|
@@ -231,11 +419,13 @@ async function resolveLinkedUser(provider, profile, tokens) {
|
|
|
231
419
|
user: serialized
|
|
232
420
|
};
|
|
233
421
|
}
|
|
234
|
-
async function redirect(provider,
|
|
422
|
+
async function redirect(provider, input) {
|
|
423
|
+
const request = normalizeSocialRequest(input);
|
|
235
424
|
const providerConfig = getConfiguredProviderConfig(provider);
|
|
236
425
|
const runtime = getProviderRuntime(provider);
|
|
237
426
|
const { guard } = resolveGuardAndProvider(provider);
|
|
238
427
|
const state = createState();
|
|
428
|
+
const browserNonce = createBrowserBindingNonce();
|
|
239
429
|
const codeVerifier = createCodeVerifier();
|
|
240
430
|
const codeChallenge = createCodeChallenge(codeVerifier);
|
|
241
431
|
await getBindings().stateStore.create({
|
|
@@ -243,6 +433,7 @@ async function redirect(provider, request) {
|
|
|
243
433
|
state,
|
|
244
434
|
codeVerifier,
|
|
245
435
|
guard,
|
|
436
|
+
browserBinding: hashBrowserBinding(browserNonce),
|
|
246
437
|
createdAt: /* @__PURE__ */ new Date()
|
|
247
438
|
});
|
|
248
439
|
const authorizationUrl = await runtime.buildAuthorizationUrl({
|
|
@@ -253,11 +444,13 @@ async function redirect(provider, request) {
|
|
|
253
444
|
codeChallenge,
|
|
254
445
|
config: providerConfig
|
|
255
446
|
});
|
|
447
|
+
const headers = new Headers({
|
|
448
|
+
location: authorizationUrl
|
|
449
|
+
});
|
|
450
|
+
headers.append("set-cookie", serializeStateCookie(provider, state, browserNonce, request));
|
|
256
451
|
return new Response(null, {
|
|
257
452
|
status: 302,
|
|
258
|
-
headers
|
|
259
|
-
location: authorizationUrl
|
|
260
|
-
}
|
|
453
|
+
headers
|
|
261
454
|
});
|
|
262
455
|
}
|
|
263
456
|
async function readCallbackParameters(request) {
|
|
@@ -286,18 +479,30 @@ async function readCallbackParameters(request) {
|
|
|
286
479
|
code: formCode ?? queryCode
|
|
287
480
|
};
|
|
288
481
|
}
|
|
289
|
-
async function callback(provider,
|
|
482
|
+
async function callback(provider, input) {
|
|
483
|
+
const request = normalizeSocialRequest(input);
|
|
290
484
|
const { state, code } = await readCallbackParameters(request);
|
|
291
485
|
if (!state || !code) {
|
|
292
|
-
return
|
|
486
|
+
return {
|
|
487
|
+
ok: false,
|
|
488
|
+
status: 400,
|
|
293
489
|
message: "Missing OAuth state or code."
|
|
294
|
-
}
|
|
490
|
+
};
|
|
295
491
|
}
|
|
296
492
|
const pending = await getBindings().stateStore.read(provider, state);
|
|
297
493
|
if (!pending) {
|
|
298
|
-
return
|
|
494
|
+
return {
|
|
495
|
+
ok: false,
|
|
496
|
+
status: 400,
|
|
299
497
|
message: "Invalid or expired OAuth state."
|
|
300
|
-
}
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
if (!verifyBrowserBinding(provider, state, pending, request)) {
|
|
501
|
+
return {
|
|
502
|
+
ok: false,
|
|
503
|
+
status: 400,
|
|
504
|
+
message: "Invalid or expired OAuth state."
|
|
505
|
+
};
|
|
301
506
|
}
|
|
302
507
|
await getBindings().stateStore.delete(provider, state);
|
|
303
508
|
const runtime = getProviderRuntime(provider);
|
|
@@ -310,29 +515,19 @@ async function callback(provider, request) {
|
|
|
310
515
|
config: providerConfig
|
|
311
516
|
});
|
|
312
517
|
const linked = await resolveLinkedUser(provider, exchanged.profile, exchanged.tokens);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
provider: linked.authProvider
|
|
316
|
-
});
|
|
317
|
-
const headers = new Headers();
|
|
318
|
-
for (const cookie of established.cookies) {
|
|
319
|
-
headers.append("set-cookie", cookie);
|
|
320
|
-
}
|
|
321
|
-
return Response.json({
|
|
322
|
-
authenticated: true,
|
|
518
|
+
return {
|
|
519
|
+
ok: true,
|
|
323
520
|
guard: linked.guard,
|
|
521
|
+
authProvider: linked.authProvider,
|
|
324
522
|
provider,
|
|
325
523
|
user: linked.user
|
|
326
|
-
}
|
|
327
|
-
status: 200,
|
|
328
|
-
headers
|
|
329
|
-
});
|
|
524
|
+
};
|
|
330
525
|
}
|
|
331
526
|
function configureSocialAuthRuntime(bindings) {
|
|
332
|
-
|
|
527
|
+
getSocialRuntimeGlobal()[SOCIAL_BINDINGS_KEY] = bindings;
|
|
333
528
|
}
|
|
334
529
|
function resetSocialAuthRuntime() {
|
|
335
|
-
|
|
530
|
+
delete getSocialRuntimeGlobal()[SOCIAL_BINDINGS_KEY];
|
|
336
531
|
}
|
|
337
532
|
var socialAuth = Object.freeze({
|
|
338
533
|
redirect,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/auth-social",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Holo-JS Framework - social auth provider contracts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"test": "vitest --run"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@holo-js/auth": "^0.1.
|
|
27
|
-
"@holo-js/config": "^0.1.
|
|
26
|
+
"@holo-js/auth": "^0.1.5",
|
|
27
|
+
"@holo-js/config": "^0.1.5"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^22.10.2",
|
|
31
31
|
"tsup": "^8.3.5",
|
|
32
32
|
"typescript": "^5.7.2",
|
|
33
|
-
"vitest": "^
|
|
33
|
+
"vitest": "^4.1.5"
|
|
34
34
|
}
|
|
35
35
|
}
|