diff --git a/CHANGELOG.md b/CHANGELOG.md index aafc37eaa..924b38fd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,9 @@ EmailPassword.init({ ### Breaking changes -- The `CloseTabScreen` [component](https://supertokens.com/img/otp-magic-link-same-time.png), previously featured in the passwordless recipe visible when a user signs in from another tab using a magic link, has been removed. Instead, users will now be automatically redirected to the specified return value from the `getRedirectionURL` function if a session exists. In cases where `getRedirectionURL` is not provided, the default redirection path is set to `/`. If you were [overriding](https://supertokens.com/docs/passwordless/advanced-customizations/react-component-override/usage) this component using `PasswordlessCloseTabScreen_Override`, you no longer need to do so. +- `user` property has been removed from `GetRedirectionURLContext`.To get the `user` object from backend, you can follow hte instructions [here](https://supertokens.com/docs/thirdpartyemailpassword/common-customizations/get-user-info). + +- The `CloseTabScreen` [component](https://supertokens.com/img/otp-magic-link-same-time.png), previously featured in the passwordless recipe visible when a user signs in from another tab using a magic link, has been removed. Users will continue to see the 'Enter OTP' screen until they refresh. If you were [overriding](https://supertokens.com/docs/passwordless/advanced-customizations/react-component-override/usage) this component using `PasswordlessCloseTabScreen_Override`, you no longer need to do so. - If your workflow involves custom validation, such as Phone Number verification, after signing in, ensure your code aligns with the updated examples found in [with-phone-password](https://github.com/supertokens/supertokens-auth-react/tree/master/examples/with-phone-password) or [with-thirdpartyemailpassword-passwordless](https://github.com/supertokens/supertokens-auth-react/tree/master/examples/with-thirdpartyemailpassword-passwordless). diff --git a/examples/for-tests-react-16/src/App.js b/examples/for-tests-react-16/src/App.js index 7bd582fd3..1b43bdb15 100644 --- a/examples/for-tests-react-16/src/App.js +++ b/examples/for-tests-react-16/src/App.js @@ -892,6 +892,9 @@ function getThirdPartyConfigs({ staticProviderList, disableDefaultUI, thirdParty console.log(`ST_LOGS THIRD_PARTY GET_REDIRECTION_URL ${context.action}`); if (context.action === "SUCCESS") { setIsNewUserToStorage("thirdparty", context.isNewRecipeUser); + if (testContext.disableRedirectionAfterSuccessfulSignInUp) { + return null; + } return context.redirectToPath || "/dashboard"; } }, diff --git a/examples/for-tests/src/App.js b/examples/for-tests/src/App.js index c898eb1f4..ca9d036e9 100644 --- a/examples/for-tests/src/App.js +++ b/examples/for-tests/src/App.js @@ -1146,6 +1146,9 @@ function getThirdPartyConfigs({ staticProviderList, disableDefaultUI, thirdParty console.log(`ST_LOGS THIRD_PARTY GET_REDIRECTION_URL ${context.action}`); if (context.action === "SUCCESS") { setIsNewUserToStorage("thirdparty", context.isNewRecipeUser); + if (testContext.disableRedirectionAfterSuccessfulSignInUp) { + return null; + } return context.redirectToPath || "/dashboard"; } }, diff --git a/lib/build/SuperTokensBranding.js b/lib/build/SuperTokensBranding.js index fad0aaeab..c6876b7f2 100644 --- a/lib/build/SuperTokensBranding.js +++ b/lib/build/SuperTokensBranding.js @@ -46,7 +46,6 @@ var Redirector = function (props) { successRedirectContext: { action: "SUCCESS", isNewRecipeUser: false, - user: undefined, redirectToPath: genericComponentOverrideContext.getRedirectToPathFromURL(), }, }, diff --git a/lib/build/emailpassword-shared6.js b/lib/build/emailpassword-shared6.js index 86d336eb2..c5f9ecf55 100644 --- a/lib/build/emailpassword-shared6.js +++ b/lib/build/emailpassword-shared6.js @@ -1177,7 +1177,7 @@ function useChildProps(recipe$1, state, dispatch, history) { ); var userContext = uiEntry.useUserContext(); var onSignInSuccess = React.useCallback( - function (response) { + function () { return genericComponentOverrideContext.__awaiter(_this, void 0, void 0, function () { return genericComponentOverrideContext.__generator(this, function (_a) { return [ @@ -1188,7 +1188,6 @@ function useChildProps(recipe$1, state, dispatch, history) { successRedirectContext: { action: "SUCCESS", isNewRecipeUser: false, - user: response.user, redirectToPath: genericComponentOverrideContext.getRedirectToPathFromURL(), }, }, @@ -1202,7 +1201,7 @@ function useChildProps(recipe$1, state, dispatch, history) { [recipe$1, userContext, history] ); var onSignUpSuccess = React.useCallback( - function (response) { + function () { return genericComponentOverrideContext.__awaiter(_this, void 0, void 0, function () { return genericComponentOverrideContext.__generator(this, function (_a) { return [ @@ -1213,7 +1212,6 @@ function useChildProps(recipe$1, state, dispatch, history) { successRedirectContext: { action: "SUCCESS", isNewRecipeUser: true, - user: response.user, redirectToPath: genericComponentOverrideContext.getRedirectToPathFromURL(), }, }, diff --git a/lib/build/genericComponentOverrideContext.js b/lib/build/genericComponentOverrideContext.js index b46c122ea..073142403 100644 --- a/lib/build/genericComponentOverrideContext.js +++ b/lib/build/genericComponentOverrideContext.js @@ -515,15 +515,7 @@ function redirectWithFullPageReload(to) { if (to.trim() === "") { to = "/"; } - // In certain cases, we invoke `Session.validateGlobalClaimsAndHandleSuccessRedirection` within a setInterval (useRedirectAfterSuccess hook), potentially causing a loop of page reloads if the redirect URL remains unchanged. - // To mitigate this, we check whether the current URL matches the redirect URL before initiating redirection. - var currentURL = new URL( - windowHandler.WindowHandlerReference.getReferenceOrThrow().windowHandler.location.getHref() - ); - var destinationURL = new URL(to, currentURL); - if (destinationURL.href !== currentURL.href) { - windowHandler.WindowHandlerReference.getReferenceOrThrow().windowHandler.location.setHref(destinationURL.href); - } + windowHandler.WindowHandlerReference.getReferenceOrThrow().windowHandler.location.setHref(to); } function redirectWithHistory(to, history) { if (to.trim() === "") { diff --git a/lib/build/passwordless-shared3.js b/lib/build/passwordless-shared3.js index 59367eeb6..7fb622085 100644 --- a/lib/build/passwordless-shared3.js +++ b/lib/build/passwordless-shared3.js @@ -12,7 +12,6 @@ var recipe = require("./session-shared2.js"); var translations = require("./translations.js"); var translationContext = require("./translationContext.js"); var button = require("./emailpassword-shared2.js"); -var session = require("./session-shared3.js"); var recipe$1 = require("./passwordless-shared2.js"); var generalError = require("./emailpassword-shared.js"); var formBase = require("./emailpassword-shared7.js"); @@ -353,7 +352,6 @@ var LinkClickedScreen = function (props) { successRedirectContext: { action: "SUCCESS", isNewRecipeUser: response.createdNewRecipeUser, - user: response.user, redirectToPath: loginAttemptInfo === null || loginAttemptInfo === void 0 ? void 0 @@ -3828,68 +3826,6 @@ function getActiveScreen(props) { throw new Error("Couldn't choose active screen; Should never happen"); } -var useRedirectAfterSuccess = function (state, recipeId, userContext, history) { - var callingConsumeCodeRef = React.useRef(false); - React.useEffect( - function () { - // We only need to start checking this if we have an active login attempt - if (state.loginAttemptInfo) { - var checkSessionIntervalHandle_1 = setInterval(function () { - return genericComponentOverrideContext.__awaiter(void 0, void 0, void 0, function () { - var hasSession; - var _a, _b; - return genericComponentOverrideContext.__generator(this, function (_c) { - switch (_c.label) { - case 0: - if (!(callingConsumeCodeRef.current === false)) return [3 /*break*/, 2]; - return [ - 4 /*yield*/, - session.SessionAPIWrapper.doesSessionExist({ - userContext: userContext, - }), - ]; - case 1: - hasSession = _c.sent(); - if (hasSession) { - clearInterval(checkSessionIntervalHandle_1); - void recipe.Session.getInstanceOrThrow().validateGlobalClaimsAndHandleSuccessRedirection( - { - rid: recipeId, - successRedirectContext: { - action: "SUCCESS", - isNewRecipeUser: false, - user: undefined, - redirectToPath: - (_b = - (_a = state.loginAttemptInfo) === null || _a === void 0 - ? void 0 - : _a.redirectToPath) !== null && _b !== void 0 - ? _b - : genericComponentOverrideContext.getRedirectToPathFromURL(), - }, - }, - userContext, - history - ); - } - _c.label = 2; - case 2: - return [2 /*return*/]; - } - }); - }); - }, 2000); - return function () { - clearInterval(checkSessionIntervalHandle_1); - }; - } - // Nothing to clean up - return; - }, - [state.loginAttemptInfo] - ); - return callingConsumeCodeRef; -}; var useFeatureReducer = function (recipeImpl, userContext) { var _a = React__namespace.useReducer( function (oldState, action) { @@ -4006,13 +3942,10 @@ var useFeatureReducer = function (recipeImpl, userContext) { ); return [state, dispatch]; }; -function useChildProps(recipe$1, dispatch, state, callingConsumeCodeRef, userContext, history) { +function useChildProps(recipe$1, dispatch, state, userContext, history) { var recipeImplementation = React__namespace.useMemo( function () { - return ( - recipe$1 && - getModifiedRecipeImplementation(recipe$1.webJSRecipe, recipe$1.config, dispatch, callingConsumeCodeRef) - ); + return recipe$1 && getModifiedRecipeImplementation(recipe$1.webJSRecipe, recipe$1.config, dispatch); }, [recipe$1] ); @@ -4029,7 +3962,6 @@ function useChildProps(recipe$1, dispatch, state, callingConsumeCodeRef, userCon successRedirectContext: { action: "SUCCESS", isNewRecipeUser: result.createdNewRecipeUser, - user: result.user, redirectToPath: genericComponentOverrideContext.getRedirectToPathFromURL(), }, }, @@ -4050,13 +3982,7 @@ var SignInUpFeature = function (props) { var _a = useFeatureReducer(props.recipe.webJSRecipe, userContext), state = _a[0], dispatch = _a[1]; - var callingConsumeCodeRef = useRedirectAfterSuccess( - state, - props.recipe.config.recipeId, - userContext, - props.history - ); - var childProps = useChildProps(props.recipe, dispatch, state, callingConsumeCodeRef, userContext, props.history); + var childProps = useChildProps(props.recipe, dispatch, state, userContext, props.history); return jsxRuntime.jsx( uiEntry.ComponentOverrideContext.Provider, genericComponentOverrideContext.__assign( @@ -4102,7 +4028,7 @@ var SignInUpFeature = function (props) { ) ); }; -function getModifiedRecipeImplementation(originalImpl, config, dispatch, callingConsumeCodeRef) { +function getModifiedRecipeImplementation(originalImpl, config, dispatch) { var _this = this; return genericComponentOverrideContext.__assign(genericComponentOverrideContext.__assign({}, originalImpl), { createCode: function (input) { @@ -4223,9 +4149,6 @@ function getModifiedRecipeImplementation(originalImpl, config, dispatch, calling return genericComponentOverrideContext.__generator(this, function (_a) { switch (_a.label) { case 0: - // We need to call consume code while callingConsume, so we don't detect - // the session creation and redirect too early - callingConsumeCodeRef.current = true; return [4 /*yield*/, originalImpl.consumeCode(input)]; case 1: res = _a.sent(); @@ -4264,7 +4187,6 @@ function getModifiedRecipeImplementation(originalImpl, config, dispatch, calling _a.sent(); _a.label = 7; case 7: - callingConsumeCodeRef.current = false; return [2 /*return*/, res]; } }); @@ -4468,4 +4390,3 @@ exports.defaultTranslationsPasswordless = defaultTranslationsPasswordless; exports.getActiveScreen = getActiveScreen; exports.useChildProps = useChildProps; exports.useFeatureReducer = useFeatureReducer; -exports.useRedirectAfterSuccess = useRedirectAfterSuccess; diff --git a/lib/build/recipe/authRecipe/types.d.ts b/lib/build/recipe/authRecipe/types.d.ts index 9e09ed432..37b707605 100644 --- a/lib/build/recipe/authRecipe/types.d.ts +++ b/lib/build/recipe/authRecipe/types.d.ts @@ -3,23 +3,14 @@ import type { NormalisedConfig as NormalisedRecipeModuleConfig, UserInput as UserInputRecipeModule, } from "../recipeModule/types"; -import type { User } from "supertokens-web-js/types"; export declare type UserInput = UserInputRecipeModule; export declare type Config = UserInput & RecipeModuleConfig; export declare type NormalisedConfig = NormalisedRecipeModuleConfig; -export declare type GetRedirectionURLContext = - | { - action: "SUCCESS"; - isNewRecipeUser: true; - user: User; - redirectToPath?: string; - } - | { - action: "SUCCESS"; - isNewRecipeUser: false; - user?: User; - redirectToPath?: string; - }; +export declare type GetRedirectionURLContext = { + action: "SUCCESS"; + isNewRecipeUser: boolean; + redirectToPath?: string; +}; export declare type OnHandleEventContext = { action: "SESSION_ALREADY_EXISTS"; }; diff --git a/lib/build/recipe/passwordless/components/features/signInAndUp/index.d.ts b/lib/build/recipe/passwordless/components/features/signInAndUp/index.d.ts index 621a064e0..f88514a9e 100644 --- a/lib/build/recipe/passwordless/components/features/signInAndUp/index.d.ts +++ b/lib/build/recipe/passwordless/components/features/signInAndUp/index.d.ts @@ -4,12 +4,6 @@ import type Recipe from "../../../recipe"; import type { ComponentOverrideMap } from "../../../types"; import type { PasswordlessSignInUpAction, SignInUpState, SignInUpChildProps } from "../../../types"; import type { RecipeInterface } from "supertokens-web-js/recipe/passwordless"; -export declare const useRedirectAfterSuccess: ( - state: SignInUpState, - recipeId: string, - userContext: any, - history: any -) => React.MutableRefObject; export declare const useFeatureReducer: ( recipeImpl: RecipeInterface | undefined, userContext: any @@ -18,7 +12,6 @@ export declare function useChildProps( recipe: Recipe, dispatch: React.Dispatch, state: SignInUpState, - callingConsumeCodeRef: React.MutableRefObject, userContext: any, history: any ): SignInUpChildProps; @@ -26,7 +19,6 @@ export declare function useChildProps( recipe: Recipe | undefined, dispatch: React.Dispatch, state: SignInUpState, - callingConsumeCodeRef: React.MutableRefObject, userContext: any, history: any ): SignInUpChildProps | undefined; diff --git a/lib/build/recipe/session/recipe.d.ts b/lib/build/recipe/session/recipe.d.ts index 7bdf0b150..e74a6af66 100644 --- a/lib/build/recipe/session/recipe.d.ts +++ b/lib/build/recipe/session/recipe.d.ts @@ -49,7 +49,11 @@ export default class Session extends RecipeModule Promise | string | undefined | null) + | (({ userContext, reason }: { userContext: any; reason: any }) => Promise | string | undefined) | undefined; export declare type SessionClaimValidator = SessionClaimValidatorWebJS & { showAccessDeniedOnFailure?: boolean; diff --git a/lib/ts/recipe/authRecipe/authWidgetWrapper.tsx b/lib/ts/recipe/authRecipe/authWidgetWrapper.tsx index 052000fe9..b826210e3 100644 --- a/lib/ts/recipe/authRecipe/authWidgetWrapper.tsx +++ b/lib/ts/recipe/authRecipe/authWidgetWrapper.tsx @@ -78,7 +78,6 @@ const Redirector = = UserInputRecipeModule; @@ -26,19 +25,11 @@ export type Config = UserInput & RecipeModuleConfig; export type NormalisedConfig = NormalisedRecipeModuleConfig; -export type GetRedirectionURLContext = - | { - action: "SUCCESS"; - isNewRecipeUser: true; - user: User; - redirectToPath?: string; - } - | { - action: "SUCCESS"; - isNewRecipeUser: false; - user?: User; - redirectToPath?: string; - }; +export type GetRedirectionURLContext = { + action: "SUCCESS"; + isNewRecipeUser: boolean; + redirectToPath?: string; +}; export type OnHandleEventContext = { action: "SESSION_ALREADY_EXISTS"; diff --git a/lib/ts/recipe/emailpassword/components/features/signInAndUp/index.tsx b/lib/ts/recipe/emailpassword/components/features/signInAndUp/index.tsx index 86def01ab..cd21f2200 100644 --- a/lib/ts/recipe/emailpassword/components/features/signInAndUp/index.tsx +++ b/lib/ts/recipe/emailpassword/components/features/signInAndUp/index.tsx @@ -41,7 +41,6 @@ import type { } from "../../../types"; import type { Dispatch } from "react"; import type { RecipeInterface } from "supertokens-web-js/recipe/emailpassword"; -import type { User } from "supertokens-web-js/types"; export const useFeatureReducer = (recipe: Recipe | undefined) => { return React.useReducer( @@ -112,43 +111,35 @@ export function useChildProps( const recipeImplementation = useMemo(() => recipe && getModifiedRecipeImplementation(recipe.webJSRecipe), [recipe]); const userContext = useUserContext(); - const onSignInSuccess = useCallback( - async (response: { user: User }): Promise => { - return Session.getInstanceOrThrow().validateGlobalClaimsAndHandleSuccessRedirection( - { - rid: recipe!.config.recipeId, - successRedirectContext: { - action: "SUCCESS", - isNewRecipeUser: false, - user: response.user, - redirectToPath: getRedirectToPathFromURL(), - }, + const onSignInSuccess = useCallback(async (): Promise => { + return Session.getInstanceOrThrow().validateGlobalClaimsAndHandleSuccessRedirection( + { + rid: recipe!.config.recipeId, + successRedirectContext: { + action: "SUCCESS", + isNewRecipeUser: false, + redirectToPath: getRedirectToPathFromURL(), }, - userContext, - history - ); - }, - [recipe, userContext, history] - ); + }, + userContext, + history + ); + }, [recipe, userContext, history]); - const onSignUpSuccess = useCallback( - async (response: { user: User }): Promise => { - return Session.getInstanceOrThrow().validateGlobalClaimsAndHandleSuccessRedirection( - { - rid: recipe!.config.recipeId, - successRedirectContext: { - action: "SUCCESS", - isNewRecipeUser: true, - user: response.user, - redirectToPath: getRedirectToPathFromURL(), - }, + const onSignUpSuccess = useCallback(async (): Promise => { + return Session.getInstanceOrThrow().validateGlobalClaimsAndHandleSuccessRedirection( + { + rid: recipe!.config.recipeId, + successRedirectContext: { + action: "SUCCESS", + isNewRecipeUser: true, + redirectToPath: getRedirectToPathFromURL(), }, - userContext, - history - ); - }, - [recipe, userContext, history] - ); + }, + userContext, + history + ); + }, [recipe, userContext, history]); return useMemo(() => { if (recipe === undefined || recipeImplementation === undefined) { diff --git a/lib/ts/recipe/passwordless/components/features/linkClickedScreen/index.tsx b/lib/ts/recipe/passwordless/components/features/linkClickedScreen/index.tsx index 99181dee9..3bce043f4 100644 --- a/lib/ts/recipe/passwordless/components/features/linkClickedScreen/index.tsx +++ b/lib/ts/recipe/passwordless/components/features/linkClickedScreen/index.tsx @@ -110,7 +110,6 @@ const LinkClickedScreen: React.FC = (props) => { successRedirectContext: { action: "SUCCESS", isNewRecipeUser: response.createdNewRecipeUser, - user: response.user, redirectToPath: loginAttemptInfo?.redirectToPath, }, }, diff --git a/lib/ts/recipe/passwordless/components/features/signInAndUp/index.tsx b/lib/ts/recipe/passwordless/components/features/signInAndUp/index.tsx index 1968133d2..fc7ab3606 100644 --- a/lib/ts/recipe/passwordless/components/features/signInAndUp/index.tsx +++ b/lib/ts/recipe/passwordless/components/features/signInAndUp/index.tsx @@ -18,14 +18,12 @@ import * as React from "react"; import { Fragment } from "react"; import { useMemo } from "react"; -import { useRef } from "react"; import { useEffect } from "react"; import { ComponentOverrideContext } from "../../../../../components/componentOverride/componentOverrideContext"; import FeatureWrapper from "../../../../../components/featureWrapper"; import { useUserContext } from "../../../../../usercontext"; import { clearErrorQueryParam, getQueryParams, getRedirectToPathFromURL } from "../../../../../utils"; -import Session from "../../../../session"; import SessionRecipe from "../../../../session/recipe"; import { getPhoneNumberUtils } from "../../../phoneNumberUtils"; import SignInUpThemeWrapper from "../../themes/signInUp"; @@ -38,48 +36,6 @@ import type { PasswordlessSignInUpAction, SignInUpState, SignInUpChildProps, Nor import type { RecipeInterface } from "supertokens-web-js/recipe/passwordless"; import type { User } from "supertokens-web-js/types"; -export const useRedirectAfterSuccess = (state: SignInUpState, recipeId: string, userContext: any, history: any) => { - const callingConsumeCodeRef = useRef(false); - - useEffect(() => { - // We only need to start checking this if we have an active login attempt - if (state.loginAttemptInfo) { - const checkSessionIntervalHandle = setInterval(async () => { - if (callingConsumeCodeRef.current === false) { - const hasSession = await Session.doesSessionExist({ - userContext, - }); - if (hasSession) { - clearInterval(checkSessionIntervalHandle); - void SessionRecipe.getInstanceOrThrow().validateGlobalClaimsAndHandleSuccessRedirection( - { - rid: recipeId, - successRedirectContext: { - action: "SUCCESS", - isNewRecipeUser: false, - user: undefined, - redirectToPath: - state.loginAttemptInfo?.redirectToPath ?? getRedirectToPathFromURL(), - }, - }, - userContext, - history - ); - } - } - }, 2000); - - return () => { - clearInterval(checkSessionIntervalHandle); - }; - } - // Nothing to clean up - return; - }, [state.loginAttemptInfo]); - - return callingConsumeCodeRef; -}; - export const useFeatureReducer = ( recipeImpl: RecipeInterface | undefined, userContext: any @@ -185,7 +141,6 @@ export function useChildProps( recipe: Recipe, dispatch: React.Dispatch, state: SignInUpState, - callingConsumeCodeRef: React.MutableRefObject, userContext: any, history: any ): SignInUpChildProps; @@ -193,7 +148,6 @@ export function useChildProps( recipe: Recipe | undefined, dispatch: React.Dispatch, state: SignInUpState, - callingConsumeCodeRef: React.MutableRefObject, userContext: any, history: any ): SignInUpChildProps | undefined; @@ -202,14 +156,11 @@ export function useChildProps( recipe: Recipe | undefined, dispatch: React.Dispatch, state: SignInUpState, - callingConsumeCodeRef: React.MutableRefObject, userContext: any, history: any ): SignInUpChildProps | undefined { const recipeImplementation = React.useMemo( - () => - recipe && - getModifiedRecipeImplementation(recipe.webJSRecipe, recipe.config, dispatch, callingConsumeCodeRef), + () => recipe && getModifiedRecipeImplementation(recipe.webJSRecipe, recipe.config, dispatch), [recipe] ); @@ -225,7 +176,6 @@ export function useChildProps( successRedirectContext: { action: "SUCCESS", isNewRecipeUser: result.createdNewRecipeUser, - user: result.user, redirectToPath: getRedirectToPathFromURL(), }, }, @@ -248,13 +198,7 @@ export const SignInUpFeature: React.FC< const recipeComponentOverrides = props.useComponentOverrides(); const userContext = useUserContext(); const [state, dispatch] = useFeatureReducer(props.recipe.webJSRecipe, userContext); - const callingConsumeCodeRef = useRedirectAfterSuccess( - state, - props.recipe.config.recipeId, - userContext, - props.history - ); - const childProps = useChildProps(props.recipe, dispatch, state, callingConsumeCodeRef, userContext, props.history)!; + const childProps = useChildProps(props.recipe, dispatch, state, userContext, props.history)!; return ( @@ -290,8 +234,7 @@ export default SignInUpFeature; function getModifiedRecipeImplementation( originalImpl: RecipeInterface, config: NormalisedConfig, - dispatch: React.Dispatch, - callingConsumeCodeRef: React.MutableRefObject + dispatch: React.Dispatch ): RecipeInterface { return { ...originalImpl, @@ -365,10 +308,6 @@ function getModifiedRecipeImplementation( }, consumeCode: async (input) => { - // We need to call consume code while callingConsume, so we don't detect - // the session creation and redirect too early - callingConsumeCodeRef.current = true; - const res = await originalImpl.consumeCode(input); if (res.status === "RESTART_FLOW_ERROR") { @@ -389,8 +328,6 @@ function getModifiedRecipeImplementation( }); } - callingConsumeCodeRef.current = false; - return res; }, diff --git a/lib/ts/recipe/session/recipe.tsx b/lib/ts/recipe/session/recipe.tsx index 35ef2b1ae..5f195837f 100644 --- a/lib/ts/recipe/session/recipe.tsx +++ b/lib/ts/recipe/session/recipe.tsx @@ -113,7 +113,11 @@ export default class Session extends RecipeModule = (props) => { successRedirectContext: { action: "SUCCESS", isNewRecipeUser: response.createdNewRecipeUser, - user: response.user, redirectToPath, }, }, diff --git a/lib/ts/recipe/thirdpartypasswordless/components/features/signInAndUp/index.tsx b/lib/ts/recipe/thirdpartypasswordless/components/features/signInAndUp/index.tsx index 2f511ffc7..75676cdc7 100644 --- a/lib/ts/recipe/thirdpartypasswordless/components/features/signInAndUp/index.tsx +++ b/lib/ts/recipe/thirdpartypasswordless/components/features/signInAndUp/index.tsx @@ -24,7 +24,6 @@ import { useUserContext } from "../../../../../usercontext"; import { useChildProps as usePasswordlessChildProps, useFeatureReducer as usePasswordlessFeatureReducer, - useRedirectAfterSuccess, } from "../../../../passwordless/components/features/signInAndUp"; import { useChildProps as useThirdPartyChildProps, @@ -120,18 +119,10 @@ const SignInAndUp: React.FC = (props) => { [pwlessDispatch, dispatch] ); - const callingConsumeCodeRef = useRedirectAfterSuccess( - pwlessState, - props.recipe.recipeID, - userContext, - props.history - ); - const pwlessChildProps = usePasswordlessChildProps( props.recipe.passwordlessRecipe, combinedPwlessDispatch, pwlessState, - callingConsumeCodeRef, userContext, props.history )!; diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 1d3419984..e25002fcb 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -30,13 +30,7 @@ export type GetRedirectionURLContext = { }; export type ValidationFailureCallback = - | (({ - userContext, - reason, - }: { - userContext: any; - reason: any; - }) => Promise | string | undefined | null) + | (({ userContext, reason }: { userContext: any; reason: any }) => Promise | string | undefined) | undefined; export type SessionClaimValidator = SessionClaimValidatorWebJS & { diff --git a/lib/ts/utils.ts b/lib/ts/utils.ts index bd7eaf4f4..3e42e0766 100644 --- a/lib/ts/utils.ts +++ b/lib/ts/utils.ts @@ -223,15 +223,7 @@ export function redirectWithFullPageReload(to: string): void { if (to.trim() === "") { to = "/"; } - - // In certain cases, we invoke `Session.validateGlobalClaimsAndHandleSuccessRedirection` within a setInterval (useRedirectAfterSuccess hook), potentially causing a loop of page reloads if the redirect URL remains unchanged. - // To mitigate this, we check whether the current URL matches the redirect URL before initiating redirection. - const currentURL = new URL(WindowHandlerReference.getReferenceOrThrow().windowHandler.location.getHref()); - const destinationURL = new URL(to, currentURL); - - if (destinationURL.href !== currentURL.href) { - WindowHandlerReference.getReferenceOrThrow().windowHandler.location.setHref(destinationURL.href); - } + WindowHandlerReference.getReferenceOrThrow().windowHandler.location.setHref(to); } export function redirectWithHistory(to: string, history: any): void { diff --git a/test/end-to-end/getRedirectionURL.test.js b/test/end-to-end/getRedirectionURL.test.js index 2b6aec9d1..c519f9d6e 100644 --- a/test/end-to-end/getRedirectionURL.test.js +++ b/test/end-to-end/getRedirectionURL.test.js @@ -413,8 +413,10 @@ describe("getRedirectionURL Tests", function () { await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { method: "POST", }).catch(console.error); + }); - await screenshotOnFailure(this, browser); + afterEach(function () { + return screenshotOnFailure(this, browser); }); it("should not do any redirection after successful sign up", async function () { @@ -494,7 +496,9 @@ describe("getRedirectionURL Tests", function () { await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { method: "POST", }).catch(console.error); + }); + afterEach(function () { return screenshotOnFailure(this, browser); }); @@ -525,7 +529,7 @@ describe("getRedirectionURL Tests", function () { }); }); - describe("ThirdPartyPasswordless recipe", function () { + describe("ThirdPartyPasswordless recipe: Magic Link", function () { let browser; let page; const exampleEmail = "test@example.com"; @@ -569,7 +573,7 @@ describe("getRedirectionURL Tests", function () { ), page.waitForNavigation({ waitUntil: "networkidle0" }), ]); - await setPasswordlessFlowType("EMAIL", "USER_INPUT_CODE"); + await setPasswordlessFlowType("EMAIL", "MAGIC_LINK"); }); after(async function () { @@ -584,7 +588,9 @@ describe("getRedirectionURL Tests", function () { await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { method: "POST", }).catch(console.error); + }); + afterEach(function () { return screenshotOnFailure(this, browser); }); @@ -595,23 +601,86 @@ describe("getRedirectionURL Tests", function () { ]); await setInputValues(page, [{ name: "email", value: exampleEmail }]); await submitForm(page); - await waitForSTElement(page, "[data-supertokens~=input][name=userInputCode]"); - - const urlBeforeSignUp = await page.url(); + await waitForSTElement(page, "[data-supertokens~=sendCodeIcon]"); 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); - // wait until network idle to ensure that the page has not been redirected + + const magicLink = device.codes[0].urlWithLinkCode; + + await page.goto(magicLink); + await page.waitForNetworkIdle(); const urlAfterSignUp = await page.url(); const newUserCheck = await page.evaluate(() => localStorage.getItem("isNewUserCheck")); assert.equal(newUserCheck, "thirdpartypasswordless-true"); - assert.equal(urlBeforeSignUp, urlAfterSignUp); + assert.equal(magicLink, urlAfterSignUp); + }); + }); + + describe("ThirdParty Recipe", function () { + let browser; + let page; + + before(async function () { + await backendBeforeEach(); + + await fetch(`${TEST_SERVER_BASE_URL}/startst`, { + method: "POST", + }).catch(console.error); + + browser = await puppeteer.launch({ + args: ["--no-sandbox", "--disable-setuid-sandbox"], + headless: true, + }); + + page = await browser.newPage(); + // We need to set the localStorage value before the page loads to ensure ST initialises with the correct value + await page.evaluateOnNewDocument(() => { + localStorage.setItem("disableRedirectionAfterSuccessfulSignInUp", "true"); + localStorage.removeItem("isNewUserCheck"); + }); + + await clearBrowserCookiesWithoutAffectingConsole(page, []); + }); + + after(async function () { + await browser.close(); + await fetch(`${TEST_SERVER_BASE_URL}/after`, { + method: "POST", + }).catch(console.error); + await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { + method: "POST", + }).catch(console.error); + }); + + afterEach(function () { + return screenshotOnFailure(this, browser); + }); + + it("should not do any redirection after successful sign up", async function () { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdparty`), + 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 urlAfterSignUp = await page.url(); + const newUserCheck = await page.evaluate(() => localStorage.getItem("isNewUserCheck")); + assert.equal(newUserCheck, "thirdparty-true"); + assert(urlAfterSignUp.includes("/auth/callback/auth0")); }); }); }); diff --git a/test/with-typescript/src/App.tsx b/test/with-typescript/src/App.tsx index ae91b138e..b0bde007c 100644 --- a/test/with-typescript/src/App.tsx +++ b/test/with-typescript/src/App.tsx @@ -459,7 +459,7 @@ function getEmailPasswordConfigs() { async getRedirectionURL(context: EmailPasswordGetRedirectionURLContext) { if (context.action === "SUCCESS") { - if (context.isNewRecipeUser && context.user.loginMethods.length === 1) { + if (context.isNewRecipeUser) { // new primary user } else { // only a recipe user was created @@ -1499,5 +1499,6 @@ export const PhoneVerifiedClaim = new BooleanClaim({ refresh: async () => { // This is something we have no way of refreshing, so this is a no-op }, + // @ts-expect-error - Returning null from onFailureRedirection is not supported onFailureRedirection: () => null, });