From 22493d357d7735ab138fc6a98d8ac28d9f6eb458 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 20 Dec 2023 15:29:14 +0530 Subject: [PATCH 1/3] Preserve query params when redirectBack is used --- CHANGELOG.md | 6 +++++ lib/build/genericComponentOverrideContext.js | 23 +++++++++++++++++--- lib/build/utils.d.ts | 1 + lib/build/version.d.ts | 2 +- lib/ts/superTokens.tsx | 4 ++-- lib/ts/utils.ts | 18 ++++++++++++++- lib/ts/version.ts | 2 +- package-lock.json | 4 ++-- package.json | 3 ++- test/end-to-end/signup.test.js | 10 ++++++++- 10 files changed, 61 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d42b7dd74..5b84a503e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) +## [0.36.1] - 2023-12-20 + +### Fixes + +- 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. + ## [0.36.0] - 2023-12-07 ### Changes diff --git a/lib/build/genericComponentOverrideContext.js b/lib/build/genericComponentOverrideContext.js index bd279f688..bd7d250bb 100644 --- a/lib/build/genericComponentOverrideContext.js +++ b/lib/build/genericComponentOverrideContext.js @@ -265,7 +265,7 @@ var SSR_ERROR = * License for the specific language governing permissions and limitations * under the License. */ -var package_version = "0.36.0"; +var package_version = "0.36.1"; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -356,7 +356,17 @@ function getRedirectToPathFromURL() { try { var normalisedURLPath = new NormalisedURLPath__default.default(param).getAsStringDangerous(); var pathQueryParams = param.split("?")[1] !== undefined ? "?".concat(param.split("?")[1]) : ""; - return normalisedURLPath + pathQueryParams; + var pathWithQueryParams = normalisedURLPath + pathQueryParams; + // Ensure a leading "/" if `normalisedUrlPath` is empty but `pathWithQueryParams` is not to ensure proper redirection. + // Example: "?test=1" will not redirect the user to `/?test=1` if we don't add a leading "/". + if ( + normalisedURLPath.length === 0 && + pathWithQueryParams.length > 0 && + !pathWithQueryParams.startsWith("/") + ) { + return "/" + pathWithQueryParams; + } + return pathWithQueryParams; } catch (_a) { return undefined; } @@ -474,6 +484,13 @@ function getCurrentNormalisedUrlPath() { windowHandler.WindowHandlerReference.getReferenceOrThrow().windowHandler.location.getPathName() ); } +function getCurrentNormalisedUrlPathWithQueryParams() { + var normalisedUrlPath = getCurrentNormalisedUrlPath().getAsStringDangerous(); + return ( + normalisedUrlPath + + windowHandler.WindowHandlerReference.getReferenceOrThrow().windowHandler.location.getSearch() + ); +} function appendQueryParamsToURL(stringUrl, queryParams) { if (queryParams === undefined) { return stringUrl; @@ -1169,7 +1186,7 @@ var SuperTokens = /** @class */ (function () { queryParams.show = options.show; } if (options.redirectBack === true) { - queryParams.redirectToPath = getCurrentNormalisedUrlPath().getAsStringDangerous(); + queryParams.redirectToPath = getCurrentNormalisedUrlPathWithQueryParams(); } return [ 4 /*yield*/, diff --git a/lib/build/utils.d.ts b/lib/build/utils.d.ts index 3eafd3f63..8810c2549 100644 --- a/lib/build/utils.d.ts +++ b/lib/build/utils.d.ts @@ -22,6 +22,7 @@ export declare function validateForm( configFormFields: NormalisedFormField[] ): Promise; export declare function getCurrentNormalisedUrlPath(): NormalisedURLPath; +export declare function getCurrentNormalisedUrlPathWithQueryParams(): string; export declare function appendQueryParamsToURL(stringUrl: string, queryParams?: Record): string; export declare function appendTrailingSlashToURL(stringUrl: string): string; export declare function matchRecipeIdUsingQueryParams(recipeId: string): () => boolean; diff --git a/lib/build/version.d.ts b/lib/build/version.d.ts index e85100a93..b9b6c1902 100644 --- a/lib/build/version.d.ts +++ b/lib/build/version.d.ts @@ -1 +1 @@ -export declare const package_version = "0.36.0"; +export declare const package_version = "0.36.1"; diff --git a/lib/ts/superTokens.tsx b/lib/ts/superTokens.tsx index 193b41108..a991488e0 100644 --- a/lib/ts/superTokens.tsx +++ b/lib/ts/superTokens.tsx @@ -28,7 +28,7 @@ import { saveCurrentLanguage, TranslationController } from "./translation/transl import { appendQueryParamsToURL, appendTrailingSlashToURL, - getCurrentNormalisedUrlPath, + getCurrentNormalisedUrlPathWithQueryParams, getDefaultCookieScope, getOriginOfPage, isTest, @@ -193,7 +193,7 @@ export default class SuperTokens { queryParams.show = options.show; } if (options.redirectBack === true) { - queryParams.redirectToPath = getCurrentNormalisedUrlPath().getAsStringDangerous(); + queryParams.redirectToPath = getCurrentNormalisedUrlPathWithQueryParams(); } let redirectUrl = await this.getRedirectUrl( diff --git a/lib/ts/utils.ts b/lib/ts/utils.ts index f13e4c19d..71de1a53a 100644 --- a/lib/ts/utils.ts +++ b/lib/ts/utils.ts @@ -80,7 +80,18 @@ export function getRedirectToPathFromURL(): string | undefined { try { const normalisedURLPath = new NormalisedURLPath(param).getAsStringDangerous(); const pathQueryParams = param.split("?")[1] !== undefined ? `?${param.split("?")[1]}` : ""; - return normalisedURLPath + pathQueryParams; + const pathWithQueryParams = normalisedURLPath + pathQueryParams; + + // Ensure a leading "/" if `normalisedUrlPath` is empty but `pathWithQueryParams` is not to ensure proper redirection. + // Example: "?test=1" will not redirect the user to `/?test=1` if we don't add a leading "/". + if ( + normalisedURLPath.length === 0 && + pathWithQueryParams.length > 0 && + !pathWithQueryParams.startsWith("/") + ) { + return "/" + pathWithQueryParams; + } + return pathWithQueryParams; } catch { return undefined; } @@ -189,6 +200,11 @@ export function getCurrentNormalisedUrlPath(): NormalisedURLPath { return new NormalisedURLPath(WindowHandlerReference.getReferenceOrThrow().windowHandler.location.getPathName()); } +export function getCurrentNormalisedUrlPathWithQueryParams(): string { + const normalisedUrlPath = getCurrentNormalisedUrlPath().getAsStringDangerous(); + return normalisedUrlPath + WindowHandlerReference.getReferenceOrThrow().windowHandler.location.getSearch(); +} + export function appendQueryParamsToURL(stringUrl: string, queryParams?: Record): string { if (queryParams === undefined) { return stringUrl; diff --git a/lib/ts/version.ts b/lib/ts/version.ts index 1fd0f9cc7..dff31d9af 100644 --- a/lib/ts/version.ts +++ b/lib/ts/version.ts @@ -12,4 +12,4 @@ * License for the specific language governing permissions and limitations * under the License. */ -export const package_version = "0.36.0"; +export const package_version = "0.36.1"; diff --git a/package-lock.json b/package-lock.json index d49cefd40..9e01540a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "supertokens-auth-react", - "version": "0.36.0", + "version": "0.36.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "supertokens-auth-react", - "version": "0.36.0", + "version": "0.36.1", "license": "Apache-2.0", "dependencies": { "intl-tel-input": "^17.0.19", diff --git a/package.json b/package.json index f666f121f..14806b32f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "supertokens-auth-react", - "version": "0.36.0", + "version": "0.36.1", "description": "ReactJS SDK that provides login functionality with SuperTokens.", "main": "./index.js", "engines": { @@ -105,6 +105,7 @@ "build": "rm -rf lib/build && npx rollup -c", "watch": "npx rollup -cw", "watch-mac": "chokidar lib/ts/* -c 'npx rollup -c'", + "watch-mac-2": "chokidar lib/ts/* -c 'npx rollup -c && cp -r lib recipe ui index.d.ts index.js package.json rollup.config.mjs webJsInterfaceSupported.json ../../supertokens-demo/frontend/node_modules/supertokens-auth-react/ && rm -rf ../../supertokens-demo/frontend/node_modules/.cache'", "pretty": "npx pretty-quick .", "build-pretty": "npm run build && npm run pretty && npm run pretty", "lint": "node other/checkTranslationKeys.js && cd lib && eslint ./ts --ext .ts,.tsx", diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index c747339ca..25f577250 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -134,10 +134,18 @@ describe("SuperTokens SignUp", function () { await page.evaluate(() => window.SuperTokens.redirectToAuth()); await page.waitForNavigation({ waitUntil: "networkidle0" }); let text = await getAuthPageHeaderText(page); - let { pathname: pathAfterRedirectToAuth } = await page.evaluate(() => window.location); + let { pathname: pathAfterRedirectToAuth, href: hrefAfterRedirectToAuth } = await page.evaluate( + () => window.location + ); + + const url = new URL(hrefAfterRedirectToAuth); + const redirectToPath = url.searchParams.get("redirectToPath"); + assert.equal(pathAfterRedirectToAuth, "/auth/"); // Only the EmailPassword recipe has this header on the sign in page assert.deepStrictEqual(text, "Sign In"); + // Test that redirecToPath contains query params + assert.equal(redirectToPath, "?authRecipe=both"); }); }); From 872fb61c3dc54d6c11d8b83ba44959e16e4dab2c Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Thu, 21 Dec 2023 01:36:02 +0530 Subject: [PATCH 2/3] PR changes --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 14806b32f..1034a633d 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,6 @@ "build": "rm -rf lib/build && npx rollup -c", "watch": "npx rollup -cw", "watch-mac": "chokidar lib/ts/* -c 'npx rollup -c'", - "watch-mac-2": "chokidar lib/ts/* -c 'npx rollup -c && cp -r lib recipe ui index.d.ts index.js package.json rollup.config.mjs webJsInterfaceSupported.json ../../supertokens-demo/frontend/node_modules/supertokens-auth-react/ && rm -rf ../../supertokens-demo/frontend/node_modules/.cache'", "pretty": "npx pretty-quick .", "build-pretty": "npm run build && npm run pretty && npm run pretty", "lint": "node other/checkTranslationKeys.js && cd lib && eslint ./ts --ext .ts,.tsx", From c2dd9d5821ff0d4f31633b1294db14adbe0259d9 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Thu, 21 Dec 2023 12:28:29 +0530 Subject: [PATCH 3/3] Update tests --- test/end-to-end/emailverification.test.js | 132 +++++++++------------- test/end-to-end/passwordless.test.js | 26 ++++- test/end-to-end/signin.test.js | 23 +++- test/end-to-end/signup.test.js | 52 +++++++-- test/end-to-end/thirdparty.test.js | 17 ++- test/helpers.js | 20 +++- 6 files changed, 179 insertions(+), 91 deletions(-) diff --git a/test/end-to-end/emailverification.test.js b/test/end-to-end/emailverification.test.js index 2d8f96c4d..66ecf038c 100644 --- a/test/end-to-end/emailverification.test.js +++ b/test/end-to-end/emailverification.test.js @@ -52,6 +52,8 @@ import { setGeneralErrorToLocalStorage, isAccountLinkingSupported, backendBeforeEach, + getDefaultSignUpFieldValues, + getTestEmail, } from "../helpers"; describe("SuperTokens Email Verification", function () { @@ -114,14 +116,11 @@ describe("SuperTokens Email Verification", function () { page.waitForNavigation({ waitUntil: "networkidle0" }), ]); await toggleSignInSignUp(page); - const email = `john.doe${Date.now()}@supertokens.io`; - await signUp(page, [ - { name: "email", value: email }, - { name: "password", value: "Str0ngP@ssw0rd" }, - { name: "name", value: "John Doe" }, - { name: "age", value: "20" }, - { name: "country", value: "" }, - ]); + + const email = getTestEmail(); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email }); + await signUp(page, fieldValues, postValues, "emailpassword"); + await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/deleteUser`, { @@ -169,14 +168,10 @@ describe("SuperTokens Email Verification", function () { page.waitForNavigation({ waitUntil: "networkidle0" }), ]); await toggleSignInSignUp(page); - const email = `john.doe${Date.now()}@supertokens.io`; - await signUp(page, [ - { name: "email", value: email }, - { name: "password", value: "Str0ngP@ssw0rd" }, - { name: "name", value: "John Doe" }, - { name: "age", value: "20" }, - { name: "country", value: "" }, - ]); + const email = getTestEmail(); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email }); + await signUp(page, fieldValues, postValues, "emailpassword"); + await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); let pathname = await page.evaluate(() => window.location.pathname); assert.deepStrictEqual(pathname, "/auth/verify-email"); @@ -264,24 +259,15 @@ describe("SuperTokens Email Verification", function () { ]); }); - 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 () { + 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 () { await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%2Fredirect-here`), + page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%2Fredirect-here%3Ffoo%3Dbar`), page.waitForNavigation({ waitUntil: "networkidle0" }), ]); await toggleSignInSignUp(page); - const rid = "emailpassword"; - await signUp( - page, - [ - { name: "email", value: "john.doe2@supertokens.io" }, - { name: "password", value: "Str0ngP@ssw0rd" }, - { name: "name", value: "John Doe" }, - { name: "age", value: "20" }, - ], - '{"formFields":[{"id":"email","value":"john.doe2@supertokens.io"},{"id":"password","value":"Str0ngP@ssw0rd"},{"id":"name","value":"John Doe"},{"id":"age","value":"20"},{"id":"country","value":""}]}', - rid - ); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "john.doe2@supertokens.io" }); + await signUp(page, fieldValues, postValues, "emailpassword"); + let pathname = await page.evaluate(() => window.location.pathname); assert.deepStrictEqual(pathname, "/auth/verify-email"); @@ -295,9 +281,36 @@ describe("SuperTokens Email Verification", function () { // click on the continue button await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); - // check that we are in /redirect-here - pathname = await page.evaluate(() => window.location.pathname); - assert.deepStrictEqual(pathname, "/redirect-here"); + // check that we are in /redirect-here?foo=bar + const urlWithQP = await page.evaluate(() => window.location.pathname + window.location.search); + assert.deepStrictEqual(urlWithQP, "/redirect-here?foo=bar"); + }); + + 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 () { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%3Ffoo%3Dbar`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: getTestEmail() }); + await signUp(page, fieldValues, postValues, "emailpassword"); + + let pathname = await page.evaluate(() => window.location.pathname); + assert.deepStrictEqual(pathname, "/auth/verify-email"); + + // we wait for email to be created + await new Promise((r) => setTimeout(r, 1000)); + + // we fetch the email verification link and go to that + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([page.waitForNavigation({ waitUntil: "networkidle0" }), page.goto(latestURLWithToken)]); + + // click on the continue button + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + + // check that we are in /?foo=bar + const urlWithQP = await page.evaluate(() => window.location.pathname + window.location.search); + assert.deepStrictEqual(urlWithQP, "/?foo=bar"); }); 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 () { it('Should show "Email Verification successful" screen when token is valid with an active session', async function () { await toggleSignInSignUp(page); - await signUp( - page, - [ - { name: "email", value: "john.doe3@supertokens.io" }, - { name: "password", value: "Str0ngP@ssw0rd" }, - { name: "name", value: "John Doe" }, - { name: "age", value: "20" }, - ], - '{"formFields":[{"id":"email","value":"john.doe3@supertokens.io"},{"id":"password","value":"Str0ngP@ssw0rd"},{"id":"name","value":"John Doe"},{"id":"age","value":"20"},{"id":"country","value":""}]}', - "emailpassword" - ); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "john.doe3@supertokens.io" }); + await signUp(page, fieldValues, postValues, "emailpassword"); const latestURLWithToken = await getLatestURLWithToken(); await Promise.all([page.waitForNavigation({ waitUntil: "networkidle0" }), page.goto(latestURLWithToken)]); @@ -518,17 +522,8 @@ describe("SuperTokens Email Verification", function () { ]); await toggleSignInSignUp(page); - await signUp( - page, - [ - { name: "email", value: "john.doe4@supertokens.io" }, - { name: "password", value: "Str0ngP@ssw0rd" }, - { name: "name", value: "John Doe" }, - { name: "age", value: "20" }, - ], - '{"formFields":[{"id":"email","value":"john.doe4@supertokens.io"},{"id":"password","value":"Str0ngP@ssw0rd"},{"id":"name","value":"John Doe"},{"id":"age","value":"20"},{"id":"country","value":""}]}', - "emailpassword" - ); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "john.doe4@supertokens.io" }); + await signUp(page, fieldValues, postValues, "emailpassword"); const latestURLWithToken = await getLatestURLWithToken(); await Promise.all([ @@ -708,17 +703,8 @@ describe("SuperTokens Email Verification general errors", function () { it('Should show "General Error" when API returns "GENERAL_ERROR"', async function () { await toggleSignInSignUp(page); - await signUp( - page, - [ - { name: "email", value: "john.doe3@supertokens.io" }, - { name: "password", value: "Str0ngP@ssw0rd" }, - { name: "name", value: "John Doe" }, - { name: "age", value: "20" }, - ], - '{"formFields":[{"id":"email","value":"john.doe3@supertokens.io"},{"id":"password","value":"Str0ngP@ssw0rd"},{"id":"name","value":"John Doe"},{"id":"age","value":"20"},{"id":"country","value":""}]}', - "emailpassword" - ); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "john.doe3@supertokens.io" }); + await signUp(page, fieldValues, postValues, "emailpassword"); const latestURLWithToken = await getLatestURLWithToken(); await page.goto(latestURLWithToken); @@ -903,18 +889,8 @@ describe("Email verification signOut errors", function () { await toggleSignInSignUp(page); await page.evaluate(() => localStorage.setItem("SHOW_GENERAL_ERROR", "SESSION SIGN_OUT")); - const rid = "emailpassword"; - await signUp( - page, - [ - { name: "email", value: "john.doe2@supertokens.io" }, - { name: "password", value: "Str0ngP@ssw0rd" }, - { name: "name", value: "John Doe" }, - { name: "age", value: "20" }, - ], - '{"formFields":[{"id":"email","value":"john.doe2@supertokens.io"},{"id":"password","value":"Str0ngP@ssw0rd"},{"id":"name","value":"John Doe"},{"id":"age","value":"20"},{"id":"country","value":""}]}', - rid - ); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "john.doe2@supertokens.io" }); + await signUp(page, fieldValues, postValues, "emailpassword"); let pathname = await page.evaluate(() => window.location.pathname); assert.deepStrictEqual(pathname, "/auth/verify-email"); diff --git a/test/end-to-end/passwordless.test.js b/test/end-to-end/passwordless.test.js index 38b1d35a6..153fcab23 100644 --- a/test/end-to-end/passwordless.test.js +++ b/test/end-to-end/passwordless.test.js @@ -736,7 +736,7 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe ]); }); - it("Successful signin w/ redirectToPath and email verification", async function () { + it("Successful signin w/ redirectToPath (w/ leading slash) and email verification", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%2Fredirect-here%3Ffoo%3Dbar&mode=REQUIRED`), page.waitForNavigation({ waitUntil: "networkidle0" }), @@ -781,6 +781,30 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe ]); }); + it("Successful signin w/ redirectPath (w/o leading slash) and email verification", async function () { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%3Ffoo%3Dbar&mode=REQUIRED`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + await setInputValues(page, [{ name: inputName, value: contactInfo }]); + await submitForm(page); + + await waitForSTElement(page, "[data-supertokens~=input][name=userInputCode]"); + + const loginAttemptInfo = JSON.parse( + await page.evaluate(() => localStorage.getItem("supertokens-passwordless-loginAttemptInfo")) + ); + const device = await getPasswordlessDevice(loginAttemptInfo); + await setInputValues(page, [{ name: "userInputCode", value: device.codes[0].userInputCode }]); + await submitForm(page); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + const { pathname, search } = await page.evaluate(() => window.location); + assert.deepStrictEqual(pathname + search, "/?foo=bar"); + }); + it("Submitting empty id", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth`), diff --git a/test/end-to-end/signin.test.js b/test/end-to-end/signin.test.js index bcbb7cff1..b37f5f32a 100644 --- a/test/end-to-end/signin.test.js +++ b/test/end-to-end/signin.test.js @@ -547,7 +547,7 @@ describe("SuperTokens SignIn", function () { ); }); - it("Successful emailPassword Sign In with redirect to keeping query params", async function () { + it("Successful emailPassword Sign In with redirectToPath (w/ leading slash) keeping query params", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%2Fredirect-here%3Ffoo%3Dbar`), page.waitForNavigation({ waitUntil: "networkidle0" }), @@ -568,6 +568,27 @@ describe("SuperTokens SignIn", function () { assert.deepStrictEqual(pathname + search, "/redirect-here?foo=bar"); }); + it("Successful emailPassword Sign In with redirectToPath (w/o leading slash) keeping query params", async function () { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%3Ffoo%3Dbar`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + // Set correct values. + await setInputValues(page, [ + { name: "email", value: "john.doe@supertokens.io" }, + { name: "password", value: "Str0ngP@ssw0rd" }, + ]); + + // Submit. + await Promise.all([ + submitFormReturnRequestAndResponse(page, SIGN_IN_API), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + const { pathname, search } = await page.evaluate(() => window.location); + assert.deepStrictEqual(pathname + search, "/?foo=bar"); + }); + describe("Successful Sign In with redirect to, with EmailPasswordAuth", async function () { it("First sign in", async function () { consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index 25f577250..ddfd233f9 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -42,6 +42,7 @@ import { setSelectDropdownValue, getInputField, isReact16, + getDefaultSignUpFieldValues, } from "../helpers"; import { @@ -126,12 +127,12 @@ describe("SuperTokens SignUp", function () { assert.deepStrictEqual(text, "Sign Up"); }); - it("should redirect to sign in w/ first auth recipe", async function () { + it("should redirect to sign in w/ first auth recipe and set redirectToPath", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}?authRecipe=both`), page.waitForNavigation({ waitUntil: "networkidle0" }), ]); - await page.evaluate(() => window.SuperTokens.redirectToAuth()); + await page.evaluate(() => window.SuperTokens.redirectToAuth({ redirectBack: true })); await page.waitForNavigation({ waitUntil: "networkidle0" }); let text = await getAuthPageHeaderText(page); let { pathname: pathAfterRedirectToAuth, href: hrefAfterRedirectToAuth } = await page.evaluate( @@ -147,6 +148,28 @@ describe("SuperTokens SignUp", function () { // Test that redirecToPath contains query params assert.equal(redirectToPath, "?authRecipe=both"); }); + + it("should redirect to sign in w/ first auth recipe without setting redirectToPath", async function () { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}?authRecipe=both`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await page.evaluate(() => window.SuperTokens.redirectToAuth({ redirectBack: false })); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + let text = await getAuthPageHeaderText(page); + let { pathname: pathAfterRedirectToAuth, href: hrefAfterRedirectToAuth } = await page.evaluate( + () => window.location + ); + + const url = new URL(hrefAfterRedirectToAuth); + const redirectToPath = url.searchParams.get("redirectToPath"); + + assert.equal(pathAfterRedirectToAuth, "/auth/"); + // Only the EmailPassword recipe has this header on the sign in page + assert.deepStrictEqual(text, "Sign In"); + // Test that redirecToPath is null + assert.equal(redirectToPath, null); + }); }); describe("SignUp test ", function () { @@ -294,18 +317,14 @@ describe("SuperTokens SignUp", function () { ]); }); - it("Successful signup with query params kept", async function () { + it("Successful signup with redirectToPath (w/ leading slash) keeping query params", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%2Fredirect-here%3Ffoo%3Dbar`), page.waitForNavigation({ waitUntil: "networkidle0" }), ]); await toggleSignInSignUp(page); - await setInputValues(page, [ - { name: "email", value: "jack.doe@supertokens.io" }, - { name: "password", value: "Str0ngP@ssw0rd" }, - { name: "name", value: "John Doe" }, - { name: "age", value: "20" }, - ]); + const { fieldValues } = getDefaultSignUpFieldValues({ email: "jack.doe@supertokens.io" }); + await setInputValues(page, fieldValues); await submitForm(page); await page.waitForNavigation({ waitUntil: "networkidle0" }); @@ -313,6 +332,21 @@ describe("SuperTokens SignUp", function () { assert.deepStrictEqual(pathname + search, "/redirect-here?foo=bar"); }); + it("Successful signup with redirectToPath (w/ leading slash) keeping query params", async function () { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%3Ffoo%3Dbar`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await toggleSignInSignUp(page); + const { fieldValues } = getDefaultSignUpFieldValues({ email: "jack.doe2@supertokens.io" }); + await setInputValues(page, fieldValues); + + await submitForm(page); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + let { pathname, search } = await page.evaluate(() => window.location); + assert.deepStrictEqual(pathname + search, "/?foo=bar"); + }); + it("should show error message on sign up with duplicate email", async function () { const requestHandler = (request) => { if (request.url().includes(EMAIL_EXISTS_API) && request.method() === "GET") { diff --git a/test/end-to-end/thirdparty.test.js b/test/end-to-end/thirdparty.test.js index 226d83f94..68f7c1b38 100644 --- a/test/end-to-end/thirdparty.test.js +++ b/test/end-to-end/thirdparty.test.js @@ -136,7 +136,7 @@ export function getThirdPartyTestCases({ authRecipe, rid, logId, signInUpPageLoa ]); }); - it("Successful sign in with Auth0 with query params kept", async function () { + it("Successful sign in with Auth0 with redirectToPath (w/ leading slash) keeping query params", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%2Fredirect-here%3Ffoo%3Dbar`), page.waitForNavigation({ waitUntil: "networkidle0" }), @@ -151,6 +151,21 @@ export function getThirdPartyTestCases({ authRecipe, rid, logId, signInUpPageLoa assert.deepStrictEqual(pathname + search, "/redirect-here?foo=bar"); }); + it("Successful sign in with Auth0 with redirectToPath (w/o leading slash) keeping query params", async function () { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%3Ffoo%3Dbar`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await assertProviders(page); + await clickOnProviderButton(page, "Auth0"); + await Promise.all([ + loginWithAuth0(page), + page.waitForResponse((response) => response.url() === SIGN_IN_UP_API && response.status() === 200), + ]); + const { pathname, search } = await page.evaluate(() => window.location); + assert.deepStrictEqual(pathname + search, "/?foo=bar"); + }); + it("Successful signin with Auth0 and email verification", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), diff --git a/test/helpers.js b/test/helpers.js index b24ff29ed..f2c384c86 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -600,6 +600,24 @@ export async function defaultSignUp(page, rid = "emailpassword") { rid ); } + +export function getDefaultSignUpFieldValues({ + name = "John Doe", + email = "john.doe@supertokens.io", + password = "Str0ngP@ssw0rd", + age = "20", +} = {}) { + const fieldValues = [ + { name: "email", value: email }, + { name: "password", value: password }, + { name: "name", value: name }, + { name: "age", value: age }, + ]; + const postValues = `{"formFields":[{"id":"email","value":"${email}"},{"id":"password","value":"${password}"},{"id":"name","value":"${name}"},{"id":"age","value":"${age}"},{"id":"country","value":""}]}`; + + return { fieldValues, postValues }; +} + export async function signUp(page, fields, postValues = undefined, rid = "emailpassword") { // Set values. await setInputValues(page, fields); @@ -879,7 +897,7 @@ export async function setGeneralErrorToLocalStorage(recipeName, action, page) { }); } -export async function getTestEmail() { +export function getTestEmail() { return `john.doe+${Date.now()}@supertokens.io`; }