Skip to content

Commit 376128a

Browse files
committed
feat: make the frontend redirection urls overrideable
1 parent 3b42ee6 commit 376128a

File tree

6 files changed

+160
-85
lines changed

6 files changed

+160
-85
lines changed

lib/build/recipe/oauth2provider/api/utils.js

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -88,27 +88,13 @@ async function loginGET({
8888
});
8989
return { redirectTo: accept.redirectTo, setCookie };
9090
}
91-
const appInfo = supertokens_1.default.getInstanceOrThrowError().appInfo;
92-
const websiteDomain = appInfo
93-
.getOrigin({
94-
request: undefined,
95-
userContext: userContext,
96-
})
97-
.getAsStringDangerous();
98-
const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous();
9991
if (shouldTryRefresh && promptParam !== "login") {
100-
const websiteDomain = appInfo
101-
.getOrigin({
102-
request: undefined,
103-
userContext: userContext,
104-
})
105-
.getAsStringDangerous();
106-
const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous();
107-
const queryParamsForTryRefreshPage = new URLSearchParams({
108-
loginChallenge,
109-
});
11092
return {
111-
redirectTo: websiteDomain + websiteBasePath + `/try-refresh?${queryParamsForTryRefreshPage.toString()}`,
93+
redirectTo: await recipeImplementation.getFrontendRedirectionURL({
94+
type: "try-refresh",
95+
loginChallenge,
96+
userContext,
97+
}),
11298
setCookie,
11399
};
114100
}
@@ -124,20 +110,16 @@ async function loginGET({
124110
});
125111
return { redirectTo: reject.redirectTo, setCookie };
126112
}
127-
const queryParamsForAuthPage = new URLSearchParams({
128-
loginChallenge,
129-
});
130-
if ((_b = loginRequest.oidcContext) === null || _b === void 0 ? void 0 : _b.login_hint) {
131-
queryParamsForAuthPage.set("hint", loginRequest.oidcContext.login_hint);
132-
}
133-
if (session !== undefined || promptParam === "login") {
134-
queryParamsForAuthPage.set("forceFreshAuth", "true");
135-
}
136-
if (tenantIdParam !== null && tenantIdParam !== constants_1.DEFAULT_TENANT_ID) {
137-
queryParamsForAuthPage.set("tenantId", tenantIdParam);
138-
}
139113
return {
140-
redirectTo: websiteDomain + websiteBasePath + `?${queryParamsForAuthPage.toString()}`,
114+
redirectTo: await recipeImplementation.getFrontendRedirectionURL({
115+
type: "login",
116+
loginChallenge,
117+
forceFreshAuth: session !== undefined || promptParam === "login",
118+
tenantId:
119+
tenantIdParam !== null && tenantIdParam !== void 0 ? tenantIdParam : constants_1.DEFAULT_TENANT_ID,
120+
hint: (_b = loginRequest.oidcContext) === null || _b === void 0 ? void 0 : _b.login_hint,
121+
userContext,
122+
}),
141123
setCookie,
142124
};
143125
}

lib/build/recipe/oauth2provider/recipeImplementation.js

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const OAuth2Client_1 = require("./OAuth2Client");
6161
const __1 = require("../..");
6262
const combinedRemoteJWKSet_1 = require("../../combinedRemoteJWKSet");
6363
const recipe_1 = __importDefault(require("../session/recipe"));
64+
const constants_1 = require("../multitenancy/constants");
6465
function getUpdatedRedirectTo(appInfo, redirectTo) {
6566
return redirectTo.replace(
6667
"{apiDomain}",
@@ -528,6 +529,34 @@ function getRecipeInterface(
528529
buildUserInfo: async function ({ user, accessTokenPayload, scopes, tenantId, userContext }) {
529530
return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext);
530531
},
532+
getFrontendRedirectionURL: async function (input) {
533+
const websiteDomain = appInfo
534+
.getOrigin({ request: undefined, userContext: input.userContext })
535+
.getAsStringDangerous();
536+
const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous();
537+
if (input.type === "login") {
538+
const queryParams = new URLSearchParams({
539+
loginChallenge: input.loginChallenge,
540+
});
541+
if (input.tenantId !== undefined && input.tenantId !== constants_1.DEFAULT_TENANT_ID) {
542+
queryParams.set("tenantId", input.tenantId);
543+
}
544+
if (input.hint !== undefined) {
545+
queryParams.set("hint", input.hint);
546+
}
547+
if (input.forceFreshAuth) {
548+
queryParams.set("forceFreshAuth", "true");
549+
}
550+
return `${websiteDomain}${websiteBasePath}?${queryParams.toString()}`;
551+
} else if (input.type === "try-refresh") {
552+
return `${websiteDomain}${websiteBasePath}/try-refresh?loginChallenge=${input.loginChallenge}`;
553+
} else if (input.type === "post-logout-fallback") {
554+
return `${websiteDomain}${websiteBasePath}`;
555+
} else if (input.type === "logout-confirmation") {
556+
return `${websiteDomain}${websiteBasePath}/oauth/logout?logoutChallenge=${input.logoutChallenge}`;
557+
}
558+
throw new Error("This should never happen: invalid type passed to getFrontendRedirectionURL");
559+
},
531560
validateOAuth2AccessToken: async function (input) {
532561
var _a, _b, _c, _d, _e;
533562
const payload = (
@@ -676,19 +705,18 @@ function getRecipeInterface(
676705
if (redirectTo === undefined) {
677706
throw new Error(resp.body);
678707
}
679-
const websiteDomain = appInfo
680-
.getOrigin({ request: undefined, userContext: input.userContext })
681-
.getAsStringDangerous();
682-
const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous();
683708
const redirectToURL = new URL(redirectTo);
684709
const logoutChallenge = redirectToURL.searchParams.get("logout_challenge");
685710
// CASE 1 (See above notes)
686711
if (logoutChallenge !== null) {
687712
// Redirect to the frontend to ask for logout confirmation if there is a valid or expired supertokens session
688713
if (input.session !== undefined || input.shouldTryRefresh) {
689714
return {
690-
redirectTo:
691-
websiteDomain + websiteBasePath + "/oauth/logout" + `?logoutChallenge=${logoutChallenge}`,
715+
redirectTo: await this.getFrontendRedirectionURL({
716+
type: "logout-confirmation",
717+
logoutChallenge,
718+
userContext: input.userContext,
719+
}),
692720
};
693721
} else {
694722
// Accept the logout challenge immediately as there is no supertokens session
@@ -703,7 +731,12 @@ function getRecipeInterface(
703731
// NOTE: If no post_logout_redirect_uri is provided, Hydra redirects to a fallback page.
704732
// In this case, we redirect the user to the /auth page.
705733
if (redirectTo.endsWith("/oauth/fallbacks/logout/callback")) {
706-
return { redirectTo: `${websiteDomain}${websiteBasePath}` };
734+
return {
735+
redirectTo: await this.getFrontendRedirectionURL({
736+
type: "post-logout-fallback",
737+
userContext: input.userContext,
738+
}),
739+
};
707740
}
708741
return { redirectTo };
709742
},

lib/build/recipe/oauth2provider/types.d.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,31 @@ export declare type RecipeInterface = {
265265
tenantId: string;
266266
userContext: UserContext;
267267
}): Promise<JSONObject>;
268+
getFrontendRedirectionURL(
269+
input:
270+
| {
271+
type: "login";
272+
loginChallenge: string;
273+
tenantId: string;
274+
forceFreshAuth: boolean;
275+
hint: string | undefined;
276+
userContext: UserContext;
277+
}
278+
| {
279+
type: "try-refresh";
280+
loginChallenge: string;
281+
userContext: UserContext;
282+
}
283+
| {
284+
type: "logout-confirmation";
285+
logoutChallenge: string;
286+
userContext: UserContext;
287+
}
288+
| {
289+
type: "post-logout-fallback";
290+
userContext: UserContext;
291+
}
292+
): Promise<string>;
268293
revokeToken(
269294
input: {
270295
token: string;

lib/ts/recipe/oauth2provider/api/utils.ts

Lines changed: 13 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -86,30 +86,14 @@ export async function loginGET({
8686
});
8787
return { redirectTo: accept.redirectTo, setCookie };
8888
}
89-
const appInfo = SuperTokens.getInstanceOrThrowError().appInfo;
90-
const websiteDomain = appInfo
91-
.getOrigin({
92-
request: undefined,
93-
userContext: userContext,
94-
})
95-
.getAsStringDangerous();
96-
const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous();
9789

9890
if (shouldTryRefresh && promptParam !== "login") {
99-
const websiteDomain = appInfo
100-
.getOrigin({
101-
request: undefined,
102-
userContext: userContext,
103-
})
104-
.getAsStringDangerous();
105-
const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous();
106-
107-
const queryParamsForTryRefreshPage = new URLSearchParams({
108-
loginChallenge,
109-
});
110-
11191
return {
112-
redirectTo: websiteDomain + websiteBasePath + `/try-refresh?${queryParamsForTryRefreshPage.toString()}`,
92+
redirectTo: await recipeImplementation.getFrontendRedirectionURL({
93+
type: "try-refresh",
94+
loginChallenge,
95+
userContext,
96+
}),
11397
setCookie,
11498
};
11599
}
@@ -126,24 +110,15 @@ export async function loginGET({
126110
return { redirectTo: reject.redirectTo, setCookie };
127111
}
128112

129-
const queryParamsForAuthPage = new URLSearchParams({
130-
loginChallenge,
131-
});
132-
133-
if (loginRequest.oidcContext?.login_hint) {
134-
queryParamsForAuthPage.set("hint", loginRequest.oidcContext.login_hint);
135-
}
136-
137-
if (session !== undefined || promptParam === "login") {
138-
queryParamsForAuthPage.set("forceFreshAuth", "true");
139-
}
140-
141-
if (tenantIdParam !== null && tenantIdParam !== DEFAULT_TENANT_ID) {
142-
queryParamsForAuthPage.set("tenantId", tenantIdParam);
143-
}
144-
145113
return {
146-
redirectTo: websiteDomain + websiteBasePath + `?${queryParamsForAuthPage.toString()}`,
114+
redirectTo: await recipeImplementation.getFrontendRedirectionURL({
115+
type: "login",
116+
loginChallenge,
117+
forceFreshAuth: session !== undefined || promptParam === "login",
118+
tenantId: tenantIdParam ?? DEFAULT_TENANT_ID,
119+
hint: loginRequest.oidcContext?.login_hint,
120+
userContext,
121+
}),
147122
setCookie,
148123
};
149124
}

lib/ts/recipe/oauth2provider/recipeImplementation.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { OAuth2Client } from "./OAuth2Client";
2929
import { getUser } from "../..";
3030
import { getCombinedJWKS } from "../../combinedRemoteJWKSet";
3131
import SessionRecipe from "../session/recipe";
32+
import { DEFAULT_TENANT_ID } from "../multitenancy/constants";
3233

3334
function getUpdatedRedirectTo(appInfo: NormalisedAppinfo, redirectTo: string) {
3435
return redirectTo.replace(
@@ -523,6 +524,37 @@ export default function getRecipeInterface(
523524
buildUserInfo: async function ({ user, accessTokenPayload, scopes, tenantId, userContext }) {
524525
return getDefaultUserInfoPayload(user, accessTokenPayload, scopes, tenantId, userContext);
525526
},
527+
getFrontendRedirectionURL: async function (input) {
528+
const websiteDomain = appInfo
529+
.getOrigin({ request: undefined, userContext: input.userContext })
530+
.getAsStringDangerous();
531+
const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous();
532+
533+
if (input.type === "login") {
534+
const queryParams = new URLSearchParams({
535+
loginChallenge: input.loginChallenge,
536+
});
537+
if (input.tenantId !== undefined && input.tenantId !== DEFAULT_TENANT_ID) {
538+
queryParams.set("tenantId", input.tenantId);
539+
}
540+
if (input.hint !== undefined) {
541+
queryParams.set("hint", input.hint);
542+
}
543+
if (input.forceFreshAuth) {
544+
queryParams.set("forceFreshAuth", "true");
545+
}
546+
547+
return `${websiteDomain}${websiteBasePath}?${queryParams.toString()}`;
548+
} else if (input.type === "try-refresh") {
549+
return `${websiteDomain}${websiteBasePath}/try-refresh?loginChallenge=${input.loginChallenge}`;
550+
} else if (input.type === "post-logout-fallback") {
551+
return `${websiteDomain}${websiteBasePath}`;
552+
} else if (input.type === "logout-confirmation") {
553+
return `${websiteDomain}${websiteBasePath}/oauth/logout?logoutChallenge=${input.logoutChallenge}`;
554+
}
555+
556+
throw new Error("This should never happen: invalid type passed to getFrontendRedirectionURL");
557+
},
526558
validateOAuth2AccessToken: async function (input) {
527559
const payload = (
528560
await jose.jwtVerify(input.token, getCombinedJWKS(SessionRecipe.getInstanceOrThrowError().config))
@@ -675,11 +707,6 @@ export default function getRecipeInterface(
675707
throw new Error(resp.body);
676708
}
677709

678-
const websiteDomain = appInfo
679-
.getOrigin({ request: undefined, userContext: input.userContext })
680-
.getAsStringDangerous();
681-
const websiteBasePath = appInfo.websiteBasePath.getAsStringDangerous();
682-
683710
const redirectToURL = new URL(redirectTo);
684711
const logoutChallenge = redirectToURL.searchParams.get("logout_challenge");
685712

@@ -688,8 +715,11 @@ export default function getRecipeInterface(
688715
// Redirect to the frontend to ask for logout confirmation if there is a valid or expired supertokens session
689716
if (input.session !== undefined || input.shouldTryRefresh) {
690717
return {
691-
redirectTo:
692-
websiteDomain + websiteBasePath + "/oauth/logout" + `?logoutChallenge=${logoutChallenge}`,
718+
redirectTo: await this.getFrontendRedirectionURL({
719+
type: "logout-confirmation",
720+
logoutChallenge,
721+
userContext: input.userContext,
722+
}),
693723
};
694724
} else {
695725
// Accept the logout challenge immediately as there is no supertokens session
@@ -706,7 +736,12 @@ export default function getRecipeInterface(
706736
// NOTE: If no post_logout_redirect_uri is provided, Hydra redirects to a fallback page.
707737
// In this case, we redirect the user to the /auth page.
708738
if (redirectTo.endsWith("/oauth/fallbacks/logout/callback")) {
709-
return { redirectTo: `${websiteDomain}${websiteBasePath}` };
739+
return {
740+
redirectTo: await this.getFrontendRedirectionURL({
741+
type: "post-logout-fallback",
742+
userContext: input.userContext,
743+
}),
744+
};
710745
}
711746

712747
return { redirectTo };

lib/ts/recipe/oauth2provider/types.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,31 @@ export type RecipeInterface = {
368368
tenantId: string;
369369
userContext: UserContext;
370370
}): Promise<JSONObject>;
371+
getFrontendRedirectionURL(
372+
input:
373+
| {
374+
type: "login";
375+
loginChallenge: string;
376+
tenantId: string;
377+
forceFreshAuth: boolean;
378+
hint: string | undefined;
379+
userContext: UserContext;
380+
}
381+
| {
382+
type: "try-refresh";
383+
loginChallenge: string;
384+
userContext: UserContext;
385+
}
386+
| {
387+
type: "logout-confirmation";
388+
logoutChallenge: string;
389+
userContext: UserContext;
390+
}
391+
| {
392+
type: "post-logout-fallback";
393+
userContext: UserContext;
394+
}
395+
): Promise<string>;
371396
revokeToken(
372397
input: {
373398
token: string;

0 commit comments

Comments
 (0)