Skip to content

Commit be656bf

Browse files
committed
feat: Add option to disable url sanitation
1 parent 804396d commit be656bf

File tree

3 files changed

+55
-18
lines changed

3 files changed

+55
-18
lines changed

lib/utils/generateAuthUrl.test.ts

+31
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,35 @@ describe("generateAuthUrl", () => {
197197

198198
expect(state).toEqual(testState);
199199
});
200+
201+
it("if disableUrlSanitization is set, should leave the redirect the URL alone", async () => {
202+
const domain = "https://auth.example.com";
203+
const options: LoginOptions = {
204+
clientId: "client123",
205+
scope: [Scopes.openid, Scopes.profile, Scopes.offline_access],
206+
redirectURL: "https://example2.com/",
207+
prompt: PromptTypes.create,
208+
};
209+
const expectedUrl =
210+
"https://auth.example.com/oauth2/auth?client_id=client123&response_type=code&redirect_uri=https%3A%2F%2Fexample2.com%2F&audience=&scope=openid+profile+offline&prompt=create&code_challenge_method=S256";
211+
212+
const result = await generateAuthUrl(
213+
domain,
214+
IssuerRouteTypes.login,
215+
options,
216+
{ disableUrlSanitization: true }
217+
);
218+
const nonce = result.url.searchParams.get("nonce");
219+
expect(nonce).not.toBeNull();
220+
expect(nonce!.length).toBe(16);
221+
const state = result.url.searchParams.get("state");
222+
expect(state).not.toBeNull();
223+
expect(state!.length).toBe(32);
224+
const codeChallenge = result.url.searchParams.get("code_challenge");
225+
expect(codeChallenge!.length).toBeGreaterThanOrEqual(27);
226+
result.url.searchParams.delete("code_challenge");
227+
result.url.searchParams.delete("nonce");
228+
result.url.searchParams.delete("state");
229+
expect(result.url.toString()).toBe(expectedUrl);
230+
});
200231
});

lib/utils/generateAuthUrl.ts

+22-17
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { IssuerRouteTypes, LoginOptions, PromptTypes } from "../types";
33
import { generateRandomString } from "./generateRandomString";
44
import { mapLoginMethodParamsForUrl } from "./mapLoginMethodParamsForUrl";
55

6+
interface generateAuthUrlConfig {
7+
disableUrlSanitization: boolean;
8+
}
9+
610
/**
711
*
812
* @param options
@@ -12,7 +16,8 @@ import { mapLoginMethodParamsForUrl } from "./mapLoginMethodParamsForUrl";
1216
export const generateAuthUrl = async (
1317
domain: string,
1418
type: IssuerRouteTypes = IssuerRouteTypes.login,
15-
options: LoginOptions,
19+
loginOptions: LoginOptions,
20+
config?: generateAuthUrlConfig,
1621
): Promise<{
1722
url: URL;
1823
state: string;
@@ -23,30 +28,30 @@ export const generateAuthUrl = async (
2328
const authPath = `${domain}/oauth2/auth`;
2429
const activeStorage = getInsecureStorage();
2530
const searchParams: Record<string, string> = {
26-
client_id: options.clientId,
27-
response_type: options.responseType || "code",
28-
...mapLoginMethodParamsForUrl(options),
31+
client_id: loginOptions.clientId,
32+
response_type: loginOptions.responseType || "code",
33+
...mapLoginMethodParamsForUrl(loginOptions, config?.disableUrlSanitization),
2934
};
3035

31-
if (!options.state) {
32-
options.state = generateRandomString(32);
36+
if (!loginOptions.state) {
37+
loginOptions.state = generateRandomString(32);
3338
}
3439
if (activeStorage) {
35-
activeStorage.setSessionItem(StorageKeys.state, options.state);
40+
activeStorage.setSessionItem(StorageKeys.state, loginOptions.state);
3641
}
37-
searchParams["state"] = options.state;
42+
searchParams["state"] = loginOptions.state;
3843

39-
if (!options.nonce) {
40-
options.nonce = generateRandomString(16);
44+
if (!loginOptions.nonce) {
45+
loginOptions.nonce = generateRandomString(16);
4146
}
42-
searchParams["nonce"] = options.nonce;
47+
searchParams["nonce"] = loginOptions.nonce;
4348
if (activeStorage) {
44-
activeStorage.setSessionItem(StorageKeys.nonce, options.nonce);
49+
activeStorage.setSessionItem(StorageKeys.nonce, loginOptions.nonce);
4550
}
4651

4752
let returnCodeVerifier = "";
48-
if (options.codeChallenge) {
49-
searchParams["code_challenge"] = options.codeChallenge;
53+
if (loginOptions.codeChallenge) {
54+
searchParams["code_challenge"] = loginOptions.codeChallenge;
5055
} else {
5156
const { codeVerifier, codeChallenge } = await generatePKCEPair();
5257
returnCodeVerifier = codeVerifier;
@@ -57,11 +62,11 @@ export const generateAuthUrl = async (
5762
}
5863
searchParams["code_challenge_method"] = "S256";
5964

60-
if (options.codeChallengeMethod) {
61-
searchParams["code_challenge_method"] = options.codeChallengeMethod;
65+
if (loginOptions.codeChallengeMethod) {
66+
searchParams["code_challenge_method"] = loginOptions.codeChallengeMethod;
6267
}
6368

64-
if (!options.prompt && type === IssuerRouteTypes.register) {
69+
if (!loginOptions.prompt && type === IssuerRouteTypes.register) {
6570
searchParams["prompt"] = PromptTypes.create;
6671
}
6772

lib/utils/mapLoginMethodParamsForUrl.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import { sanitizeUrl } from "./sanitizeUrl";
33

44
export const mapLoginMethodParamsForUrl = (
55
options: Partial<LoginMethodParams>,
6+
disableUrlSanitization: boolean = false,
67
): Record<string, string> => {
78
const translate: Record<string, string | undefined> = {
89
login_hint: options.loginHint,
910
is_create_org: options.isCreateOrg?.toString(),
1011
connection_id: options.connectionId,
1112
redirect_uri: options.redirectURL
12-
? sanitizeUrl(options.redirectURL)
13+
? disableUrlSanitization ? options.redirectURL : sanitizeUrl(options.redirectURL)
1314
: undefined,
1415
audience: options.audience || "",
1516
scope: options.scope?.join(" ") || "email profile openid offline",

0 commit comments

Comments
 (0)