@sanity/sdk 3.2.0 → 3.3.0-rc.1
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/_exports/_internal.d.ts +3 -3
- package/dist/_exports/_internal.js +4 -4
- package/dist/_exports/agent.d.ts +1 -1
- package/dist/_exports/agent.js +1 -1
- package/dist/_exports/comlink.d.ts +1 -1
- package/dist/_exports/dashboard.d.ts +1 -1
- package/dist/{authStore-BO2vch3I.d.ts → authStore-7dZCfKUq.d.ts} +20 -5
- package/dist/authStore-7dZCfKUq.d.ts.map +1 -0
- package/dist/{clientStore-BI4D8HhJ.js → clientStore-QDrJNTM4.js} +14 -10
- package/dist/clientStore-QDrJNTM4.js.map +1 -0
- package/dist/{createSanityInstance-BQIqyGLX.d.ts → createSanityInstance--Lrldto8.d.ts} +6 -3
- package/dist/createSanityInstance--Lrldto8.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +182 -142
- package/dist/index.js.map +1 -1
- package/dist/{topicStore-D0WqhQGF.d.ts → topicStore-CAnJsiqI.d.ts} +2 -2
- package/dist/{topicStore-D0WqhQGF.d.ts.map → topicStore-CAnJsiqI.d.ts.map} +1 -1
- package/dist/{version-BcRmCiYi.js → version-CYs30C7v.js} +3 -3
- package/dist/{version-BcRmCiYi.js.map → version-CYs30C7v.js.map} +1 -1
- package/package.json +3 -3
- package/src/auth/authStore.test.ts +18 -0
- package/src/auth/authStore.ts +4 -1
- package/src/auth/handleAuthCallback.test.ts +33 -0
- package/src/auth/handleAuthCallback.ts +11 -1
- package/src/auth/logout.test.ts +9 -1
- package/src/auth/logout.ts +18 -1
- package/src/auth/oauth/oauthActions.test.ts +178 -1
- package/src/auth/oauth/oauthActions.ts +111 -46
- package/src/auth/oauth/oauthAuth.test.ts +23 -0
- package/src/auth/oauth/oauthAuth.ts +18 -8
- package/src/config/authConfig.ts +5 -2
- package/dist/authStore-BO2vch3I.d.ts.map +0 -1
- package/dist/clientStore-BI4D8HhJ.js.map +0 -1
- package/dist/createSanityInstance-BQIqyGLX.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { D as getAuthLogger, E as REQUEST_TAG_PREFIX, O as configureLogging$1, S as AuthStateType, T as DEFAULT_BASE, b as getTokenFromLocation, c as getDashboardOrganizationId, d as getTokenState, f as setAuthToken, h as getCleanedUrl, i as authStore, k as createLogger, l as getIsInDashboardState, m as getAuthCode, n as getClient, o as getAuthState, p as createLoggedInAuthState, r as getClientState, s as getCurrentUserState, u as getLoginUrlState, w as DEFAULT_API_VERSION, y as getDefaultLocation } from "./clientStore-
|
|
1
|
+
import { D as getAuthLogger, E as REQUEST_TAG_PREFIX, O as configureLogging$1, S as AuthStateType, T as DEFAULT_BASE, b as getTokenFromLocation, c as getDashboardOrganizationId, d as getTokenState, f as setAuthToken, h as getCleanedUrl, i as authStore, k as createLogger, l as getIsInDashboardState, m as getAuthCode, n as getClient, o as getAuthState, p as createLoggedInAuthState, r as getClientState, s as getCurrentUserState, u as getLoginUrlState, w as DEFAULT_API_VERSION, y as getDefaultLocation } from "./clientStore-QDrJNTM4.js";
|
|
2
2
|
import { c as isReleasePerspective, d as isMediaLibraryResource, i as bindActionGlobally, l as isCanvasResource, n as bindActionByResource, o as defineStore, r as bindActionByResourceAndPerspective, t as createStateSourceAction, u as isDatasetResource } from "./createStateSourceAction-DdAhEfnZ.js";
|
|
3
|
-
import { A as clearPendingTransaction, B as assertDatasetResource, C as getCommentsState, D as toCommentsKeyParts, E as resolveComments, F as rollbackCommentUpdate, G as defineMutation, H as provisionAddonDataset, I as setCommentCreateError, K as randomId, L as setPendingTransaction, M as receiveComment, N as removeCommentById, O as addComment, P as restoreComments, R as normalizeComment, T as resolveCommentThreads, U as COMMENTS_API_VERSION, V as getAddonDatasetState, W as defineFetcher, _ as updateLastLoadMoreRequest, a as resolveQuery, b as commentsStore, c as getAllReleasesState, d as getUsersKey, f as initializeRequest, g as setUsersError, h as setUsersData, j as getCommentsKey, k as applyCommentUpdate, l as addSubscription, m as removeSubscription, o as getPerspectiveState, p as parseUsersKey, q as randomUuid, r as getQueryState, s as getActiveReleasesState, t as CORE_SDK_VERSION, u as cancelRequest, v as PROJECT_API_VERSION, x as getCommentThreadsState, y as USERS_STATE_CLEAR_DELAY, z as toCommentFieldPath } from "./version-
|
|
3
|
+
import { A as clearPendingTransaction, B as assertDatasetResource, C as getCommentsState, D as toCommentsKeyParts, E as resolveComments, F as rollbackCommentUpdate, G as defineMutation, H as provisionAddonDataset, I as setCommentCreateError, K as randomId, L as setPendingTransaction, M as receiveComment, N as removeCommentById, O as addComment, P as restoreComments, R as normalizeComment, T as resolveCommentThreads, U as COMMENTS_API_VERSION, V as getAddonDatasetState, W as defineFetcher, _ as updateLastLoadMoreRequest, a as resolveQuery, b as commentsStore, c as getAllReleasesState, d as getUsersKey, f as initializeRequest, g as setUsersError, h as setUsersData, j as getCommentsKey, k as applyCommentUpdate, l as addSubscription, m as removeSubscription, o as getPerspectiveState, p as parseUsersKey, q as randomUuid, r as getQueryState, s as getActiveReleasesState, t as CORE_SDK_VERSION, u as cancelRequest, v as PROJECT_API_VERSION, x as getCommentThreadsState, y as USERS_STATE_CLEAR_DELAY, z as toCommentFieldPath } from "./version-CYs30C7v.js";
|
|
4
4
|
import { r as omitProperty, t as isDeepEqual } from "./object-CxXlSVg7.js";
|
|
5
5
|
import { n as setCleanupTimeout } from "./setCleanupTimeout-BiM6q1KF.js";
|
|
6
6
|
import { t as _defineProperty } from "./defineProperty-Bho9Ngsi.js";
|
|
@@ -303,101 +303,6 @@ function observeOrganizationVerificationState(instance, projectIds) {
|
|
|
303
303
|
}), distinctUntilChanged((prev, curr) => prev.error === curr.error));
|
|
304
304
|
}
|
|
305
305
|
/**
|
|
306
|
-
* @public
|
|
307
|
-
*/
|
|
308
|
-
const handleAuthCallback = bindActionGlobally(authStore, async ({ state, instance }, locationHref = getDefaultLocation()) => {
|
|
309
|
-
let logger = getAuthLogger(instance), { providedToken, callbackUrl, clientFactory, apiHost, storageArea, storageKey } = state.get().options;
|
|
310
|
-
if (providedToken) return logger.debug("Skipping auth callback - token already provided"), !1;
|
|
311
|
-
let { authState } = state.get();
|
|
312
|
-
if (authState.type === "logging-in" && authState.isExchangingToken) return logger.debug("Skipping auth callback - token exchange already in progress"), !1;
|
|
313
|
-
let cleanedUrl = getCleanedUrl(locationHref), tokenFromUrl = getTokenFromLocation(locationHref);
|
|
314
|
-
if (tokenFromUrl) return logger.info("Auth token found in URL, logging in"), state.set("setTokenFromUrl", { authState: createLoggedInAuthState(tokenFromUrl, null) }), cleanedUrl;
|
|
315
|
-
let authCode = getAuthCode(callbackUrl, locationHref);
|
|
316
|
-
if (!authCode) return logger.debug("No auth code found in callback URL"), !1;
|
|
317
|
-
let parsedUrl = new URL(locationHref), dashboardContext = {};
|
|
318
|
-
try {
|
|
319
|
-
let contextParam = parsedUrl.searchParams.get("_context");
|
|
320
|
-
if (contextParam) {
|
|
321
|
-
let parsedContext = JSON.parse(contextParam);
|
|
322
|
-
parsedContext && typeof parsedContext == "object" && (delete parsedContext.sid, dashboardContext = parsedContext, logger.debug("Dashboard context parsed from callback URL", { hasDashboardContext: !0 }));
|
|
323
|
-
}
|
|
324
|
-
} catch (err) {
|
|
325
|
-
logger.warn("Failed to parse dashboard context from callback URL", { error: err });
|
|
326
|
-
}
|
|
327
|
-
logger.info("Exchanging auth code for token"), state.set("exchangeSessionForToken", {
|
|
328
|
-
authState: {
|
|
329
|
-
type: "logging-in",
|
|
330
|
-
isExchangingToken: !0
|
|
331
|
-
},
|
|
332
|
-
dashboardContext
|
|
333
|
-
});
|
|
334
|
-
try {
|
|
335
|
-
let client = clientFactory({
|
|
336
|
-
apiVersion: DEFAULT_API_VERSION,
|
|
337
|
-
requestTagPrefix: REQUEST_TAG_PREFIX,
|
|
338
|
-
useProjectHostname: !1,
|
|
339
|
-
useCdn: !1,
|
|
340
|
-
...apiHost && { apiHost }
|
|
341
|
-
});
|
|
342
|
-
logger.debug("Fetching token from auth endpoint");
|
|
343
|
-
let { token } = await client.request({
|
|
344
|
-
method: "GET",
|
|
345
|
-
url: "/auth/fetch",
|
|
346
|
-
query: { sid: authCode },
|
|
347
|
-
tag: "fetch-token"
|
|
348
|
-
});
|
|
349
|
-
return logger.info("Auth token obtained successfully, user logged in"), storageArea?.setItem(storageKey, JSON.stringify({ token })), state.set("setToken", { authState: createLoggedInAuthState(token, null) }), cleanedUrl;
|
|
350
|
-
} catch (error) {
|
|
351
|
-
return logger.error("Failed to exchange auth code for token", { error }), state.set("exchangeSessionForTokenError", { authState: {
|
|
352
|
-
type: "error",
|
|
353
|
-
error
|
|
354
|
-
} }), cleanedUrl;
|
|
355
|
-
}
|
|
356
|
-
}), logout = bindActionGlobally(authStore, async ({ state, instance }) => {
|
|
357
|
-
let logger = getAuthLogger(instance), { clientFactory, apiHost, providedToken, storageArea, storageKey } = state.get().options;
|
|
358
|
-
if (providedToken) {
|
|
359
|
-
logger.debug("Skipping logout - token is statically provided");
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
let { authState } = state.get();
|
|
363
|
-
if (authState.type === "logged-out" && authState.isDestroyingSession) {
|
|
364
|
-
logger.debug("Skipping logout - already in progress");
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
let token = authState.type === "logged-in" && authState.token;
|
|
368
|
-
try {
|
|
369
|
-
if (token) {
|
|
370
|
-
logger.info("Logging out user"), state.set("loggingOut", { authState: {
|
|
371
|
-
type: "logged-out",
|
|
372
|
-
isDestroyingSession: !0
|
|
373
|
-
} });
|
|
374
|
-
let client = clientFactory({
|
|
375
|
-
token,
|
|
376
|
-
requestTagPrefix: REQUEST_TAG_PREFIX,
|
|
377
|
-
apiVersion: DEFAULT_API_VERSION,
|
|
378
|
-
...apiHost && { apiHost },
|
|
379
|
-
useProjectHostname: !1,
|
|
380
|
-
useCdn: !1
|
|
381
|
-
});
|
|
382
|
-
logger.debug("Calling logout endpoint"), await client.request({
|
|
383
|
-
url: "/auth/logout",
|
|
384
|
-
method: "POST",
|
|
385
|
-
tag: "logout"
|
|
386
|
-
});
|
|
387
|
-
} else logger.debug("No token to logout - already logged out");
|
|
388
|
-
} catch (error) {
|
|
389
|
-
throw logger.error("Logout request failed", { error }), error;
|
|
390
|
-
} finally {
|
|
391
|
-
logger.info("User logged out, clearing stored tokens"), state.set("logoutSuccess", {
|
|
392
|
-
authState: {
|
|
393
|
-
type: "logged-out",
|
|
394
|
-
isDestroyingSession: !1
|
|
395
|
-
},
|
|
396
|
-
oauthTokens: void 0
|
|
397
|
-
}), storageArea?.removeItem(storageKey), storageArea?.removeItem(`${storageKey}_last_refresh`);
|
|
398
|
-
}
|
|
399
|
-
});
|
|
400
|
-
/**
|
|
401
306
|
* Encodes a byte array as a base64url string (RFC 4648 §5) with no padding,
|
|
402
307
|
* as required for PKCE `code_verifier`/`code_challenge` values.
|
|
403
308
|
*
|
|
@@ -440,12 +345,35 @@ async function generateCodeChallenge(codeVerifier) {
|
|
|
440
345
|
return base64UrlEncode(new Uint8Array(digest));
|
|
441
346
|
}
|
|
442
347
|
/** sessionStorage key for the PKCE `code_verifier`. */
|
|
443
|
-
const OAUTH_VERIFIER_KEY = "__sanity_oauth_verifier", OAUTH_STATE_KEY = "__sanity_oauth_state"
|
|
348
|
+
const OAUTH_VERIFIER_KEY = "__sanity_oauth_verifier", OAUTH_STATE_KEY = "__sanity_oauth_state", OAUTH_RETURN_TO_KEY = "__sanity_oauth_return_to", OAUTH_CALLBACK_PARAMS = [
|
|
349
|
+
"code",
|
|
350
|
+
"state",
|
|
351
|
+
"error",
|
|
352
|
+
"error_description"
|
|
353
|
+
];
|
|
354
|
+
/** Strips the OAuth callback params from a URL. */
|
|
355
|
+
function stripOAuthParams(href) {
|
|
356
|
+
let url = new URL(href, DEFAULT_BASE);
|
|
357
|
+
for (let param of OAUTH_CALLBACK_PARAMS) url.searchParams.delete(param);
|
|
358
|
+
return url.toString();
|
|
359
|
+
}
|
|
360
|
+
/** Whether `href` is the registered redirect URI (origin + pathname; query and hash ignored). */
|
|
361
|
+
function isCallbackRoute(href, redirectUri) {
|
|
362
|
+
let current = new URL(href, DEFAULT_BASE), redirect = new URL(redirectUri, DEFAULT_BASE);
|
|
363
|
+
return current.origin === redirect.origin && current.pathname === redirect.pathname;
|
|
364
|
+
}
|
|
444
365
|
/** Builds the RFC 8707 resource indicator for an organisation. */
|
|
445
366
|
function getResourceIndicator(organizationId) {
|
|
446
367
|
return `urn:io.sanity:organization:${organizationId}`;
|
|
447
368
|
}
|
|
448
369
|
/**
|
|
370
|
+
* Appends the RFC 8707 `resource` indicator to `params` when an organisation
|
|
371
|
+
* id is configured. Without one, the request is left unscoped.
|
|
372
|
+
*/
|
|
373
|
+
function appendResourceIndicator(params, oauth) {
|
|
374
|
+
oauth.organizationId && params.append("resource", getResourceIndicator(oauth.organizationId));
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
449
377
|
* Serialises tokens for storage, converting `expiresAt` to an ISO string.
|
|
450
378
|
*
|
|
451
379
|
* @internal
|
|
@@ -478,6 +406,16 @@ function createOAuthClient(options) {
|
|
|
478
406
|
...options.apiHost && { apiHost: options.apiHost }
|
|
479
407
|
});
|
|
480
408
|
}
|
|
409
|
+
/** POSTs form-encoded params to the OAuth token endpoint. */
|
|
410
|
+
function postTokenRequest(client, params, tag) {
|
|
411
|
+
return client.request({
|
|
412
|
+
method: "POST",
|
|
413
|
+
url: "/auth/oauth/token",
|
|
414
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
415
|
+
body: params.toString(),
|
|
416
|
+
tag
|
|
417
|
+
});
|
|
418
|
+
}
|
|
481
419
|
/** Converts a token endpoint response to the {@link OAuthTokens} shape. */
|
|
482
420
|
function toOAuthTokens(response) {
|
|
483
421
|
return {
|
|
@@ -498,37 +436,26 @@ function toOAuthTokens(response) {
|
|
|
498
436
|
const startOAuthAuthorization = bindActionGlobally(authStore, async ({ state, instance }) => {
|
|
499
437
|
let logger = getAuthLogger(instance), options = getOAuthOptions(state.get()), codeVerifier = generateCodeVerifier(), oauthState = generateState(), codeChallenge = await generateCodeChallenge(codeVerifier), session = typeof sessionStorage < "u" ? sessionStorage : void 0;
|
|
500
438
|
session?.setItem(OAUTH_VERIFIER_KEY, codeVerifier), session?.setItem(OAUTH_STATE_KEY, oauthState);
|
|
439
|
+
let currentHref = getDefaultLocation();
|
|
440
|
+
if (currentHref !== "http://localhost") {
|
|
441
|
+
let returnTo = isCallbackRoute(currentHref, options.oauth.redirectUri) ? stripOAuthParams(currentHref) : currentHref;
|
|
442
|
+
session?.setItem(OAUTH_RETURN_TO_KEY, returnTo);
|
|
443
|
+
}
|
|
501
444
|
let authorizeUrl = new URL("/v1/auth/oauth/authorize", options.apiHost ?? "https://api.sanity.io");
|
|
502
|
-
authorizeUrl.searchParams.set("response_type", "code"), authorizeUrl.searchParams.set("client_id", options.oauth.clientId), authorizeUrl.searchParams.set("redirect_uri", options.oauth.redirectUri), authorizeUrl.searchParams.set("state", oauthState), authorizeUrl.searchParams.set("code_challenge", codeChallenge), authorizeUrl.searchParams.set("code_challenge_method", "S256"), authorizeUrl.searchParams
|
|
445
|
+
authorizeUrl.searchParams.set("response_type", "code"), authorizeUrl.searchParams.set("client_id", options.oauth.clientId), authorizeUrl.searchParams.set("redirect_uri", options.oauth.redirectUri), authorizeUrl.searchParams.set("state", oauthState), authorizeUrl.searchParams.set("code_challenge", codeChallenge), authorizeUrl.searchParams.set("code_challenge_method", "S256"), appendResourceIndicator(authorizeUrl.searchParams, options.oauth), logger.info("Starting OAuth authorization"), typeof window < "u" && typeof window.location?.assign == "function" && window.location.assign(authorizeUrl.toString());
|
|
503
446
|
}), handleOAuthCallback = bindActionGlobally(authStore, async ({ state, instance }, locationHref = getDefaultLocation()) => {
|
|
504
447
|
let logger = getAuthLogger(instance), options = getOAuthOptions(state.get()), { authState } = state.get();
|
|
505
448
|
if (authState.type === "logging-in" && authState.isExchangingToken) return logger.debug("Skipping OAuth callback - token exchange already in progress"), !1;
|
|
506
|
-
let callbackUrl = new URL(locationHref, DEFAULT_BASE), code = callbackUrl.searchParams.get("code"), returnedState = callbackUrl.searchParams.get("state"), error = callbackUrl.searchParams.get("error"), errorDescription = callbackUrl.searchParams.get("error_description"), session = typeof sessionStorage < "u" ? sessionStorage : void 0,
|
|
507
|
-
for (let param of [
|
|
508
|
-
"code",
|
|
509
|
-
"state",
|
|
510
|
-
"error",
|
|
511
|
-
"error_description"
|
|
512
|
-
]) cleanedUrlObj.searchParams.delete(param);
|
|
513
|
-
let cleanedUrl = cleanedUrlObj.toString();
|
|
514
|
-
if (error) return logger.warn("OAuth callback returned an error", {
|
|
515
|
-
error,
|
|
516
|
-
errorDescription
|
|
517
|
-
}), clearOAuthArtifacts(session), state.set("oauthCallbackError", { authState: {
|
|
449
|
+
let callbackUrl = new URL(locationHref, DEFAULT_BASE), code = callbackUrl.searchParams.get("code"), returnedState = callbackUrl.searchParams.get("state"), error = callbackUrl.searchParams.get("error"), errorDescription = callbackUrl.searchParams.get("error_description"), session = typeof sessionStorage < "u" ? sessionStorage : void 0, cleanedUrl = stripOAuthParams(locationHref), rejectCallback = (name, message) => authState.type === "logged-in" ? (logger.warn(`${message} — ignoring callback, session already established`), cleanedUrl) : (logger.error(`${message} — rejecting callback`), clearOAuthArtifacts(session), state.set(name, { authState: {
|
|
518
450
|
type: "error",
|
|
519
|
-
error: Error(
|
|
520
|
-
} }), cleanedUrl;
|
|
451
|
+
error: Error(message)
|
|
452
|
+
} }), cleanedUrl);
|
|
453
|
+
if (error) return rejectCallback("oauthCallbackError", errorDescription ? `${error}: ${errorDescription}` : error);
|
|
521
454
|
if (!code) return logger.debug("No OAuth code found in callback URL"), !1;
|
|
522
455
|
let storedState = session?.getItem("__sanity_oauth_state") ?? null;
|
|
523
|
-
if (!returnedState || !storedState || returnedState !== storedState) return
|
|
524
|
-
type: "error",
|
|
525
|
-
error: /* @__PURE__ */ Error("OAuth state mismatch")
|
|
526
|
-
} }), cleanedUrl;
|
|
456
|
+
if (!returnedState || !storedState || returnedState !== storedState) return rejectCallback("oauthStateMismatch", "OAuth state mismatch");
|
|
527
457
|
let codeVerifier = session?.getItem("__sanity_oauth_verifier") ?? null;
|
|
528
|
-
if (!codeVerifier) return
|
|
529
|
-
type: "error",
|
|
530
|
-
error: /* @__PURE__ */ Error("OAuth code verifier missing")
|
|
531
|
-
} }), cleanedUrl;
|
|
458
|
+
if (!codeVerifier) return rejectCallback("oauthVerifierMissing", "OAuth code verifier missing");
|
|
532
459
|
logger.info("Exchanging OAuth code for tokens"), state.set("oauthExchange", { authState: {
|
|
533
460
|
type: "logging-in",
|
|
534
461
|
isExchangingToken: !0
|
|
@@ -539,19 +466,16 @@ const startOAuthAuthorization = bindActionGlobally(authStore, async ({ state, in
|
|
|
539
466
|
code,
|
|
540
467
|
code_verifier: codeVerifier,
|
|
541
468
|
redirect_uri: options.oauth.redirectUri,
|
|
542
|
-
client_id: options.oauth.clientId
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
tag: "oauth.token"
|
|
550
|
-
}));
|
|
551
|
-
return options.storageArea?.setItem(options.storageKey, serializeTokens(tokens)), clearOAuthArtifacts(session), logger.info("OAuth tokens obtained, user logged in"), state.set("oauthLoggedIn", {
|
|
469
|
+
client_id: options.oauth.clientId
|
|
470
|
+
});
|
|
471
|
+
appendResourceIndicator(params, options.oauth);
|
|
472
|
+
let tokens = toOAuthTokens(await postTokenRequest(client, params, "oauth.token"));
|
|
473
|
+
options.storageArea?.setItem(options.storageKey, serializeTokens(tokens));
|
|
474
|
+
let returnTo = getSameOriginReturnTo(session, options.oauth.redirectUri);
|
|
475
|
+
return clearOAuthArtifacts(session), logger.info("OAuth tokens obtained, user logged in"), state.set("oauthLoggedIn", {
|
|
552
476
|
authState: createLoggedInAuthState(tokens.accessToken, null),
|
|
553
477
|
oauthTokens: tokens
|
|
554
|
-
}), cleanedUrl;
|
|
478
|
+
}), returnTo ?? cleanedUrl;
|
|
555
479
|
} catch (exchangeError) {
|
|
556
480
|
return logger.error("Failed to exchange OAuth code for tokens", { error: exchangeError }), clearOAuthArtifacts(session), state.set("oauthExchangeError", { authState: {
|
|
557
481
|
type: "error",
|
|
@@ -594,15 +518,10 @@ async function doRefreshOAuthTokens({ state, instance }) {
|
|
|
594
518
|
let client = createOAuthClient(options), params = new URLSearchParams({
|
|
595
519
|
grant_type: "refresh_token",
|
|
596
520
|
refresh_token: current.refreshToken,
|
|
597
|
-
client_id: options.oauth.clientId
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
url: "/auth/oauth/token",
|
|
602
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
603
|
-
body: params.toString(),
|
|
604
|
-
tag: "oauth.refresh"
|
|
605
|
-
}));
|
|
521
|
+
client_id: options.oauth.clientId
|
|
522
|
+
});
|
|
523
|
+
appendResourceIndicator(params, options.oauth);
|
|
524
|
+
let tokens = toOAuthTokens(await postTokenRequest(client, params, "oauth.refresh"));
|
|
606
525
|
return tokens.refreshToken ||= current.refreshToken, options.storageArea?.setItem(options.storageKey, serializeTokens(tokens)), logger.info("OAuth tokens refreshed"), state.set("oauthRefreshed", {
|
|
607
526
|
authState: createLoggedInAuthState(tokens.accessToken, null),
|
|
608
527
|
oauthTokens: tokens
|
|
@@ -653,8 +572,129 @@ const revokeOAuthTokens = bindActionGlobally(authStore, async ({ state, instance
|
|
|
653
572
|
}), getOAuthTokensState = bindActionGlobally(authStore, createStateSourceAction(({ state }) => state.oauthTokens ?? null));
|
|
654
573
|
/** Removes the transient PKCE artifacts from session storage. */
|
|
655
574
|
function clearOAuthArtifacts(session) {
|
|
656
|
-
session?.removeItem(OAUTH_VERIFIER_KEY), session?.removeItem(OAUTH_STATE_KEY);
|
|
575
|
+
session?.removeItem(OAUTH_VERIFIER_KEY), session?.removeItem(OAUTH_STATE_KEY), session?.removeItem(OAUTH_RETURN_TO_KEY);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Reads the stashed pre-authorize location, only honouring it when it shares
|
|
579
|
+
* the registered redirect URI's origin so a tampered value cannot redirect
|
|
580
|
+
* off-site. (`origin` includes the scheme, so non-http(s) URLs are rejected.)
|
|
581
|
+
*/
|
|
582
|
+
function getSameOriginReturnTo(session, redirectUri) {
|
|
583
|
+
let returnTo = session?.getItem("__sanity_oauth_return_to") ?? null;
|
|
584
|
+
if (!returnTo) return null;
|
|
585
|
+
try {
|
|
586
|
+
return new URL(returnTo).origin === new URL(redirectUri).origin ? returnTo : null;
|
|
587
|
+
} catch {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
657
590
|
}
|
|
591
|
+
/**
|
|
592
|
+
* Handles the auth redirect callback. In OAuth mode this delegates to
|
|
593
|
+
* {@link handleOAuthCallback}; otherwise it exchanges the legacy `sid` for a
|
|
594
|
+
* long-lived token.
|
|
595
|
+
*
|
|
596
|
+
* @public
|
|
597
|
+
*/
|
|
598
|
+
const handleAuthCallback = bindActionGlobally(authStore, async ({ state, instance }, locationHref = getDefaultLocation()) => {
|
|
599
|
+
let logger = getAuthLogger(instance), { providedToken, callbackUrl, clientFactory, apiHost, storageArea, storageKey, oauth } = state.get().options;
|
|
600
|
+
if (providedToken) return logger.debug("Skipping auth callback - token already provided"), !1;
|
|
601
|
+
if (oauth) return logger.debug("OAuth mode - delegating to handleOAuthCallback"), handleOAuthCallback(instance, locationHref);
|
|
602
|
+
let { authState } = state.get();
|
|
603
|
+
if (authState.type === "logging-in" && authState.isExchangingToken) return logger.debug("Skipping auth callback - token exchange already in progress"), !1;
|
|
604
|
+
let cleanedUrl = getCleanedUrl(locationHref), tokenFromUrl = getTokenFromLocation(locationHref);
|
|
605
|
+
if (tokenFromUrl) return logger.info("Auth token found in URL, logging in"), state.set("setTokenFromUrl", { authState: createLoggedInAuthState(tokenFromUrl, null) }), cleanedUrl;
|
|
606
|
+
let authCode = getAuthCode(callbackUrl, locationHref);
|
|
607
|
+
if (!authCode) return logger.debug("No auth code found in callback URL"), !1;
|
|
608
|
+
let parsedUrl = new URL(locationHref), dashboardContext = {};
|
|
609
|
+
try {
|
|
610
|
+
let contextParam = parsedUrl.searchParams.get("_context");
|
|
611
|
+
if (contextParam) {
|
|
612
|
+
let parsedContext = JSON.parse(contextParam);
|
|
613
|
+
parsedContext && typeof parsedContext == "object" && (delete parsedContext.sid, dashboardContext = parsedContext, logger.debug("Dashboard context parsed from callback URL", { hasDashboardContext: !0 }));
|
|
614
|
+
}
|
|
615
|
+
} catch (err) {
|
|
616
|
+
logger.warn("Failed to parse dashboard context from callback URL", { error: err });
|
|
617
|
+
}
|
|
618
|
+
logger.info("Exchanging auth code for token"), state.set("exchangeSessionForToken", {
|
|
619
|
+
authState: {
|
|
620
|
+
type: "logging-in",
|
|
621
|
+
isExchangingToken: !0
|
|
622
|
+
},
|
|
623
|
+
dashboardContext
|
|
624
|
+
});
|
|
625
|
+
try {
|
|
626
|
+
let client = clientFactory({
|
|
627
|
+
apiVersion: DEFAULT_API_VERSION,
|
|
628
|
+
requestTagPrefix: REQUEST_TAG_PREFIX,
|
|
629
|
+
useProjectHostname: !1,
|
|
630
|
+
useCdn: !1,
|
|
631
|
+
...apiHost && { apiHost }
|
|
632
|
+
});
|
|
633
|
+
logger.debug("Fetching token from auth endpoint");
|
|
634
|
+
let { token } = await client.request({
|
|
635
|
+
method: "GET",
|
|
636
|
+
url: "/auth/fetch",
|
|
637
|
+
query: { sid: authCode },
|
|
638
|
+
tag: "fetch-token"
|
|
639
|
+
});
|
|
640
|
+
return logger.info("Auth token obtained successfully, user logged in"), storageArea?.setItem(storageKey, JSON.stringify({ token })), state.set("setToken", { authState: createLoggedInAuthState(token, null) }), cleanedUrl;
|
|
641
|
+
} catch (error) {
|
|
642
|
+
return logger.error("Failed to exchange auth code for token", { error }), state.set("exchangeSessionForTokenError", { authState: {
|
|
643
|
+
type: "error",
|
|
644
|
+
error
|
|
645
|
+
} }), cleanedUrl;
|
|
646
|
+
}
|
|
647
|
+
}), logout = bindActionGlobally(authStore, async ({ state, instance }) => {
|
|
648
|
+
let logger = getAuthLogger(instance), { clientFactory, apiHost, providedToken, storageArea, storageKey, oauth } = state.get().options;
|
|
649
|
+
if (providedToken) {
|
|
650
|
+
logger.debug("Skipping logout - token is statically provided");
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
let { authState } = state.get();
|
|
654
|
+
if (authState.type === "logged-out" && authState.isDestroyingSession) {
|
|
655
|
+
logger.debug("Skipping logout - already in progress");
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
if (oauth) {
|
|
659
|
+
logger.debug("OAuth mode - revoking tokens instead of calling /auth/logout"), state.set("loggingOut", { authState: {
|
|
660
|
+
type: "logged-out",
|
|
661
|
+
isDestroyingSession: !0
|
|
662
|
+
} }), await revokeOAuthTokens(instance);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
let token = authState.type === "logged-in" && authState.token;
|
|
666
|
+
try {
|
|
667
|
+
if (token) {
|
|
668
|
+
logger.info("Logging out user"), state.set("loggingOut", { authState: {
|
|
669
|
+
type: "logged-out",
|
|
670
|
+
isDestroyingSession: !0
|
|
671
|
+
} });
|
|
672
|
+
let client = clientFactory({
|
|
673
|
+
token,
|
|
674
|
+
requestTagPrefix: REQUEST_TAG_PREFIX,
|
|
675
|
+
apiVersion: DEFAULT_API_VERSION,
|
|
676
|
+
...apiHost && { apiHost },
|
|
677
|
+
useProjectHostname: !1,
|
|
678
|
+
useCdn: !1
|
|
679
|
+
});
|
|
680
|
+
logger.debug("Calling logout endpoint"), await client.request({
|
|
681
|
+
url: "/auth/logout",
|
|
682
|
+
method: "POST",
|
|
683
|
+
tag: "logout"
|
|
684
|
+
});
|
|
685
|
+
} else logger.debug("No token to logout - already logged out");
|
|
686
|
+
} catch (error) {
|
|
687
|
+
throw logger.error("Logout request failed", { error }), error;
|
|
688
|
+
} finally {
|
|
689
|
+
logger.info("User logged out, clearing stored tokens"), state.set("logoutSuccess", {
|
|
690
|
+
authState: {
|
|
691
|
+
type: "logged-out",
|
|
692
|
+
isDestroyingSession: !1
|
|
693
|
+
},
|
|
694
|
+
oauthTokens: void 0
|
|
695
|
+
}), storageArea?.removeItem(storageKey), storageArea?.removeItem(`${storageKey}_last_refresh`);
|
|
696
|
+
}
|
|
697
|
+
});
|
|
658
698
|
function weaken(node) {
|
|
659
699
|
return Array.isArray(node) ? node.map(weaken) : node && typeof node == "object" ? "_ref" in node ? {
|
|
660
700
|
...node,
|