@sanity/sdk 3.3.0-rc.1 → 3.3.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.
Files changed (51) hide show
  1. package/dist/_exports/_internal.d.ts +13 -5
  2. package/dist/_exports/_internal.d.ts.map +1 -1
  3. package/dist/_exports/_internal.js +24 -6
  4. package/dist/_exports/_internal.js.map +1 -1
  5. package/dist/_exports/agent.d.ts +1 -1
  6. package/dist/_exports/agent.js +1 -1
  7. package/dist/_exports/comlink.d.ts +1 -1
  8. package/dist/_exports/dashboard.d.ts +1 -1
  9. package/dist/_exports/dashboard.js +1 -1
  10. package/dist/{authStore-7dZCfKUq.d.ts → applications-pRS8tuC2.d.ts} +350 -363
  11. package/dist/applications-pRS8tuC2.d.ts.map +1 -0
  12. package/dist/{clientStore-QDrJNTM4.js → clientStore-BI4D8HhJ.js} +10 -14
  13. package/dist/clientStore-BI4D8HhJ.js.map +1 -0
  14. package/dist/{createSanityInstance--Lrldto8.d.ts → createSanityInstance-BQIqyGLX.d.ts} +3 -6
  15. package/dist/createSanityInstance-BQIqyGLX.d.ts.map +1 -0
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +142 -182
  18. package/dist/index.js.map +1 -1
  19. package/dist/{topicStore-Ct7IFNtY.js → topicStore-BJ54_X6H.js} +3 -1
  20. package/dist/topicStore-BJ54_X6H.js.map +1 -0
  21. package/dist/{topicStore-CAnJsiqI.d.ts → topicStore-D-jEwzlX.d.ts} +54 -25
  22. package/dist/topicStore-D-jEwzlX.d.ts.map +1 -0
  23. package/dist/{version-CYs30C7v.js → version-CD4aBigm.js} +3 -3
  24. package/dist/{version-CYs30C7v.js.map → version-CD4aBigm.js.map} +1 -1
  25. package/package.json +7 -7
  26. package/src/_exports/_internal.ts +2 -0
  27. package/src/applications/applicationOrigin.test.ts +51 -0
  28. package/src/applications/applicationOrigin.ts +26 -0
  29. package/src/auth/authStore.test.ts +0 -18
  30. package/src/auth/authStore.ts +1 -4
  31. package/src/auth/handleAuthCallback.test.ts +0 -33
  32. package/src/auth/handleAuthCallback.ts +1 -11
  33. package/src/auth/logout.test.ts +1 -9
  34. package/src/auth/logout.ts +1 -18
  35. package/src/auth/oauth/oauthActions.test.ts +1 -178
  36. package/src/auth/oauth/oauthActions.ts +46 -111
  37. package/src/auth/oauth/oauthAuth.test.ts +0 -23
  38. package/src/auth/oauth/oauthAuth.ts +8 -18
  39. package/src/config/authConfig.ts +2 -5
  40. package/src/dashboard/messageBus/bus.test.ts +39 -0
  41. package/src/dashboard/messageBus/bus.ts +32 -22
  42. package/src/dashboard/messageBus/topics.test.ts +36 -2
  43. package/src/dashboard/messageBus/topics.ts +51 -16
  44. package/src/installations/installations.test-d.ts +11 -0
  45. package/src/installations/installations.test.ts +6 -4
  46. package/src/installations/installations.ts +13 -2
  47. package/dist/authStore-7dZCfKUq.d.ts.map +0 -1
  48. package/dist/clientStore-QDrJNTM4.js.map +0 -1
  49. package/dist/createSanityInstance--Lrldto8.d.ts.map +0 -1
  50. package/dist/topicStore-CAnJsiqI.d.ts.map +0 -1
  51. package/dist/topicStore-Ct7IFNtY.js.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-QDrJNTM4.js";
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-BI4D8HhJ.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-CYs30C7v.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-CD4aBigm.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,6 +303,101 @@ 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
+ /**
306
401
  * Encodes a byte array as a base64url string (RFC 4648 §5) with no padding,
307
402
  * as required for PKCE `code_verifier`/`code_challenge` values.
308
403
  *
@@ -345,35 +440,12 @@ async function generateCodeChallenge(codeVerifier) {
345
440
  return base64UrlEncode(new Uint8Array(digest));
346
441
  }
347
442
  /** sessionStorage key for the PKCE `code_verifier`. */
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
- }
443
+ const OAUTH_VERIFIER_KEY = "__sanity_oauth_verifier", OAUTH_STATE_KEY = "__sanity_oauth_state";
365
444
  /** Builds the RFC 8707 resource indicator for an organisation. */
366
445
  function getResourceIndicator(organizationId) {
367
446
  return `urn:io.sanity:organization:${organizationId}`;
368
447
  }
369
448
  /**
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
- /**
377
449
  * Serialises tokens for storage, converting `expiresAt` to an ISO string.
378
450
  *
379
451
  * @internal
@@ -406,16 +478,6 @@ function createOAuthClient(options) {
406
478
  ...options.apiHost && { apiHost: options.apiHost }
407
479
  });
408
480
  }
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
- }
419
481
  /** Converts a token endpoint response to the {@link OAuthTokens} shape. */
420
482
  function toOAuthTokens(response) {
421
483
  return {
@@ -436,26 +498,37 @@ function toOAuthTokens(response) {
436
498
  const startOAuthAuthorization = bindActionGlobally(authStore, async ({ state, instance }) => {
437
499
  let logger = getAuthLogger(instance), options = getOAuthOptions(state.get()), codeVerifier = generateCodeVerifier(), oauthState = generateState(), codeChallenge = await generateCodeChallenge(codeVerifier), session = typeof sessionStorage < "u" ? sessionStorage : void 0;
438
500
  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
- }
444
501
  let authorizeUrl = new URL("/v1/auth/oauth/authorize", options.apiHost ?? "https://api.sanity.io");
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());
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.append("resource", getResourceIndicator(options.oauth.organizationId)), logger.info("Starting OAuth authorization"), typeof window < "u" && typeof window.location?.assign == "function" && window.location.assign(authorizeUrl.toString());
446
503
  }), handleOAuthCallback = bindActionGlobally(authStore, async ({ state, instance }, locationHref = getDefaultLocation()) => {
447
504
  let logger = getAuthLogger(instance), options = getOAuthOptions(state.get()), { authState } = state.get();
448
505
  if (authState.type === "logging-in" && authState.isExchangingToken) return logger.debug("Skipping OAuth callback - token exchange already in progress"), !1;
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: {
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, cleanedUrlObj = new URL(locationHref, DEFAULT_BASE);
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: {
450
518
  type: "error",
451
- error: Error(message)
452
- } }), cleanedUrl);
453
- if (error) return rejectCallback("oauthCallbackError", errorDescription ? `${error}: ${errorDescription}` : error);
519
+ error: Error(errorDescription ? `${error}: ${errorDescription}` : error)
520
+ } }), cleanedUrl;
454
521
  if (!code) return logger.debug("No OAuth code found in callback URL"), !1;
455
522
  let storedState = session?.getItem("__sanity_oauth_state") ?? null;
456
- if (!returnedState || !storedState || returnedState !== storedState) return rejectCallback("oauthStateMismatch", "OAuth state mismatch");
523
+ if (!returnedState || !storedState || returnedState !== storedState) return logger.error("OAuth state mismatch — rejecting callback"), clearOAuthArtifacts(session), state.set("oauthStateMismatch", { authState: {
524
+ type: "error",
525
+ error: /* @__PURE__ */ Error("OAuth state mismatch")
526
+ } }), cleanedUrl;
457
527
  let codeVerifier = session?.getItem("__sanity_oauth_verifier") ?? null;
458
- if (!codeVerifier) return rejectCallback("oauthVerifierMissing", "OAuth code verifier missing");
528
+ if (!codeVerifier) return logger.error("OAuth code verifier missing — cannot exchange code"), clearOAuthArtifacts(session), state.set("oauthVerifierMissing", { authState: {
529
+ type: "error",
530
+ error: /* @__PURE__ */ Error("OAuth code verifier missing")
531
+ } }), cleanedUrl;
459
532
  logger.info("Exchanging OAuth code for tokens"), state.set("oauthExchange", { authState: {
460
533
  type: "logging-in",
461
534
  isExchangingToken: !0
@@ -466,16 +539,19 @@ const startOAuthAuthorization = bindActionGlobally(authStore, async ({ state, in
466
539
  code,
467
540
  code_verifier: codeVerifier,
468
541
  redirect_uri: options.oauth.redirectUri,
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", {
542
+ client_id: options.oauth.clientId,
543
+ resource: getResourceIndicator(options.oauth.organizationId)
544
+ }), tokens = toOAuthTokens(await client.request({
545
+ method: "POST",
546
+ url: "/auth/oauth/token",
547
+ headers: { "content-type": "application/x-www-form-urlencoded" },
548
+ body: params.toString(),
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", {
476
552
  authState: createLoggedInAuthState(tokens.accessToken, null),
477
553
  oauthTokens: tokens
478
- }), returnTo ?? cleanedUrl;
554
+ }), cleanedUrl;
479
555
  } catch (exchangeError) {
480
556
  return logger.error("Failed to exchange OAuth code for tokens", { error: exchangeError }), clearOAuthArtifacts(session), state.set("oauthExchangeError", { authState: {
481
557
  type: "error",
@@ -518,10 +594,15 @@ async function doRefreshOAuthTokens({ state, instance }) {
518
594
  let client = createOAuthClient(options), params = new URLSearchParams({
519
595
  grant_type: "refresh_token",
520
596
  refresh_token: current.refreshToken,
521
- client_id: options.oauth.clientId
522
- });
523
- appendResourceIndicator(params, options.oauth);
524
- let tokens = toOAuthTokens(await postTokenRequest(client, params, "oauth.refresh"));
597
+ client_id: options.oauth.clientId,
598
+ resource: getResourceIndicator(options.oauth.organizationId)
599
+ }), tokens = toOAuthTokens(await client.request({
600
+ method: "POST",
601
+ url: "/auth/oauth/token",
602
+ headers: { "content-type": "application/x-www-form-urlencoded" },
603
+ body: params.toString(),
604
+ tag: "oauth.refresh"
605
+ }));
525
606
  return tokens.refreshToken ||= current.refreshToken, options.storageArea?.setItem(options.storageKey, serializeTokens(tokens)), logger.info("OAuth tokens refreshed"), state.set("oauthRefreshed", {
526
607
  authState: createLoggedInAuthState(tokens.accessToken, null),
527
608
  oauthTokens: tokens
@@ -572,129 +653,8 @@ const revokeOAuthTokens = bindActionGlobally(authStore, async ({ state, instance
572
653
  }), getOAuthTokensState = bindActionGlobally(authStore, createStateSourceAction(({ state }) => state.oauthTokens ?? null));
573
654
  /** Removes the transient PKCE artifacts from session storage. */
574
655
  function clearOAuthArtifacts(session) {
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
- }
656
+ session?.removeItem(OAUTH_VERIFIER_KEY), session?.removeItem(OAUTH_STATE_KEY);
590
657
  }
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
- });
698
658
  function weaken(node) {
699
659
  return Array.isArray(node) ? node.map(weaken) : node && typeof node == "object" ? "_ref" in node ? {
700
660
  ...node,