Skip to content

Commit 5caaa0d

Browse files
authored
fix: preserve query params when redirectBack is used (#779)
* Preserve query params when redirectBack is used * PR changes * Update tests
1 parent 51976b8 commit 5caaa0d

15 files changed

+239
-103
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
77

8+
## [0.36.1] - 2023-12-20
9+
10+
### Fixes
11+
12+
- Previously, when calling `redirectToAuth` with the `redirectBack` option, query parameters were stripped when redirecting back to the previous page after authentication. This issue has been fixed, and now query parameters are preserved as intended.
13+
814
## [0.36.0] - 2023-12-07
915

1016
### Changes

lib/build/genericComponentOverrideContext.js

Lines changed: 20 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/build/utils.d.ts

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/build/version.d.ts

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/ts/superTokens.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { saveCurrentLanguage, TranslationController } from "./translation/transl
2828
import {
2929
appendQueryParamsToURL,
3030
appendTrailingSlashToURL,
31-
getCurrentNormalisedUrlPath,
31+
getCurrentNormalisedUrlPathWithQueryParams,
3232
getDefaultCookieScope,
3333
getOriginOfPage,
3434
isTest,
@@ -193,7 +193,7 @@ export default class SuperTokens {
193193
queryParams.show = options.show;
194194
}
195195
if (options.redirectBack === true) {
196-
queryParams.redirectToPath = getCurrentNormalisedUrlPath().getAsStringDangerous();
196+
queryParams.redirectToPath = getCurrentNormalisedUrlPathWithQueryParams();
197197
}
198198

199199
let redirectUrl = await this.getRedirectUrl(

lib/ts/utils.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,18 @@ export function getRedirectToPathFromURL(): string | undefined {
8080
try {
8181
const normalisedURLPath = new NormalisedURLPath(param).getAsStringDangerous();
8282
const pathQueryParams = param.split("?")[1] !== undefined ? `?${param.split("?")[1]}` : "";
83-
return normalisedURLPath + pathQueryParams;
83+
const pathWithQueryParams = normalisedURLPath + pathQueryParams;
84+
85+
// Ensure a leading "/" if `normalisedUrlPath` is empty but `pathWithQueryParams` is not to ensure proper redirection.
86+
// Example: "?test=1" will not redirect the user to `/?test=1` if we don't add a leading "/".
87+
if (
88+
normalisedURLPath.length === 0 &&
89+
pathWithQueryParams.length > 0 &&
90+
!pathWithQueryParams.startsWith("/")
91+
) {
92+
return "/" + pathWithQueryParams;
93+
}
94+
return pathWithQueryParams;
8495
} catch {
8596
return undefined;
8697
}
@@ -189,6 +200,11 @@ export function getCurrentNormalisedUrlPath(): NormalisedURLPath {
189200
return new NormalisedURLPath(WindowHandlerReference.getReferenceOrThrow().windowHandler.location.getPathName());
190201
}
191202

203+
export function getCurrentNormalisedUrlPathWithQueryParams(): string {
204+
const normalisedUrlPath = getCurrentNormalisedUrlPath().getAsStringDangerous();
205+
return normalisedUrlPath + WindowHandlerReference.getReferenceOrThrow().windowHandler.location.getSearch();
206+
}
207+
192208
export function appendQueryParamsToURL(stringUrl: string, queryParams?: Record<string, string>): string {
193209
if (queryParams === undefined) {
194210
return stringUrl;

lib/ts/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
* License for the specific language governing permissions and limitations
1313
* under the License.
1414
*/
15-
export const package_version = "0.36.0";
15+
export const package_version = "0.36.1";

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "supertokens-auth-react",
3-
"version": "0.36.0",
3+
"version": "0.36.1",
44
"description": "ReactJS SDK that provides login functionality with SuperTokens.",
55
"main": "./index.js",
66
"engines": {

test/end-to-end/emailverification.test.js

Lines changed: 54 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ import {
5252
setGeneralErrorToLocalStorage,
5353
isAccountLinkingSupported,
5454
backendBeforeEach,
55+
getDefaultSignUpFieldValues,
56+
getTestEmail,
5557
} from "../helpers";
5658

5759
describe("SuperTokens Email Verification", function () {
@@ -114,14 +116,11 @@ describe("SuperTokens Email Verification", function () {
114116
page.waitForNavigation({ waitUntil: "networkidle0" }),
115117
]);
116118
await toggleSignInSignUp(page);
117-
const email = `john.doe${Date.now()}@supertokens.io`;
118-
await signUp(page, [
119-
{ name: "email", value: email },
120-
{ name: "password", value: "Str0ngP@ssw0rd" },
121-
{ name: "name", value: "John Doe" },
122-
{ name: "age", value: "20" },
123-
{ name: "country", value: "" },
124-
]);
119+
120+
const email = getTestEmail();
121+
const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email });
122+
await signUp(page, fieldValues, postValues, "emailpassword");
123+
125124
await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']");
126125

127126
await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/deleteUser`, {
@@ -169,14 +168,10 @@ describe("SuperTokens Email Verification", function () {
169168
page.waitForNavigation({ waitUntil: "networkidle0" }),
170169
]);
171170
await toggleSignInSignUp(page);
172-
const email = `john.doe${Date.now()}@supertokens.io`;
173-
await signUp(page, [
174-
{ name: "email", value: email },
175-
{ name: "password", value: "Str0ngP@ssw0rd" },
176-
{ name: "name", value: "John Doe" },
177-
{ name: "age", value: "20" },
178-
{ name: "country", value: "" },
179-
]);
171+
const email = getTestEmail();
172+
const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email });
173+
await signUp(page, fieldValues, postValues, "emailpassword");
174+
180175
await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']");
181176
let pathname = await page.evaluate(() => window.location.pathname);
182177
assert.deepStrictEqual(pathname, "/auth/verify-email");
@@ -264,24 +259,15 @@ describe("SuperTokens Email Verification", function () {
264259
]);
265260
});
266261

267-
it("Should redirect to verify email screen on successful sign up when mode is REQUIRED and email is not verified and then post verification should redirect with original redirectPath and newUser", async function () {
262+
it("Should redirect to verify email screen on successful sign up when mode is REQUIRED and email is not verified and then post verification should redirect with original redirectPath (w/ leading slash) and newUser", async function () {
268263
await Promise.all([
269-
page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%2Fredirect-here`),
264+
page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%2Fredirect-here%3Ffoo%3Dbar`),
270265
page.waitForNavigation({ waitUntil: "networkidle0" }),
271266
]);
272267
await toggleSignInSignUp(page);
273-
const rid = "emailpassword";
274-
await signUp(
275-
page,
276-
[
277-
{ name: "email", value: "[email protected]" },
278-
{ name: "password", value: "Str0ngP@ssw0rd" },
279-
{ name: "name", value: "John Doe" },
280-
{ name: "age", value: "20" },
281-
],
282-
'{"formFields":[{"id":"email","value":"[email protected]"},{"id":"password","value":"Str0ngP@ssw0rd"},{"id":"name","value":"John Doe"},{"id":"age","value":"20"},{"id":"country","value":""}]}',
283-
rid
284-
);
268+
const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "[email protected]" });
269+
await signUp(page, fieldValues, postValues, "emailpassword");
270+
285271
let pathname = await page.evaluate(() => window.location.pathname);
286272
assert.deepStrictEqual(pathname, "/auth/verify-email");
287273

@@ -295,9 +281,36 @@ describe("SuperTokens Email Verification", function () {
295281
// click on the continue button
296282
await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]);
297283

298-
// check that we are in /redirect-here
299-
pathname = await page.evaluate(() => window.location.pathname);
300-
assert.deepStrictEqual(pathname, "/redirect-here");
284+
// check that we are in /redirect-here?foo=bar
285+
const urlWithQP = await page.evaluate(() => window.location.pathname + window.location.search);
286+
assert.deepStrictEqual(urlWithQP, "/redirect-here?foo=bar");
287+
});
288+
289+
it("Should redirect to verify email screen on successful sign up when mode is REQUIRED and email is not verified and then post verification should redirect with original redirectPath (w/o leading slash) and newUser", async function () {
290+
await Promise.all([
291+
page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%3Ffoo%3Dbar`),
292+
page.waitForNavigation({ waitUntil: "networkidle0" }),
293+
]);
294+
await toggleSignInSignUp(page);
295+
const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: getTestEmail() });
296+
await signUp(page, fieldValues, postValues, "emailpassword");
297+
298+
let pathname = await page.evaluate(() => window.location.pathname);
299+
assert.deepStrictEqual(pathname, "/auth/verify-email");
300+
301+
// we wait for email to be created
302+
await new Promise((r) => setTimeout(r, 1000));
303+
304+
// we fetch the email verification link and go to that
305+
const latestURLWithToken = await getLatestURLWithToken();
306+
await Promise.all([page.waitForNavigation({ waitUntil: "networkidle0" }), page.goto(latestURLWithToken)]);
307+
308+
// click on the continue button
309+
await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]);
310+
311+
// check that we are in /?foo=bar
312+
const urlWithQP = await page.evaluate(() => window.location.pathname + window.location.search);
313+
assert.deepStrictEqual(urlWithQP, "/?foo=bar");
301314
});
302315

303316
it("Should redirect to verify email screen on successful sign in when mode is REQUIRED and email is not verified", async function () {
@@ -451,17 +464,8 @@ describe("SuperTokens Email Verification", function () {
451464

452465
it('Should show "Email Verification successful" screen when token is valid with an active session', async function () {
453466
await toggleSignInSignUp(page);
454-
await signUp(
455-
page,
456-
[
457-
{ name: "email", value: "[email protected]" },
458-
{ name: "password", value: "Str0ngP@ssw0rd" },
459-
{ name: "name", value: "John Doe" },
460-
{ name: "age", value: "20" },
461-
],
462-
'{"formFields":[{"id":"email","value":"[email protected]"},{"id":"password","value":"Str0ngP@ssw0rd"},{"id":"name","value":"John Doe"},{"id":"age","value":"20"},{"id":"country","value":""}]}',
463-
"emailpassword"
464-
);
467+
const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "[email protected]" });
468+
await signUp(page, fieldValues, postValues, "emailpassword");
465469

466470
const latestURLWithToken = await getLatestURLWithToken();
467471
await Promise.all([page.waitForNavigation({ waitUntil: "networkidle0" }), page.goto(latestURLWithToken)]);
@@ -518,17 +522,8 @@ describe("SuperTokens Email Verification", function () {
518522
]);
519523
await toggleSignInSignUp(page);
520524

521-
await signUp(
522-
page,
523-
[
524-
{ name: "email", value: "[email protected]" },
525-
{ name: "password", value: "Str0ngP@ssw0rd" },
526-
{ name: "name", value: "John Doe" },
527-
{ name: "age", value: "20" },
528-
],
529-
'{"formFields":[{"id":"email","value":"[email protected]"},{"id":"password","value":"Str0ngP@ssw0rd"},{"id":"name","value":"John Doe"},{"id":"age","value":"20"},{"id":"country","value":""}]}',
530-
"emailpassword"
531-
);
525+
const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "[email protected]" });
526+
await signUp(page, fieldValues, postValues, "emailpassword");
532527

533528
const latestURLWithToken = await getLatestURLWithToken();
534529
await Promise.all([
@@ -708,17 +703,8 @@ describe("SuperTokens Email Verification general errors", function () {
708703

709704
it('Should show "General Error" when API returns "GENERAL_ERROR"', async function () {
710705
await toggleSignInSignUp(page);
711-
await signUp(
712-
page,
713-
[
714-
{ name: "email", value: "[email protected]" },
715-
{ name: "password", value: "Str0ngP@ssw0rd" },
716-
{ name: "name", value: "John Doe" },
717-
{ name: "age", value: "20" },
718-
],
719-
'{"formFields":[{"id":"email","value":"[email protected]"},{"id":"password","value":"Str0ngP@ssw0rd"},{"id":"name","value":"John Doe"},{"id":"age","value":"20"},{"id":"country","value":""}]}',
720-
"emailpassword"
721-
);
706+
const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "[email protected]" });
707+
await signUp(page, fieldValues, postValues, "emailpassword");
722708

723709
const latestURLWithToken = await getLatestURLWithToken();
724710
await page.goto(latestURLWithToken);
@@ -903,18 +889,8 @@ describe("Email verification signOut errors", function () {
903889
await toggleSignInSignUp(page);
904890
await page.evaluate(() => localStorage.setItem("SHOW_GENERAL_ERROR", "SESSION SIGN_OUT"));
905891

906-
const rid = "emailpassword";
907-
await signUp(
908-
page,
909-
[
910-
{ name: "email", value: "[email protected]" },
911-
{ name: "password", value: "Str0ngP@ssw0rd" },
912-
{ name: "name", value: "John Doe" },
913-
{ name: "age", value: "20" },
914-
],
915-
'{"formFields":[{"id":"email","value":"[email protected]"},{"id":"password","value":"Str0ngP@ssw0rd"},{"id":"name","value":"John Doe"},{"id":"age","value":"20"},{"id":"country","value":""}]}',
916-
rid
917-
);
892+
const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "[email protected]" });
893+
await signUp(page, fieldValues, postValues, "emailpassword");
918894

919895
let pathname = await page.evaluate(() => window.location.pathname);
920896
assert.deepStrictEqual(pathname, "/auth/verify-email");

test/end-to-end/passwordless.test.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe
736736
]);
737737
});
738738

739-
it("Successful signin w/ redirectToPath and email verification", async function () {
739+
it("Successful signin w/ redirectToPath (w/ leading slash) and email verification", async function () {
740740
await Promise.all([
741741
page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%2Fredirect-here%3Ffoo%3Dbar&mode=REQUIRED`),
742742
page.waitForNavigation({ waitUntil: "networkidle0" }),
@@ -781,6 +781,30 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe
781781
]);
782782
});
783783

784+
it("Successful signin w/ redirectPath (w/o leading slash) and email verification", async function () {
785+
await Promise.all([
786+
page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%3Ffoo%3Dbar&mode=REQUIRED`),
787+
page.waitForNavigation({ waitUntil: "networkidle0" }),
788+
]);
789+
790+
await setInputValues(page, [{ name: inputName, value: contactInfo }]);
791+
await submitForm(page);
792+
793+
await waitForSTElement(page, "[data-supertokens~=input][name=userInputCode]");
794+
795+
const loginAttemptInfo = JSON.parse(
796+
await page.evaluate(() => localStorage.getItem("supertokens-passwordless-loginAttemptInfo"))
797+
);
798+
const device = await getPasswordlessDevice(loginAttemptInfo);
799+
await setInputValues(page, [{ name: "userInputCode", value: device.codes[0].userInputCode }]);
800+
await submitForm(page);
801+
802+
await page.waitForNavigation({ waitUntil: "networkidle0" });
803+
804+
const { pathname, search } = await page.evaluate(() => window.location);
805+
assert.deepStrictEqual(pathname + search, "/?foo=bar");
806+
});
807+
784808
it("Submitting empty id", async function () {
785809
await Promise.all([
786810
page.goto(`${TEST_CLIENT_BASE_URL}/auth`),

0 commit comments

Comments
 (0)