diff --git a/.storybook/main.ts b/.storybook/main.ts index eeae939f3..e894225d5 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -8,8 +8,8 @@ const config: StorybookConfig = { "@storybook/addon-actions", "@storybook/addon-links", "@storybook/addon-interactions", - "@storybook/addon-designs", // "@storybook/addon-actions/register", + "@storybook/addon-designs", ], async webpackFinal(config, { configType }) { if (config.module?.rules) { diff --git a/CHANGELOG.md b/CHANGELOG.md index fa44ca008..28b9b7ff1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,146 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## [0.42.0] - 2024-05-24 + +### Breaking changes + +- Removed `ThirdPartyEmailPassword` and `ThirdPartyPasswordless` recipes. +- `EmailPassword` recipe: + + - Removed embeddable components: `SignInUp`, `SignInAndUpTheme` + - Removed overridable components: `EmailPasswordSignIn`, `EmailPasswordSignInFooter`, `EmailPasswordSignInHeader`, `EmailPasswordSignUp`, `EmailPasswordSignUpFooter`, `EmailPasswordSignUpHeader` + - Moved `useShadowDom`, `defaultToSignUp`, `privacyPolicyLink`, `termsOfServiceLink` from the config to the root configuration passed to `SuperTokens.init`. + +- `Passwordless` recipe: + + - Removed embeddable components: `SignInUp` + - Removed overrideable components: `PasswordlessSignInUpHeader_Override`, `PasswordlessSignInUpFooter_Override` + - Removed the `guessInternationPhoneNumberFromInputPhoneNumber` configurable callback, since now the user sets if they are entering email or a phone number explicitly + - Moved `useShadowDom`, `privacyPolicyLink`, `termsOfServiceLink` from the config to the root configuration passed to `SuperTokens.init`. + - Changed the UX of the contactinfo entry form for `EMAIL_OR_PHONE` + - Added `defaultToEmail` configuration option, which decides if the contact info input form starts in the email or phone state if the `contactMethod` is set to `EMAIL_OR_PHONE` + +- `ThirdParty` recipe: + + - Removed embeddable components: `SignInAndUp` + - Removed overridable components: `ThirdPartySignInAndUpHeader`, `ThirdPartySignUpFooter` + - Moved `useShadowDom`, `privacyPolicyLink`, `termsOfServiceLink` from the config to the root configuration passed to `SuperTokens.init`. + +- Removed `SESSION_ALREADY_EXISTS` event from auth recipes and moved it into the `Session` recipe +- Added new keys to `data-supertokens` props of several elements +- Updated some styles to work with the updated UI structure +- Renamed translation strings to reflect the new component/UI structure: + - `EMAIL_PASSWORD_SIGN_IN_FORGOT_PW_LINK` instead of `EMAIL_PASSWORD_SIGN_IN_FOOTER_FORGOT_PW_LINK` + - `AUTH_PAGE_HEADER_TITLE_SIGN_IN_AND_UP` instead of `PWLESS_SIGN_IN_UP_HEADER_TITLE` and `THIRD_PARTY_SIGN_IN_AND_UP_HEADER_TITLE` + - `AUTH_PAGE_HEADER_TITLE_SIGN_IN` instead of `EMAIL_PASSWORD_SIGN_IN_HEADER_TITLE` + - `AUTH_PAGE_HEADER_SUBTITLE_SIGN_UP_START` instead of `EMAIL_PASSWORD_SIGN_IN_HEADER_SUBTITLE_START` + - `AUTH_PAGE_HEADER_SUBTITLE_SIGN_UP_SIGN_IN_LINK` instead of `EMAIL_PASSWORD_SIGN_IN_HEADER_SUBTITLE_SIGN_UP_LINK` + - `AUTH_PAGE_HEADER_SUBTITLE_SIGN_UP_END` instead of `EMAIL_PASSWORD_SIGN_IN_HEADER_SUBTITLE_END` + - `AUTH_PAGE_HEADER_TITLE_SIGN_UP` instead of `EMAIL_PASSWORD_SIGN_UP_HEADER_TITLE` + - `AUTH_PAGE_HEADER_SUBTITLE_SIGN_IN_START` instead of `EMAIL_PASSWORD_SIGN_UP_HEADER_SUBTITLE_START` + - `AUTH_PAGE_HEADER_SUBTITLE_SIGN_IN_SIGN_UP_LINK` instead of `EMAIL_PASSWORD_SIGN_UP_HEADER_SUBTITLE_SIGN_IN_LINK` + - `AUTH_PAGE_HEADER_SUBTITLE_SIGN_IN_END` instead of `EMAIL_PASSWORD_SIGN_UP_HEADER_SUBTITLE_END` + - `AUTH_PAGE_FOOTER_START` instead of `EMAIL_PASSWORD_SIGN_UP_FOOTER_START`, `PWLESS_SIGN_IN_UP_FOOTER_START` and `THIRD_PARTY_SIGN_IN_UP_FOOTER_START` + - `AUTH_PAGE_FOOTER_TOS` instead of: `EMAIL_PASSWORD_SIGN_UP_FOOTER_TOS`, `PWLESS_SIGN_IN_UP_FOOTER_TOS` and `THIRD_PARTY_SIGN_IN_UP_FOOTER_TOS` + - `AUTH_PAGE_FOOTER_AND` instead of `EMAIL_PASSWORD_SIGN_UP_FOOTER_AND`, `PWLESS_SIGN_IN_UP_FOOTER_AND` and `THIRD_PARTY_SIGN_IN_UP_FOOTER_AND` + - `AUTH_PAGE_FOOTER_PP` instead of `EMAIL_PASSWORD_SIGN_UP_FOOTER_PP`, `PWLESS_SIGN_IN_UP_FOOTER_PP` and `THIRD_PARTY_SIGN_IN_UP_FOOTER_PP` + - `AUTH_PAGE_FOOTER_END` instead of `EMAIL_PASSWORD_SIGN_UP_FOOTER_END`, `PWLESS_SIGN_IN_UP_FOOTER_END` and `THIRD_PARTY_SIGN_IN_UP_FOOTER_END` + +### Changes + +- Added overrideable components: + - General: `AuthPageComponentList`, `AuthPageHeader`, `AuthPageFooter`. + - These can be overridden by using `AuthRecipeComponentsOverrideContextProvider` + - `Passwordless`: `PasswordlessContinueWithPasswordless_Override` +- Added new embeddable components: + - General: `AuthPage` and `AuthPageTheme` + - `EmailPassword`: `SignInTheme`, `SignUpTheme` +- Added a new `style` prop to the root level config + +### Migration guide + +#### Removed recipes + +- If you were using `ThirdPartyEmailPassword`, you should now init `ThirdParty` and `EmailPassword` recipes separately. The config for the individual recipes are mostly the same, except the syntax may be different. Check our recipe guides for [ThirdParty](https://supertokens.com/docs/thirdparty/introduction) and [EmailPassword](https://supertokens.com/docs/emailpassword/introduction) for more information. + +- If you were using `ThirdPartyPasswordless`, you should now init `ThirdParty` and `Passwordless` recipes separately. The config for the individual recipes are mostly the same, except the syntax may be different. Check our recipe guides for [ThirdParty](https://supertokens.com/docs/thirdparty/introduction) and [Passwordless](https://supertokens.com/docs/passwordless/introduction) for more information. + +#### Moved configuration options + +Several configuration options (`useShadowDom`, `defaultToSignUp`, `privacyPolicyLink`, `termsOfServiceLink`) were moved to the root configuration (the one passed directly to `SuperTokens.init`) out of recipe specific configs. The function of these props remain identical, the only necessary migration is moving them: + +Before: + +```tsx +SuperTokens.init({ + appInfo: { + // appInfo + }, + recipeList: [ + EmailPassword.init({ + useShadowDom: false, + signInAndUpFeature: { + defaultToSignUp: true, + signUpForm: { + privacyPolicyLink: "http://example.com", + termsOfServiceLink: "http://example.com", + }, + }, + }), + Session.init(), + ], +}); +``` + +After: + +```tsx +SuperTokens.init({ + appInfo: { + // appInfo + }, + useShadowDom: false, + defaultToSignUp: true, + privacyPolicyLink: "http://example.com", + termsOfServiceLink: "http://example.com", + recipeList: [EmailPassword.init(), Session.init()], +}); +``` + +#### Removed embeddable components + +The auth page related embeddable components (`SignInAndUp`, `SignInUp`) and the related theme components were removed. We instead recommend you to use the new `AuthPage` component with the appropriate pre-built UI objects and factorsIds set. For more information check the following [guide](https://supertokens.com/docs/thirdpartyemailpassword/common-customizations/embed-sign-in-up-form) + +Before: + +```tsx +import { SignInAndUp } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; + +const Component = () => { + const navigate = useNavigate(); + + return ; +}; +``` + +After: + +```tsx +import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; +import { AuthPage } from "supertokens-auth-react/ui"; + +const Component = () => { + const navigate = useNavigate(); + + return ; +}; +``` + +#### Updated styles + +Some parts of our default styles have been changed, including some changes to the `data-supertokens` props added to elements. Since customizations can take many forms we cannot give you exact guidance on how/what needs to change. In almost all cases, no updates should be required, but please check that your custom styles still work as you expect. + ## [0.41.1] - 2024-05-13 ### Fixes diff --git a/examples/for-tests-react-16/src/App.js b/examples/for-tests-react-16/src/App.js index 321720262..41c323070 100644 --- a/examples/for-tests-react-16/src/App.js +++ b/examples/for-tests-react-16/src/App.js @@ -11,8 +11,6 @@ import EmailVerification from "supertokens-auth-react/recipe/emailverification"; import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; import Passwordless from "supertokens-auth-react/recipe/passwordless"; import ThirdParty from "supertokens-auth-react/recipe/thirdparty"; -import ThirdPartyEmailPassword from "supertokens-auth-react/recipe/thirdpartyemailpassword"; -import ThirdPartyPasswordless from "supertokens-auth-react/recipe/thirdpartypasswordless"; import UserRoles from "supertokens-auth-react/recipe/userroles"; import Multitenancy from "supertokens-auth-react/recipe/multitenancy"; import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth"; @@ -92,12 +90,6 @@ if (getQueryParams("passwordlessDefaultCountry")) { const passwordlessDefaultCountry = window.localStorage.getItem("passwordlessDefaultCountry") || undefined; -if (getQueryParams("passwordlessDisablePhoneGuess")) { - window.localStorage.setItem("passwordlessDisablePhoneGuess", getQueryParams("passwordlessDisablePhoneGuess")); -} - -const passwordlessDisablePhoneGuess = window.localStorage.getItem("passwordlessDisablePhoneGuess") || undefined; - if (getQueryParams("authRecipe")) { window.localStorage.setItem("authRecipe", getQueryParams("authRecipe")); } @@ -255,22 +247,19 @@ let recipeList = [ let enabledRecipes = getEnabledRecipes(); -if (enabledRecipes.includes("thirdparty")) { +if ( + enabledRecipes.includes("thirdparty") || + enabledRecipes.includes("thirdpartyemailpassword") || + enabledRecipes.includes("thirdpartypasswordless") +) { recipeList = [getThirdPartyConfigs(testContext), ...recipeList]; } -if (enabledRecipes.includes("emailpassword")) { +if (enabledRecipes.includes("emailpassword") || enabledRecipes.includes("thirdpartyemailpassword")) { recipeList = [getEmailPasswordConfigs(testContext), ...recipeList]; } -if (enabledRecipes.includes("thirdpartyemailpassword")) { - recipeList = [getThirdPartyEmailPasswordConfigs(testContext), ...recipeList]; -} -if (enabledRecipes.includes("passwordless")) { +if (enabledRecipes.includes("passwordless") || enabledRecipes.includes("thirdpartypasswordless")) { recipeList = [getPasswordlessConfigs(testContext), ...recipeList]; } -if (enabledRecipes.includes("thirdpartypasswordless")) { - recipeList = [getThirdPartyPasswordlessConfigs(testContext), ...recipeList]; -} - if (emailVerificationMode !== "OFF") { recipeList.push(getEmailVerificationConfigs(testContext)); } @@ -295,10 +284,10 @@ SuperTokens.init({ languageTranslations: { translations: { en: { - PWLESS_SIGN_IN_UP_FOOTER_TOS: "TOS", + AUTH_PAGE_FOOTER_TOS: "TOS", }, hu: { - PWLESS_SIGN_IN_UP_FOOTER_TOS: "ÁSZF", + AUTH_PAGE_FOOTER_TOS: "ÁSZF", }, }, }, @@ -308,8 +297,6 @@ SuperTokens.init({ emailpassword: "EMAIL_PASSWORD", thirdparty: "THIRD_PARTY", passwordless: "PASSWORDLESS", - thirdpartypasswordless: "THIRDPARTYPASSWORDLESS", - thirdpartyemailpassword: "THIRD_PARTY_EMAIL_PASSWORD", }[context.recipeId]; console.log(`ST_LOGS SUPERTOKENS GET_REDIRECTION_URL SUCCESS ${logId}`); @@ -322,14 +309,19 @@ SuperTokens.init({ console.log(`ST_LOGS SUPERTOKENS GET_REDIRECTION_URL ${context.action}`); } }, + useShadowDom, + privacyPolicyLink: "https://supertokens.com/legal/privacy-policy", + termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions", + defaultToSignUp, + disableAuthRoute: testContext.disableDefaultUI, recipeList, }); /* App */ function App() { useEffect(() => { - window.addEventListener("TPEP.getAuthorisationURLWithQueryParamsAndSetState", async () => { - ThirdPartyEmailPassword.getAuthorisationURLWithQueryParamsAndSetState({ + window.addEventListener("TP.getAuthorisationURLWithQueryParamsAndSetState", async () => { + ThirdParty.getAuthorisationURLWithQueryParamsAndSetState({ providerId: "google", authorisationURL: "", userContext: { @@ -447,18 +439,8 @@ export function DashboardHelper({ redirectOnLogout, ...props } = {}) { const [sessionInfoUsingFetch, setSessionInfoUsingFetch] = useState(undefined); async function logout() { - const useRecipe = getQueryParams("rid") || authRecipe; - if (useRecipe === "thirdparty") { - await ThirdParty.signOut(); - } else if (useRecipe === "thirdpartyemailpassword") { - await ThirdPartyEmailPassword.signOut(); - } else if (useRecipe === "passwordless") { - await Passwordless.signOut(); - } else if (useRecipe === "thirdpartypasswordless") { - await ThirdPartyPasswordless.signOut(); - } else { - await EmailPassword.signOut(); - } + await Session.signOut(); + if (redirectOnLogout) { if (props.history === undefined) { window.location.href = "/auth"; @@ -546,7 +528,6 @@ function SessionInfoTable({ sessionInfo }) { function getEmailVerificationConfigs({ disableDefaultUI }) { return EmailVerification.init({ - useShadowDom, disableDefaultUI, sendVerifyEmailScreen: { style: theme, @@ -617,10 +598,6 @@ function getEmailPasswordConfigs({ disableDefaultUI }) { log(`DOES_EMAIL_EXIST`); return implementation.doesEmailExist(...args); }, - sendPasswordResetEmail(...args) { - log(`SEND_PASSWORD_RESET_EMAIL`); - return implementation.sendPasswordResetEmail(...args); - }, signIn(...args) { log(`SIGN_IN`); return implementation.signIn(...args); @@ -629,9 +606,28 @@ function getEmailPasswordConfigs({ disableDefaultUI }) { log(`SIGN_UP`); return implementation.signUp(...args); }, - submitNewPassword(...args) { + sendPasswordResetEmail(input) { + if (input.userContext["key"] !== undefined) { + log(`SEND_PASSWORD_RESET_EMAIL RECEIVED_USER_CONTEXT`); + } + + log(`SEND_PASSWORD_RESET_EMAIL`); + return implementation.sendPasswordResetEmail(input); + }, + getResetPasswordTokenFromURL(input) { + if (input.userContext["key"] !== undefined) { + log(`GET_RESET_TOKEN_FROM_URL RECEIVED_USER_CONTEXT`); + } + + return implementation.getResetPasswordTokenFromURL(input); + }, + submitNewPassword(input) { + if (input.userContext["key"] !== undefined) { + log(`SUBMIT_NEW_PASSWORD RECEIVED_USER_CONTEXT`); + } + log(`SUBMIT_NEW_PASSWORD`); - return implementation.submitNewPassword(...args); + return implementation.submitNewPassword(input); }, }; }, @@ -646,7 +642,6 @@ function getEmailPasswordConfigs({ disableDefaultUI }) { onHandleEvent: async (context) => { console.log(`ST_LOGS EMAIL_PASSWORD ON_HANDLE_EVENT ${context.action}`); }, - useShadowDom, resetPasswordUsingTokenFeature: { disableDefaultUI, enterEmailForm: { @@ -657,150 +652,18 @@ function getEmailPasswordConfigs({ disableDefaultUI }) { }, }, signInAndUpFeature: { - disableDefaultUI, - defaultToSignUp, signInForm: { style: theme.style, }, signUpForm: { style: theme.style, - privacyPolicyLink: "https://supertokens.com/legal/privacy-policy", - termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions", formFields, }, }, }); } -function getThirdPartyPasswordlessConfigs({ staticProviderList, disableDefaultUI, thirdPartyRedirectURL }) { - let providers = [ - ThirdParty.Github.init(), - ThirdParty.Google.init(), - ThirdParty.Facebook.init(), - ThirdParty.Apple.init(), - { - id: "custom", - name: "Custom", - }, - { - id: "auth0", - name: "Auth0", - getRedirectURL: thirdPartyRedirectURL !== null ? () => thirdPartyRedirectURL : undefined, - }, - { - id: "mock-provider", - name: "Mock Provider", - }, - ]; - if (staticProviderList) { - const ids = JSON.parse(staticProviderList); - providers = ids.map((id) => providers.find((p) => p.id === id) || { id, name: id }); - } - - return ThirdPartyPasswordless.init({ - override: { - functions: (implementation) => { - const log = logWithPrefix(`ST_LOGS THIRDPARTYPASSWORDLESS OVERRIDE`); - - return { - ...implementation, - doesPasswordlessUserEmailExist(...args) { - log(`DOES_PASSWORDLESS_USER_EMAIL_EXIST`); - return implementation.doesPasswordlessUserEmailExist(...args); - }, - doesPasswordlessUserPhoneNumberExist(...args) { - log(`DOES_PASSWORDLESS_USER_PHONE_NUMBER_EXIST`); - return implementation.doesPasswordlessUserPhoneNumberExist(...args); - }, - createPasswordlessCode(...args) { - log(`CREATE_CODE`); - return implementation.createPasswordlessCode(...args); - }, - resendPasswordlessCode(...args) { - log(`RESEND_CODE`); - return implementation.resendPasswordlessCode(...args); - }, - consumePasswordlessCode(...args) { - log(`CONSUME_CODE`); - return implementation.consumePasswordlessCode(...args); - }, - getPasswordlessLoginAttemptInfo(...args) { - log(`GET_LOGIN_ATTEMPT_INFO`); - return implementation.getPasswordlessLoginAttemptInfo(...args); - }, - setPasswordlessLoginAttemptInfo(...args) { - log(`SET_LOGIN_ATTEMPT_INFO`); - return implementation.setPasswordlessLoginAttemptInfo(...args); - }, - clearPasswordlessLoginAttemptInfo(...args) { - log(`CLEAR_LOGIN_ATTEMPT_INFO`); - return implementation.clearPasswordlessLoginAttemptInfo(...args); - }, - getAuthorisationURLFromBackend(...args) { - log(`GET_OAUTH_AUTHORISATION_URL`); - return implementation.getAuthorisationURLFromBackend(...args); - }, - getThirdPartyStateAndOtherInfoFromStorage(...args) { - log(`GET_OAUTH_STATE`); - return implementation.getThirdPartyStateAndOtherInfoFromStorage(...args); - }, - getThirdPartyAuthorisationURLWithQueryParamsAndSetState(...args) { - log(`GET_AUTH_URL_WITH_QUERY_PARAMS_AND_SET_STATE`); - return implementation.getThirdPartyAuthorisationURLWithQueryParamsAndSetState(...args); - }, - setThirdPartyStateAndOtherInfoToStorage(...args) { - log(`SET_OAUTH_STATE`); - return implementation.setThirdPartyStateAndOtherInfoToStorage(...args); - }, - thirdPartySignInAndUp(...args) { - log(`THIRD_PARTY_SIGN_IN_AND_UP`); - return implementation.thirdPartySignInAndUp(...args); - }, - }; - }, - }, - preAPIHook: async (context) => { - console.log(`ST_LOGS THIRDPARTYPASSWORDLESS PRE_API_HOOKS ${context.action}`); - return context; - }, - getRedirectionURL: async (context) => { - console.log(`ST_LOGS THIRDPARTYPASSWORDLESS GET_REDIRECTION_URL ${context.action}`); - }, - onHandleEvent: async (context) => { - console.log(`ST_LOGS THIRDPARTYPASSWORDLESS ON_HANDLE_EVENT ${context.action}`); - }, - useShadowDom, - contactMethod: passwordlessContactMethodType, - disablePasswordless: false, - signInUpFeature: { - disableDefaultUI, - style: theme.style, - thirdPartyProviderAndEmailOrPhoneFormStyle: ` - [data-supertokens~=providerCustom] { - color: red; - }, - `, - privacyPolicyLink: "https://supertokens.io/legal/privacy-policy", - termsOfServiceLink: "https://supertokens.io/legal/terms-and-conditions", - providers, - defaultCountry: passwordlessDefaultCountry, - resendEmailOrSMSGapInSeconds: 2, - guessInternationPhoneNumberFromInputPhoneNumber: passwordlessDisablePhoneGuess - ? () => undefined - : undefined, - }, - linkClickedScreenFeature: { - disableDefaultUI, - style: theme.style, - }, - mfaFeature: { - disableDefaultUI, - style: theme, - }, - }); -} - -function getPasswordlessConfigs({ disableDefaultUI }) { +function getPasswordlessConfigs({ disableDefaultUI, defaultToEmail }) { return Passwordless.init({ override: { functions: (implementation) => { @@ -853,19 +716,12 @@ function getPasswordlessConfigs({ disableDefaultUI }) { onHandleEvent: async (context) => { console.log(`ST_LOGS PASSWORDLESS ON_HANDLE_EVENT ${context.action}`); }, - useShadowDom, contactMethod: passwordlessContactMethodType, signInUpFeature: { defaultCountry: passwordlessDefaultCountry, - guessInternationPhoneNumberFromInputPhoneNumber: passwordlessDisablePhoneGuess - ? () => undefined - : undefined, + defaultToEmail, resendEmailOrSMSGapInSeconds: 2, - disableDefaultUI, style: theme.style, - - privacyPolicyLink: "https://supertokens.com/legal/privacy-policy", - termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions", }, linkClickedScreenFeature: { disableDefaultUI, @@ -925,135 +781,18 @@ function getThirdPartyConfigs({ staticProviderList, disableDefaultUI, thirdParty return { ...implementation, - getAuthorisationURLWithQueryParamsAndSetState(...args) { - log(`GET_AUTH_URL_WITH_QUERY_PARAMS_AND_SET_STATE`); - return implementation.getAuthorisationURLWithQueryParamsAndSetState(...args); - }, - getAuthorisationURLFromBackend(...args) { - log(`GET_OAUTH_AUTHORISATION_URL`); - return implementation.getAuthorisationURLFromBackend(...args); - }, - getStateAndOtherInfoFromStorage(...args) { - log(`GET_OAUTH_STATE`); - return implementation.getStateAndOtherInfoFromStorage(...args); - }, - setStateAndOtherInfoToStorage(...args) { - log(`SET_OAUTH_STATE`); - return implementation.setStateAndOtherInfoToStorage(...args); - }, - signInAndUp(...args) { - log(`SIGN_IN_AND_UP`); - return implementation.signInAndUp(...args); - }, - }; - }, - }, - useShadowDom, - signInAndUpFeature: { - disableDefaultUI, - style: theme.style, - privacyPolicyLink: "https://supertokens.com/legal/privacy-policy", - termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions", - providers, - }, - - oAuthCallbackScreen: { - style: theme.style, - }, - }); -} - -function getThirdPartyEmailPasswordConfigs({ staticProviderList, disableDefaultUI, thirdPartyRedirectURL }) { - let providers = [ - ThirdParty.Github.init(), - ThirdParty.Google.init(), - ThirdParty.Facebook.init(), - ThirdParty.Apple.init(), - { - id: "custom", - name: "Custom", - }, - { - id: "auth0", - name: "Auth0", - getRedirectURL: thirdPartyRedirectURL !== null ? () => thirdPartyRedirectURL : undefined, - }, - { - id: "mock-provider", - name: "Mock Provider", - }, - ]; - if (staticProviderList) { - const ids = JSON.parse(staticProviderList); - providers = ids.map((id) => providers.find((p) => p.id === id) || { id, name: id }); - } - return ThirdPartyEmailPassword.init({ - preAPIHook: async (context) => { - console.log(`ST_LOGS THIRD_PARTY_EMAIL_PASSWORD PRE_API_HOOKS ${context.action}`); - return context; - }, - getRedirectionURL: async (context) => { - console.log(`ST_LOGS THIRD_PARTY_EMAIL_PASSWORD GET_REDIRECTION_URL ${context.action}`); - if (context.action === "SUCCESS") { - setIsNewUserToStorage("thirdpartyemailpassword", context.isNewRecipeUser); - return context.redirectToPath || "/dashboard"; - } - }, - onHandleEvent: async (context) => { - console.log(`ST_LOGS THIRD_PARTY_EMAIL_PASSWORD ON_HANDLE_EVENT ${context.action}`); - }, - override: { - functions: (implementation) => { - const log = logWithPrefix(`ST_LOGS THIRD_PARTY_EMAIL_PASSWORD OVERRIDE`); - - return { - ...implementation, - getAuthorisationURLWithQueryParamsAndSetState(input) { - if (input.userContext["key"] !== undefined) { - log(`GET_AUTH_URL_WITH_QUERY_PARAMS_AND_SET_STATE RECEIVED_USER_CONTEXT`); - } - - log(`GET_AUTH_URL_WITH_QUERY_PARAMS_AND_SET_STATE`); - return implementation.getAuthorisationURLWithQueryParamsAndSetState(input); - }, generateStateToSendToOAuthProvider(input) { if (input.userContext["key"] !== undefined) { log(`GENERATE_STATE RECEIVED_USER_CONTEXT`); } - return implementation.generateStateToSendToOAuthProvider(input); }, - thirdPartySignInAndUp(input) { - if (input.userContext["key"] !== undefined) { - log(`SIGN_IN_AND_UP RECEIVED_USER_CONTEXT`); - } - - log(`SIGN_IN_AND_UP`); - return implementation.thirdPartySignInAndUp(input); - }, - emailPasswordSignIn(...args) { - log(`SIGN_IN_AND_UP`); - return implementation.emailPasswordSignIn(...args); - }, - emailPasswordSignUp(...args) { - log(`SIGN_IN_AND_UP`); - return implementation.emailPasswordSignUp(...args); - }, - setStateAndOtherInfoToStorage(input) { - if (input.userContext["key"] !== undefined) { - log(`SET_OAUTH_STATE RECEIVED_USER_CONTEXT`); - } - - log(`SET_OAUTH_STATE`); - return implementation.setStateAndOtherInfoToStorage(input); - }, - getStateAndOtherInfoFromStorage(input) { + getAuthorisationURLWithQueryParamsAndSetState(input) { if (input.userContext["key"] !== undefined) { - log(`GET_OAUTH_STATE RECEIVED_USER_CONTEXT`); + log(`GET_AUTH_URL_WITH_QUERY_PARAMS_AND_SET_STATE RECEIVED_USER_CONTEXT`); } - - log(`GET_OAUTH_STATE`); - return implementation.getStateAndOtherInfoFromStorage(input); + log(`GET_AUTH_URL_WITH_QUERY_PARAMS_AND_SET_STATE`); + return implementation.getAuthorisationURLWithQueryParamsAndSetState(input); }, getAuthorisationURLFromBackend(input) { if (input.userContext["key"] !== undefined) { @@ -1079,33 +818,31 @@ function getThirdPartyEmailPasswordConfigs({ staticProviderList, disableDefaultU return implementation.getAuthorisationURLFromBackend(input); }, - submitNewPassword(input) { + getStateAndOtherInfoFromStorage(input) { if (input.userContext["key"] !== undefined) { - log(`SUBMIT_NEW_PASSWORD RECEIVED_USER_CONTEXT`); + log(`GET_OAUTH_STATE RECEIVED_USER_CONTEXT`); } - log(`SUBMIT_NEW_PASSWORD`); - return implementation.submitNewPassword(input); + log(`GET_OAUTH_STATE`); + return implementation.getStateAndOtherInfoFromStorage(input); }, - sendPasswordResetEmail(input) { + + setStateAndOtherInfoToStorage(input) { if (input.userContext["key"] !== undefined) { - log(`SEND_PASSWORD_RESET_EMAIL RECEIVED_USER_CONTEXT`); + log(`SET_OAUTH_STATE RECEIVED_USER_CONTEXT`); } - log(`SEND_PASSWORD_RESET_EMAIL`); - return implementation.sendPasswordResetEmail(input); + log(`SET_OAUTH_STATE`); + return implementation.setStateAndOtherInfoToStorage(input); }, - getResetPasswordTokenFromURL(input) { + signInAndUp(input) { if (input.userContext["key"] !== undefined) { - log(`GET_RESET_TOKEN_FROM_URL RECEIVED_USER_CONTEXT`); + log(`SIGN_IN_AND_UP RECEIVED_USER_CONTEXT`); } - - return implementation.getResetPasswordTokenFromURL(input); - }, - doesEmailExist(...args) { - log(`DOES_EMAIL_EXIST`); - return implementation.doesEmailExist(...args); + log(`SIGN_IN_AND_UP`); + return implementation.signInAndUp(input); }, + getAuthStateFromURL(input) { if (input.userContext["key"] !== undefined) { log(`GET_AUTH_STATE_FROM_URL RECEIVED_USER_CONTEXT`); @@ -1123,22 +860,10 @@ function getThirdPartyEmailPasswordConfigs({ staticProviderList, disableDefaultU }; }, }, - useShadowDom, - resetPasswordUsingTokenFeature: { - disableDefaultUI, - }, signInAndUpFeature: { - disableDefaultUI, - signInForm: {}, - signUpForm: { - formFields, - privacyPolicyLink: "https://supertokens.com/legal/privacy-policy", - termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions", - }, style: theme.style, providers, }, - disableEmailPassword: false, oAuthCallbackScreen: { style: theme.style, diff --git a/examples/for-tests-react-16/src/AppWithReactDomRouter.js b/examples/for-tests-react-16/src/AppWithReactDomRouter.js index 3dbce5ee8..f24b1ef67 100644 --- a/examples/for-tests-react-16/src/AppWithReactDomRouter.js +++ b/examples/for-tests-react-16/src/AppWithReactDomRouter.js @@ -1,20 +1,13 @@ import React, { useState } from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -import { SignInAndUp } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; +import { getSuperTokensRoutesForReactRouterDom, AuthPage } from "supertokens-auth-react/ui"; +import { ResetPasswordUsingToken } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { SessionAuth } from "supertokens-auth-react/recipe/session"; -import { - ResetPasswordUsingToken, - SignInAndUp as TPSignInAndUp, - ThirdPartySignInAndUpCallback, -} from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; import { BaseComponent, Home, Contact, Dashboard, DashboardNoAuthRequired } from "./App"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; -import { ThirdPartyPasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartypasswordless/prebuiltui"; import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; -import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { ThirdPartyPreBuiltUI, SignInAndUpCallback } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; import { MultiFactorAuthPreBuiltUI } from "supertokens-auth-react/recipe/multifactorauth/prebuiltui"; import { TOTPPreBuiltUI } from "supertokens-auth-react/recipe/totp/prebuiltui"; import { AccessDeniedScreen } from "supertokens-auth-react/recipe/session/prebuiltui"; @@ -26,21 +19,15 @@ function AppWithReactDomRouter(props) { const emailVerificationMode = window.localStorage.getItem("mode") || "OFF"; let recipePreBuiltUIList = [TOTPPreBuiltUI]; - if (enabledRecipes.includes("emailpassword")) { - recipePreBuiltUIList.push(EmailPasswordPreBuiltUI); - } - if (enabledRecipes.includes("thirdparty")) { + if (enabledRecipes.some((r) => r.startsWith("thirdparty"))) { recipePreBuiltUIList.push(ThirdPartyPreBuiltUI); } - if (enabledRecipes.includes("thirdpartyemailpassword")) { - recipePreBuiltUIList.push(ThirdPartyEmailPasswordPreBuiltUI); + if (enabledRecipes.some((r) => r.endsWith("emailpassword"))) { + recipePreBuiltUIList.push(EmailPasswordPreBuiltUI); } - if (enabledRecipes.includes("passwordless")) { + if (enabledRecipes.some((r) => r.endsWith("passwordless"))) { recipePreBuiltUIList.push(PasswordlessPreBuiltUI); } - if (enabledRecipes.includes("thirdpartypasswordless")) { - recipePreBuiltUIList.push(ThirdPartyPasswordlessPreBuiltUI); - } if (emailVerificationMode !== "OFF") { recipePreBuiltUIList.push(EmailVerificationPreBuiltUI); @@ -128,7 +115,24 @@ function AppWithReactDomRouter(props) { /> } /> - } /> + + } + /> + + } + /> {/* User context paths */} {isForUserContext && ( @@ -147,7 +151,7 @@ function AppWithReactDomRouter(props) { } /> diff --git a/examples/for-tests-react-16/src/AppWithReactDomRouterV5.js b/examples/for-tests-react-16/src/AppWithReactDomRouterV5.js index b24796bb7..b7c04784f 100644 --- a/examples/for-tests-react-16/src/AppWithReactDomRouterV5.js +++ b/examples/for-tests-react-16/src/AppWithReactDomRouterV5.js @@ -1,11 +1,8 @@ import React, { useState } from "react"; import { BrowserRouter as Router, Switch, Route } from "react-router-domv5"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +import { getSuperTokensRoutesForReactRouterDom, AuthPage } from "supertokens-auth-react/ui"; import { SessionAuth } from "supertokens-auth-react/recipe/session"; -import { SignInAndUp } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { BaseComponent, Home, Contact, Dashboard, DashboardNoAuthRequired } from "./App"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; -import { ThirdPartyPasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartypasswordless/prebuiltui"; import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; @@ -21,22 +18,15 @@ function AppWithReactDomRouter(props) { const emailVerificationMode = window.localStorage.getItem("mode") || "OFF"; let recipePreBuiltUIList = [TOTPPreBuiltUI]; - if (enabledRecipes.includes("emailpassword")) { - recipePreBuiltUIList.push(EmailPasswordPreBuiltUI); - } - if (enabledRecipes.includes("thirdparty")) { + if (enabledRecipes.some((r) => r.startsWith("thirdparty"))) { recipePreBuiltUIList.push(ThirdPartyPreBuiltUI); } - if (enabledRecipes.includes("thirdpartyemailpassword")) { - recipePreBuiltUIList.push(ThirdPartyEmailPasswordPreBuiltUI); + if (enabledRecipes.some((r) => r.endsWith("emailpassword"))) { + recipePreBuiltUIList.push(EmailPasswordPreBuiltUI); } - if (enabledRecipes.includes("passwordless")) { + if (enabledRecipes.some((r) => r.endsWith("passwordless"))) { recipePreBuiltUIList.push(PasswordlessPreBuiltUI); } - if (enabledRecipes.includes("thirdpartypasswordless")) { - recipePreBuiltUIList.push(ThirdPartyPasswordlessPreBuiltUI); - } - if (emailVerificationMode !== "OFF") { recipePreBuiltUIList.push(EmailVerificationPreBuiltUI); } @@ -112,8 +102,20 @@ function AppWithReactDomRouter(props) { - + + + } + /> diff --git a/examples/for-tests-react-16/src/AppWithoutRouter.js b/examples/for-tests-react-16/src/AppWithoutRouter.js index 7937b2898..859ee1ea3 100644 --- a/examples/for-tests-react-16/src/AppWithoutRouter.js +++ b/examples/for-tests-react-16/src/AppWithoutRouter.js @@ -1,8 +1,6 @@ import React, { useEffect, useState } from "react"; import { BaseComponent, Home } from "./App"; import { getRoutingComponent, canHandleRoute } from "supertokens-auth-react/ui"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; -import { ThirdPartyPasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartypasswordless/prebuiltui"; import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; @@ -25,21 +23,15 @@ function Routing() { const emailVerificationMode = window.localStorage.getItem("mode") || "OFF"; let recipePreBuiltUIList = []; - if (enabledRecipes.includes("thirdparty")) { + if (enabledRecipes.some((r) => r.startsWith("thirdparty"))) { recipePreBuiltUIList.push(ThirdPartyPreBuiltUI); } - if (enabledRecipes.includes("emailpassword")) { + if (enabledRecipes.some((r) => r.endsWith("emailpassword"))) { recipePreBuiltUIList.push(EmailPasswordPreBuiltUI); } - if (enabledRecipes.includes("thirdpartyemailpassword")) { - recipePreBuiltUIList.push(ThirdPartyEmailPasswordPreBuiltUI); - } - if (enabledRecipes.includes("passwordless")) { + if (enabledRecipes.some((r) => r.endsWith("passwordless"))) { recipePreBuiltUIList.push(PasswordlessPreBuiltUI); } - if (enabledRecipes.includes("thirdpartypasswordless")) { - recipePreBuiltUIList.push(ThirdPartyPasswordlessPreBuiltUI); - } if (emailVerificationMode !== "OFF") { recipePreBuiltUIList.push(EmailVerificationPreBuiltUI); diff --git a/examples/for-tests-react-16/src/testContext.js b/examples/for-tests-react-16/src/testContext.js index cbfe55cec..0d1a4caf3 100644 --- a/examples/for-tests-react-16/src/testContext.js +++ b/examples/for-tests-react-16/src/testContext.js @@ -20,6 +20,7 @@ export function getTestContext() { ? localStorage.getItem("firstFactors").split(", ") : undefined, enableMFA: localStorage.getItem("enableMFA") === "true", + defaultToEmail: localStorage.getItem("defaultToEmail") !== "false", disableRedirectionAfterSuccessfulSignInUp: localStorage.getItem("disableRedirectionAfterSuccessfulSignInUp") === "true", }; @@ -31,7 +32,7 @@ export function getEnabledRecipes() { let enabledRecipes = []; - if (testContext.enableAllRecipes) { + if (testContext.enableAllRecipes || testContext.authRecipe === "all") { enabledRecipes = [ "emailpassword", "thirdparty", diff --git a/examples/for-tests/src/App.js b/examples/for-tests/src/App.js index 9b28b6d16..048337ec8 100644 --- a/examples/for-tests/src/App.js +++ b/examples/for-tests/src/App.js @@ -11,8 +11,6 @@ import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; import Passwordless from "supertokens-auth-react/recipe/passwordless"; import ThirdParty from "supertokens-auth-react/recipe/thirdparty"; import Multitenancy from "supertokens-auth-react/recipe/multitenancy"; -import ThirdPartyEmailPassword from "supertokens-auth-react/recipe/thirdpartyemailpassword"; -import ThirdPartyPasswordless from "supertokens-auth-react/recipe/thirdpartypasswordless"; import UserRoles from "supertokens-auth-react/recipe/userroles"; import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth"; import TOTP from "supertokens-auth-react/recipe/totp"; @@ -88,12 +86,6 @@ if (getQueryParams("passwordlessDefaultCountry")) { const passwordlessDefaultCountry = window.localStorage.getItem("passwordlessDefaultCountry") || undefined; -if (getQueryParams("passwordlessDisablePhoneGuess")) { - window.localStorage.setItem("passwordlessDisablePhoneGuess", getQueryParams("passwordlessDisablePhoneGuess")); -} - -const passwordlessDisablePhoneGuess = window.localStorage.getItem("passwordlessDisablePhoneGuess") || undefined; - if (getQueryParams("authRecipe")) { window.localStorage.setItem("authRecipe", getQueryParams("authRecipe")); } @@ -409,7 +401,7 @@ let recipeList = [ console.log(`ST_LOGS SESSION PRE_API_HOOKS ${ctx.action}`); } - if (localStorage.getItem(`SHOW_GENERAL_ERROR`) === `SESSION ${ctx.action}`) { + if (localStorage.getItem(`SHOW_GENERAL_ERROR`)?.includes(ctx.action)) { let requestBody = ctx.requestInit.body === undefined ? "{}" : ctx.requestInit.body; let jsonBody = JSON.parse(requestBody); jsonBody = { @@ -431,21 +423,19 @@ let recipeList = [ let enabledRecipes = getEnabledRecipes(); -if (enabledRecipes.includes("thirdparty")) { +if ( + enabledRecipes.includes("thirdparty") || + enabledRecipes.includes("thirdpartyemailpassword") || + enabledRecipes.includes("thirdpartypasswordless") +) { recipeList = [getThirdPartyConfigs(testContext), ...recipeList]; } -if (enabledRecipes.includes("emailpassword")) { +if (enabledRecipes.includes("emailpassword") || enabledRecipes.includes("thirdpartyemailpassword")) { recipeList = [getEmailPasswordConfigs(testContext), ...recipeList]; } -if (enabledRecipes.includes("thirdpartyemailpassword")) { - recipeList = [getThirdPartyEmailPasswordConfigs(testContext), ...recipeList]; -} -if (enabledRecipes.includes("passwordless")) { +if (enabledRecipes.includes("passwordless") || enabledRecipes.includes("thirdpartypasswordless")) { recipeList = [getPasswordlessConfigs(testContext), ...recipeList]; } -if (enabledRecipes.includes("thirdpartypasswordless")) { - recipeList = [getThirdPartyPasswordlessConfigs(testContext), ...recipeList]; -} if (emailVerificationMode !== "OFF") { recipeList.push(getEmailVerificationConfigs(testContext)); @@ -471,10 +461,10 @@ SuperTokens.init({ languageTranslations: { translations: { en: { - PWLESS_SIGN_IN_UP_FOOTER_TOS: "TOS", + AUTH_PAGE_FOOTER_TOS: "TOS", }, hu: { - PWLESS_SIGN_IN_UP_FOOTER_TOS: "ÁSZF", + AUTH_PAGE_FOOTER_TOS: "ÁSZF", }, }, }, @@ -499,14 +489,19 @@ SuperTokens.init({ console.log(`ST_LOGS SUPERTOKENS GET_REDIRECTION_URL ${context.action}`); } }, + useShadowDom, + privacyPolicyLink: "https://supertokens.com/legal/privacy-policy", + termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions", + defaultToSignUp, + disableAuthRoute: testContext.disableDefaultUI, recipeList, }); /* App */ function App() { useEffect(() => { - window.addEventListener("TPEP.getAuthorisationURLWithQueryParamsAndSetState", async () => { - ThirdPartyEmailPassword.getAuthorisationURLWithQueryParamsAndSetState({ + window.addEventListener("TP.getAuthorisationURLWithQueryParamsAndSetState", async () => { + ThirdParty.getAuthorisationURLWithQueryParamsAndSetState({ providerId: "google", authorisationURL: "", userContext: { @@ -705,7 +700,6 @@ function SessionInfoTable({ sessionInfo }) { function getEmailVerificationConfigs({ disableDefaultUI }) { return EmailVerification.init({ - useShadowDom, disableDefaultUI, sendVerifyEmailScreen: { style: theme, @@ -739,7 +733,7 @@ function getEmailVerificationConfigs({ disableDefaultUI }) { }, }, preAPIHook: async (context) => { - if (localStorage.getItem(`SHOW_GENERAL_ERROR`) === `EMAIL_VERIFICATION ${context.action}`) { + if (localStorage.getItem(`SHOW_GENERAL_ERROR`)?.includes(context.action)) { let errorFromStorage = localStorage.getItem("TRANSLATED_GENERAL_ERROR"); let jsonBody = JSON.parse(context.requestInit.body); @@ -799,6 +793,14 @@ function getSignInFormFields(formType) { }, ]; default: + return [ + { + id: "email", + }, + { + id: "test", + }, + ]; return; } } @@ -820,10 +822,6 @@ function getEmailPasswordConfigs({ disableDefaultUI, formFieldType }) { log(`DOES_EMAIL_EXIST`); return implementation.doesEmailExist(...args); }, - sendPasswordResetEmail(...args) { - log(`SEND_PASSWORD_RESET_EMAIL`); - return implementation.sendPasswordResetEmail(...args); - }, signIn(...args) { log(`SIGN_IN`); return implementation.signIn(...args); @@ -832,15 +830,34 @@ function getEmailPasswordConfigs({ disableDefaultUI, formFieldType }) { log(`SIGN_UP`); return implementation.signUp(...args); }, - submitNewPassword(...args) { + sendPasswordResetEmail(input) { + if (input.userContext["key"] !== undefined) { + log(`SEND_PASSWORD_RESET_EMAIL RECEIVED_USER_CONTEXT`); + } + + log(`SEND_PASSWORD_RESET_EMAIL`); + return implementation.sendPasswordResetEmail(input); + }, + getResetPasswordTokenFromURL(input) { + if (input.userContext["key"] !== undefined) { + log(`GET_RESET_TOKEN_FROM_URL RECEIVED_USER_CONTEXT`); + } + + return implementation.getResetPasswordTokenFromURL(input); + }, + submitNewPassword(input) { + if (input.userContext["key"] !== undefined) { + log(`SUBMIT_NEW_PASSWORD RECEIVED_USER_CONTEXT`); + } + log(`SUBMIT_NEW_PASSWORD`); - return implementation.submitNewPassword(...args); + return implementation.submitNewPassword(input); }, }; }, }, preAPIHook: async (context) => { - if (localStorage.getItem(`SHOW_GENERAL_ERROR`) === `EMAIL_PASSWORD ${context.action}`) { + if (localStorage.getItem(`SHOW_GENERAL_ERROR`)?.includes(context.action)) { let errorFromStorage = localStorage.getItem("TRANSLATED_GENERAL_ERROR"); if (context.action === "EMAIL_EXISTS") { @@ -864,7 +881,6 @@ function getEmailPasswordConfigs({ disableDefaultUI, formFieldType }) { onHandleEvent: async (context) => { console.log(`ST_LOGS EMAIL_PASSWORD ON_HANDLE_EVENT ${context.action}`); }, - useShadowDom, resetPasswordUsingTokenFeature: { disableDefaultUI, enterEmailForm: { @@ -875,163 +891,19 @@ function getEmailPasswordConfigs({ disableDefaultUI, formFieldType }) { }, }, signInAndUpFeature: { - disableDefaultUI, - defaultToSignUp, signInForm: { style: theme, formFields: getSignInFormFields(formFieldType.signIn), }, signUpForm: { style: theme, - privacyPolicyLink: "https://supertokens.com/legal/privacy-policy", - termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions", formFields: getSignUpFormFields(formFieldType.signUp), }, }, }); } -function getThirdPartyPasswordlessConfigs({ staticProviderList, disableDefaultUI, thirdPartyRedirectURL }) { - let providers = [ - ThirdParty.Github.init(), - ThirdParty.Google.init(), - ThirdParty.Facebook.init(), - ThirdParty.Apple.init(), - { - id: "custom", - name: "Custom", - }, - { - id: "auth0", - name: "Auth0", - getRedirectURL: thirdPartyRedirectURL !== null ? () => thirdPartyRedirectURL : undefined, - }, - { - id: "mock-provider", - name: "Mock Provider", - }, - ]; - if (staticProviderList) { - const ids = JSON.parse(staticProviderList); - providers = ids.map((id) => providers.find((p) => p.id === id) || { id, name: id }); - } - return ThirdPartyPasswordless.init({ - override: { - functions: (implementation) => { - const log = logWithPrefix(`ST_LOGS THIRDPARTYPASSWORDLESS OVERRIDE`); - - return { - ...implementation, - doesPasswordlessUserEmailExist(...args) { - log(`DOES_PASSWORDLESS_USER_EMAIL_EXIST`); - return implementation.doesPasswordlessUserEmailExist(...args); - }, - doesPasswordlessUserPhoneNumberExist(...args) { - log(`DOES_PASSWORDLESS_USER_PHONE_NUMBER_EXIST`); - return implementation.doesPasswordlessUserPhoneNumberExist(...args); - }, - createPasswordlessCode(...args) { - log(`CREATE_CODE`); - return implementation.createPasswordlessCode(...args); - }, - resendPasswordlessCode(...args) { - log(`RESEND_CODE`); - return implementation.resendPasswordlessCode(...args); - }, - consumePasswordlessCode(...args) { - log(`CONSUME_CODE`); - return implementation.consumePasswordlessCode(...args); - }, - getPasswordlessLoginAttemptInfo(...args) { - log(`GET_LOGIN_ATTEMPT_INFO`); - return implementation.getPasswordlessLoginAttemptInfo(...args); - }, - setPasswordlessLoginAttemptInfo(...args) { - log(`SET_LOGIN_ATTEMPT_INFO`); - return implementation.setPasswordlessLoginAttemptInfo(...args); - }, - clearPasswordlessLoginAttemptInfo(...args) { - log(`CLEAR_LOGIN_ATTEMPT_INFO`); - return implementation.clearPasswordlessLoginAttemptInfo(...args); - }, - getAuthorisationURLFromBackend(...args) { - log(`GET_OAUTH_AUTHORISATION_URL`); - return implementation.getAuthorisationURLFromBackend(...args); - }, - getThirdPartyStateAndOtherInfoFromStorage(...args) { - log(`GET_OAUTH_STATE`); - return implementation.getThirdPartyStateAndOtherInfoFromStorage(...args); - }, - getThirdPartyAuthorisationURLWithQueryParamsAndSetState(...args) { - log(`GET_AUTH_URL_WITH_QUERY_PARAMS_AND_SET_STATE`); - return implementation.getThirdPartyAuthorisationURLWithQueryParamsAndSetState(...args); - }, - setThirdPartyStateAndOtherInfoToStorage(...args) { - log(`SET_OAUTH_STATE`); - return implementation.setThirdPartyStateAndOtherInfoToStorage(...args); - }, - thirdPartySignInAndUp(...args) { - log(`THIRD_PARTY_SIGN_IN_AND_UP`); - return implementation.thirdPartySignInAndUp(...args); - }, - }; - }, - }, - preAPIHook: async (context) => { - if (localStorage.getItem(`SHOW_GENERAL_ERROR`) === `THIRD_PARTY_PASSWORDLESS ${context.action}`) { - if (context.action === "GET_AUTHORISATION_URL") { - context.url += "&generalError=true"; - } else { - let jsonBody = JSON.parse(context.requestInit.body); - jsonBody = { - ...jsonBody, - generalError: true, - }; - context.requestInit.body = JSON.stringify(jsonBody); - } - } - - console.log(`ST_LOGS THIRDPARTYPASSWORDLESS PRE_API_HOOKS ${context.action}`); - return context; - }, - getRedirectionURL: async (context) => { - console.log(`ST_LOGS THIRDPARTYPASSWORDLESS GET_REDIRECTION_URL ${context.action}`); - }, - onHandleEvent: async (context) => { - console.log(`ST_LOGS THIRDPARTYPASSWORDLESS ON_HANDLE_EVENT ${context.action}`); - }, - useShadowDom, - contactMethod: passwordlessContactMethodType, - disablePasswordless: false, - signInUpFeature: { - disableDefaultUI, - style: theme, - thirdPartyProviderAndEmailOrPhoneFormStyle: ` - [data-supertokens~=providerCustom] { - color: red; - }, - `, - privacyPolicyLink: "https://supertokens.io/legal/privacy-policy", - termsOfServiceLink: "https://supertokens.io/legal/terms-and-conditions", - providers, - defaultCountry: passwordlessDefaultCountry, - resendEmailOrSMSGapInSeconds: 2, - guessInternationPhoneNumberFromInputPhoneNumber: passwordlessDisablePhoneGuess - ? () => undefined - : undefined, - }, - linkClickedScreenFeature: { - disableDefaultUI, - style: theme, - }, - mfaFeature: { - disableDefaultUI, - style: theme, - }, - }); -} - -function getPasswordlessConfigs({ disableDefaultUI }) { +function getPasswordlessConfigs({ disableDefaultUI, defaultToEmail }) { return Passwordless.init({ override: { functions: (implementation) => { @@ -1075,7 +947,7 @@ function getPasswordlessConfigs({ disableDefaultUI }) { }, }, preAPIHook: async (context) => { - if (localStorage.getItem(`SHOW_GENERAL_ERROR`) === `PASSWORDLESS ${context.action}`) { + if (localStorage.getItem(`SHOW_GENERAL_ERROR`)?.includes(context.action)) { let jsonBody = JSON.parse(context.requestInit.body); jsonBody = { ...jsonBody, @@ -1093,19 +965,12 @@ function getPasswordlessConfigs({ disableDefaultUI }) { onHandleEvent: async (context) => { console.log(`ST_LOGS PASSWORDLESS ON_HANDLE_EVENT ${context.action}`); }, - useShadowDom, contactMethod: passwordlessContactMethodType, signInUpFeature: { defaultCountry: passwordlessDefaultCountry, - guessInternationPhoneNumberFromInputPhoneNumber: passwordlessDisablePhoneGuess - ? () => undefined - : undefined, + defaultToEmail, resendEmailOrSMSGapInSeconds: 2, - disableDefaultUI, style: theme, - - privacyPolicyLink: "https://supertokens.com/legal/privacy-policy", - termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions", }, linkClickedScreenFeature: { disableDefaultUI, @@ -1154,7 +1019,8 @@ function getThirdPartyConfigs({ staticProviderList, disableDefaultUI, thirdParty } return ThirdParty.init({ preAPIHook: async (context) => { - if (localStorage.getItem(`SHOW_GENERAL_ERROR`) === `THIRD_PARTY ${context.action}`) { + if (localStorage.getItem(`SHOW_GENERAL_ERROR`)?.includes(context.action)) { + // TODO if (context.action === "GET_AUTHORISATION_URL") { context.url += "&generalError=true"; } else { @@ -1182,153 +1048,19 @@ function getThirdPartyConfigs({ staticProviderList, disableDefaultUI, thirdParty return { ...implementation, - getAuthorisationURLWithQueryParamsAndSetState(...args) { - log(`GET_AUTH_URL_WITH_QUERY_PARAMS_AND_SET_STATE`); - return implementation.getAuthorisationURLWithQueryParamsAndSetState(...args); - }, - getAuthorisationURLFromBackend(...args) { - log(`GET_OAUTH_AUTHORISATION_URL`); - return implementation.getAuthorisationURLFromBackend(...args); - }, - getStateAndOtherInfoFromStorage(...args) { - log(`GET_OAUTH_STATE`); - return implementation.getStateAndOtherInfoFromStorage(...args); - }, - setStateAndOtherInfoToStorage(...args) { - log(`SET_OAUTH_STATE`); - return implementation.setStateAndOtherInfoToStorage(...args); - }, - signInAndUp(...args) { - log(`SIGN_IN_AND_UP`); - return implementation.signInAndUp(...args); - }, - }; - }, - }, - useShadowDom, - signInAndUpFeature: { - disableDefaultUI, - style: theme, - privacyPolicyLink: "https://supertokens.com/legal/privacy-policy", - termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions", - providers, - }, - - oAuthCallbackScreen: { - style: theme, - }, - }); -} - -function getThirdPartyEmailPasswordConfigs({ - staticProviderList, - disableDefaultUI, - thirdPartyRedirectURL, - formFieldType, -}) { - let providers = [ - ThirdParty.Github.init(), - ThirdParty.Google.init(), - ThirdParty.Facebook.init(), - ThirdParty.Apple.init(), - { - id: "custom", - name: "Custom", - }, - { - id: "auth0", - name: "Auth0", - getRedirectURL: thirdPartyRedirectURL !== null ? () => thirdPartyRedirectURL : undefined, - }, - { - id: "mock-provider", - name: "Mock Provider", - }, - ]; - if (staticProviderList) { - const ids = JSON.parse(staticProviderList); - providers = ids.map((id) => providers.find((p) => p.id === id) || { id, name: id }); - } - return ThirdPartyEmailPassword.init({ - preAPIHook: async (context) => { - if (localStorage.getItem(`SHOW_GENERAL_ERROR`) === `THIRD_PARTY_EMAIL_PASSWORD ${context.action}`) { - if (context.action === "GET_AUTHORISATION_URL" || context.action === "EMAIL_EXISTS") { - context.url += "&generalError=true"; - } else { - let jsonBody = JSON.parse(context.requestInit.body); - jsonBody = { - ...jsonBody, - generalError: true, - }; - context.requestInit.body = JSON.stringify(jsonBody); - } - } - - console.log(`ST_LOGS THIRD_PARTY_EMAIL_PASSWORD PRE_API_HOOKS ${context.action}`); - return context; - }, - getRedirectionURL: async (context) => { - console.log(`ST_LOGS THIRD_PARTY_EMAIL_PASSWORD GET_REDIRECTION_URL ${context.action}`); - if (context.action === "SUCCESS") { - setIsNewUserToStorage("thirdpartyemailpassword", context.isNewRecipeUser); - return context.redirectToPath || "/dashboard"; - } - }, - onHandleEvent: async (context) => { - console.log(`ST_LOGS THIRD_PARTY_EMAIL_PASSWORD ON_HANDLE_EVENT ${context.action}`); - }, - override: { - functions: (implementation) => { - const log = logWithPrefix(`ST_LOGS THIRD_PARTY_EMAIL_PASSWORD OVERRIDE`); - - return { - ...implementation, - getAuthorisationURLWithQueryParamsAndSetState(input) { - if (input.userContext["key"] !== undefined) { - log(`GET_AUTH_URL_WITH_QUERY_PARAMS_AND_SET_STATE RECEIVED_USER_CONTEXT`); - } - log(`GET_AUTH_URL_WITH_QUERY_PARAMS_AND_SET_STATE`); - return implementation.getAuthorisationURLWithQueryParamsAndSetState(input); - }, generateStateToSendToOAuthProvider(input) { if (input.userContext["key"] !== undefined) { log(`GENERATE_STATE RECEIVED_USER_CONTEXT`); } - return implementation.generateStateToSendToOAuthProvider(input); }, - thirdPartySignInAndUp(input) { - if (input.userContext["key"] !== undefined) { - log(`SIGN_IN_AND_UP RECEIVED_USER_CONTEXT`); - } - - log(`SIGN_IN_AND_UP`); - return implementation.thirdPartySignInAndUp(input); - }, - emailPasswordSignIn(...args) { - log(`SIGN_IN_AND_UP`); - return implementation.emailPasswordSignIn(...args); - }, - emailPasswordSignUp(...args) { - log(`SIGN_IN_AND_UP`); - return implementation.emailPasswordSignUp(...args); - }, - setStateAndOtherInfoToStorage(input) { - if (input.userContext["key"] !== undefined) { - log(`SET_OAUTH_STATE RECEIVED_USER_CONTEXT`); - } - - log(`SET_OAUTH_STATE`); - return implementation.setStateAndOtherInfoToStorage(input); - }, - getStateAndOtherInfoFromStorage(input) { + getAuthorisationURLWithQueryParamsAndSetState(input) { if (input.userContext["key"] !== undefined) { - log(`GET_OAUTH_STATE RECEIVED_USER_CONTEXT`); + log(`GET_AUTH_URL_WITH_QUERY_PARAMS_AND_SET_STATE RECEIVED_USER_CONTEXT`); } - - log(`GET_OAUTH_STATE`); - return implementation.getStateAndOtherInfoFromStorage(input); + log(`GET_AUTH_URL_WITH_QUERY_PARAMS_AND_SET_STATE`); + return implementation.getAuthorisationURLWithQueryParamsAndSetState(input); }, getAuthorisationURLFromBackend(input) { if (input.userContext["key"] !== undefined) { @@ -1354,33 +1086,31 @@ function getThirdPartyEmailPasswordConfigs({ return implementation.getAuthorisationURLFromBackend(input); }, - submitNewPassword(input) { + getStateAndOtherInfoFromStorage(input) { if (input.userContext["key"] !== undefined) { - log(`SUBMIT_NEW_PASSWORD RECEIVED_USER_CONTEXT`); + log(`GET_OAUTH_STATE RECEIVED_USER_CONTEXT`); } - log(`SUBMIT_NEW_PASSWORD`); - return implementation.submitNewPassword(input); + log(`GET_OAUTH_STATE`); + return implementation.getStateAndOtherInfoFromStorage(input); }, - sendPasswordResetEmail(input) { + + setStateAndOtherInfoToStorage(input) { if (input.userContext["key"] !== undefined) { - log(`SEND_PASSWORD_RESET_EMAIL RECEIVED_USER_CONTEXT`); + log(`SET_OAUTH_STATE RECEIVED_USER_CONTEXT`); } - log(`SEND_PASSWORD_RESET_EMAIL`); - return implementation.sendPasswordResetEmail(input); + log(`SET_OAUTH_STATE`); + return implementation.setStateAndOtherInfoToStorage(input); }, - getResetPasswordTokenFromURL(input) { + signInAndUp(input) { if (input.userContext["key"] !== undefined) { - log(`GET_RESET_TOKEN_FROM_URL RECEIVED_USER_CONTEXT`); + log(`SIGN_IN_AND_UP RECEIVED_USER_CONTEXT`); } - - return implementation.getResetPasswordTokenFromURL(input); - }, - doesEmailExist(...args) { - log(`DOES_EMAIL_EXIST`); - return implementation.doesEmailExist(...args); + log(`SIGN_IN_AND_UP`); + return implementation.signInAndUp(input); }, + getAuthStateFromURL(input) { if (input.userContext["key"] !== undefined) { log(`GET_AUTH_STATE_FROM_URL RECEIVED_USER_CONTEXT`); @@ -1398,24 +1128,10 @@ function getThirdPartyEmailPasswordConfigs({ }; }, }, - useShadowDom, - resetPasswordUsingTokenFeature: { - disableDefaultUI, - }, signInAndUpFeature: { - disableDefaultUI, - signInForm: { - formFields: getSignInFormFields(formFieldType.signIn), - }, - signUpForm: { - formFields: getSignUpFormFields(formFieldType.signUp), - privacyPolicyLink: "https://supertokens.com/legal/privacy-policy", - termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions", - }, style: theme, providers, }, - disableEmailPassword: false, oAuthCallbackScreen: { style: theme, diff --git a/examples/for-tests/src/AppWithReactDomRouter.js b/examples/for-tests/src/AppWithReactDomRouter.js index 7445724e4..a545bb447 100644 --- a/examples/for-tests/src/AppWithReactDomRouter.js +++ b/examples/for-tests/src/AppWithReactDomRouter.js @@ -1,19 +1,12 @@ import React, { useState } from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; -import { SignInAndUp } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; +import { ResetPasswordUsingToken } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { SessionAuth } from "supertokens-auth-react/recipe/session"; -import { - ResetPasswordUsingToken, - SignInAndUp as TPSignInAndUp, - ThirdPartySignInAndUpCallback, -} from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; -import { ThirdPartyPasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartypasswordless/prebuiltui"; +import { getSuperTokensRoutesForReactRouterDom, AuthPage } from "supertokens-auth-react/ui"; import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; -import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { ThirdPartyPreBuiltUI, SignInAndUpCallback } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; import { AccessDeniedScreen } from "supertokens-auth-react/recipe/session/prebuiltui"; import { MultiFactorAuthPreBuiltUI } from "supertokens-auth-react/recipe/multifactorauth/prebuiltui"; import { TOTPPreBuiltUI } from "supertokens-auth-react/recipe/totp/prebuiltui"; @@ -38,21 +31,15 @@ function AppWithReactDomRouter(props) { const websiteBasePath = window.localStorage.getItem("websiteBasePath") || undefined; let recipePreBuiltUIList = [TOTPPreBuiltUI]; - if (enabledRecipes.includes("emailpassword")) { - recipePreBuiltUIList.push(EmailPasswordPreBuiltUI); - } - if (enabledRecipes.includes("thirdparty")) { + if (enabledRecipes.some((r) => r.startsWith("thirdparty"))) { recipePreBuiltUIList.push(ThirdPartyPreBuiltUI); } - if (enabledRecipes.includes("thirdpartyemailpassword")) { - recipePreBuiltUIList.push(ThirdPartyEmailPasswordPreBuiltUI); + if (enabledRecipes.some((r) => r.endsWith("emailpassword"))) { + recipePreBuiltUIList.push(EmailPasswordPreBuiltUI); } - if (enabledRecipes.includes("passwordless")) { + if (enabledRecipes.some((r) => r.endsWith("passwordless"))) { recipePreBuiltUIList.push(PasswordlessPreBuiltUI); } - if (enabledRecipes.includes("thirdpartypasswordless")) { - recipePreBuiltUIList.push(ThirdPartyPasswordlessPreBuiltUI); - } if (emailVerificationMode !== "OFF") { recipePreBuiltUIList.push(EmailVerificationPreBuiltUI); } @@ -127,7 +114,24 @@ function AppWithReactDomRouter(props) { /> } /> - } /> + + } + /> + + } + /> {/* User context paths */} {isForUserContext && ( @@ -146,7 +150,7 @@ function AppWithReactDomRouter(props) { } /> diff --git a/examples/for-tests/src/AppWithoutRouter.js b/examples/for-tests/src/AppWithoutRouter.js index 7937b2898..859ee1ea3 100644 --- a/examples/for-tests/src/AppWithoutRouter.js +++ b/examples/for-tests/src/AppWithoutRouter.js @@ -1,8 +1,6 @@ import React, { useEffect, useState } from "react"; import { BaseComponent, Home } from "./App"; import { getRoutingComponent, canHandleRoute } from "supertokens-auth-react/ui"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; -import { ThirdPartyPasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartypasswordless/prebuiltui"; import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; @@ -25,21 +23,15 @@ function Routing() { const emailVerificationMode = window.localStorage.getItem("mode") || "OFF"; let recipePreBuiltUIList = []; - if (enabledRecipes.includes("thirdparty")) { + if (enabledRecipes.some((r) => r.startsWith("thirdparty"))) { recipePreBuiltUIList.push(ThirdPartyPreBuiltUI); } - if (enabledRecipes.includes("emailpassword")) { + if (enabledRecipes.some((r) => r.endsWith("emailpassword"))) { recipePreBuiltUIList.push(EmailPasswordPreBuiltUI); } - if (enabledRecipes.includes("thirdpartyemailpassword")) { - recipePreBuiltUIList.push(ThirdPartyEmailPasswordPreBuiltUI); - } - if (enabledRecipes.includes("passwordless")) { + if (enabledRecipes.some((r) => r.endsWith("passwordless"))) { recipePreBuiltUIList.push(PasswordlessPreBuiltUI); } - if (enabledRecipes.includes("thirdpartypasswordless")) { - recipePreBuiltUIList.push(ThirdPartyPasswordlessPreBuiltUI); - } if (emailVerificationMode !== "OFF") { recipePreBuiltUIList.push(EmailVerificationPreBuiltUI); diff --git a/examples/for-tests/src/testContext.js b/examples/for-tests/src/testContext.js index 04a3e6b6e..02a14c3a1 100644 --- a/examples/for-tests/src/testContext.js +++ b/examples/for-tests/src/testContext.js @@ -21,6 +21,7 @@ export function getTestContext() { signUp: localStorage.getItem("SIGNUP_SETTING_TYPE"), }, enableMFA: localStorage.getItem("enableMFA") === "true", + defaultToEmail: localStorage.getItem("defaultToEmail") !== "false", disableRedirectionAfterSuccessfulSignInUp: localStorage.getItem("disableRedirectionAfterSuccessfulSignInUp") === "true", }; @@ -32,7 +33,7 @@ export function getEnabledRecipes() { let enabledRecipes = []; - if (testContext.enableAllRecipes) { + if (testContext.enableAllRecipes || testContext.authRecipe === "all") { enabledRecipes = [ "emailpassword", "thirdparty", diff --git a/examples/with-account-linking/backend/config.ts b/examples/with-account-linking/backend/config.ts index 5f9017cad..65ad51394 100644 --- a/examples/with-account-linking/backend/config.ts +++ b/examples/with-account-linking/backend/config.ts @@ -1,4 +1,5 @@ -import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword"; +import ThirdParty from "supertokens-node/recipe/thirdparty"; +import EmailPassword from "supertokens-node/recipe/emailpassword"; import Session from "supertokens-node/recipe/session"; import Passwordless from "supertokens-node/recipe/passwordless"; import AccountLinking from "supertokens-node/recipe/accountlinking"; @@ -29,7 +30,7 @@ export function getOtp() { export const SuperTokensConfig: TypeInput = { supertokens: { // this is the location of the SuperTokens core. - connectionURI: "https://try.supertokens.com", + connectionURI: "http://localhost:9000", }, appInfo: { appName: "SuperTokens Demo App", @@ -56,33 +57,37 @@ export const SuperTokensConfig: TypeInput = { }; }, }), - ThirdPartyEmailPassword.init({ - providers: [ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - config: { - thirdPartyId: "google", - clients: [ - { - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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", - }, - ], + EmailPassword.init(), + ThirdParty.init({ + signInAndUpFeature: { + providers: [ + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + { + config: { + thirdPartyId: "google", + clients: [ + { + clientId: + "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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", + }, + ], + }, }, - }, - { - config: { - thirdPartyId: "github", - clients: [ - { - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - ], + { + config: { + thirdPartyId: "github", + clients: [ + { + clientId: "467101b197249757c71f", + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + }, + ], + }, }, - }, - ], + ], + }, }), Passwordless.init({ contactMethod: "EMAIL_OR_PHONE", diff --git a/examples/with-account-linking/frontend/src/LinkingPage/index.tsx b/examples/with-account-linking/frontend/src/LinkingPage/index.tsx index 3e02bde1c..1e116dd3f 100644 --- a/examples/with-account-linking/frontend/src/LinkingPage/index.tsx +++ b/examples/with-account-linking/frontend/src/LinkingPage/index.tsx @@ -1,8 +1,9 @@ import React, { useCallback, useEffect, useState } from "react"; import { NavLink, useLocation } from "react-router-dom"; import { useSessionContext } from "supertokens-auth-react/recipe/session"; -import { redirectToThirdPartyLogin, emailPasswordSignUp } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; -import Passwordless from "supertokens-auth-react/recipe/passwordless"; +import { redirectToThirdPartyLogin } from "supertokens-auth-react/recipe/thirdparty"; +import { signUp } from "supertokens-auth-react/recipe/emailpassword"; +import Passwordless, { clearLoginAttemptInfo } from "supertokens-auth-react/recipe/passwordless"; import { getApiDomain } from "../config"; import "./styles.css"; @@ -28,7 +29,7 @@ export const LinkingPage: React.FC = () => { }, [setUserInfo]); const addPassword = useCallback(async () => { - const resp = await emailPasswordSignUp({ + const resp = await signUp({ formFields: [ { id: "email", value: userInfo.user.emails[0] }, { id: "password", value: password }, @@ -41,6 +42,7 @@ export const LinkingPage: React.FC = () => { } else { setSuccess("Successfully added password"); setError(null); + loadUserInfo(); } }, [setError, setSuccess, password]); @@ -96,6 +98,7 @@ export const LinkingPage: React.FC = () => { window.alert("OTP expired. Please try again"); setShowEnterOTPField(false); } else { + clearLoginAttemptInfo(); setSuccess("Successfully added phone number"); setShowEnterOTPField(false); loadUserInfo(); @@ -118,8 +121,8 @@ export const LinkingPage: React.FC = () => { } let passwordLoginMethods = userInfo?.user.loginMethods.filter((lm: any) => lm.recipeId === "emailpassword"); - let thirdPartyLoginMethod = userInfo?.user.loginMethods.filter((lm: any) => lm.recipeId === "thirdparty"); - let phoneLoginMethod = userInfo?.user.loginMethods.filter((lm: any) => lm.recipeId === "passwordless"); + let thirdPartyLoginMethods = userInfo?.user.loginMethods.filter((lm: any) => lm.recipeId === "thirdparty"); + let phoneLoginMethods = userInfo?.user.loginMethods.filter((lm: any) => lm.recipeId === "passwordless"); return (
@@ -138,7 +141,7 @@ export const LinkingPage: React.FC = () => { Email: {lm.email}
))} - {thirdPartyLoginMethod.map((lm: any) => ( + {thirdPartyLoginMethods.map((lm: any) => (
{lm.recipeId} {lm.recipeUserId} @@ -147,7 +150,7 @@ export const LinkingPage: React.FC = () => {
))} - {phoneLoginMethod.map((lm: any) => ( + {phoneLoginMethods.map((lm: any) => (
{lm.recipeId} {lm.recipeUserId} @@ -157,18 +160,21 @@ export const LinkingPage: React.FC = () => { ))} )} - {passwordLoginMethods?.length === 0 && ( -
{ - addPassword(); - ev.preventDefault(); - return false; - }}> - setPassword(ev.currentTarget.value)}> - -
- )} - {phoneLoginMethod?.length === 0 && ( + {passwordLoginMethods?.length === 0 && + (thirdPartyLoginMethods?.some((lm: any) => lm.email !== undefined && lm.verified) ? ( +
{ + addPassword(); + ev.preventDefault(); + return false; + }}> + setPassword(ev.currentTarget.value)}> + +
+ ) : ( +
Please add an email address by connecting a google account before adding a password
+ ))} + {phoneLoginMethods?.length === 0 && (
{ if (showEnterOTPField) { @@ -195,7 +201,7 @@ export const LinkingPage: React.FC = () => { )}
)} - {thirdPartyLoginMethod?.length === 0 && ( + {thirdPartyLoginMethods?.length === 0 && (
{ ev.preventDefault(); diff --git a/examples/with-account-linking/frontend/src/config.tsx b/examples/with-account-linking/frontend/src/config.tsx index dbf43746a..dba6e8f0a 100644 --- a/examples/with-account-linking/frontend/src/config.tsx +++ b/examples/with-account-linking/frontend/src/config.tsx @@ -1,10 +1,13 @@ -import ThirdPartyEmailPassword, { Google, Github, Apple } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; +import ThirdParty, { Google, Github, Apple } from "supertokens-auth-react/recipe/thirdparty"; +import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; +import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; import Session from "supertokens-auth-react/recipe/session"; import Passwordless from "supertokens-auth-react/recipe/passwordless"; +import MultiFactorAuth from "supertokens-auth-react/lib/build/recipe/multifactorauth/recipe"; export function getApiDomain() { const apiPort = process.env.REACT_APP_API_PORT || 3001; @@ -30,13 +33,14 @@ export const SuperTokensConfig = { EmailVerification.init({ mode: "REQUIRED", }), - ThirdPartyEmailPassword.init({ + EmailPassword.init(), + ThirdParty.init({ signInAndUpFeature: { providers: [Github.init(), Google.init()], }, }), Passwordless.init({ - contactMethod: "EMAIL_OR_PHONE", + contactMethod: "PHONE", }), Session.init(), ], @@ -46,4 +50,9 @@ export const recipeDetails = { docsLink: "https://supertokens.com/docs/thirdpartyemailpassword/introduction", }; -export const PreBuiltUIList = [ThirdPartyEmailPasswordPreBuiltUI, PasswordlessPreBuiltUI, EmailVerificationPreBuiltUI]; +export const PreBuiltUIList = [ + ThirdPartyPreBuiltUI, + EmailPasswordPreBuiltUI, + PasswordlessPreBuiltUI, + EmailVerificationPreBuiltUI, +]; diff --git a/examples/with-cli-login/api-server/index.ts b/examples/with-cli-login/api-server/index.ts index dbce67816..2c17782b4 100644 --- a/examples/with-cli-login/api-server/index.ts +++ b/examples/with-cli-login/api-server/index.ts @@ -4,7 +4,8 @@ import supertokens from "supertokens-node"; import Session from "supertokens-node/recipe/session"; import { verifySession } from "supertokens-node/recipe/session/framework/express"; import { middleware, errorHandler, SessionRequest } from "supertokens-node/framework/express"; -import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword"; +import ThirdParty from "supertokens-node/recipe/thirdparty"; +import EmailPassword from "supertokens-node/recipe/emailpassword"; import Passwordless from "supertokens-node/recipe/passwordless"; import UserMetadata from "supertokens-node/recipe/usermetadata"; import Dashboard from "supertokens-node/recipe/dashboard"; @@ -32,49 +33,53 @@ supertokens.init({ websiteDomain, }, recipeList: [ - ThirdPartyEmailPassword.init({ - providers: [ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - config: { - thirdPartyId: "google", - clients: [ - { - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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", - }, - ], + EmailPassword.init(), + ThirdParty.init({ + signInAndUpFeature: { + providers: [ + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + { + config: { + thirdPartyId: "google", + clients: [ + { + clientId: + "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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", + }, + ], + }, }, - }, - { - config: { - thirdPartyId: "github", - clients: [ - { - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - ], + { + config: { + thirdPartyId: "github", + clients: [ + { + clientId: "467101b197249757c71f", + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + }, + ], + }, }, - }, - { - config: { - thirdPartyId: "apple", - clients: [ - { - clientId: "4398792-io.supertokens.example.service", - additionalConfig: { - keyId: "7M48Y4RYDL", - privateKey: - "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - teamId: "YWQCXGJRJL", + { + config: { + thirdPartyId: "apple", + clients: [ + { + clientId: "4398792-io.supertokens.example.service", + additionalConfig: { + keyId: "7M48Y4RYDL", + privateKey: + "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + teamId: "YWQCXGJRJL", + }, }, - }, - ], + ], + }, }, - }, - ], + ], + }, }), UserMetadata.init(), JWT.init({ diff --git a/examples/with-cli-login/src/App.tsx b/examples/with-cli-login/src/App.tsx index e405958a1..1f29b4e51 100644 --- a/examples/with-cli-login/src/App.tsx +++ b/examples/with-cli-login/src/App.tsx @@ -2,8 +2,10 @@ import { useState } from "react"; import "./App.css"; import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -import ThirdPartyEmailPassword from "supertokens-auth-react/recipe/thirdpartyemailpassword"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; +import ThirdParty from "supertokens-auth-react/recipe/thirdparty"; +import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; +import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import Session, { SessionAuth } from "supertokens-auth-react/recipe/session"; import Home from "./Home"; import { Routes, BrowserRouter as Router, Route, useLocation } from "react-router-dom"; @@ -35,13 +37,10 @@ SuperTokens.init({ } }, recipeList: [ - ThirdPartyEmailPassword.init({ + EmailPassword.init(), + ThirdParty.init({ signInAndUpFeature: { - providers: [ - ThirdPartyEmailPassword.Github.init(), - ThirdPartyEmailPassword.Google.init(), - ThirdPartyEmailPassword.Apple.init(), - ], + providers: [ThirdParty.Github.init(), ThirdParty.Google.init(), ThirdParty.Apple.init()], }, }), Session.init(), @@ -67,7 +66,8 @@ function App() { {/* This shows the login UI on "/auth" route */} {getSuperTokensRoutesForReactRouterDom(require("react-router-dom"), [ - ThirdPartyEmailPasswordPreBuiltUI, + ThirdPartyPreBuiltUI, + EmailPasswordPreBuiltUI, ])} { - return { - ...oI, - thirdPartySignInUpPOST: async (input) => { - const res = await oI.thirdPartySignInUpPOST(input); + apis: (oI) => ({ + ...oI, + + emailExistsGET: async function (input) { + let email = input.email; + let signInResponse = await EmailPassword.SignIn(input.tenantId, email, FAKE_PASSWORD); + if (signInResponse.status === "OK") { + // this means that the user had signed up, but not set their password. + // so we the email doesn't yet exist for sign in purposes + return { + status: "OK", + exists: false, + }; + } else { + return oI.emailExistsGET(input); + } + }, + signInPOST: async function (input) { + let password = input.formFields.filter((i) => i.id === "password")[0].value; + if (password === FAKE_PASSWORD) { + return { + status: "WRONG_CREDENTIALS_ERROR", + }; + } + const res = await oI.signInPOST(input); + if (res.status === "OK") { await res.session.setClaimValue(RealPasswordClaim, true, input.userContext); - return res; - }, - emailPasswordEmailExistsGET: async function (input) { - let email = input.email; - let signInResponse = await ThirdPartyEmailPassword.emailPasswordSignIn( - input.tenantId, + } + return res; + }, + signUpPOST: async function (input) { + // We remove claim checking here, since this needs to be callable without the second factor completed + let session = await Session.getSession(input.options.req, input.options.res, { + sessionRequired: false, + overrideGlobalClaimValidators: () => [], + }); + if (session === undefined) { + // copied from https://github.com/supertokens/supertokens-node/blob/master/lib/ts/recipe/emailpassword/api/implementation.ts#L137 + let email = input.formFields.filter((f) => f.id === "email")[0].value; + let password = input.formFields.filter((f) => f.id === "password")[0].value; + + let response = await input.options.recipeImplementation.signUp({ email, - FAKE_PASSWORD - ); - if (signInResponse.status === "OK") { - // this means that the user had signed up, but not set their password. - // so we the email doesn't yet exist for sign in purposes - return { - status: "OK", - exists: false, - }; - } else { - return oI.emailPasswordEmailExistsGET(input); - } - }, - emailPasswordSignInPOST: async function (input) { - let password = input.formFields.filter((i) => i.id === "password")[0].value; - if (password === FAKE_PASSWORD) { - return { - status: "WRONG_CREDENTIALS_ERROR", - }; - } - const res = await oI.emailPasswordSignInPOST(input); - if (res.status === "OK") { - await res.session.setClaimValue(RealPasswordClaim, true, input.userContext); - } - return res; - }, - emailPasswordSignUpPOST: async function (input) { - // We remove claim checking here, since this needs to be callable without the second factor completed - let session = await Session.getSession(input.options.req, input.options.res, { - sessionRequired: false, - overrideGlobalClaimValidators: () => [], + password, + userContext: input.userContext, }); - if (session === undefined) { - // copied from https://github.com/supertokens/supertokens-node/blob/master/lib/ts/recipe/emailpassword/api/implementation.ts#L137 - let email = input.formFields.filter((f) => f.id === "email")[0].value; - let password = input.formFields.filter((f) => f.id === "password")[0].value; - - let response = await input.options.recipeImplementation.signUp({ - email, - password, - userContext: input.userContext, - }); - if (response.status === "EMAIL_ALREADY_EXISTS_ERROR") { - // if the input password is the fake password, and that's - // what's in the db too, then we shall treat this as a success, - // but unverify their email. - let signInResponse = await ThirdPartyEmailPassword.emailPasswordSignIn( - input.tenantId, - email, - FAKE_PASSWORD - ); - if (signInResponse.status === "WRONG_CREDENTIALS_ERROR") { - return response; - } else { - await EmailVerification.unverifyEmail(signInResponse.recipeUserId, email); - response = { - status: "OK", - user: signInResponse.user, - recipeUserId: signInResponse.recipeUserId, - }; - } + if (response.status === "EMAIL_ALREADY_EXISTS_ERROR") { + // if the input password is the fake password, and that's + // what's in the db too, then we shall treat this as a success, + // but unverify their email. + let signInResponse = await EmailPassword.signIn(input.tenantId, email, FAKE_PASSWORD); + if (signInResponse.status === "WRONG_CREDENTIALS_ERROR") { + return response; + } else { + await EmailVerification.unverifyEmail(signInResponse.recipeUserId, email); + response = { + status: "OK", + user: signInResponse.user, + recipeUserId: signInResponse.recipeUserId, + }; } - let user = response.user; + } + let user = response.user; - // we have just created a user with the fake password. - // so we mark their session as unusable by the APIs - const newSession = await Session.createNewSession( - input.options.req, - input.options.res, - input.tenantId, - response.recipeUserId, - { - ...RealPasswordClaim.build( - user.id, - response.recipeUserId, - input.tenantId, - input.userContext - ), - }, - {} - ); - return { - ...response, - status: "OK", - user, - session: newSession, - }; - } else { - // session exists.. so the user is trying to change their password now - let recipeUserId = session.getRecipeUserId(); - let password = input.formFields.filter((f) => f.id === "password")[0].value; + // we have just created a user with the fake password. + // so we mark their session as unusable by the APIs + const newSession = await Session.createNewSession( + input.options.req, + input.options.res, + input.tenantId, + response.recipeUserId, + { + ...RealPasswordClaim.build( + user.id, + response.recipeUserId, + input.tenantId, + input.userContext + ), + }, + {} + ); + return { + ...response, + status: "OK", + user, + session: newSession, + }; + } else { + // session exists.. so the user is trying to change their password now + let recipeUserId = session.getRecipeUserId(); + let password = input.formFields.filter((f) => f.id === "password")[0].value; - if (password === FAKE_PASSWORD) { - throw new Error("User should not use this password"); - } + if (password === FAKE_PASSWORD) { + throw new Error("User should not use this password"); + } - // now we modify the user's password to the new password + change the session to set RealPasswordClaim to true - await ThirdPartyEmailPassword.updateEmailOrPassword({ - recipeUserId, - password, - }); + // now we modify the user's password to the new password + change the session to set RealPasswordClaim to true + await EmailPassword.updateEmailOrPassword({ + recipeUserId, + password, + }); - await session.setClaimValue(RealPasswordClaim, true); + await session.setClaimValue(RealPasswordClaim, true); - let user = await supertokens.getUser(recipeUserId.getAsString()); - return { - status: "OK", - user, - recipeUserId, - session, - }; - } + let user = await supertokens.getUser(recipeUserId.getAsString()); + return { + status: "OK", + user, + recipeUserId, + session, + }; + } + }, + }), + }, + }), + ThirdParty.init({ + override: { + apis: (oI) => { + return { + ...oI, + signInUpPOST: async (input) => { + const res = await oI.thirdPartySignInUpPOST(input); + await res.session.setClaimValue(RealPasswordClaim, true, input.userContext); + return res; }, }; }, diff --git a/examples/with-emailverification-then-password-thirdpartyemailpassword/src/App.js b/examples/with-emailverification-then-password-thirdpartyemailpassword/src/App.js index 630ea8582..b450afcf3 100644 --- a/examples/with-emailverification-then-password-thirdpartyemailpassword/src/App.js +++ b/examples/with-emailverification-then-password-thirdpartyemailpassword/src/App.js @@ -3,13 +3,10 @@ import "./App.css"; import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -import ThirdPartyEmailPassword, { - Google, - Github, - Apple, - ThirdpartyEmailPasswordComponentsOverrideProvider, -} from "supertokens-auth-react/recipe/thirdpartyemailpassword"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; +import ThirdParty, { Google, Github, Apple } from "supertokens-auth-react/recipe/thirdparty"; +import EmailPassword, { EmailPasswordComponentsOverrideProvider } from "supertokens-auth-react/recipe/emailpassword"; +import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; import Session, { SessionAuth } from "supertokens-auth-react/recipe/session"; import Home from "./Home"; @@ -48,7 +45,8 @@ SuperTokens.init({ EmailVerification.init({ mode: "REQUIRED", }), - ThirdPartyEmailPassword.init({ + EmailPassword.init(), + ThirdParty.init({ signInAndUpFeature: { providers: [Github.init(), Google.init(), Apple.init()], }, @@ -74,15 +72,8 @@ function App() { return ( - { - if (window.location.pathname === "/set-password") { - return null; - } else { - return ; - } - }, EmailPasswordSignUpForm_Override: CustomSignUp, }}>
@@ -91,7 +82,8 @@ function App() { {/* This shows the login UI on "/auth" route */} {getSuperTokensRoutesForReactRouterDom(require("react-router-dom"), [ - ThirdPartyEmailPasswordPreBuiltUI, + ThirdPartyPreBuiltUI, + EmailPasswordPreBuiltUI, EmailVerificationPreBuiltUI, ])}
-
+
); } diff --git a/examples/with-emailverification-then-password-thirdpartyemailpassword/src/CustomSignUp/index.js b/examples/with-emailverification-then-password-thirdpartyemailpassword/src/CustomSignUp/index.js index d1b812175..49a4fb739 100644 --- a/examples/with-emailverification-then-password-thirdpartyemailpassword/src/CustomSignUp/index.js +++ b/examples/with-emailverification-then-password-thirdpartyemailpassword/src/CustomSignUp/index.js @@ -41,18 +41,21 @@ export default function CustomSignUp({ DefaultComponent, ...props }) { // we hide all the unnecessary UI components document .querySelector("#supertokens-root") - .shadowRoot.querySelector("div > div > div:nth-child(2)").style.display = "none"; + .shadowRoot.querySelector("[data-supertokens~=headerSubtitle]").style.display = "none"; document .querySelector("#supertokens-root") - .shadowRoot.querySelector("div > div > div:nth-child(3)").style.display = "none"; + .shadowRoot.querySelector("[data-supertokens~=divider]").style.display = "none"; document .querySelector("#supertokens-root") - .shadowRoot.querySelector("div > div > div:nth-child(4)").style.display = "none"; + .shadowRoot.querySelector("[data-supertokens~=formRow]:nth-child(1)").style.display = "none"; document .querySelector("#supertokens-root") - .shadowRoot.querySelector("form > div:nth-child(2) > div").style.display = "none"; - document.querySelector("#supertokens-root").shadowRoot.querySelector("div > div > div").innerText = - "Set Password"; + .shadowRoot.querySelector( + "[data-supertokens~=formRow]:nth-child(2) [data-supertokens~=label]" + ).style.display = "none"; + document + .querySelector("#supertokens-root") + .shadowRoot.querySelector("[data-supertokens~=headerTitle]").innerText = "Set Password"; document .querySelector("#supertokens-root") .shadowRoot.querySelector("form > div:nth-child(3) > button").innerText = "CONTINUE"; diff --git a/examples/with-emailverification-then-password-thirdpartyemailpassword/src/Home/index.js b/examples/with-emailverification-then-password-thirdpartyemailpassword/src/Home/index.js index 57478d40e..6da059171 100644 --- a/examples/with-emailverification-then-password-thirdpartyemailpassword/src/Home/index.js +++ b/examples/with-emailverification-then-password-thirdpartyemailpassword/src/Home/index.js @@ -3,7 +3,7 @@ import Logout from "./Logout"; import SuccessView from "./SuccessView"; import { useSessionContext } from "supertokens-auth-react/recipe/session"; import { useNavigate } from "react-router-dom"; -import { signOut } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; +import { signOut } from "supertokens-auth-react/recipe/session"; import { RealPasswordClaim } from "../realPasswordClaim"; export default function Home() { diff --git a/examples/with-emailverification-then-password-thirdpartyemailpassword/src/SetPassword/index.js b/examples/with-emailverification-then-password-thirdpartyemailpassword/src/SetPassword/index.js index f34a6787c..e33aab5d5 100644 --- a/examples/with-emailverification-then-password-thirdpartyemailpassword/src/SetPassword/index.js +++ b/examples/with-emailverification-then-password-thirdpartyemailpassword/src/SetPassword/index.js @@ -1,23 +1,20 @@ import React from "react"; -import { SignInAndUp } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; +import { AuthPage } from "supertokens-auth-react/ui"; +import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; /* You can build your own UI here to ask the user's password and then call the sign up API. -But for demo purposes, I reused the component and used JS to modify it as needed. +But for demo purposes, I reused the component and used JS to modify it as needed. That being said, I recommend to build your own UI for this page. */ export default function SetPassword() { - React.useEffect(() => { - // This block is only necessary cause I am reusing - const urlParams = new URLSearchParams(window.location.search); - const show = urlParams.get("show"); - if (show === null) { - urlParams.set("show", "signup"); - window.location.search = urlParams.toString(); - return; - } - }, []); - - return ; + return ( + + ); } diff --git a/examples/with-emailverification-then-password-thirdpartyemailpassword/test/basic.test.js b/examples/with-emailverification-then-password-thirdpartyemailpassword/test/basic.test.js index 5e65c6b15..45fccc031 100644 --- a/examples/with-emailverification-then-password-thirdpartyemailpassword/test/basic.test.js +++ b/examples/with-emailverification-then-password-thirdpartyemailpassword/test/basic.test.js @@ -30,7 +30,6 @@ const { const SuperTokensNode = require("supertokens-node"); const Session = require("supertokens-node/recipe/session"); const EmailVerification = require("supertokens-node/recipe/emailverification"); -const ThirdPartyEmailPassword = require("supertokens-node/recipe/thirdpartyemailpassword"); // Run the tests in a DOM environment. require("jsdom-global")(); @@ -48,7 +47,7 @@ SuperTokensNode.init({ websiteDomain: websiteDomain, appName: "testNode", }, - recipeList: [EmailVerification.init({ mode: "OPTIONAL" }), ThirdPartyEmailPassword.init(), Session.init()], + recipeList: [EmailVerification.init({ mode: "OPTIONAL" }), Session.init()], }); describe("SuperTokens Example Basic tests", function () { @@ -63,7 +62,7 @@ describe("SuperTokens Example Basic tests", function () { headless: true, }); page = await browser.newPage(); - page.on("console", (e) => console.log(e.text())); + // page.on("console", (e) => console.log(e.text())); }); after(async function () { diff --git a/examples/with-emailverification-with-otp/api-server/server.ts b/examples/with-emailverification-with-otp/api-server/server.ts index a3dcecafd..bd77b5eee 100644 --- a/examples/with-emailverification-with-otp/api-server/server.ts +++ b/examples/with-emailverification-with-otp/api-server/server.ts @@ -1,7 +1,8 @@ import express from "express"; import supertokens from "supertokens-node"; import Session from "supertokens-node/recipe/session"; -import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword"; +import ThirdParty from "supertokens-node/recipe/thirdparty"; +import EmailPassword from "supertokens-node/recipe/emailpassword"; import EmailVerification from "supertokens-node/recipe/emailverification"; import MultiFactorAuth from "supertokens-node/recipe/multifactorauth"; import AccountLinking from "supertokens-node/recipe/accountlinking"; @@ -56,33 +57,37 @@ supertokens.init({ contactMethod: "EMAIL", flowType: "USER_INPUT_CODE", }), - ThirdPartyEmailPassword.init({ - providers: [ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - config: { - thirdPartyId: "google", - clients: [ - { - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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", - }, - ], + EmailPassword.init(), + ThirdParty.init({ + signInAndUpFeature: { + providers: [ + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + { + config: { + thirdPartyId: "google", + clients: [ + { + clientId: + "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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", + }, + ], + }, }, - }, - { - config: { - thirdPartyId: "github", - clients: [ - { - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - ], + { + config: { + thirdPartyId: "github", + clients: [ + { + clientId: "467101b197249757c71f", + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + }, + ], + }, }, - }, - ], + ], + }, }), Session.init(), // initializes session features Dashboard.init(), diff --git a/examples/with-emailverification-with-otp/src/App.tsx b/examples/with-emailverification-with-otp/src/App.tsx index 74e74dda5..ba778ab04 100644 --- a/examples/with-emailverification-with-otp/src/App.tsx +++ b/examples/with-emailverification-with-otp/src/App.tsx @@ -1,9 +1,11 @@ import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -import ThirdPartyEmailpassword, { Github, Google } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; +import ThirdParty, { Github, Google } from "supertokens-auth-react/recipe/thirdparty"; +import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; import Passwordless from "supertokens-auth-react/recipe/passwordless"; import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; +import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; import Session, { SessionAuth } from "supertokens-auth-react/recipe/session"; import "./App.css"; @@ -29,11 +31,14 @@ SuperTokens.init({ websiteDomain: getWebsiteDomain(), }, recipeList: [ - MultiFactorAuth.init(), + MultiFactorAuth.init({ + firstFactors: ["emailpassword", "thirdparty"], + }), Passwordless.init({ contactMethod: "EMAIL", }), - ThirdPartyEmailpassword.init({ + EmailPassword.init(), + ThirdParty.init({ signInAndUpFeature: { providers: [Github.init(), Google.init()], }, @@ -49,7 +54,8 @@ function App() { {getSuperTokensRoutesForReactRouterDom(require("react-router-dom"), [ - ThirdPartyEmailPasswordPreBuiltUI, + ThirdPartyPreBuiltUI, + EmailPasswordPreBuiltUI, PasswordlessPreBuiltUI, ])} ({ + ...oI, + sendEmail: (input) => { + input.emailVerifyLink = input.emailVerifyLink.replace( + getWebsiteDomain(), + `${getWebsiteDomain()}/#` + ); + return oI.sendEmail(input); + }, + }), + }, }), ], }; diff --git a/examples/with-hasura-thirdpartyemailpassword/api-server.js b/examples/with-hasura-thirdpartyemailpassword/api-server.js index 8258e8f30..7b4dbd3c6 100644 --- a/examples/with-hasura-thirdpartyemailpassword/api-server.js +++ b/examples/with-hasura-thirdpartyemailpassword/api-server.js @@ -5,7 +5,8 @@ require("dotenv").config(); let supertokens = require("supertokens-node"); let Session = require("supertokens-node/recipe/session"); let { middleware, errorHandler } = require("supertokens-node/framework/express"); -let ThirdPartyEmailPassword = require("supertokens-node/recipe/thirdpartyemailpassword"); +let ThirdParty = require("supertokens-node/recipe/thirdparty"); +let EmailPassword = require("supertokens-node/recipe/emailpassword"); let EmailVerification = require("supertokens-node/recipe/emailverification"); let Dashboard = require("supertokens-node/recipe/dashboard"); @@ -28,49 +29,53 @@ supertokens.init({ EmailVerification.init({ mode: "REQUIRED", }), - ThirdPartyEmailPassword.init({ - providers: [ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - config: { - thirdPartyId: "google", - clients: [ - { - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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", - }, - ], + EmailPassword.init(), + ThirdParty.init({ + signInAndUpFeature: { + providers: [ + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + { + config: { + thirdPartyId: "google", + clients: [ + { + clientId: + "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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", + }, + ], + }, }, - }, - { - config: { - thirdPartyId: "github", - clients: [ - { - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - ], + { + config: { + thirdPartyId: "github", + clients: [ + { + clientId: "467101b197249757c71f", + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + }, + ], + }, }, - }, - { - config: { - thirdPartyId: "apple", - clients: [ - { - clientId: "4398792-io.supertokens.example.service", - additionalConfig: { - keyId: "7M48Y4RYDL", - privateKey: - "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - teamId: "YWQCXGJRJL", + { + config: { + thirdPartyId: "apple", + clients: [ + { + clientId: "4398792-io.supertokens.example.service", + additionalConfig: { + keyId: "7M48Y4RYDL", + privateKey: + "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + teamId: "YWQCXGJRJL", + }, }, - }, - ], + ], + }, }, - }, - ], + ], + }, }), Session.init({ exposeAccessTokenToFrontendInCookieBasedAuth: true, diff --git a/examples/with-hasura-thirdpartyemailpassword/src/App.js b/examples/with-hasura-thirdpartyemailpassword/src/App.js index 088473985..93f162ac4 100644 --- a/examples/with-hasura-thirdpartyemailpassword/src/App.js +++ b/examples/with-hasura-thirdpartyemailpassword/src/App.js @@ -2,9 +2,11 @@ import { useState } from "react"; import "./App.css"; import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -import ThirdPartyEmailPassword, { Google, Github, Apple } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; +import ThirdParty, { Google, Github, Apple } from "supertokens-auth-react/recipe/thirdparty"; +import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; +import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; import Session, { SessionAuth } from "supertokens-auth-react/recipe/session"; import Home from "./Home"; @@ -34,7 +36,8 @@ SuperTokens.init({ EmailVerification.init({ mode: "REQUIRED", }), - ThirdPartyEmailPassword.init({ + EmailPassword.init(), + ThirdParty.init({ signInAndUpFeature: { providers: [Github.init(), Google.init(), Apple.init()], }, @@ -54,7 +57,8 @@ function App() { {/* This shows the login UI on "/auth" route */} {getSuperTokensRoutesForReactRouterDom(require("react-router-dom"), [ - ThirdPartyEmailPasswordPreBuiltUI, + ThirdPartyPreBuiltUI, + EmailPasswordPreBuiltUI, EmailVerificationPreBuiltUI, ])} {/* This shows the login UI on "/auth" route */} {getSuperTokensRoutesForReactRouterDom(require("react-router-dom"), [ - ThirdPartyEmailPasswordPreBuiltUI, + ThirdPartyPreBuiltUI, + EmailPasswordPreBuiltUI, EmailVerificationPreBuiltUI, ])} ", + }, + appInfo: { + appName: "SuperTokens Demo App", + apiDomain, + websiteDomain, + }, + // debug: true, + recipeList: [ + EmailVerification.init({ + mode: "REQUIRED", + }), + EmailPassword.init(), + ThirdParty.init({ + signInAndUpFeature: { + providers: [ + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + { + config: { + thirdPartyId: "google", + clients: [ + { + clientId: + "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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", + }, + ], + }, + }, + { + config: { + thirdPartyId: "github", + clients: [ + { + clientId: "467101b197249757c71f", + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + }, + ], + }, + }, + { + config: { + thirdPartyId: "apple", + clients: [ + { + clientId: "4398792-io.supertokens.example.service", + additionalConfig: { + keyId: "7M48Y4RYDL", + privateKey: + "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + teamId: "YWQCXGJRJL", + }, + }, + ], + }, + }, + ], + }, + }), + UserMetadata.init(), + Passwordless.init({ + contactMethod: "PHONE", + flowType: "USER_INPUT_CODE", + smsDelivery: { + override: (oI) => { + return { + ...oI, + sendSms: async function (input) { + console.log(input); + }, + }; + }, + }, + override: { + apis: (oI) => { + return { + ...oI, + createCodePOST: async function (input) { + if (oI.createCodePOST === undefined) { + throw new Error("Should never come here"); + } + /** + * + * We want to make sure that the OTP being generated is for the + * same number that belongs to this user. + */ + + // We remove claim checking here, since this needs to be callable without the second factor completed + let session = await Session.getSession(input.options.req, input.options.res, { + overrideGlobalClaimValidators: () => [], + }); + if (session === undefined) { + throw new Error("Should never come here"); + } + + let phoneNumber: string = session.getAccessTokenPayload().phoneNumber; + + if (phoneNumber !== undefined) { + if (!("phoneNumber" in input) || input.phoneNumber !== phoneNumber) { + throw new Error("Should never come here"); + } + } + + return oI.createCodePOST(input); + }, + + consumeCodePOST: async function (input) { + if (oI.consumeCodePOST === undefined) { + throw new Error("Should never come here"); + } + // we should already have a session here since this is called + // after phone password login + // We remove claim checking here, since this needs to be callable without the second factor completed + let session = await Session.getSession(input.options.req, input.options.res, { + overrideGlobalClaimValidators: () => [], + }); + if (session === undefined) { + throw new Error("Should never come here"); + } + + // we add the session to the user context so that the createNewSession + // function doesn't create a new session + input.userContext.session = session; + let resp = await oI.consumeCodePOST(input); + + if (resp.status === "OK") { + // OTP verification was successful. We can now mark the + // session's payload as SecondFactorClaim: true so that + // the user has access to API routes and the frontend UI + await resp.session.setClaimValue(SecondFactorClaim, true); + + // we associate the passwordless user ID with the thirdpartyemailpassword + // user ID, so that later on, we can fetch the phone number. + await UserMetadata.updateUserMetadata(session.getUserId(), { + passwordlessUserId: resp.user.id, + }); + } + + return resp; + }, + }; + }, + }, + }), + Session.init({ + override: { + functions: (originalImplementation) => { + return { + ...originalImplementation, + getGlobalClaimValidators: (input) => [ + ...input.claimValidatorsAddedByOtherRecipes, + SecondFactorClaim.validators.hasValue(true), + ], + createNewSession: async function (input) { + if (input.userContext.session !== undefined) { + /** + * This will be true for passwordless login. + */ + return input.userContext.session; + } + let userMetadata = await UserMetadata.getUserMetadata(input.userId); + let phoneNumber: string | undefined = undefined; + if (userMetadata.metadata.passwordlessUserId !== undefined) { + // we alreay have a phone number associated with this user, + // so we will add it to the access token payload so that + // we can send an OTP to it without asking the end user. + let passwordlessUserInfo = await supertokens.getUser( + userMetadata.metadata.passwordlessUserId as string, + input.userContext + ); + phoneNumber = passwordlessUserInfo?.phoneNumbers[0]; + } + return originalImplementation.createNewSession({ + ...input, + accessTokenPayload: { + ...input.accessTokenPayload, + ...(await SecondFactorClaim.build( + input.userId, + input.recipeUserId, + input.tenantId, + input.accessTokenPayload, + input.userContext + )), + phoneNumber, + }, + }); + }, + }; + }, + }, + }), + Dashboard.init(), + ], +}); + +const app = express(); + +app.use( + cors({ + origin: websiteDomain, + allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + methods: ["GET", "PUT", "POST", "DELETE"], + credentials: true, + }) +); + +app.use(middleware()); + +// An example API that requires session verification +app.get("/sessioninfo", verifySession(), async (req: SessionRequest, res) => { + let session = req.session!; + res.send({ + sessionHandle: session.getHandle(), + userId: session.getUserId(), + accessTokenPayload: session.getAccessTokenPayload(), + }); +}); + +app.use(errorHandler()); + +app.use((err: any, req: any, res: any, next: any) => { + console.log(err); + res.status(500).send("Internal error: " + err.message); +}); + +app.listen(apiPort, () => console.log(`API Server listening on port ${apiPort}`)); diff --git a/examples/with-legacy-2fa/api-server/secondFactorClaim.ts b/examples/with-legacy-2fa/api-server/secondFactorClaim.ts new file mode 100644 index 000000000..2e8d0d4bb --- /dev/null +++ b/examples/with-legacy-2fa/api-server/secondFactorClaim.ts @@ -0,0 +1,6 @@ +import { BooleanClaim } from "supertokens-node/recipe/session/claims"; + +export const SecondFactorClaim = new BooleanClaim({ + fetchValue: () => false, + key: "2fa-completed", +}); diff --git a/examples/with-legacy-2fa/api-server/tsconfig.json b/examples/with-legacy-2fa/api-server/tsconfig.json new file mode 100644 index 000000000..8a91acaae --- /dev/null +++ b/examples/with-legacy-2fa/api-server/tsconfig.json @@ -0,0 +1,62 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} diff --git a/examples/with-legacy-2fa/package.json b/examples/with-legacy-2fa/package.json new file mode 100644 index 000000000..93d608e5e --- /dev/null +++ b/examples/with-legacy-2fa/package.json @@ -0,0 +1,60 @@ +{ + "name": "with-thirdpartyemailpassword-2fa-passwordless", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "^13.3.0", + "@testing-library/user-event": "^13.5.0", + "@types/cors": "^2.8.12", + "@types/jest": "^27.5.2", + "@types/node": "^16.11.38", + "@types/react": "^18.0.10", + "@types/react-dom": "^18.0.5", + "axios": "^0.27.2", + "cors": "^2.8.5", + "dotenv": "^16.0.1", + "express": "^4.18.1", + "helmet": "^5.1.0", + "morgan": "^1.10.0", + "npm-run-all": "^4.1.5", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "react-router-dom": "^6.3.0", + "react-scripts": "^5.0.1", + "supertokens-auth-react": "latest", + "supertokens-node": "latest", + "ts-node-dev": "^2.0.0", + "typescript": "^4.7.2", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "npm-run-all --parallel spa api-server", + "api-server": "npx ts-node-dev --project api-server/tsconfig.json api-server/index.ts", + "spa": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "nodemon": "^2.0.16" + } +} diff --git a/examples/with-legacy-2fa/public/favicon.ico b/examples/with-legacy-2fa/public/favicon.ico new file mode 100644 index 000000000..a11777cc4 Binary files /dev/null and b/examples/with-legacy-2fa/public/favicon.ico differ diff --git a/examples/with-legacy-2fa/public/index.html b/examples/with-legacy-2fa/public/index.html new file mode 100644 index 000000000..ce6d2bcbe --- /dev/null +++ b/examples/with-legacy-2fa/public/index.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + React App + + + +
+ + diff --git a/examples/with-legacy-2fa/public/logo192.png b/examples/with-legacy-2fa/public/logo192.png new file mode 100644 index 000000000..fc44b0a37 Binary files /dev/null and b/examples/with-legacy-2fa/public/logo192.png differ diff --git a/examples/with-legacy-2fa/public/logo512.png b/examples/with-legacy-2fa/public/logo512.png new file mode 100644 index 000000000..a4e47a654 Binary files /dev/null and b/examples/with-legacy-2fa/public/logo512.png differ diff --git a/examples/with-legacy-2fa/public/manifest.json b/examples/with-legacy-2fa/public/manifest.json new file mode 100644 index 000000000..f01493ff0 --- /dev/null +++ b/examples/with-legacy-2fa/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/examples/with-legacy-2fa/public/robots.txt b/examples/with-legacy-2fa/public/robots.txt new file mode 100644 index 000000000..e9e57dc4d --- /dev/null +++ b/examples/with-legacy-2fa/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/examples/with-legacy-2fa/src/App.css b/examples/with-legacy-2fa/src/App.css new file mode 100644 index 000000000..8a98a2341 --- /dev/null +++ b/examples/with-legacy-2fa/src/App.css @@ -0,0 +1,27 @@ +.App { + display: flex; + flex-direction: column; + width: 100vw; + height: 100vh; + font-family: Rubik; +} + +.fill { + display: flex; + flex-direction: column; + justify-content: center; + flex: 1; +} + +.sessionButton { + padding-left: 13px; + padding-right: 13px; + padding-top: 8px; + padding-bottom: 8px; + background-color: black; + border-radius: 10px; + cursor: pointer; + color: white; + font-weight: bold; + font-size: 17px; +} diff --git a/examples/with-legacy-2fa/src/App.test.tsx b/examples/with-legacy-2fa/src/App.test.tsx new file mode 100644 index 000000000..9b2f4d07b --- /dev/null +++ b/examples/with-legacy-2fa/src/App.test.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import App from "./App"; + +test("renders learn react link", () => { + render(); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/examples/with-legacy-2fa/src/App.tsx b/examples/with-legacy-2fa/src/App.tsx new file mode 100644 index 000000000..c97d3f13b --- /dev/null +++ b/examples/with-legacy-2fa/src/App.tsx @@ -0,0 +1,154 @@ +import React, { useEffect } from "react"; +import { EmailVerificationClaim } from "supertokens-auth-react/recipe/emailverification"; +import "./App.css"; +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { + getSuperTokensRoutesForReactRouterDom, + AuthRecipeComponentsOverrideContextProvider, +} from "supertokens-auth-react/ui"; +import ThirdParty from "supertokens-auth-react/recipe/thirdparty"; +import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; +import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; +import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; +import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; +import EmailVerification from "supertokens-auth-react/recipe/emailverification"; +import Session, { SessionAuth, useSessionContext } from "supertokens-auth-react/recipe/session"; +import Home from "./Home"; +import { Routes, BrowserRouter as Router, Route } from "react-router-dom"; +import Footer from "./Footer"; +import Passwordless, { PasswordlessComponentsOverrideProvider } from "supertokens-auth-react/recipe/passwordless"; +import SecondFactor from "./SecondFactor"; +import { SecondFactorClaim } from "./secondFactorClaim"; +import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth"; + +export function getApiDomain() { + const apiPort = process.env.REACT_APP_API_PORT || 3001; + const apiUrl = process.env.REACT_APP_API_URL || `http://localhost:${apiPort}`; + return apiUrl; +} + +export function getWebsiteDomain() { + const websitePort = process.env.REACT_APP_WEBSITE_PORT || 3000; + const websiteUrl = process.env.REACT_APP_WEBSITE_URL || `http://localhost:${websitePort}`; + return websiteUrl; +} + +SuperTokens.init({ + appInfo: { + appName: "SuperTokens Demo App", // TODO: Your app name + apiDomain: getApiDomain(), // TODO: Change to your app's API domain + websiteDomain: getWebsiteDomain(), // TODO: Change to your app's website domain + }, + recipeList: [ + EmailVerification.init({ + mode: "REQUIRED", + }), + EmailPassword.init(), + ThirdParty.init({ + signInAndUpFeature: { + providers: [ThirdParty.Github.init(), ThirdParty.Google.init(), ThirdParty.Apple.init()], + }, + }), + Passwordless.init({ + contactMethod: "PHONE", + }), + MultiFactorAuth.init({ + firstFactors: ["emailpassword", "thirdparty"], + }), + Session.init({ + override: { + functions: (oI) => ({ + ...oI, + getGlobalClaimValidators: ({ claimValidatorsAddedByOtherRecipes }) => { + return [ + SecondFactorClaim.validators.isTrue(), + ...claimValidatorsAddedByOtherRecipes.filter( + (v) => v.id !== MultiFactorAuth.MultiFactorAuthClaim.id + ), + ]; + }, + }), + }, + }), + ], +}); + +const prebuiltUIs = [ + ThirdPartyPreBuiltUI, + EmailPasswordPreBuiltUI, + EmailVerificationPreBuiltUI, + PasswordlessPreBuiltUI, +]; + +function App() { + return ( + + { + if (props.factorIds.includes("otp-phone")) { +
+ Second factor auth +
; + } + return ; + }, + }}> + { + const session = useSessionContext(); + + if (session.loading !== true && session.accessTokenPayload.phoneNumber === undefined) { + // this will show the change phone number button + return ; + } + + // this will hide the change phone number button + return null; + }, + }}> +
+
+ + {/* This shows the login UI on "/auth" route */} + {getSuperTokensRoutesForReactRouterDom(require("react-router-dom"), prebuiltUIs)} + + + + } + /> + + + + } + /> + +
+
+
+
+
+
+ ); +} + +export default function AppWithRouter() { + return ( + + + + ); +} diff --git a/examples/with-legacy-2fa/src/Footer/index.tsx b/examples/with-legacy-2fa/src/Footer/index.tsx new file mode 100644 index 000000000..90f4d8eb5 --- /dev/null +++ b/examples/with-legacy-2fa/src/Footer/index.tsx @@ -0,0 +1,18 @@ +export default function Footer() { + return ( +
+ React Demo app. Made with ❤️ using supertokens.com +
+ ); +} diff --git a/examples/with-legacy-2fa/src/Home/CallAPIView.tsx b/examples/with-legacy-2fa/src/Home/CallAPIView.tsx new file mode 100644 index 000000000..140336a54 --- /dev/null +++ b/examples/with-legacy-2fa/src/Home/CallAPIView.tsx @@ -0,0 +1,16 @@ +import axios from "axios"; +import { getApiDomain } from "../App"; + +export default function CallAPIView() { + async function callAPIClicked() { + // this will also automatically refresh the session if needed + let response = await axios.get(getApiDomain() + "/sessioninfo"); + window.alert("Session Information:\n" + JSON.stringify(response.data, null, 2)); + } + + return ( +
+ Call API +
+ ); +} diff --git a/examples/with-legacy-2fa/src/Home/Logout.tsx b/examples/with-legacy-2fa/src/Home/Logout.tsx new file mode 100644 index 000000000..b6dece96a --- /dev/null +++ b/examples/with-legacy-2fa/src/Home/Logout.tsx @@ -0,0 +1,32 @@ +export default function Logout(props: { logoutClicked: () => void }) { + let logoutClicked = props.logoutClicked; + + return ( +
+
+ SIGN OUT +
+
+ ); +} diff --git a/examples/with-legacy-2fa/src/Home/SuccessView.tsx b/examples/with-legacy-2fa/src/Home/SuccessView.tsx new file mode 100644 index 000000000..2c2b67915 --- /dev/null +++ b/examples/with-legacy-2fa/src/Home/SuccessView.tsx @@ -0,0 +1,52 @@ +import CallAPIView from "./CallAPIView"; + +export default function SuccessView(props: { userId: string }) { + let userId = props.userId; + + return ( +
+ + 🥳🎉 + + Login successful +
+
+ Your user ID is +
+ {userId} +
+
+
+
+ +
+
+
+ ------------------------------------ +
+
+
+ + ); +} diff --git a/examples/with-legacy-2fa/src/Home/index.tsx b/examples/with-legacy-2fa/src/Home/index.tsx new file mode 100644 index 000000000..79ea057a4 --- /dev/null +++ b/examples/with-legacy-2fa/src/Home/index.tsx @@ -0,0 +1,26 @@ +import React, { useEffect } from "react"; +import Logout from "./Logout"; +import SuccessView from "./SuccessView"; +import { useSessionContext, signOut } from "supertokens-auth-react/recipe/session"; +import { useNavigate } from "react-router-dom"; + +export default function Home() { + const session = useSessionContext(); + const navigate = useNavigate(); + + async function logoutClicked() { + await signOut(); + navigate("/auth"); + } + + if (session.loading) { + return null; + } + + return ( +
+ + +
+ ); +} diff --git a/examples/with-legacy-2fa/src/SecondFactor/index.tsx b/examples/with-legacy-2fa/src/SecondFactor/index.tsx new file mode 100644 index 000000000..b1cbe3561 --- /dev/null +++ b/examples/with-legacy-2fa/src/SecondFactor/index.tsx @@ -0,0 +1,102 @@ +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { redirectToAuth } from "supertokens-auth-react"; +import Passwordless from "supertokens-auth-react/recipe/passwordless"; +import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; +import Session, { useSessionContext } from "supertokens-auth-react/recipe/session"; +import { AuthPage, AuthPageTheme, AuthPageThemeProps } from "supertokens-auth-react/ui"; +import { SecondFactorClaim } from "../secondFactorClaim"; + +const CustomAuthPageTheme: React.FC = (props) => { + let [showDefaultUI, setShowDefaultUI] = useState(false); + const session = useSessionContext(); + const navigate = useNavigate(); + + useEffect(() => { + let aborting = false; + async function effect() { + if (session.loading === true) { + return; + } + + if (!session.invalidClaims.some((e) => e.id === SecondFactorClaim.id)) { + navigate("/"); + return; + } + + const phoneNumber = session.accessTokenPayload.phoneNumber; + + // If don't have a phone number we show the default UI (which should be the phone form) + if (phoneNumber === undefined) { + setShowDefaultUI(true); + } else { + const attemptInfo = await Passwordless.getLoginAttemptInfo(); + + if (aborting) { + return; + } + if (attemptInfo === undefined) { + const additionalAttemptInfo = { + lastResend: Date.now(), + contactMethod: "PHONE", + contactInfo: phoneNumber, + redirectToPath: "/", + }; + + const res = await Passwordless.createCode({ + phoneNumber, + userContext: { additionalAttemptInfo }, + }); + + if (res.status === "OK") { + props.rebuildAuthPage(); + } else { + props.onError(res.reason ?? res.status); + } + } + // if we have an OTP flow (or we just tried to start one) we show the default UI which should be the OTP input + setShowDefaultUI(true); + } + } + effect(); + return () => { + aborting = true; + }; + }, [session.loading]); + + if (showDefaultUI) { + return ( + <> + + +
{ + await Passwordless.clearLoginAttemptInfo(); + await Session.signOut(); + redirectToAuth({ redirectBack: false }); + }} + style={{ + cursor: "pointer", + color: "blue", + textDecoration: "underline", + margin: "auto", + width: "fit-content", + }}> + Login with another account +
+ + ); + } + return <>; +}; + +export default function SecondFactor() { + return ( + + { + // @ts-ignore We ignore the error about missing props, since they'll be set by the feature component + + } + + ); +} diff --git a/examples/with-legacy-2fa/src/index.css b/examples/with-legacy-2fa/src/index.css new file mode 100644 index 000000000..04146b5e7 --- /dev/null +++ b/examples/with-legacy-2fa/src/index.css @@ -0,0 +1,11 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", + "Droid Sans", "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; +} diff --git a/examples/with-legacy-2fa/src/index.tsx b/examples/with-legacy-2fa/src/index.tsx new file mode 100644 index 000000000..ba09b3613 --- /dev/null +++ b/examples/with-legacy-2fa/src/index.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; +import reportWebVitals from "./reportWebVitals"; + +const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); +root.render( + + + +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/examples/with-legacy-2fa/src/logo.svg b/examples/with-legacy-2fa/src/logo.svg new file mode 100644 index 000000000..9dfc1c058 --- /dev/null +++ b/examples/with-legacy-2fa/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/with-legacy-2fa/src/react-app-env.d.ts b/examples/with-legacy-2fa/src/react-app-env.d.ts new file mode 100644 index 000000000..6431bc5fc --- /dev/null +++ b/examples/with-legacy-2fa/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/with-legacy-2fa/src/reportWebVitals.ts b/examples/with-legacy-2fa/src/reportWebVitals.ts new file mode 100644 index 000000000..0677b6701 --- /dev/null +++ b/examples/with-legacy-2fa/src/reportWebVitals.ts @@ -0,0 +1,15 @@ +import { ReportHandler } from "web-vitals"; + +const reportWebVitals = (onPerfEntry?: ReportHandler) => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/examples/with-legacy-2fa/src/secondFactorClaim.tsx b/examples/with-legacy-2fa/src/secondFactorClaim.tsx new file mode 100644 index 000000000..fe1479d36 --- /dev/null +++ b/examples/with-legacy-2fa/src/secondFactorClaim.tsx @@ -0,0 +1,9 @@ +import { BooleanClaim } from "supertokens-auth-react/recipe/session"; + +export const SecondFactorClaim = new BooleanClaim({ + id: "2fa-completed", + refresh: async () => { + // This is something we have no way of refreshing, so this is a no-op + }, + onFailureRedirection: () => "/second-factor", +}); diff --git a/examples/with-legacy-2fa/src/setupTests.ts b/examples/with-legacy-2fa/src/setupTests.ts new file mode 100644 index 000000000..1dd407a63 --- /dev/null +++ b/examples/with-legacy-2fa/src/setupTests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import "@testing-library/jest-dom"; diff --git a/examples/with-legacy-2fa/test/basic.test.js b/examples/with-legacy-2fa/test/basic.test.js new file mode 100644 index 000000000..156669f77 --- /dev/null +++ b/examples/with-legacy-2fa/test/basic.test.js @@ -0,0 +1,179 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/* + * Imports + */ + +const assert = require("assert"); +const puppeteer = require("puppeteer"); +const fetch = require("isomorphic-fetch"); +const { + getTestEmail, + setInputValues, + submitForm, + toggleSignInSignUp, + waitForSTElement, +} = require("../../../test/exampleTestHelpers"); + +const SuperTokensNode = require("supertokens-node"); +const Session = require("supertokens-node/recipe/session"); +const Passwordless = require("supertokens-node/recipe/passwordless"); +const EmailVerification = require("supertokens-node/recipe/emailverification"); + +// Run the tests in a DOM environment. +require("jsdom-global")(); + +const apiDomain = "http://localhost:3001"; +const websiteDomain = "http://localhost:3000"; +SuperTokensNode.init({ + supertokens: { + // We are running these tests without running a local ST instance + connectionURI: "https://try.supertokens.com", + }, + appInfo: { + // These largely shouldn't matter except for creating links which we can change anyway + apiDomain: apiDomain, + websiteDomain: websiteDomain, + appName: "testNode", + }, + recipeList: [ + Passwordless.init({ + contactMethod: "EMAIL_OR_PHONE", + flowType: "USER_INPUT_CODE_AND_MAGIC_LINK", + }), + EmailVerification.init({ + mode: "OPTIONAL", + }), + Session.init(), + ], +}); + +describe("SuperTokens Example Basic tests", function () { + let browser; + let page; + const email = getTestEmail(); + // maybe we could/should randomize this.. + const phoneNumber = "+18004444444"; + const testPW = "Str0ngP@ssw0rd"; + const testOTP = "test123456"; + + before(async function () { + browser = await puppeteer.launch({ + args: ["--no-sandbox", "--disable-setuid-sandbox"], + headless: true, + }); + page = await browser.newPage(); + // page.on("console", c => console.log(c.text())); + }); + + after(async function () { + await browser.close(); + }); + + describe("Email Password test", function () { + it("Successful signup with credentials", async function () { + await Promise.all([page.goto(websiteDomain), page.waitForNavigation({ waitUntil: "networkidle0" })]); + + // redirected to /auth + await toggleSignInSignUp(page); + await setInputValues(page, [ + { name: "email", value: email }, + { name: "password", value: testPW }, + ]); + await submitForm(page); + + // Sent the otp + await waitForSTElement(page, "[name=phoneNumber_text]"); + assert.strictEqual(page.url(), websiteDomain + "/second-factor"); + + // Attempt reloading Home + await Promise.all([page.goto(websiteDomain), page.waitForNavigation({ waitUntil: "networkidle0" })]); + // Redirect back to OTP screen + await waitForSTElement(page, "[name=phoneNumber_text]"); + await setInputValues(page, [ + { + name: "phoneNumber_text", + value: phoneNumber, + }, + ]); + await submitForm(page); + + // Redirected to email verification screen (OTP screen from passwordless w/ overrides) + await waitForSTElement(page, "[name=userInputCode]"); + // Attempt reloading Home + await Promise.all([page.goto(websiteDomain), page.waitForNavigation({ waitUntil: "networkidle0" })]); + await waitForSTElement(page, "[name=userInputCode]"); + + const loginAttemptInfo = JSON.parse( + await page.evaluate(() => localStorage.getItem("supertokens-passwordless-loginAttemptInfo")) + ); + // Create a new OTP and use it (we don't have access to the originally sent one) + await Passwordless.createNewCodeForDevice({ + tenantId: "public", + deviceId: loginAttemptInfo.deviceId, + userInputCode: testOTP, + }); + await setInputValues(page, [{ name: "userInputCode", value: testOTP }]); + await submitForm(page); + + const userId = await page.evaluate(() => window.__supertokensSessionRecipe.getUserId()); + + // We get to the email verification screen + await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); + // Attempt reloading Home + await Promise.all([page.goto(websiteDomain), page.waitForNavigation({ waitUntil: "networkidle0" })]); + await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); + + // Attempt reloading second factor page + await Promise.all([ + page.goto(`${websiteDomain}/second-factor`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); + + // Create a new token and use it (we don't have access to the originally sent one) + const tokenInfo = await EmailVerification.createEmailVerificationToken( + "public", + SuperTokensNode.convertToRecipeUserId(userId), + email + ); + await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo.token}`); + await submitForm(page); + + await page.waitForSelector(".sessionButton"); + + // Attempt reloading second factor page + await Promise.all([ + page.goto(`${websiteDomain}/second-factor`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + const callApiBtn = await page.waitForSelector(".sessionButton"); + let setAlertContent; + let alertContent = new Promise((res) => (setAlertContent = res)); + page.on("dialog", async (dialog) => { + setAlertContent(dialog.message()); + await dialog.dismiss(); + }); + await callApiBtn.click(); + + const alertText = await alertContent; + assert(alertText.startsWith("Session Information:")); + const sessionInfo = JSON.parse(alertText.replace(/^Session Information:/, "")); + assert.strictEqual(sessionInfo.userId, userId); + }); + }); +}); diff --git a/examples/with-legacy-2fa/tsconfig.json b/examples/with-legacy-2fa/tsconfig.json new file mode 100644 index 000000000..c0555cbc6 --- /dev/null +++ b/examples/with-legacy-2fa/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"] +} diff --git a/examples/with-multifactorauth-phone-chooser/backend/config.ts b/examples/with-multifactorauth-phone-chooser/backend/config.ts index a76083d5a..8a8ae1fcd 100644 --- a/examples/with-multifactorauth-phone-chooser/backend/config.ts +++ b/examples/with-multifactorauth-phone-chooser/backend/config.ts @@ -1,4 +1,5 @@ -import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword"; +import ThirdParty from "supertokens-node/recipe/thirdparty"; +import EmailPassword from "supertokens-node/recipe/emailpassword"; import Session from "supertokens-node/recipe/session"; import Passwordless from "supertokens-node/recipe/passwordless"; import UserMetadata from "supertokens-node/recipe/usermetadata"; @@ -53,33 +54,37 @@ export const SuperTokensConfig: TypeInput = { shouldRequireVerification: true, }), }), - ThirdPartyEmailPassword.init({ - providers: [ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - config: { - thirdPartyId: "google", - clients: [ - { - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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", - }, - ], + EmailPassword.init(), + ThirdParty.init({ + signInAndUpFeature: { + providers: [ + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + { + config: { + thirdPartyId: "google", + clients: [ + { + clientId: + "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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", + }, + ], + }, }, - }, - { - config: { - thirdPartyId: "github", - clients: [ - { - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - ], + { + config: { + thirdPartyId: "github", + clients: [ + { + clientId: "467101b197249757c71f", + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + }, + ], + }, }, - }, - ], + ], + }, }), Passwordless.init({ smsDelivery: { diff --git a/examples/with-multifactorauth-phone-chooser/frontend/src/config.tsx b/examples/with-multifactorauth-phone-chooser/frontend/src/config.tsx index d5ebd1221..98a69011c 100644 --- a/examples/with-multifactorauth-phone-chooser/frontend/src/config.tsx +++ b/examples/with-multifactorauth-phone-chooser/frontend/src/config.tsx @@ -1,6 +1,8 @@ -import ThirdPartyEmailPassword, { Google, Github, Apple } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; +import ThirdParty, { Google, Github, Apple } from "supertokens-auth-react/recipe/thirdparty"; +import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; +import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; import Session from "supertokens-auth-react/recipe/session"; @@ -35,6 +37,7 @@ export const SuperTokensConfig = { mode: "REQUIRED", }), MultiFactorAuth.init({ + firstFactors: ["emailpassword", "thirdparty"], getRedirectionURL: async (ctx) => { if (await Passwordless.getLoginAttemptInfo()) { return; @@ -49,7 +52,8 @@ export const SuperTokensConfig = { } }, }), - ThirdPartyEmailPassword.init({ + EmailPassword.init(), + ThirdParty.init({ signInAndUpFeature: { providers: [Github.init(), Google.init()], }, @@ -66,7 +70,8 @@ export const recipeDetails = { }; export const PreBuiltUIList = [ - ThirdPartyEmailPasswordPreBuiltUI, + ThirdPartyPreBuiltUI, + EmailPasswordPreBuiltUI, PasswordlessPreBuiltUI, EmailVerificationPreBuiltUI, MultiFactorAuthPreBuiltUI, diff --git a/examples/with-multifactorauth-recovery-codes/backend/config.ts b/examples/with-multifactorauth-recovery-codes/backend/config.ts index 44e0df014..055afb150 100644 --- a/examples/with-multifactorauth-recovery-codes/backend/config.ts +++ b/examples/with-multifactorauth-recovery-codes/backend/config.ts @@ -1,4 +1,5 @@ -import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword"; +import ThirdParty from "supertokens-node/recipe/thirdparty"; +import EmailPassword from "supertokens-node/recipe/emailpassword"; import Session from "supertokens-node/recipe/session"; import Passwordless from "supertokens-node/recipe/passwordless"; import UserMetadata from "supertokens-node/recipe/usermetadata"; @@ -97,33 +98,37 @@ export const SuperTokensConfig: TypeInput = { }), }, }), - ThirdPartyEmailPassword.init({ - providers: [ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - config: { - thirdPartyId: "google", - clients: [ - { - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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", - }, - ], + EmailPassword.init(), + ThirdParty.init({ + signInAndUpFeature: { + providers: [ + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + { + config: { + thirdPartyId: "google", + clients: [ + { + clientId: + "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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", + }, + ], + }, }, - }, - { - config: { - thirdPartyId: "github", - clients: [ - { - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - ], + { + config: { + thirdPartyId: "github", + clients: [ + { + clientId: "467101b197249757c71f", + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + }, + ], + }, }, - }, - ], + ], + }, }), Session.init({ override: { diff --git a/examples/with-multifactorauth-recovery-codes/frontend/src/config.tsx b/examples/with-multifactorauth-recovery-codes/frontend/src/config.tsx index 89c4d4918..ef156ddac 100644 --- a/examples/with-multifactorauth-recovery-codes/frontend/src/config.tsx +++ b/examples/with-multifactorauth-recovery-codes/frontend/src/config.tsx @@ -1,6 +1,8 @@ -import ThirdPartyEmailPassword, { Google, Github, Apple } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; +import ThirdParty, { Google, Github, Apple } from "supertokens-auth-react/recipe/thirdparty"; +import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; +import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; import Session from "supertokens-auth-react/recipe/session"; @@ -36,9 +38,12 @@ export const SuperTokensConfig = { EmailVerification.init({ mode: "REQUIRED", }), - MultiFactorAuth.init(), + MultiFactorAuth.init({ + firstFactors: ["emailpassword", "thirdparty"], + }), TOTP.init(), - ThirdPartyEmailPassword.init({ + EmailPassword.init(), + ThirdParty.init({ signInAndUpFeature: { providers: [Github.init(), Google.init()], }, @@ -67,7 +72,8 @@ export const recipeDetails = { }; export const PreBuiltUIList = [ - ThirdPartyEmailPasswordPreBuiltUI, + ThirdPartyPreBuiltUI, + EmailPasswordPreBuiltUI, PasswordlessPreBuiltUI, EmailVerificationPreBuiltUI, MultiFactorAuthPreBuiltUI, diff --git a/examples/with-multiple-email-sign-in/src/Home/index.tsx b/examples/with-multiple-email-sign-in/src/Home/index.tsx index 317af8d44..b0aa6e460 100644 --- a/examples/with-multiple-email-sign-in/src/Home/index.tsx +++ b/examples/with-multiple-email-sign-in/src/Home/index.tsx @@ -2,7 +2,7 @@ import React from "react"; import Logout from "./Logout"; import SuccessView from "./SuccessView"; import { useNavigate } from "react-router-dom"; -import { signOut } from "supertokens-auth-react/recipe/emailpassword"; +import { signOut } from "supertokens-auth-react/recipe/session"; export default function Home() { const navigate = useNavigate(); diff --git a/examples/with-netlify/src/Home/index.tsx b/examples/with-netlify/src/Home/index.tsx index 5777e347d..77bdca1f2 100644 --- a/examples/with-netlify/src/Home/index.tsx +++ b/examples/with-netlify/src/Home/index.tsx @@ -3,7 +3,7 @@ import Logout from "./Logout"; import SuccessView from "./SuccessView"; import { useSessionContext } from "supertokens-auth-react/recipe/session"; import { useNavigate } from "react-router-dom"; -import { signOut } from "supertokens-auth-react/recipe/emailpassword"; +import { signOut } from "supertokens-auth-react/recipe/session"; export default function Home() { const sessionContext = useSessionContext(); diff --git a/examples/with-no-session-on-sign-up-thirdpartyemailpassword/api-server/index.js b/examples/with-no-session-on-sign-up-thirdpartyemailpassword/api-server/index.js index 48b909fe8..d9efc441b 100644 --- a/examples/with-no-session-on-sign-up-thirdpartyemailpassword/api-server/index.js +++ b/examples/with-no-session-on-sign-up-thirdpartyemailpassword/api-server/index.js @@ -6,7 +6,8 @@ let supertokens = require("supertokens-node"); let Session = require("supertokens-node/recipe/session"); let { verifySession } = require("supertokens-node/recipe/session/framework/express"); let { middleware, errorHandler } = require("supertokens-node/framework/express"); -let ThirdPartyEmailPassword = require("supertokens-node/recipe/thirdpartyemailpassword"); +let ThirdParty = require("supertokens-node/recipe/thirdparty"); +let EmailPassword = require("supertokens-node/recipe/emailpassword"); let Dashboard = require("supertokens-node/recipe/dashboard"); const apiPort = process.env.REACT_APP_API_PORT || 3001; @@ -27,78 +28,90 @@ supertokens.init({ websiteDomain, // TODO: Change to your app's website domain }, recipeList: [ - ThirdPartyEmailPassword.init({ - providers: [ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - config: { - thirdPartyId: "google", - clients: [ - { - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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", - }, - ], - }, - }, - { - config: { - thirdPartyId: "github", - clients: [ - { - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - ], - }, - }, - { - config: { - thirdPartyId: "apple", - clients: [ - { - clientId: "4398792-io.supertokens.example.service", - additionalConfig: { - keyId: "7M48Y4RYDL", - privateKey: - "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - teamId: "YWQCXGJRJL", - }, - }, - ], - }, - }, - ], + EmailPassword.init({ override: { functions: (oI) => { return { ...oI, - thirdPartySignInUp: async function (input) { - let resp = await oI.thirdPartySignInUp(input); - if (resp.status === "OK") { - if (resp.createdNewRecipeUser) { - // we set this context here so that - // the createNewSession recipe function can - // check this and NOT create a new session - // (since we want to disable session creation during sign up) - input.userContext.isNewRecipeUser = true; - } - } - return resp; - }, - emailPasswordSignUp: async function (input) { + signUp: async function (input) { input.userContext.isNewRecipeUser = true; // we set this context here so that // the createNewSession recipe function can // check this and NOT create a new session // (since we want to disable session creation during sign up) - return oI.emailPasswordSignUp(input); + return oI.signUp(input); }, }; }, }, }), + ThirdParty.init({ + signInAndUpFeature: { + providers: [ + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + { + config: { + thirdPartyId: "google", + clients: [ + { + clientId: + "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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", + }, + ], + }, + }, + { + config: { + thirdPartyId: "github", + clients: [ + { + clientId: "467101b197249757c71f", + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + }, + ], + }, + }, + { + config: { + thirdPartyId: "apple", + clients: [ + { + clientId: "4398792-io.supertokens.example.service", + additionalConfig: { + keyId: "7M48Y4RYDL", + privateKey: + "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", + teamId: "YWQCXGJRJL", + }, + }, + ], + }, + }, + ], + override: { + functions: (oI) => { + return { + ...oI, + signInUp: async function (input) { + let resp = await oI.signInUp(input); + if (resp.status === "OK") { + if (resp.createdNewRecipeUser) { + // we set this context here so that + // the createNewSession recipe function can + // check this and NOT create a new session + // (since we want to disable session creation during sign up) + input.userContext.isNewRecipeUser = true; + } + } + return resp; + }, + }; + }, + }, + }, + }), Session.init({ override: { functions: (oI) => { diff --git a/examples/with-no-session-on-sign-up-thirdpartyemailpassword/src/App.js b/examples/with-no-session-on-sign-up-thirdpartyemailpassword/src/App.js index 458c9e04a..906328463 100644 --- a/examples/with-no-session-on-sign-up-thirdpartyemailpassword/src/App.js +++ b/examples/with-no-session-on-sign-up-thirdpartyemailpassword/src/App.js @@ -1,14 +1,14 @@ import React from "react"; import "./App.css"; import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -import ThirdPartyEmailPassword, { - Google, - Github, - Apple, - ThirdpartyEmailPasswordComponentsOverrideProvider, -} from "supertokens-auth-react/recipe/thirdpartyemailpassword"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; +import { + getSuperTokensRoutesForReactRouterDom, + AuthRecipeComponentsOverrideContextProvider, +} from "supertokens-auth-react/ui"; +import ThirdParty, { Google, Github, Apple } from "supertokens-auth-react/recipe/thirdparty"; +import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; +import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import Session, { SessionAuth } from "supertokens-auth-react/recipe/session"; import Home from "./Home"; import { Routes, BrowserRouter as Router, Route } from "react-router-dom"; @@ -33,7 +33,18 @@ SuperTokens.init({ websiteDomain: getWebsiteDomain(), // TODO: Change to your app's website domain }, recipeList: [ - ThirdPartyEmailPassword.init({ + EmailPassword.init({ + onHandleEvent: (context) => { + if (context.action === "SUCCESS") { + if (context.isNewRecipeUser) { + // we save info in localstorage to indicate to the UI that we should show + // a sign in message in the sign in page. + localStorage.setItem("showSignInMessage", "true"); + } + } + }, + }), + ThirdParty.init({ onHandleEvent: (context) => { if (context.action === "SUCCESS") { if (context.isNewRecipeUser) { @@ -54,9 +65,9 @@ SuperTokens.init({ function App() { return ( - { + AuthPageHeader_Override: ({ DefaultComponent, ...props }) => { if (props.isSignUp) { return ; } else { @@ -75,7 +86,8 @@ function App() { {/* This shows the login UI on "/auth" route */} {getSuperTokensRoutesForReactRouterDom(require("react-router-dom"), [ - ThirdPartyEmailPasswordPreBuiltUI, + ThirdPartyPreBuiltUI, + EmailPasswordPreBuiltUI, ])}
- + ); } diff --git a/examples/with-no-session-on-sign-up-thirdpartyemailpassword/src/Home/index.js b/examples/with-no-session-on-sign-up-thirdpartyemailpassword/src/Home/index.js index 3bf296ad4..77bdca1f2 100644 --- a/examples/with-no-session-on-sign-up-thirdpartyemailpassword/src/Home/index.js +++ b/examples/with-no-session-on-sign-up-thirdpartyemailpassword/src/Home/index.js @@ -3,7 +3,7 @@ import Logout from "./Logout"; import SuccessView from "./SuccessView"; import { useSessionContext } from "supertokens-auth-react/recipe/session"; import { useNavigate } from "react-router-dom"; -import { signOut } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; +import { signOut } from "supertokens-auth-react/recipe/session"; export default function Home() { const sessionContext = useSessionContext(); diff --git a/examples/with-okta-multi-tenant-pkce-flow/src/routes/dashboard.tsx b/examples/with-okta-multi-tenant-pkce-flow/src/routes/dashboard.tsx index 1f3188178..8135454f5 100644 --- a/examples/with-okta-multi-tenant-pkce-flow/src/routes/dashboard.tsx +++ b/examples/with-okta-multi-tenant-pkce-flow/src/routes/dashboard.tsx @@ -1,4 +1,4 @@ -import { signOut } from "supertokens-auth-react/recipe/thirdparty"; +import { signOut } from "supertokens-auth-react/recipe/session"; import Session from "supertokens-auth-react/recipe/session"; // This route is accessible only to logged in users diff --git a/examples/with-one-login-many-subdomains/api-server.js b/examples/with-one-login-many-subdomains/api-server.js index 0ec9907a7..c4519b5a7 100644 --- a/examples/with-one-login-many-subdomains/api-server.js +++ b/examples/with-one-login-many-subdomains/api-server.js @@ -7,8 +7,9 @@ let Session = require("supertokens-node/recipe/session"); let { verifySession } = require("supertokens-node/recipe/session/framework/express"); let { middleware, errorHandler } = require("supertokens-node/framework/express"); let Multitenancy = require("supertokens-node/recipe/multitenancy"); -let ThirdPartyEmailPassword = require("supertokens-node/recipe/thirdpartyemailpassword"); -let ThirdPartyPasswordless = require("supertokens-node/recipe/thirdpartypasswordless"); +let ThirdParty = require("supertokens-node/recipe/thirdparty"); +let EmailPassword = require("supertokens-node/recipe/emailpassword"); +let Passwordless = require("supertokens-node/recipe/passwordless"); let Dashboard = require("supertokens-node/recipe/dashboard"); let EmailVerification = require("supertokens-node/recipe/emailverification"); @@ -40,8 +41,9 @@ supertokens.init({ return [tenantId + ".example.com", "example.com"]; }, }), - ThirdPartyEmailPassword.init(), - ThirdPartyPasswordless.init({ contactMethod: "EMAIL", flowType: "USER_INPUT_CODE_AND_MAGIC_LINK" }), + ThirdParty.init(), + EmailPassword.init(), + Passwordless.init({ contactMethod: "EMAIL", flowType: "USER_INPUT_CODE_AND_MAGIC_LINK" }), Session.init(), Dashboard.init(), ], diff --git a/examples/with-one-login-many-subdomains/frontend/src/App.js b/examples/with-one-login-many-subdomains/frontend/src/App.js index 142ef59e9..cb081732a 100644 --- a/examples/with-one-login-many-subdomains/frontend/src/App.js +++ b/examples/with-one-login-many-subdomains/frontend/src/App.js @@ -1,8 +1,9 @@ import "./App.css"; import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -import ThirdPartyEmailPassword from "supertokens-auth-react/recipe/thirdpartyemailpassword"; -import ThirdPartyPasswordless from "supertokens-auth-react/recipe/thirdpartypasswordless"; +import ThirdParty from "supertokens-auth-react/recipe/thirdparty"; +import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; +import Passwordless from "supertokens-auth-react/recipe/passwordless"; import Multitenancy, { AllowedDomainsClaim } from "supertokens-auth-react/recipe/multitenancy"; import Session, { SessionAuth } from "supertokens-auth-react/recipe/session"; import Home from "./Home"; @@ -38,8 +39,9 @@ SuperTokens.init({ }), }, }), - ThirdPartyEmailPassword.init(), - ThirdPartyPasswordless.init({ + ThirdParty.init(), + EmailPassword.init(), + Passwordless.init({ contactMethod: "EMAIL", }), Session.init({ diff --git a/examples/with-one-login-many-subdomains/frontend/src/AuthPage.jsx b/examples/with-one-login-many-subdomains/frontend/src/AuthPage.jsx index 2a903225c..7199aa1c3 100644 --- a/examples/with-one-login-many-subdomains/frontend/src/AuthPage.jsx +++ b/examples/with-one-login-many-subdomains/frontend/src/AuthPage.jsx @@ -3,10 +3,10 @@ import * as reactRouterDom from "react-router-dom"; import { Routes } from "react-router-dom"; import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; import { useSessionContext } from "supertokens-auth-react/recipe/session"; -import { ThirdpartyEmailPasswordComponentsOverrideProvider } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; -import { ThirdpartyPasswordlessComponentsOverrideProvider } from "supertokens-auth-react/recipe/thirdpartypasswordless"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; -import { ThirdPartyPasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartypasswordless/prebuiltui"; +import { AuthRecipeComponentsOverrideContextProvider } from "supertokens-auth-react/ui"; +import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; +import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; import { getWebsiteBasePath } from "./utils"; import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; import { TenantSelector } from "./TenantSelector"; @@ -43,9 +43,9 @@ export const AuthPage = () => { new URLSearchParams(location.search).has("tenantId") // or we are on a link (e.g.: email verification) that contains the tenantId ) { return ( - { + AuthPageFooter_Override: ({ DefaultComponent, ...props }) => { return (
@@ -59,35 +59,19 @@ export const AuthPage = () => { ); }, }}> - { - return ( -
- - { - localStorage.removeItem("tenantId"); - setHasSelectedTenantId(undefined); - }} - /> -
- ); - }, - }}> - - {getSuperTokensRoutesForReactRouterDom( - reactRouterDom, - [ - ThirdPartyEmailPasswordPreBuiltUI, - ThirdPartyPasswordlessPreBuiltUI, - EmailVerificationPreBuiltUI, - ], - getWebsiteBasePath() - )} - -
- + + {getSuperTokensRoutesForReactRouterDom( + reactRouterDom, + [ + ThirdPartyPreBuiltUI, + EmailPasswordPreBuiltUI, + PasswordlessPreBuiltUI, + EmailVerificationPreBuiltUI, + ], + getWebsiteBasePath() + )} + + ); } else { return ; diff --git a/examples/with-one-login-per-subdomain/api-server.js b/examples/with-one-login-per-subdomain/api-server.js index 4707387fb..0967fb90e 100644 --- a/examples/with-one-login-per-subdomain/api-server.js +++ b/examples/with-one-login-per-subdomain/api-server.js @@ -4,8 +4,9 @@ let supertokens = require("supertokens-node"); let Session = require("supertokens-node/recipe/session"); let { verifySession } = require("supertokens-node/recipe/session/framework/express"); let { middleware, errorHandler } = require("supertokens-node/framework/express"); -let ThirdPartyEmailPassword = require("supertokens-node/recipe/thirdpartyemailpassword"); -let ThirdPartyPasswordless = require("supertokens-node/recipe/thirdpartypasswordless"); +let ThirdParty = require("supertokens-node/recipe/thirdparty"); +let EmailPassword = require("supertokens-node/recipe/emailpassword"); +let Passwordless = require("supertokens-node/recipe/passwordless"); let Dashboard = require("supertokens-node/recipe/dashboard"); let Multitenancy = require("supertokens-node/recipe/multitenancy"); let EmailVerification = require("supertokens-node/recipe/emailverification"); @@ -50,7 +51,7 @@ supertokens.init({ return [tenantId + ".example.com"]; }, }), - ThirdPartyPasswordless.init({ + Passwordless.init({ contactMethod: "EMAIL", flowType: "USER_INPUT_CODE_AND_MAGIC_LINK", emailDelivery: { @@ -66,7 +67,7 @@ supertokens.init({ }), }, }), - ThirdPartyEmailPassword.init({ + EmailPassword.init({ emailDelivery: { override: (oI) => ({ ...oI, @@ -80,6 +81,7 @@ supertokens.init({ }), }, }), + ThirdParty.init(), Session.init(), Dashboard.init(), ], diff --git a/examples/with-one-login-per-subdomain/src/App.js b/examples/with-one-login-per-subdomain/src/App.js index 99fe54f15..5c60d20c4 100644 --- a/examples/with-one-login-per-subdomain/src/App.js +++ b/examples/with-one-login-per-subdomain/src/App.js @@ -4,10 +4,12 @@ import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui import EmailVerification from "supertokens-auth-react/recipe/emailverification"; import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; import Multitenancy, { AllowedDomainsClaim } from "supertokens-auth-react/recipe/multitenancy"; -import ThirdPartyEmailPassword from "supertokens-auth-react/recipe/thirdpartyemailpassword"; -import ThirdPartyPasswordless from "supertokens-auth-react/recipe/thirdpartypasswordless"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; -import { ThirdPartyPasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartypasswordless/prebuiltui"; +import ThirdParty from "supertokens-auth-react/recipe/thirdparty"; +import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; +import Passwordless from "supertokens-auth-react/recipe/passwordless"; +import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; +import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; import Session, { SessionAuth, getClaimValue } from "supertokens-auth-react/recipe/session"; import { Routes, BrowserRouter as Router, Route } from "react-router-dom"; import Home from "./Home"; @@ -33,8 +35,9 @@ SuperTokens.init({ }), }, }), - ThirdPartyEmailPassword.init(), - ThirdPartyPasswordless.init({ + ThirdParty.init(), + EmailPassword.init(), + Passwordless.init({ contactMethod: "EMAIL", }), Session.init({ @@ -68,8 +71,9 @@ function App() {
{getSuperTokensRoutesForReactRouterDom(require("react-router-dom"), [ - ThirdPartyEmailPasswordPreBuiltUI, - ThirdPartyPasswordlessPreBuiltUI, + ThirdPartyPreBuiltUI, + EmailPasswordPreBuiltUI, + PasswordlessPreBuiltUI, EmailVerificationPreBuiltUI, ])} ({ ...oI, getGlobalClaimValidators: ({ claimValidatorsAddedByOtherRecipes }) => { - return [...claimValidatorsAddedByOtherRecipes, PhoneVerifiedClaim.validators.isTrue()]; + return [ + ...claimValidatorsAddedByOtherRecipes.filter( + (v) => v.id !== MultiFactorAuth.MultiFactorAuthClaim.id + ), + PhoneVerifiedClaim.validators.isTrue(), + ]; }, }), }, diff --git a/examples/with-phone-password/src/Home/index.tsx b/examples/with-phone-password/src/Home/index.tsx index d7bb0ecc3..4aac8fd3b 100644 --- a/examples/with-phone-password/src/Home/index.tsx +++ b/examples/with-phone-password/src/Home/index.tsx @@ -3,7 +3,7 @@ import Logout from "./Logout"; import SuccessView from "./SuccessView"; import { useSessionContext } from "supertokens-auth-react/recipe/session"; import { useNavigate } from "react-router-dom"; -import { signOut } from "supertokens-auth-react/recipe/emailpassword"; +import { signOut } from "supertokens-auth-react/recipe/session"; import { PhoneVerifiedClaim } from "../phoneVerifiedClaim"; export default function Home() { diff --git a/examples/with-phone-password/src/PhoneVerification/index.tsx b/examples/with-phone-password/src/PhoneVerification/index.tsx index fbdb220fa..00d045561 100644 --- a/examples/with-phone-password/src/PhoneVerification/index.tsx +++ b/examples/with-phone-password/src/PhoneVerification/index.tsx @@ -1,11 +1,14 @@ import { useState, useEffect } from "react"; import { useSessionContext } from "supertokens-auth-react/recipe/session"; -import { SignInUp, SignInUpTheme } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; import * as reactRouterDom from "react-router-dom"; +import { AuthPage, AuthPageTheme, AuthPageThemeProps } from "supertokens-auth-react/ui"; +import Passwordless from "supertokens-auth-react/recipe/passwordless"; +import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; -const CustomSignInUpTheme: typeof SignInUpTheme = (props) => { +const CustomSignInUpTheme = (props: AuthPageThemeProps) => { let [showDefaultUI, setShowDefaultUI] = useState(false); const session = useSessionContext(); + useEffect(() => { let aborting = false; async function effect() { @@ -13,20 +16,37 @@ const CustomSignInUpTheme: typeof SignInUpTheme = (props) => { return; } - let phoneNumber = session.accessTokenPayload.phoneNumber; + const phoneNumber = session.accessTokenPayload.phoneNumber; + // If don't have a phone number we show the default UI (which should be the phone form) if (phoneNumber === undefined) { setShowDefaultUI(true); } else { - // we start the OTP flow if it's not started already - if (props.featureState.loginAttemptInfo === undefined) { - await props.recipeImplementation.createCode({ phoneNumber, userContext: props.userContext }); - } + const attemptInfo = await Passwordless.getLoginAttemptInfo(); if (aborting) { return; } - // if we have an OTP flow (or we just started one) we show the default UI which should be the OTP input + if (attemptInfo === undefined) { + const additionalAttemptInfo = { + lastResend: Date.now(), + contactMethod: "PHONE", + contactInfo: phoneNumber, + redirectToPath: "/dashboard", + }; + + const res = await Passwordless.createCode({ + phoneNumber, + userContext: { additionalAttemptInfo }, + }); + + if (res.status === "OK") { + props.rebuildAuthPage(); + } else { + props.onError(res.reason ?? res.status); + } + } + // if we have an OTP flow (or we just tried to start one) we show the default UI which should be the OTP input setShowDefaultUI(true); } } @@ -34,10 +54,10 @@ const CustomSignInUpTheme: typeof SignInUpTheme = (props) => { return () => { aborting = true; }; - }, []); + }, [session.loading]); if (showDefaultUI) { - return ; + return ; } return <>; }; @@ -46,11 +66,15 @@ export default function PhoneVerification() { const navigate = reactRouterDom.useNavigate(); return ( - + { // @ts-ignore We ignore the error about missing props, since they'll be set by the feature component } - + ); } diff --git a/examples/with-sign-in-up-split-emailpassword/src/App.js b/examples/with-sign-in-up-split-emailpassword/src/App.js index bd487ddd7..012391ebe 100644 --- a/examples/with-sign-in-up-split-emailpassword/src/App.js +++ b/examples/with-sign-in-up-split-emailpassword/src/App.js @@ -1,8 +1,12 @@ import { useState, useEffect } from "react"; import "./App.css"; import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -import EmailPassword, { EmailPasswordComponentsOverrideProvider } from "supertokens-auth-react/recipe/emailpassword"; +import { + getSuperTokensRoutesForReactRouterDom, + AuthPage, + AuthRecipeComponentsOverrideContextProvider, +} from "supertokens-auth-react/ui"; +import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import Session, { SessionAuth } from "supertokens-auth-react/recipe/session"; import Home from "./Home"; @@ -28,41 +32,23 @@ SuperTokens.init({ apiDomain: getApiDomain(), // TODO: Change to your app's API domain websiteDomain: getWebsiteDomain(), // TODO: Change to your app's website domain }, - recipeList: [ - EmailPassword.init({ - signInAndUpFeature: { - disableDefaultImplementation: true, // this disables showing the default sign in + sign up component - - signInForm: { - // hides the element that shows switch to the sign up form and the divider in the sign in UI - style: ` - [data-supertokens~=headerSubtitle] { - display: none; - } - [data-supertokens~=divider] { - display: none; - } - `, - }, - signUpForm: { - // hides the element that shows switch to the sign in form - style: ` - [data-supertokens~=headerSubtitle] { - display: none; - } - `, - }, - }, - getRedirectionURL: function (context) { - if (context.action === "SIGN_IN_AND_UP") { - // if the user is not logged in, we want to send - // them to the sign in page. - return "/signin"; - } - }, - }), - Session.init(), - ], + disableAuthRoute: true, + style: ` + [data-supertokens~=headerSubtitle] { + display: none; + } + [data-supertokens~=divider] { + display: none; + } + `, + getRedirectionURL: function (context) { + if (context.action === "TO_AUTH") { + // if the user is not logged in, we want to send + // them to the sign in page. + return "/signin"; + } + }, + recipeList: [EmailPassword.init(), Session.init()], }); function App() { @@ -70,59 +56,23 @@ function App() { return ( - { - /* if the user visits the /signin route, we want to show the - default implementation. If thy visit the /signup - route (which also renders the component), - we want to show the sign up UI, so we set the query parm to ?show=signup - which shows the sign up UI. - */ - const [showUI, setShowUI] = useState(false); - useEffect(() => { - if (window.location.pathname === "/signin") { - setShowUI(true); - } else if (window.location.pathname === "/signup") { - window.location.href = "/signup?show=signup"; - } else { - setShowUI(true); - } - }, []); - if (showUI) { - return ; - } else { - return null; - } - }, - EmailPasswordSignUp_Override: ({ DefaultComponent, ...props }) => { - /* if the user visits the /signup route, we want to show the - default implementation. If thy visit the /signin?show=signup - route, we want to show the sign in UI, so we redirect them to /signin - which shows the sign in UI. - */ - const [showUI, setShowUI] = useState(false); - useEffect(() => { - if (window.location.pathname === "/signup") { - setShowUI(true); - } else if (window.location.pathname === "/signin") { - window.location.href = "/signin"; - } else { - setShowUI(true); - } - }, []); - if (showUI) { - return ; - } else { - return null; - } - }, - EmailPasswordSignInHeader_Override: ({ DefaultComponent, ...props }) => { - return ( + AuthPageHeader_Override: ({ DefaultComponent, ...props }) => { + return props.isSignUp ? ( +
+ + Click{" "} + + here + {" "} + to sign in +
+ ) : (
Click{" "} - + here {" "} to sign up @@ -143,8 +93,8 @@ function App() { path="/" element={ /* This protects the "/" route so that it shows - only if the user is logged in. - Else it redirects the user to "/auth" */ + only if the user is logged in. + Else it redirects the user to "/auth" */ { updateShowSessionExpiredPopup(true); @@ -155,20 +105,26 @@ function App() { } /> {/* we want to render the sign in component in /signin. - We will override the component to only show the sign in - UI on this route. See the init function call above for how to do this*/} - } /> + We will override the component to only show the sign in + UI on this route. See the init function call above for how to do this*/} + } + /> {/* we want to render the sign up component in /signup. - We will override the component to only show the sign up - UI on this route. See the init function call above for how to do this*/} - } /> + We will override the component to only show the sign up + UI on this route. See the init function call above for how to do this*/} + } + />
- + ); } diff --git a/examples/with-sign-in-up-split-emailpassword/src/Home/index.js b/examples/with-sign-in-up-split-emailpassword/src/Home/index.js index 9fe0d1733..3b232a4fd 100644 --- a/examples/with-sign-in-up-split-emailpassword/src/Home/index.js +++ b/examples/with-sign-in-up-split-emailpassword/src/Home/index.js @@ -3,7 +3,7 @@ import Logout from "./Logout"; import SuccessView from "./SuccessView"; import { useSessionContext } from "supertokens-auth-react/recipe/session"; import { useNavigate } from "react-router-dom"; -import { signOut } from "supertokens-auth-react/recipe/emailpassword"; +import { signOut } from "supertokens-auth-react/recipe/session"; export default function Home() { const session = useSessionContext(); diff --git a/examples/with-supabase/config/backendConfig.ts b/examples/with-supabase/config/backendConfig.ts index a8f4dd4f6..cf617714a 100644 --- a/examples/with-supabase/config/backendConfig.ts +++ b/examples/with-supabase/config/backendConfig.ts @@ -1,4 +1,5 @@ -import ThirdPartyEmailPasswordNode from "supertokens-node/recipe/thirdpartyemailpassword"; +import ThirdPartyNode from "supertokens-node/recipe/thirdparty"; +import EmailPasswordNode from "supertokens-node/recipe/emailpassword"; import SessionNode from "supertokens-node/recipe/session"; import EmailVerificationNode from "supertokens-node/recipe/emailverification"; import { appInfo } from "./appInfo"; @@ -18,60 +19,96 @@ export let backendConfig = (): TypeInput => { EmailVerificationNode.init({ mode: "REQUIRED", }), - ThirdPartyEmailPasswordNode.init({ - providers: [ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - config: { - thirdPartyId: "google", - clients: [ - { - clientId: process.env.GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, - }, - ], + EmailPasswordNode.init({ + override: { + apis: (originalImplementation) => ({ + ...originalImplementation, + + signUpPOST: async function (input) { + if (originalImplementation.signUpPOST === undefined) { + throw Error("Should never come here"); + } + + let response = await originalImplementation.signUpPOST(input); + + if (response.status === "OK") { + // retrieve the accessTokenPayload from the user's session + const accessTokenPayload = response.session.getAccessTokenPayload(); + + // create a supabase client with the supabase_token from the accessTokenPayload + const supabase = getSupabase(accessTokenPayload.supabase_token); + + // store the user's email mapped to their userId in Supabase + const { error } = await supabase + .from("users") + .insert({ email: response.user.emails[0], user_id: response.user.id }); + + if (error !== null) { + throw error; + } + } + + return response; }, - }, - { - config: { - thirdPartyId: "github", - clients: [ - { - clientId: process.env.GITHUB_CLIENT_ID, - clientSecret: process.env.GITHUB_CLIENT_SECRET, - }, - ], + }), + }, + }), + ThirdPartyNode.init({ + signInAndUpFeature: { + providers: [ + // We have provided you with development keys which you can use for testing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + { + config: { + thirdPartyId: "google", + clients: [ + { + clientId: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + }, + ], + }, }, - }, - { - config: { - thirdPartyId: "apple", - clients: [ - { - clientId: process.env.APPLE_CLIENT_ID, - additionalConfig: { - keyId: process.env.APPLE_KEY_ID, - privateKey: process.env.APPLE_PRIVATE_KEY.replace(/\\n/g, "\n"), - teamId: process.env.APPLE_TEAM_ID, + { + config: { + thirdPartyId: "github", + clients: [ + { + clientId: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET, + }, + ], + }, + }, + { + config: { + thirdPartyId: "apple", + clients: [ + { + clientId: process.env.APPLE_CLIENT_ID, + additionalConfig: { + keyId: process.env.APPLE_KEY_ID, + privateKey: process.env.APPLE_PRIVATE_KEY.replace(/\\n/g, "\n"), + teamId: process.env.APPLE_TEAM_ID, + }, }, - }, - ], + ], + }, }, - }, - ], + ], + }, override: { apis: (originalImplementation) => { return { ...originalImplementation, - // the thirdPartySignInUpPost function handles sign up/in via Social login - thirdPartySignInUpPOST: async function (input) { - if (originalImplementation.thirdPartySignInUpPOST === undefined) { + // the signInUpPost function handles sign up/in via Social login + signInUpPOST: async function (input) { + if (originalImplementation.signInUpPOST === undefined) { throw Error("Should never come here"); } // call the sign up/in api for social login - let response = await originalImplementation.thirdPartySignInUpPOST(input); + let response = await originalImplementation.signInUpPOST(input); // check that there is no issue with sign up and that a new user is created if (response.status === "OK" && response.createdNewRecipeUser) { @@ -84,35 +121,7 @@ export let backendConfig = (): TypeInput => { // store the user's email mapped to their userId in Supabase const { error } = await supabase .from("users") - .insert({ email: response.user.email, user_id: response.user.id }); - - if (error !== null) { - throw error; - } - } - - return response; - }, - - // the emailPasswordSignUpPOST function handles sign up via Email-Password - emailPasswordSignUpPOST: async function (input) { - if (originalImplementation.emailPasswordSignUpPOST === undefined) { - throw Error("Should never come here"); - } - - let response = await originalImplementation.emailPasswordSignUpPOST(input); - - if (response.status === "OK") { - // retrieve the accessTokenPayload from the user's session - const accessTokenPayload = response.session.getAccessTokenPayload(); - - // create a supabase client with the supabase_token from the accessTokenPayload - const supabase = getSupabase(accessTokenPayload.supabase_token); - - // store the user's email mapped to their userId in Supabase - const { error } = await supabase - .from("users") - .insert({ email: response.user.email, user_id: response.user.id }); + .insert({ email: response.user.emails[0], user_id: response.user.id }); if (error !== null) { throw error; diff --git a/examples/with-supabase/config/frontendConfig.ts b/examples/with-supabase/config/frontendConfig.ts index 69e6f28e4..4c63549be 100644 --- a/examples/with-supabase/config/frontendConfig.ts +++ b/examples/with-supabase/config/frontendConfig.ts @@ -1,4 +1,5 @@ -import ThirdPartyEmailPasswordReact from "supertokens-auth-react/recipe/thirdpartyemailpassword"; +import ThirdPartyReact from "supertokens-auth-react/recipe/thirdparty"; +import EmailPasswordReact from "supertokens-auth-react/recipe/emailpassword"; import SessionReact from "supertokens-auth-react/recipe/session"; import EmailVerificationReact from "supertokens-auth-react/recipe/emailverification"; import { appInfo } from "./appInfo"; @@ -10,15 +11,16 @@ export let frontendConfig = () => { EmailVerificationReact.init({ mode: "REQUIRED", }), - ThirdPartyEmailPasswordReact.init({ + ThirdPartyReact.init({ signInAndUpFeature: { providers: [ - ThirdPartyEmailPasswordReact.Google.init(), - ThirdPartyEmailPasswordReact.Github.init(), - ThirdPartyEmailPasswordReact.Apple.init(), + ThirdPartyReact.Google.init(), + ThirdPartyReact.Github.init(), + ThirdPartyReact.Apple.init(), ], }, }), + EmailPasswordReact.init(), SessionReact.init(), ], }; diff --git a/examples/with-supabase/pages/auth/[[...path]].tsx b/examples/with-supabase/pages/auth/[[...path]].tsx index 82392caec..adb50a706 100644 --- a/examples/with-supabase/pages/auth/[[...path]].tsx +++ b/examples/with-supabase/pages/auth/[[...path]].tsx @@ -5,18 +5,19 @@ import dynamic from "next/dynamic"; import SuperTokens from "supertokens-auth-react"; import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui"; import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; +import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; +import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; const SuperTokensComponentNoSSR = dynamic<{}>( new Promise((res) => - res(() => getRoutingComponent([EmailVerificationPreBuiltUI, ThirdPartyEmailPasswordPreBuiltUI])) + res(() => getRoutingComponent([EmailVerificationPreBuiltUI, ThirdPartyPreBuiltUI, EmailPasswordPreBuiltUI])) ), { ssr: false } ); export default function Auth() { useEffect(() => { - if (canHandleRoute([EmailVerificationPreBuiltUI, ThirdPartyEmailPasswordPreBuiltUI]) === false) { + if (canHandleRoute([EmailVerificationPreBuiltUI, ThirdPartyPreBuiltUI, EmailPasswordPreBuiltUI]) === false) { SuperTokens.redirectToAuth(); } }, []); diff --git a/examples/with-supabase/pages/index.tsx b/examples/with-supabase/pages/index.tsx index 43f86ceaa..55447005a 100644 --- a/examples/with-supabase/pages/index.tsx +++ b/examples/with-supabase/pages/index.tsx @@ -2,9 +2,8 @@ import React, { useState, useEffect } from "react"; import Head from "next/head"; import styles from "../styles/Home.module.css"; import { redirectToAuth } from "supertokens-auth-react"; -import ThirdPartyEmailPassword from "supertokens-auth-react/recipe/thirdpartyemailpassword"; import dynamic from "next/dynamic"; -import { useSessionContext, SessionAuth } from "supertokens-auth-react/recipe/session"; +import { useSessionContext, SessionAuth, signOut } from "supertokens-auth-react/recipe/session"; import { getSupabase } from "../utils/supabase"; export default function Home() { @@ -46,7 +45,7 @@ function ProtectedPage() { }, [sessionContext]); async function logoutClicked() { - await ThirdPartyEmailPassword.signOut(); + await signOut(); redirectToAuth(); } diff --git a/examples/with-svelte-react-thirdpartyemailpassword/server.js b/examples/with-svelte-react-thirdpartyemailpassword/server.js index 0b6e2964b..457ed0bf5 100644 --- a/examples/with-svelte-react-thirdpartyemailpassword/server.js +++ b/examples/with-svelte-react-thirdpartyemailpassword/server.js @@ -2,7 +2,8 @@ const express = require("express"); const supertokens = require("supertokens-node"); const Session = require("supertokens-node/recipe/session"); const EmailVerification = require("supertokens-node/recipe/emailverification"); -const ThirdPartyEmailPassword = require("supertokens-node/recipe/thirdpartyemailpassword"); +const ThirdParty = require("supertokens-node/recipe/thirdparty"); +const EmailPassword = require("supertokens-node/recipe/emailpassword"); const { middleware, errorHandler } = require("supertokens-node/framework/express"); const cors = require("cors"); const Dashboard = require("supertokens-node/recipe/dashboard"); @@ -26,33 +27,37 @@ supertokens.init({ websiteDomain, // TODO: Change to your app's website domain }, recipeList: [ - ThirdPartyEmailPassword.init({ - providers: [ - // We have provided you with development keys which you can use for testsing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - config: { - thirdPartyId: "google", - clients: [ - { - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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", - }, - ], + EmailPassword.init(), + ThirdParty.init({ + signInAndUpFeature: { + providers: [ + // We have provided you with development keys which you can use for testsing. + // IMPORTANT: Please replace them with your own OAuth keys for production use. + { + config: { + thirdPartyId: "google", + clients: [ + { + clientId: + "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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", + }, + ], + }, }, - }, - { - config: { - thirdPartyId: "github", - clients: [ - { - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - ], + { + config: { + thirdPartyId: "github", + clients: [ + { + clientId: "467101b197249757c71f", + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + }, + ], + }, }, - }, - ], + ], + }, }), Session.init(), // initializes session features Dashboard.init(), diff --git a/examples/with-svelte-react-thirdpartyemailpassword/src/App.svelte b/examples/with-svelte-react-thirdpartyemailpassword/src/App.svelte index 97f0d1e53..e568cb68f 100644 --- a/examples/with-svelte-react-thirdpartyemailpassword/src/App.svelte +++ b/examples/with-svelte-react-thirdpartyemailpassword/src/App.svelte @@ -4,12 +4,14 @@ import ReactDOM from "react-dom"; import SuperTokens from "supertokens-auth-react"; import { getRoutingComponent, canHandleRoute } from "supertokens-auth-react/ui"; - import ThirdPartyEmailPassword, { + import ThirdParty, { Github, Google, signOut, - } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; - import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; + } from "supertokens-auth-react/recipe/thirdparty"; + import EmailPassword from "supertokens-auth-react/recipe/emailpassword"; + import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui"; + import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui"; import Session from "supertokens-auth-react/recipe/session"; import { Router, Route } from "svelte-navigator"; import { onMount } from "svelte"; @@ -22,7 +24,8 @@ websiteDomain: "http://localhost:8080", // TODO: Change to your app's website domain }, recipeList: [ - ThirdPartyEmailPassword.init({ + EmailPassword.init(), + ThirdParty.init({ signInAndUpFeature: { providers: [Github.init(), Google.init()], }, @@ -33,8 +36,8 @@ class SuperTokensComponent extends React.Component { render() { - if (canHandleRoute([ThirdPartyEmailPasswordPreBuiltUI])) { - return getRoutingComponent([ThirdPartyEmailPasswordPreBuiltUI]); + if (canHandleRoute([ThirdPartyPreBuiltUI, EmailPasswordPreBuiltUI])) { + return getRoutingComponent([ThirdPartyPreBuiltUI, EmailPasswordPreBuiltUI]); } return "Route not found"; } diff --git a/examples/with-svelte-react-thirdpartyemailpassword/src/Navbar.svelte b/examples/with-svelte-react-thirdpartyemailpassword/src/Navbar.svelte index 3679733ca..25ca5cba9 100644 --- a/examples/with-svelte-react-thirdpartyemailpassword/src/Navbar.svelte +++ b/examples/with-svelte-react-thirdpartyemailpassword/src/Navbar.svelte @@ -1,7 +1,7 @@