@stackframe/tanstack-start 2.8.93 → 2.8.95
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/esm/generated/quetzal-translations.d.ts +2 -2
- package/dist/esm/lib/auth.d.ts +3 -1
- package/dist/esm/lib/auth.d.ts.map +1 -1
- package/dist/esm/lib/auth.js +4 -4
- package/dist/esm/lib/auth.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.d.ts +30 -3
- package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js +197 -31
- package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/common.js +1 -1
- package/dist/esm/lib/stack-app/url-targets.d.ts +1 -2
- package/dist/esm/lib/stack-app/url-targets.d.ts.map +1 -1
- package/dist/esm/lib/stack-app/url-targets.js +19 -31
- package/dist/esm/lib/stack-app/url-targets.js.map +1 -1
- package/dist/esm/lib/stack-app/url-targets.test.js +46 -3
- package/dist/esm/lib/stack-app/url-targets.test.js.map +1 -1
- package/dist/generated/quetzal-translations.d.ts +2 -2
- package/dist/lib/auth.d.ts +3 -1
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +4 -4
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
- package/dist/lib/stack-app/apps/implementations/client-app-impl.d.ts +30 -3
- package/dist/lib/stack-app/apps/implementations/client-app-impl.d.ts.map +1 -1
- package/dist/lib/stack-app/apps/implementations/client-app-impl.js +194 -28
- package/dist/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/common.js +1 -1
- package/dist/lib/stack-app/url-targets.d.ts +1 -2
- package/dist/lib/stack-app/url-targets.d.ts.map +1 -1
- package/dist/lib/stack-app/url-targets.js +18 -31
- package/dist/lib/stack-app/url-targets.js.map +1 -1
- package/dist/lib/stack-app/url-targets.test.js +46 -3
- package/dist/lib/stack-app/url-targets.test.js.map +1 -1
- package/package.json +3 -3
|
@@ -4,12 +4,13 @@ import React, { useCallback, useMemo } from "react";
|
|
|
4
4
|
import { KnownErrors, StackClientInterface } from "@stackframe/stack-shared";
|
|
5
5
|
import { suspend, suspendIfSsr, use } from "@stackframe/stack-shared/dist/utils/react";
|
|
6
6
|
import { deepPlainEquals, omit } from "@stackframe/stack-shared/dist/utils/objects";
|
|
7
|
-
import { isRelative } from "@stackframe/stack-shared/dist/utils/urls";
|
|
7
|
+
import { createUrlIfValid, isRelative } from "@stackframe/stack-shared/dist/utils/urls";
|
|
8
8
|
import { Result } from "@stackframe/stack-shared/dist/utils/results";
|
|
9
9
|
import { deindent, mergeScopeStrings } from "@stackframe/stack-shared/dist/utils/strings";
|
|
10
10
|
import { isBrowserLike } from "@stackframe/stack-shared/dist/utils/env";
|
|
11
11
|
import * as tanstackStartServerContext from "@stackframe/tanstack-start/tanstack-start-server-context";
|
|
12
12
|
import { clientVersion, createCache, createCacheBySession, createEmptyTokenStore, getAnalyticsBaseUrl, getDefaultExtraRequestHeaders, getDefaultProjectId, getDefaultPublishableClientKey, getUrls, resolveApiUrls, resolveConstructorOptions, useAsyncCache } from "./common.js";
|
|
13
|
+
import { getTrustedParentDomain, validateRedirectUrl } from "@stackframe/stack-shared/dist/utils/redirect-urls";
|
|
13
14
|
import { decodeBase32, decodeBase64, encodeBase32, encodeBase64 } from "@stackframe/stack-shared/dist/utils/bytes";
|
|
14
15
|
import { stackAppInternalsSymbol } from "../../common.js";
|
|
15
16
|
import { adminProjectCreateOptionsToCrud } from "../../projects/index.js";
|
|
@@ -24,12 +25,12 @@ import * as TanStackRouter from "@tanstack/react-router";
|
|
|
24
25
|
import * as cookie from "cookie";
|
|
25
26
|
import { constructRedirectUrl } from "../../../../utils/url.js";
|
|
26
27
|
import { callOAuthCallback, getNewOAuthProviderOrScopeUrl } from "../../../auth.js";
|
|
27
|
-
import { createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookie, deleteCookieClient, isSecure, saveVerifierAndState, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie.js";
|
|
28
|
+
import { createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookie, deleteCookieClient, getCookieClient, isSecure, saveVerifierAndState, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie.js";
|
|
28
29
|
import { envVars } from "../../../env.js";
|
|
29
30
|
import { apiKeyCreationOptionsToCrud } from "../../api-keys/index.js";
|
|
30
31
|
import { contactChannelCreateOptionsToCrud, contactChannelUpdateOptionsToCrud } from "../../contact-channels/index.js";
|
|
31
32
|
import { teamCreateOptionsToCrud, teamUpdateOptionsToCrud } from "../../teams/index.js";
|
|
32
|
-
import { buildCliAuthConfirmUrl, isHostedHandlerUrlForProject, resolveHandlerUrls } from "../../url-targets.js";
|
|
33
|
+
import { buildCliAuthConfirmUrl, getHostedHandlerUrl, isHostedHandlerUrlForProject, resolveHandlerUrls } from "../../url-targets.js";
|
|
33
34
|
import { userUpdateOptionsToCrud, withUserDestructureGuard } from "../../users/index.js";
|
|
34
35
|
import { EventTracker } from "./event-tracker.js";
|
|
35
36
|
import { crossDomainAuthQueryParams, getCrossDomainHandoffParamsFromCurrentUrl, planRedirectToHandler } from "./redirect-page-urls.js";
|
|
@@ -39,6 +40,24 @@ import { mountDevTool } from "../../../../dev-tool/index.js";
|
|
|
39
40
|
|
|
40
41
|
//#region src/lib/stack-app/apps/implementations/client-app-impl.ts
|
|
41
42
|
const prefetchedCrossDomainHandoffTtlMs = 3300 * 1e3;
|
|
43
|
+
const nestedCrossDomainAuthQueryParams = {
|
|
44
|
+
refreshTokenId: "stack_nested_cross_domain_auth_refresh_token_id",
|
|
45
|
+
callbackUrl: "stack_nested_cross_domain_auth_callback_url",
|
|
46
|
+
redirectUri: "redirect_uri",
|
|
47
|
+
state: "state",
|
|
48
|
+
codeChallenge: "code_challenge",
|
|
49
|
+
codeChallengeMethod: "code_challenge_method",
|
|
50
|
+
afterCallbackRedirectUrl: "after_callback_redirect_url"
|
|
51
|
+
};
|
|
52
|
+
const oauthCallbackResponseQueryParams = [
|
|
53
|
+
"code",
|
|
54
|
+
"state",
|
|
55
|
+
"error",
|
|
56
|
+
"error_description",
|
|
57
|
+
"errorCode",
|
|
58
|
+
"message",
|
|
59
|
+
"details"
|
|
60
|
+
];
|
|
42
61
|
const allClientApps = /* @__PURE__ */ new Map();
|
|
43
62
|
const STACK_AUTHORIZATION_VALUE_PREFIX = "stackauth_";
|
|
44
63
|
function getAuthorizationHeaderValueFromAuthJson(authJson) {
|
|
@@ -106,7 +125,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
106
125
|
`);
|
|
107
126
|
const location = await getNewOAuthProviderOrScopeUrl(this._interface, {
|
|
108
127
|
provider: options.providerId,
|
|
109
|
-
redirectUrl: this.
|
|
128
|
+
redirectUrl: this._getOAuthCallbackRedirectUri(),
|
|
110
129
|
errorRedirectUrl: this.urls.error,
|
|
111
130
|
providerScope: mergeScopeStrings(options.scope || "", (this._oauthScopesOnSignIn[options.providerId] ?? []).join(" "))
|
|
112
131
|
}, options.session);
|
|
@@ -241,7 +260,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
241
260
|
for (const account of matchingAccounts) if ((await account.getAccessToken({ scopes })).status === "ok") return account;
|
|
242
261
|
const location = await getNewOAuthProviderOrScopeUrl(this._interface, {
|
|
243
262
|
provider,
|
|
244
|
-
redirectUrl: this.
|
|
263
|
+
redirectUrl: this._getOAuthCallbackRedirectUri(),
|
|
245
264
|
errorRedirectUrl: this.urls.error,
|
|
246
265
|
providerScope: mergeScopeStrings(scopeString, (this._oauthScopesOnSignIn[provider] ?? []).join(" "))
|
|
247
266
|
}, session);
|
|
@@ -345,6 +364,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
345
364
|
this._prefetchedCrossDomainHandoffParams = null;
|
|
346
365
|
this._prefetchedCrossDomainHandoffParamsFetchedAt = 0;
|
|
347
366
|
this._isPrefetchingCrossDomainHandoffParams = false;
|
|
367
|
+
this._pendingAuthResolutionPromises = [];
|
|
348
368
|
this._memoryTokenStore = createEmptyTokenStore();
|
|
349
369
|
this._nextServerCookiesTokenStores = /* @__PURE__ */ new WeakMap();
|
|
350
370
|
this._requestTokenStores = /* @__PURE__ */ new WeakMap();
|
|
@@ -414,6 +434,12 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
414
434
|
});
|
|
415
435
|
this._eventTracker.start();
|
|
416
436
|
}
|
|
437
|
+
if (isBrowserLike() && this._isOAuthCallbackUrlHosted() && this._currentUrlLooksLikeStackOAuthCallback()) this._trackPendingAuthResolution(async () => {
|
|
438
|
+
if (isBrowserLike()) await this.callOAuthCallback({ dontWarnAboutMissingQueryParams: true });
|
|
439
|
+
});
|
|
440
|
+
if (isBrowserLike()) this._trackPendingAuthResolution(async () => {
|
|
441
|
+
await this._maybeHandleNestedCrossDomainAuth();
|
|
442
|
+
});
|
|
417
443
|
if (isBrowserLike() && resolvedOptions.devTool !== false) mountDevTool(this);
|
|
418
444
|
}
|
|
419
445
|
_initUniqueIdentifier() {
|
|
@@ -421,6 +447,126 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
421
447
|
if (allClientApps.has(this._uniqueIdentifier)) throw new StackAssertionError("A Stack client app with the same unique identifier already exists");
|
|
422
448
|
allClientApps.set(this._uniqueIdentifier, [this._extraOptions?.checkString ?? void 0, this]);
|
|
423
449
|
}
|
|
450
|
+
_trackPendingAuthResolution(callback) {
|
|
451
|
+
const promise = (async () => {
|
|
452
|
+
await Promise.resolve();
|
|
453
|
+
try {
|
|
454
|
+
await callback();
|
|
455
|
+
} catch (error) {
|
|
456
|
+
captureError("pending-auth-resolution-failed", error);
|
|
457
|
+
}
|
|
458
|
+
})();
|
|
459
|
+
this._pendingAuthResolutionPromises.push(promise);
|
|
460
|
+
runAsynchronously(async () => {
|
|
461
|
+
try {
|
|
462
|
+
await promise;
|
|
463
|
+
} finally {
|
|
464
|
+
this._pendingAuthResolutionPromises = this._pendingAuthResolutionPromises.filter((p) => p !== promise);
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
async _awaitPendingAuthResolutions(overrideTokenStoreInit, options) {
|
|
469
|
+
if (options?.awaitPendingAuthResolutions === false || overrideTokenStoreInit !== void 0 || !this._hasPersistentTokenStore() || this._pendingAuthResolutionPromises.length === 0) return;
|
|
470
|
+
await Promise.all(this._pendingAuthResolutionPromises);
|
|
471
|
+
}
|
|
472
|
+
_usePendingAuthResolutions(overrideTokenStoreInit) {
|
|
473
|
+
if (overrideTokenStoreInit !== void 0 || !this._hasPersistentTokenStore() || this._pendingAuthResolutionPromises.length === 0) return;
|
|
474
|
+
use(Promise.all(this._pendingAuthResolutionPromises));
|
|
475
|
+
}
|
|
476
|
+
_isOAuthCallbackUrlHosted() {
|
|
477
|
+
const oauthCallbackTarget = this._urlOptions.oauthCallback ?? this._urlOptions.default;
|
|
478
|
+
return typeof oauthCallbackTarget !== "string" && oauthCallbackTarget?.type === "hosted";
|
|
479
|
+
}
|
|
480
|
+
_currentUrlLooksLikeOAuthCallback() {
|
|
481
|
+
if (typeof window === "undefined") return false;
|
|
482
|
+
const currentUrl = new URL(window.location.href);
|
|
483
|
+
return currentUrl.searchParams.has("code") && currentUrl.searchParams.has("state") || currentUrl.searchParams.has("errorCode") && currentUrl.searchParams.has("message");
|
|
484
|
+
}
|
|
485
|
+
_currentUrlLooksLikeStackOAuthCallback() {
|
|
486
|
+
if (typeof window === "undefined") return false;
|
|
487
|
+
const currentUrl = new URL(window.location.href);
|
|
488
|
+
const state = currentUrl.searchParams.get("state");
|
|
489
|
+
if (!currentUrl.searchParams.has("code") || state == null) return false;
|
|
490
|
+
return getCookieClient(`stack-oauth-outer-${state}`) != null;
|
|
491
|
+
}
|
|
492
|
+
_getOAuthCallbackRedirectUri() {
|
|
493
|
+
if (!this._isOAuthCallbackUrlHosted()) return this.urls.oauthCallback;
|
|
494
|
+
if (typeof window === "undefined") throw new StackAssertionError("Hosted OAuth callback URLs require a browser environment to use the current URL as the redirect URI");
|
|
495
|
+
const currentUrl = new URL(window.location.href);
|
|
496
|
+
for (const param of oauthCallbackResponseQueryParams) currentUrl.searchParams.delete(param);
|
|
497
|
+
return currentUrl.toString();
|
|
498
|
+
}
|
|
499
|
+
async _getCurrentRefreshTokenIdIfSignedIn(options) {
|
|
500
|
+
const tokens = await (await this._getSession(void 0, options)).getOrFetchLikelyValidTokens(0, null);
|
|
501
|
+
if (tokens?.refreshToken == null) return null;
|
|
502
|
+
return tokens.accessToken.payload.refresh_token_id;
|
|
503
|
+
}
|
|
504
|
+
async _addNestedCrossDomainAuthParamsToRedirectUrl(options) {
|
|
505
|
+
const targetUrl = new URL(options.url, options.currentUrl);
|
|
506
|
+
if (targetUrl.origin === options.currentUrl.origin) return options.url;
|
|
507
|
+
const refreshTokenId = await this._getCurrentRefreshTokenIdIfSignedIn({ awaitPendingAuthResolutions: options.awaitPendingAuthResolutions });
|
|
508
|
+
if (refreshTokenId == null) return options.url;
|
|
509
|
+
targetUrl.searchParams.set(nestedCrossDomainAuthQueryParams.refreshTokenId, refreshTokenId);
|
|
510
|
+
targetUrl.searchParams.set(nestedCrossDomainAuthQueryParams.callbackUrl, new URL(this._getOAuthCallbackRedirectUri(), options.currentUrl).toString());
|
|
511
|
+
return targetUrl.toString();
|
|
512
|
+
}
|
|
513
|
+
async _maybeHandleNestedCrossDomainAuth() {
|
|
514
|
+
if (typeof window === "undefined") return false;
|
|
515
|
+
const currentUrl = new URL(window.location.href);
|
|
516
|
+
if (currentUrl.searchParams.has("code") && currentUrl.searchParams.has("state")) return false;
|
|
517
|
+
const refreshTokenId = currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.refreshTokenId);
|
|
518
|
+
if (refreshTokenId == null) return false;
|
|
519
|
+
const redirectUri = currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.redirectUri);
|
|
520
|
+
const state = currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.state);
|
|
521
|
+
const codeChallenge = currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.codeChallenge);
|
|
522
|
+
if (redirectUri != null || state != null || codeChallenge != null) {
|
|
523
|
+
if (redirectUri == null || state == null || codeChallenge == null) throw new StackAssertionError("Nested cross-domain auth callback URL is missing OAuth request parameters", {
|
|
524
|
+
redirectUri,
|
|
525
|
+
state,
|
|
526
|
+
codeChallenge
|
|
527
|
+
});
|
|
528
|
+
if ((currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.codeChallengeMethod) ?? "S256") !== "S256") throw new StackAssertionError("Nested cross-domain auth only supports S256 PKCE");
|
|
529
|
+
if (isRelative(redirectUri)) throw new Error("Nested cross-domain auth redirect URI must be absolute.");
|
|
530
|
+
const redirectUriUrl = new URL(redirectUri);
|
|
531
|
+
if (!await this._isTrusted(redirectUriUrl.toString())) throw new Error(`Nested cross-domain auth redirect URI ${redirectUri} is not trusted.`);
|
|
532
|
+
const afterCallbackRedirectUrlString = currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.afterCallbackRedirectUrl);
|
|
533
|
+
const afterCallbackRedirectUrl = afterCallbackRedirectUrlString == null ? redirectUriUrl : new URL(afterCallbackRedirectUrlString, redirectUriUrl);
|
|
534
|
+
if (!await this._isTrusted(afterCallbackRedirectUrl.toString())) throw new Error(`Nested cross-domain auth after-callback redirect URL ${afterCallbackRedirectUrlString} is not trusted.`);
|
|
535
|
+
if (await this._getCurrentRefreshTokenIdIfSignedIn({ awaitPendingAuthResolutions: false }) !== refreshTokenId) throw new Error("Nested cross-domain auth source session does not match the requested refresh token ID.");
|
|
536
|
+
await this._redirectTo({
|
|
537
|
+
url: await this._createCrossDomainAuthRedirectUrl({
|
|
538
|
+
redirectUri: redirectUriUrl.toString(),
|
|
539
|
+
state,
|
|
540
|
+
codeChallenge,
|
|
541
|
+
afterCallbackRedirectUrl: afterCallbackRedirectUrl.toString(),
|
|
542
|
+
awaitPendingAuthResolutions: false
|
|
543
|
+
}),
|
|
544
|
+
replace: true
|
|
545
|
+
});
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
if (await this._getCurrentRefreshTokenIdIfSignedIn({ awaitPendingAuthResolutions: false }) === refreshTokenId) return false;
|
|
549
|
+
const callbackUrlString = currentUrl.searchParams.get(nestedCrossDomainAuthQueryParams.callbackUrl);
|
|
550
|
+
if (callbackUrlString == null) throw new StackAssertionError("Nested cross-domain auth URL is missing callback URL");
|
|
551
|
+
if (isRelative(callbackUrlString)) throw new Error("Nested cross-domain auth callback URL must be absolute.");
|
|
552
|
+
const callbackUrl = new URL(callbackUrlString);
|
|
553
|
+
if (!await this._isTrusted(callbackUrl.toString())) throw new Error(`Nested cross-domain auth callback URL ${callbackUrlString} is not trusted.`);
|
|
554
|
+
const afterCallbackRedirectUrl = new URL(currentUrl);
|
|
555
|
+
afterCallbackRedirectUrl.searchParams.delete(nestedCrossDomainAuthQueryParams.refreshTokenId);
|
|
556
|
+
afterCallbackRedirectUrl.searchParams.delete(nestedCrossDomainAuthQueryParams.callbackUrl);
|
|
557
|
+
const { state: newState, codeChallenge: newCodeChallenge } = await this._getCrossDomainHandoffParamsForRedirect(currentUrl);
|
|
558
|
+
callbackUrl.searchParams.set(nestedCrossDomainAuthQueryParams.refreshTokenId, refreshTokenId);
|
|
559
|
+
callbackUrl.searchParams.set(nestedCrossDomainAuthQueryParams.redirectUri, new URL(this._getOAuthCallbackRedirectUri(), currentUrl).toString());
|
|
560
|
+
callbackUrl.searchParams.set(nestedCrossDomainAuthQueryParams.state, newState);
|
|
561
|
+
callbackUrl.searchParams.set(nestedCrossDomainAuthQueryParams.codeChallenge, newCodeChallenge);
|
|
562
|
+
callbackUrl.searchParams.set(nestedCrossDomainAuthQueryParams.codeChallengeMethod, "S256");
|
|
563
|
+
callbackUrl.searchParams.set(nestedCrossDomainAuthQueryParams.afterCallbackRedirectUrl, afterCallbackRedirectUrl.toString());
|
|
564
|
+
await this._redirectTo({
|
|
565
|
+
url: callbackUrl,
|
|
566
|
+
replace: true
|
|
567
|
+
});
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
424
570
|
/**
|
|
425
571
|
* Cloudflare workers does not allow use of randomness on the global scope (on which the Stack app is probably
|
|
426
572
|
* initialized). For that reason, we generate the unique identifier lazily when it is first needed instead of in the
|
|
@@ -613,15 +759,18 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
613
759
|
await setOrDeleteCookie(this._getRefreshTokenDefaultCookieNameForSecure(isSecure$1), null, cookieOptions);
|
|
614
760
|
});
|
|
615
761
|
}
|
|
762
|
+
async _getTrustedRedirectConfig() {
|
|
763
|
+
const project = Result.orThrow(await this._currentProjectCache.getOrWait([], "write-only"));
|
|
764
|
+
return {
|
|
765
|
+
allowLocalhost: project.config.allow_localhost,
|
|
766
|
+
trustedDomains: [...project.config.domains.map((d) => d.domain), new URL(getHostedHandlerUrl({
|
|
767
|
+
projectId: this.projectId,
|
|
768
|
+
pagePath: ""
|
|
769
|
+
})).origin]
|
|
770
|
+
};
|
|
771
|
+
}
|
|
616
772
|
async _getTrustedParentDomain(currentDomain) {
|
|
617
|
-
|
|
618
|
-
const trustedWildcards = domains.filter((d) => d.startsWith("**."));
|
|
619
|
-
const parts = currentDomain.split(".");
|
|
620
|
-
for (let i = parts.length - 2; i >= 0; i--) {
|
|
621
|
-
const parentDomain = parts.slice(i).join(".");
|
|
622
|
-
if (domains.includes(parentDomain) && trustedWildcards.includes("**." + parentDomain)) return parentDomain;
|
|
623
|
-
}
|
|
624
|
-
return null;
|
|
773
|
+
return getTrustedParentDomain(currentDomain, (await this._getTrustedRedirectConfig()).trustedDomains);
|
|
625
774
|
}
|
|
626
775
|
_getBrowserCookieTokenStore() {
|
|
627
776
|
if (!isBrowserLike()) throw new Error("Cannot use cookie token store on the server!");
|
|
@@ -778,11 +927,13 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
778
927
|
sessionsBySessionKey.set(sessionKey, session);
|
|
779
928
|
return session;
|
|
780
929
|
}
|
|
781
|
-
async _getSession(overrideTokenStoreInit) {
|
|
930
|
+
async _getSession(overrideTokenStoreInit, options) {
|
|
931
|
+
await this._awaitPendingAuthResolutions(overrideTokenStoreInit, options);
|
|
782
932
|
const tokenStore = this._getOrCreateTokenStore(await this._createCookieHelper(overrideTokenStoreInit), overrideTokenStoreInit);
|
|
783
933
|
return this._getSessionFromTokenStore(tokenStore);
|
|
784
934
|
}
|
|
785
935
|
_useSession(overrideTokenStoreInit) {
|
|
936
|
+
this._usePendingAuthResolutions(overrideTokenStoreInit);
|
|
786
937
|
const tokenStore = this._useTokenStore(overrideTokenStoreInit);
|
|
787
938
|
const subscribe = useCallback((cb) => {
|
|
788
939
|
return subscribeSessionRefresh({
|
|
@@ -1238,7 +1389,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
1238
1389
|
const scopeString = options?.scopes?.join(" ") ?? "";
|
|
1239
1390
|
const location = await getNewOAuthProviderOrScopeUrl(app._interface, {
|
|
1240
1391
|
provider,
|
|
1241
|
-
redirectUrl: app.
|
|
1392
|
+
redirectUrl: app._getOAuthCallbackRedirectUri(),
|
|
1242
1393
|
errorRedirectUrl: app.urls.error,
|
|
1243
1394
|
providerScope: mergeScopeStrings(scopeString, (app._oauthScopesOnSignIn[provider] ?? []).join(" "))
|
|
1244
1395
|
}, session);
|
|
@@ -1772,10 +1923,17 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
1772
1923
|
}
|
|
1773
1924
|
async _isTrusted(url) {
|
|
1774
1925
|
if (isRelative(url)) return true;
|
|
1775
|
-
|
|
1776
|
-
return
|
|
1926
|
+
const parsedUrl = createUrlIfValid(url);
|
|
1927
|
+
if (parsedUrl == null) return false;
|
|
1928
|
+
if (typeof window !== "undefined" && window.location.origin === parsedUrl.origin) return true;
|
|
1929
|
+
if (isHostedHandlerUrlForProject({
|
|
1777
1930
|
url,
|
|
1778
1931
|
projectId: this.projectId
|
|
1932
|
+
})) return true;
|
|
1933
|
+
const trustedRedirectConfig = await this._getTrustedRedirectConfig();
|
|
1934
|
+
return validateRedirectUrl(parsedUrl, {
|
|
1935
|
+
allowLocalhost: trustedRedirectConfig.allowLocalhost,
|
|
1936
|
+
trustedDomains: trustedRedirectConfig.trustedDomains
|
|
1779
1937
|
});
|
|
1780
1938
|
}
|
|
1781
1939
|
get urls() {
|
|
@@ -1824,6 +1982,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
1824
1982
|
};
|
|
1825
1983
|
}
|
|
1826
1984
|
_getLocalOAuthCallbackHandlerUrl() {
|
|
1985
|
+
if (this._isOAuthCallbackUrlHosted()) return this._getOAuthCallbackRedirectUri();
|
|
1827
1986
|
return resolveHandlerUrls({
|
|
1828
1987
|
urls: {
|
|
1829
1988
|
...this._urlOptions,
|
|
@@ -1834,7 +1993,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
1834
1993
|
}).oauthCallback;
|
|
1835
1994
|
}
|
|
1836
1995
|
async _createCrossDomainAuthRedirectUrl(options) {
|
|
1837
|
-
const session = await this._getSession();
|
|
1996
|
+
const session = await this._getSession(void 0, { awaitPendingAuthResolutions: options.awaitPendingAuthResolutions });
|
|
1838
1997
|
const response = await this._interface.sendClientRequest("/auth/oauth/cross-domain/authorize", {
|
|
1839
1998
|
method: "POST",
|
|
1840
1999
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1888,7 +2047,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
1888
2047
|
...options
|
|
1889
2048
|
});
|
|
1890
2049
|
}
|
|
1891
|
-
async _redirectToHandler(handlerName, options) {
|
|
2050
|
+
async _redirectToHandler(handlerName, options, internalOptions) {
|
|
1892
2051
|
const rawHandlerUrl = getUrls(this._urlOptions, { projectId: this.projectId })[handlerName];
|
|
1893
2052
|
if (!rawHandlerUrl) throw new Error(`No URL for handler name ${handlerName}`);
|
|
1894
2053
|
const currentUrl = typeof window === "undefined" ? null : new URL(window.location.href);
|
|
@@ -1905,7 +2064,8 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
1905
2064
|
redirectUri: plan.redirectUri,
|
|
1906
2065
|
state: plan.state,
|
|
1907
2066
|
codeChallenge: plan.codeChallenge,
|
|
1908
|
-
afterCallbackRedirectUrl: plan.afterCallbackRedirectUrl
|
|
2067
|
+
afterCallbackRedirectUrl: plan.afterCallbackRedirectUrl,
|
|
2068
|
+
awaitPendingAuthResolutions: internalOptions?.awaitPendingAuthResolutions
|
|
1909
2069
|
});
|
|
1910
2070
|
await this._redirectTo({
|
|
1911
2071
|
url: crossDomainRedirectUrl,
|
|
@@ -1913,7 +2073,12 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
1913
2073
|
});
|
|
1914
2074
|
return;
|
|
1915
2075
|
}
|
|
1916
|
-
await this.
|
|
2076
|
+
const redirectUrl = currentUrl != null && handlerName !== "signOut" && handlerName !== "afterSignOut" && handlerName !== "oauthCallback" ? await this._addNestedCrossDomainAuthParamsToRedirectUrl({
|
|
2077
|
+
url: plan.url,
|
|
2078
|
+
currentUrl,
|
|
2079
|
+
awaitPendingAuthResolutions: internalOptions?.awaitPendingAuthResolutions
|
|
2080
|
+
}) : plan.url;
|
|
2081
|
+
await this._redirectIfTrusted(redirectUrl, options);
|
|
1917
2082
|
}
|
|
1918
2083
|
_redirectToHandlerDuringRender(handlerName, options) {
|
|
1919
2084
|
if (this._redirectMethod === "tanstack-start" && !isBrowserLike()) {
|
|
@@ -2170,7 +2335,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
2170
2335
|
const executeOAuth = async (challenge) => {
|
|
2171
2336
|
return await this._interface.authorizeOAuth({
|
|
2172
2337
|
provider,
|
|
2173
|
-
redirectUrl: constructRedirectUrl(this.
|
|
2338
|
+
redirectUrl: constructRedirectUrl(this._getOAuthCallbackRedirectUri(), "redirectUrl"),
|
|
2174
2339
|
errorRedirectUrl: constructRedirectUrl(this.urls.error, "errorRedirectUrl"),
|
|
2175
2340
|
afterCallbackRedirectUrl,
|
|
2176
2341
|
type: "authenticate",
|
|
@@ -2218,7 +2383,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
2218
2383
|
try {
|
|
2219
2384
|
return await callback();
|
|
2220
2385
|
} catch (e) {
|
|
2221
|
-
if (KnownErrors.MultiFactorAuthenticationRequired.isInstance(e)) return Result.ok(await this._experimentalMfa(e, await this._getSession()));
|
|
2386
|
+
if (KnownErrors.MultiFactorAuthenticationRequired.isInstance(e)) return Result.ok(await this._experimentalMfa(e, await this._getSession(void 0, { awaitPendingAuthResolutions: false })));
|
|
2222
2387
|
throw e;
|
|
2223
2388
|
}
|
|
2224
2389
|
}
|
|
@@ -2293,8 +2458,8 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
2293
2458
|
}
|
|
2294
2459
|
if (result.status === "ok") {
|
|
2295
2460
|
await this._signInToAccountWithTokens(result.data);
|
|
2296
|
-
if (!options?.noRedirect) if (result.data.newUser) await this.
|
|
2297
|
-
else await this.
|
|
2461
|
+
if (!options?.noRedirect) if (result.data.newUser) await this._redirectToHandler("afterSignUp", { replace: true }, { awaitPendingAuthResolutions: false });
|
|
2462
|
+
else await this._redirectToHandler("afterSignIn", { replace: true }, { awaitPendingAuthResolutions: false });
|
|
2298
2463
|
return Result.ok(void 0);
|
|
2299
2464
|
} else return Result.error(result.error);
|
|
2300
2465
|
}
|
|
@@ -2409,10 +2574,10 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
2409
2574
|
return Result.ok(void 0);
|
|
2410
2575
|
} else return Result.error(result.error);
|
|
2411
2576
|
}
|
|
2412
|
-
async callOAuthCallback() {
|
|
2577
|
+
async callOAuthCallback(options) {
|
|
2413
2578
|
if (typeof window === "undefined") throw new Error("callOAuthCallback can currently only be called in a browser environment");
|
|
2414
|
-
this._ensurePersistentTokenStore();
|
|
2415
|
-
let oauthCallbackRedirectUri = this.
|
|
2579
|
+
if (this._currentUrlLooksLikeOAuthCallback()) this._ensurePersistentTokenStore();
|
|
2580
|
+
let oauthCallbackRedirectUri = this._getOAuthCallbackRedirectUri();
|
|
2416
2581
|
const currentUrl = new URL(window.location.href);
|
|
2417
2582
|
if (currentUrl.searchParams.get(crossDomainAuthQueryParams.marker) === "1") {
|
|
2418
2583
|
currentUrl.searchParams.delete("code");
|
|
@@ -2422,7 +2587,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
2422
2587
|
let result;
|
|
2423
2588
|
try {
|
|
2424
2589
|
result = await this._catchMfaRequiredError(async () => {
|
|
2425
|
-
return await callOAuthCallback(this._interface, oauthCallbackRedirectUri);
|
|
2590
|
+
return await callOAuthCallback(this._interface, oauthCallbackRedirectUri, options);
|
|
2426
2591
|
});
|
|
2427
2592
|
} catch (e) {
|
|
2428
2593
|
if (KnownErrors.InvalidTotpCode.isInstance(e)) {
|
|
@@ -2431,6 +2596,7 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
2431
2596
|
} else throw e;
|
|
2432
2597
|
}
|
|
2433
2598
|
if (result.status === "ok" && result.data) {
|
|
2599
|
+
this._ensurePersistentTokenStore();
|
|
2434
2600
|
await this._signInToAccountWithTokens(result.data);
|
|
2435
2601
|
if ("afterCallbackRedirectUrl" in result.data && result.data.afterCallbackRedirectUrl) {
|
|
2436
2602
|
await this._redirectTo({
|
|
@@ -2439,10 +2605,10 @@ var _StackClientAppImplIncomplete = class _StackClientAppImplIncomplete {
|
|
|
2439
2605
|
});
|
|
2440
2606
|
return true;
|
|
2441
2607
|
} else if (result.data.newUser) {
|
|
2442
|
-
await this.
|
|
2608
|
+
await this._redirectToHandler("afterSignUp", { replace: true }, { awaitPendingAuthResolutions: false });
|
|
2443
2609
|
return true;
|
|
2444
2610
|
} else {
|
|
2445
|
-
await this.
|
|
2611
|
+
await this._redirectToHandler("afterSignIn", { replace: true }, { awaitPendingAuthResolutions: false });
|
|
2446
2612
|
return true;
|
|
2447
2613
|
}
|
|
2448
2614
|
}
|