From 2a68c1f7b2708c5c3210bca5e3531838206f8a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mih=C3=A1ly=20Lengyel?= Date: Thu, 23 May 2024 22:41:37 +0200 Subject: [PATCH] feat!: remove combo recipes (#820) * feat!: remove combo recipes and add auth page feature comp (WIP) * feat!: changed pwless email or phone input to use a switcher link instead * test: update test to match new behaviour * test: update stories * fix: validation and ux fixes * fix: styling fixes * feat: added defaultToEmail to configure which input is shown first for EMAIL_OR_PHONE * test: update/extend storybook stories * feat: fix UI review comments * test: fixing combo tests * refactor: test fixes * fix: self-review cleanup * chore: add changelog * feat: review discussions/comments * fix: fix when we throw specific error message for missing prebuiltuis * test: update unit tests * test: update react-16 test app to match the main one * chore: update size limits * test: update e2e test * fix: test and fix when the continue with pwless button shows up * docs: add with-legacy-2fa example * feat: make the AuthPage re-render if queryparams are updated * test: update test to match current behaviour * docs: update example apps to latest interface * docs: fix redirection path in example * test: add missing items to override unit test * chore: update web-js dep version * docs: navigate away from secondary factor screen if it is completed in legacy-2fa example * feat: disable the thirdparty factor if both the static and tenant provider lists are empty * chore: extend changelog with a migration guide * chore: update version numbers * feat: rework how we disable thirdparty if no providers are there to save bundle size * docs: fix dependency in example * chore: update size limit --- .storybook/main.ts | 2 +- CHANGELOG.md | 140 + examples/for-tests-react-16/src/App.js | 393 +- .../src/AppWithReactDomRouter.js | 52 +- .../src/AppWithReactDomRouterV5.js | 34 +- .../src/AppWithoutRouter.js | 14 +- .../for-tests-react-16/src/testContext.js | 3 +- examples/for-tests/src/App.js | 426 +- .../for-tests/src/AppWithReactDomRouter.js | 52 +- examples/for-tests/src/AppWithoutRouter.js | 14 +- examples/for-tests/src/testContext.js | 3 +- .../with-account-linking/backend/config.ts | 59 +- .../frontend/src/LinkingPage/index.tsx | 46 +- .../frontend/src/config.tsx | 19 +- examples/with-cli-login/api-server/index.ts | 85 +- examples/with-cli-login/src/App.tsx | 18 +- examples/with-cli-login/src/Home/index.tsx | 2 +- .../src/Home/index.js | 2 +- .../api-server.js | 237 +- .../src/App.js | 28 +- .../src/CustomSignUp/index.js | 15 +- .../src/Home/index.js | 2 +- .../src/SetPassword/index.js | 25 +- .../test/basic.test.js | 5 +- .../api-server/server.ts | 57 +- .../src/App.tsx | 16 +- .../test/basic.test.js | 2 - examples/with-hash-router/backend/config.ts | 12 + .../api-server.js | 85 +- .../src/App.js | 12 +- .../src/Home/index.js | 2 +- examples/with-i18next/api-server.js | 85 +- examples/with-i18next/src/App.js | 49 +- examples/with-i18next/src/Home/index.js | 2 +- examples/with-legacy-2fa/.gitignore | 23 + examples/with-legacy-2fa/README.md | 59 + examples/with-legacy-2fa/api-server/index.ts | 253 + .../api-server/secondFactorClaim.ts | 6 + .../with-legacy-2fa/api-server/tsconfig.json | 62 + examples/with-legacy-2fa/package.json | 60 + examples/with-legacy-2fa/public/favicon.ico | Bin 0 -> 3870 bytes examples/with-legacy-2fa/public/index.html | 39 + examples/with-legacy-2fa/public/logo192.png | Bin 0 -> 5347 bytes examples/with-legacy-2fa/public/logo512.png | Bin 0 -> 9664 bytes examples/with-legacy-2fa/public/manifest.json | 25 + examples/with-legacy-2fa/public/robots.txt | 3 + examples/with-legacy-2fa/src/App.css | 27 + examples/with-legacy-2fa/src/App.test.tsx | 9 + examples/with-legacy-2fa/src/App.tsx | 154 + examples/with-legacy-2fa/src/Footer/index.tsx | 18 + .../with-legacy-2fa/src/Home/CallAPIView.tsx | 16 + examples/with-legacy-2fa/src/Home/Logout.tsx | 32 + .../with-legacy-2fa/src/Home/SuccessView.tsx | 52 + examples/with-legacy-2fa/src/Home/index.tsx | 26 + .../src/SecondFactor/index.tsx | 102 + examples/with-legacy-2fa/src/index.css | 11 + examples/with-legacy-2fa/src/index.tsx | 17 + examples/with-legacy-2fa/src/logo.svg | 1 + .../with-legacy-2fa/src/react-app-env.d.ts | 1 + .../with-legacy-2fa/src/reportWebVitals.ts | 15 + .../with-legacy-2fa/src/secondFactorClaim.tsx | 9 + examples/with-legacy-2fa/src/setupTests.ts | 5 + examples/with-legacy-2fa/test/basic.test.js | 179 + examples/with-legacy-2fa/tsconfig.json | 20 + .../backend/config.ts | 57 +- .../frontend/src/config.tsx | 13 +- .../backend/config.ts | 57 +- .../frontend/src/config.tsx | 16 +- .../src/Home/index.tsx | 2 +- examples/with-netlify/src/Home/index.tsx | 2 +- .../api-server/index.js | 131 +- .../src/App.js | 38 +- .../src/Home/index.js | 2 +- .../src/routes/dashboard.tsx | 2 +- .../api-server.js | 10 +- .../frontend/src/App.js | 10 +- .../frontend/src/AuthPage.jsx | 54 +- .../api-server.js | 10 +- .../with-one-login-per-subdomain/src/App.js | 20 +- examples/with-phone-password-mfa/src/App.tsx | 4 - .../src/Home/index.tsx | 2 +- examples/with-phone-password/README.md | 1 - examples/with-phone-password/src/App.tsx | 15 +- .../with-phone-password/src/Home/index.tsx | 2 +- .../src/PhoneVerification/index.tsx | 48 +- .../src/App.js | 146 +- .../src/Home/index.js | 2 +- .../with-supabase/config/backendConfig.ts | 153 +- .../with-supabase/config/frontendConfig.ts | 12 +- .../with-supabase/pages/auth/[[...path]].tsx | 7 +- examples/with-supabase/pages/index.tsx | 5 +- .../server.js | 57 +- .../src/App.svelte | 15 +- .../src/Navbar.svelte | 2 +- .../frontend/src/config.tsx | 2 +- .../api-server/index.ts | 115 +- .../src/App.tsx | 113 +- .../src/Home/index.tsx | 2 +- .../api-server/index.js | 94 +- .../src/Home/index.tsx | 2 +- .../src/renderer.tsx | 27 +- frontendDriverInterfaceSupported.json | 2 +- lib/build/SuperTokensBranding.js | 28 - lib/build/authCompWrapper.js | 16 + lib/build/authRecipe-shared.js | 110 +- lib/build/authRecipe-shared2.js | 133 +- lib/build/components/assets/blockedIcon.d.ts | 3 +- lib/build/components/assets/otpEmailIcon.d.ts | 3 +- lib/build/components/assets/otpSMSIcon.d.ts | 3 +- lib/build/components/assets/totpIcon.d.ts | 3 +- lib/build/components/authCompWrapper.d.ts | 9 + lib/build/emailpassword-shared.js | 26 +- lib/build/emailpassword-shared2.js | 30 +- lib/build/emailpassword-shared3.js | 689 +- lib/build/emailpassword-shared4.js | 589 +- lib/build/emailpassword-shared5.js | 920 ++- lib/build/emailpassword-shared6.js | 102 - lib/build/emailpassword-shared7.js | 1680 ---- lib/build/emailpassword-shared8.js | 63 - lib/build/emailpassword-shared9.js | 864 -- lib/build/emailpassword.js | 10 +- lib/build/emailpasswordprebuiltui.js | 1467 +++- lib/build/emailverification-shared2.js | 8 +- lib/build/emailverificationprebuiltui.js | 34 +- lib/build/genericComponentOverrideContext.js | 29 +- lib/build/index.js | 2 + lib/build/index2.js | 1444 +++- lib/build/multifactorauthprebuiltui.js | 34 +- lib/build/passwordless-shared.js | 527 ++ lib/build/passwordless-shared2.js | 771 -- lib/build/passwordless-shared3.js | 5827 ------------- lib/build/passwordless.js | 8 +- lib/build/passwordlessprebuiltui.js | 7313 ++++++++++++++++- .../recipe/authRecipe/authWidgetWrapper.d.ts | 18 - .../componentOverrideContext.d.ts | 2 +- .../components/feature/authPage/authPage.d.ts | 17 + .../theme/authPage/authPageComponentList.d.ts | 3 + .../theme/authPage/authPageFooter.d.ts | 8 + .../theme/authPage/authPageHeader.d.ts | 9 + .../components/theme/authPage/index.d.ts | 5 + .../components/theme}/themeBase.d.ts | 0 lib/build/recipe/authRecipe/index.d.ts | 1 + lib/build/recipe/authRecipe/types.d.ts | 41 +- lib/build/recipe/authRecipe/utils.d.ts | 5 + .../features/signInAndUp/index.d.ts | 35 - .../components/features/signin/index.d.ts | 21 + .../components/features/signup/index.d.ts | 21 + .../components/library/formBase.d.ts | 6 +- .../components/library/formRow.d.ts | 5 +- .../signInForm.d.ts => signIn/index.d.ts} | 7 +- .../components/themes/signInAndUp/index.d.ts | 5 - .../components/themes/signInAndUp/signIn.d.ts | 16 - .../themes/signInAndUp/signInFooter.d.ts | 4 - .../themes/signInAndUp/signInHeader.d.ts | 4 - .../components/themes/signInAndUp/signUp.d.ts | 14 - .../themes/signInAndUp/signUpFooter.d.ts | 5 - .../themes/signInAndUp/signUpHeader.d.ts | 4 - .../signUpForm.d.ts => signUp/index.d.ts} | 4 + .../components/themes/translations.d.ts | 30 +- .../recipe/emailpassword/prebuiltui.d.ts | 121 +- lib/build/recipe/emailpassword/recipe.d.ts | 3 +- lib/build/recipe/emailpassword/types.d.ts | 59 +- .../components/themes/translations.d.ts | 15 + .../recipe/emailverification/prebuiltui.d.ts | 41 + .../components/themes/translations.d.ts | 15 + lib/build/recipe/multifactorauth/index.d.ts | 14 +- .../recipe/multifactorauth/prebuiltui.d.ts | 41 +- lib/build/recipe/multifactorauth/types.d.ts | 14 +- .../dynamicLoginMethodsSpinner/index.d.ts | 5 +- .../continueWithPasswordless/index.d.ts | 13 + .../components/features/linkSent/index.d.ts | 23 + .../components/features/mfa/index.d.ts | 2 +- .../features/signInAndUp/index.d.ts | 29 +- .../features/signInAndUpEPCombo/index.d.ts | 24 + .../features/userInputCode/index.d.ts | 23 + .../continueWithPasswordless/index.d.ts | 8 + .../themes/linkClickedScreen/index.d.ts | 3 +- .../linkSent.d.ts => linkSent/index.d.ts} | 7 + .../components/themes/signInUp/index.d.ts | 14 +- .../themes/signInUp/phoneNumberInput.d.ts | 2 +- .../themes/signInUp/signInUpFooter.d.ts | 5 - .../themes/signInUp/signInUpHeader.d.ts | 2 - .../themes/signInUp/userInputCodeForm.d.ts | 7 - .../continueWithPasswordlessFooter.d.ts | 18 + .../themes/signInUpEPCombo/emailForm.d.ts | 3 + .../signInUpEPCombo/emailOrPhoneForm.d.ts | 3 + .../themes/signInUpEPCombo/index.d.ts | 8 + .../components/themes/translations.d.ts | 32 +- .../userInputCodeFormFooter.d.ts | 0 .../userInputCodeFormHeader.d.ts | 0 .../userInputCodeFormScreen.d.ts | 19 + .../defaultPhoneNumberValidator.d.ts | 3 + lib/build/recipe/passwordless/prebuiltui.d.ts | 109 +- lib/build/recipe/passwordless/recipe.d.ts | 13 +- lib/build/recipe/passwordless/types.d.ts | 167 +- lib/build/recipe/passwordless/utils.d.ts | 5 - lib/build/recipe/passwordless/validators.d.ts | 11 - lib/build/recipe/recipeModule/types.d.ts | 4 +- lib/build/recipe/recipeRouter/index.d.ts | 6 +- .../components/themes/translations.d.ts | 15 + lib/build/recipe/session/prebuiltui.d.ts | 28 + lib/build/recipe/session/types.d.ts | 2 - .../features/signInAndUp/index.d.ts | 26 +- .../components/library/providerButton.d.ts | 6 +- .../themes/signInAndUp/providersForm.d.ts | 8 +- .../themes/signInAndUp/signInAndUpHeader.d.ts | 2 - .../themes/signInAndUp/signUpFooter.d.ts | 5 - .../themes/signInAndUpCallback/index.d.ts | 5 +- .../components/themes/translations.d.ts | 21 +- lib/build/recipe/thirdparty/index.d.ts | 16 +- lib/build/recipe/thirdparty/prebuiltui.d.ts | 64 +- lib/build/recipe/thirdparty/recipe.d.ts | 3 +- lib/build/recipe/thirdparty/types.d.ts | 33 +- .../componentOverrideContext.d.ts | 9 - .../features/signInAndUp/index.d.ts | 11 - .../components/themes/signInAndUp/header.d.ts | 6 - .../components/themes/signInAndUp/index.d.ts | 8 - .../components/themes/translations.d.ts | 109 - .../functionOverrides.d.ts | 7 - .../recipe/thirdpartyemailpassword/index.d.ts | 245 - .../thirdpartyemailpassword/prebuiltui.d.ts | 80 - .../thirdpartyemailpassword/recipe.d.ts | 40 - .../emailPasswordImplementation.d.ts | 6 - .../thirdPartyImplementation.d.ts | 6 - .../recipe/thirdpartyemailpassword/types.d.ts | 112 - .../recipe/thirdpartyemailpassword/utils.d.ts | 2 - .../features/signInAndUp/index.d.ts | 11 - .../components/themes/signInUp/header.d.ts | 2 - .../components/themes/signInUp/index.d.ts | 4 - .../components/themes/themeBase.d.ts | 8 - .../components/themes/translations.d.ts | 86 - .../functionOverrides.d.ts | 7 - .../recipe/thirdpartypasswordless/index.d.ts | 242 - .../thirdpartypasswordless/prebuiltui.d.ts | 99 - .../recipe/thirdpartypasswordless/recipe.d.ts | 40 - .../passwordlessImplementation.d.ts | 6 - .../thirdPartyImplementation.d.ts | 6 - .../recipe/thirdpartypasswordless/types.d.ts | 157 - .../recipe/thirdpartypasswordless/utils.d.ts | 2 - .../totp/components/themes/translations.d.ts | 15 + lib/build/recipe/totp/prebuiltui.d.ts | 47 + lib/build/recipe/totp/recipe.d.ts | 5 +- lib/build/session-shared.js | 304 +- lib/build/session-shared2.js | 10 - lib/build/session.js | 310 +- lib/build/sessionprebuiltui.js | 23 +- lib/build/superTokens.d.ts | 6 + lib/build/thirdparty-shared.js | 17 +- lib/build/thirdparty-shared2.js | 919 --- lib/build/thirdparty.js | 3 +- lib/build/thirdpartyemailpassword-shared.js | 551 -- lib/build/thirdpartyemailpassword.js | 247 - .../thirdpartyemailpasswordprebuiltui.js | 599 -- lib/build/thirdpartypasswordless-shared.js | 491 -- lib/build/thirdpartypasswordless.js | 319 - lib/build/thirdpartypasswordlessprebuiltui.js | 717 -- lib/build/thirdpartyprebuiltui.js | 748 +- lib/build/totpprebuiltui.js | 50 +- lib/build/translation/translations.d.ts | 15 + lib/build/translations.js | 35 - lib/build/types.d.ts | 43 + lib/build/ui-entry.js | 24 +- lib/build/ui/index.d.ts | 59 +- lib/build/ui/types.d.ts | 4 - lib/build/utils.d.ts | 1 + lib/build/version.d.ts | 2 +- .../authCompWrapper.tsx} | 25 +- lib/ts/components/featureWrapper.tsx | 2 +- lib/ts/components/routingComponent.tsx | 14 + lib/ts/components/superTokensRoute.tsx | 52 +- lib/ts/components/superTokensRouteV6.tsx | 56 +- .../recipe/authRecipe/authWidgetWrapper.tsx | 103 - .../componentOverrideContext.tsx | 2 +- .../components/feature/authPage/authPage.tsx | 447 + .../theme/authPage/authPageComponentList.tsx} | 42 +- .../theme/authPage/authPageFooter.tsx} | 27 +- .../theme/authPage/authPageHeader.tsx | 80 + .../components/theme/authPage/index.tsx | 83 + .../components/theme}/themeBase.tsx | 2 +- lib/ts/recipe/authRecipe/index.ts | 1 + lib/ts/recipe/authRecipe/types.ts | 48 +- lib/ts/recipe/authRecipe/utils.ts | 38 + .../resetPasswordUsingToken/index.tsx | 2 +- .../components/features/signInAndUp/index.tsx | 376 - .../components/features/signin/index.tsx | 191 + .../components/features/signup/index.tsx | 230 + .../components/library/formBase.tsx | 130 +- .../components/library/input.tsx | 2 +- .../themes/resetPasswordUsingToken/index.tsx | 6 +- .../resetPasswordEmail.tsx | 2 +- .../submitNewPassword.tsx | 2 +- .../signInForm.tsx => signIn/index.tsx} | 28 +- .../components/themes/signInAndUp/index.tsx | 70 - .../components/themes/signInAndUp/signIn.tsx | 38 - .../themes/signInAndUp/signInFooter.tsx | 29 - .../themes/signInAndUp/signInHeader.tsx | 39 - .../components/themes/signInAndUp/signUp.tsx | 44 - .../themes/signInAndUp/signUpFooter.tsx | 54 - .../signUpForm.tsx => signUp/index.tsx} | 30 +- .../components/themes/styles.css | 16 +- .../components/themes/themeBase.tsx | 2 +- .../components/themes/translations.ts | 15 +- .../recipe/emailpassword/functionOverrides.ts | 2 - lib/ts/recipe/emailpassword/prebuiltui.tsx | 109 +- lib/ts/recipe/emailpassword/recipe.tsx | 3 + lib/ts/recipe/emailpassword/types.ts | 94 +- lib/ts/recipe/emailpassword/utils.ts | 8 - .../features/emailVerification/index.tsx | 3 +- .../themes/emailVerification/index.tsx | 7 +- .../recipe/emailverification/prebuiltui.tsx | 8 + .../features/factorChooser/index.tsx | 3 +- .../components/themes/factorChooser/index.tsx | 8 +- lib/ts/recipe/multifactorauth/prebuiltui.tsx | 14 +- lib/ts/recipe/multifactorauth/types.ts | 2 +- .../dynamicLoginMethodsSpinner/index.tsx | 4 +- .../dynamicLoginMethodsSpinner/index.tsx | 5 +- .../continueWithPasswordless/index.tsx | 48 + .../features/linkClickedScreen/index.tsx | 2 +- .../components/features/linkSent/index.tsx | 256 + .../components/features/mfa/index.tsx | 7 +- .../components/features/signInAndUp/index.tsx | 281 +- .../features/signInAndUpEPCombo/index.tsx | 331 + .../features/userInputCode/index.tsx | 258 + .../themes/continueWithPasswordless/index.tsx | 59 + .../themes/linkClickedScreen/index.tsx | 6 +- .../linkSent.tsx => linkSent/index.tsx} | 25 +- .../components/themes/mfa/index.tsx | 14 +- .../components/themes/signInUp/emailForm.tsx | 11 +- .../themes/signInUp/emailOrPhoneForm.tsx | 194 +- .../components/themes/signInUp/index.tsx | 107 +- .../components/themes/signInUp/phoneForm.tsx | 12 +- .../themes/signInUp/phoneNumberInput.tsx | 43 +- .../themes/signInUp/resendButton.tsx | 2 +- .../themes/signInUp/signInUpFooter.tsx | 54 - .../themes/signInUp/signInUpHeader.tsx | 32 - .../continueWithPasswordlessFooter.tsx | 66 + .../themes/signInUpEPCombo/emailForm.tsx | 108 + .../signInUpEPCombo/emailOrPhoneForm.tsx | 173 + .../themes/signInUpEPCombo/index.tsx | 79 + .../passwordless/components/themes/styles.css | 31 +- .../components/themes/translations.ts | 21 +- .../userInputCodeFormFooter.tsx | 0 .../userInputCodeFormHeader.tsx | 0 .../userInputCodeFormScreen.tsx} | 122 +- .../defaultPhoneNumberValidator.ts | 18 + .../recipe/passwordless/functionOverrides.ts | 1 - lib/ts/recipe/passwordless/prebuiltui.tsx | 270 +- lib/ts/recipe/passwordless/recipe.tsx | 17 +- lib/ts/recipe/passwordless/types.ts | 155 +- lib/ts/recipe/passwordless/utils.ts | 93 +- lib/ts/recipe/passwordless/validators.ts | 120 - lib/ts/recipe/recipeModule/types.ts | 4 +- lib/ts/recipe/recipeModule/utils.ts | 10 +- lib/ts/recipe/recipeRouter/index.tsx | 7 +- .../features/accessDeniedScreen/index.tsx | 3 +- .../themes/accessDeniedScreenTheme/index.tsx | 5 +- lib/ts/recipe/session/prebuiltui.tsx | 8 + lib/ts/recipe/session/types.ts | 2 - .../components/features/signInAndUp/index.tsx | 108 +- .../features/signInAndUpCallback/index.tsx | 2 +- .../components/themes/signInAndUp/index.tsx | 34 +- .../themes/signInAndUp/providersForm.tsx | 9 +- .../themes/signInAndUpCallback/index.tsx | 7 +- .../thirdparty/components/themes/styles.css | 17 +- .../components/themes/translations.ts | 7 - lib/ts/recipe/thirdparty/functionOverrides.ts | 1 - lib/ts/recipe/thirdparty/index.ts | 3 +- lib/ts/recipe/thirdparty/prebuiltui.tsx | 87 +- lib/ts/recipe/thirdparty/recipe.tsx | 4 +- lib/ts/recipe/thirdparty/types.ts | 59 +- lib/ts/recipe/thirdparty/utils.ts | 7 - .../componentOverrideContext.tsx | 7 - .../components/features/signInAndUp/index.tsx | 147 - .../components/themes/signInAndUp/header.tsx | 41 - .../components/themes/signInAndUp/index.tsx | 143 - .../components/themes/styles.css | 42 - .../components/themes/translations.ts | 11 - .../functionOverrides.ts | 171 - .../recipe/thirdpartyemailpassword/index.ts | 332 - .../thirdpartyemailpassword/prebuiltui.tsx | 191 - .../recipe/thirdpartyemailpassword/recipe.tsx | 205 - .../emailPasswordImplementation.ts | 32 - .../thirdPartyImplementation.ts | 30 - .../recipe/thirdpartyemailpassword/types.ts | 142 - .../recipe/thirdpartyemailpassword/utils.ts | 92 - .../components/features/signInAndUp/index.tsx | 176 - .../components/themes/signInUp/header.tsx | 35 - .../components/themes/signInUp/index.tsx | 212 - .../components/themes/styles.css | 42 - .../components/themes/themeBase.tsx | 41 - .../components/themes/translations.ts | 12 - .../functionOverrides.ts | 142 - lib/ts/recipe/thirdpartypasswordless/index.ts | 344 - .../thirdpartypasswordless/prebuiltui.tsx | 209 - .../recipe/thirdpartypasswordless/recipe.tsx | 206 - .../passwordlessImplementation.ts | 22 - .../thirdPartyImplementation.ts | 15 - lib/ts/recipe/thirdpartypasswordless/types.ts | 199 - lib/ts/recipe/thirdpartypasswordless/utils.ts | 77 - .../totp/components/features/mfa/index.tsx | 5 +- .../totp/components/themes/mfa/index.tsx | 11 +- lib/ts/recipe/totp/prebuiltui.tsx | 8 + lib/ts/styles/styles.css | 39 + lib/ts/superTokens.tsx | 15 + lib/ts/translation/translations.ts | 20 + lib/ts/types.ts | 53 +- lib/ts/ui/index.tsx | 50 +- lib/ts/ui/types.ts | 4 - lib/ts/utils.ts | 14 +- lib/ts/version.ts | 2 +- package-lock.json | 32 +- package.json | 40 +- recipe/thirdpartyemailpassword/index.d.ts | 17 - recipe/thirdpartyemailpassword/index.js | 20 - .../thirdpartyemailpassword/prebuiltui.d.ts | 17 - recipe/thirdpartyemailpassword/prebuiltui.js | 20 - recipe/thirdpartypasswordless/index.d.ts | 17 - recipe/thirdpartypasswordless/index.js | 20 - recipe/thirdpartypasswordless/prebuiltui.d.ts | 17 - recipe/thirdpartypasswordless/prebuiltui.js | 20 - rollup.config.mjs | 4 - stories/allrecipes.stories.tsx | 348 + stories/authPage.stories.tsx | 207 +- stories/emailpassword.stories.tsx | 6 +- stories/emailverification.stories.tsx | 4 +- stories/factorChooser.stories.tsx | 1 - stories/otpMFA.stories.tsx | 3 - stories/passwordless.stories.tsx | 9 + stories/thirdpartyemailpassword.stories.tsx | 2 +- stories/thirdpartypasswordless.stories.tsx | 16 +- stories/totpMFA.stories.tsx | 2 - stories/utils.ts | 88 +- test/constants.js | 1 + test/end-to-end/accountlinking.test.js | 20 +- test/end-to-end/getRedirectionURL.test.js | 12 +- test/end-to-end/mfa.factorscreen.otp.test.js | 97 +- test/end-to-end/mfa.factorscreen.totp.test.js | 4 +- test/end-to-end/mfa.firstFactors.test.js | 2 +- test/end-to-end/mfa.helpers.js | 14 +- test/end-to-end/mfa.signin.test.js | 6 +- ...multitenancy.dynamic_login_methods.test.js | 70 +- test/end-to-end/multitenancy.mock.test.js | 26 +- .../multitenancy.tenant_interactions.test.js | 16 +- test/end-to-end/passwordless.test.js | 636 +- .../resetpasswordusingtoken.test.js | 8 +- test/end-to-end/routing.test.js | 8 +- test/end-to-end/signin.test.js | 10 +- test/end-to-end/signup.test.js | 42 +- test/end-to-end/thirdparty.test.js | 7 +- .../thirdpartyemailpassword.test.js | 75 +- .../thirdpartypasswordless.pwless.test.js | 2 +- .../end-to-end/thirdpartypasswordless.test.js | 45 +- .../thirdpartypasswordless.tp.test.js | 15 +- test/end-to-end/userContext.test.js | 50 +- test/helpers.js | 12 +- test/server/index.js | 615 +- test/server/package-lock.json | 13 +- test/server/package.json | 2 +- test/server/utils.js | 2 + test/unit/authPage.test.tsx | 182 + test/unit/componentOverrides.test.tsx | 245 +- test/unit/exports.test.tsx | 177 +- test/unit/index.test.ts | 11 +- .../emailpassword/emailPassword.test.tsx | 10 +- .../recipe/emailpassword/signInUp.test.tsx | 32 +- .../recipe/passwordless/passwordless.test.ts | 26 - .../recipe/passwordless/signInUp.test.tsx | 27 +- test/unit/recipe/thirdparty/signInUp.test.tsx | 29 +- .../unit/recipe/thirdparty/thirdParty.test.ts | 12 - .../thirdpartyemailpassword/signInUp.test.tsx | 99 - .../thirdPartyEmailPassword.test.ts | 115 - .../thirdpartypasswordless/signInUp.test.tsx | 100 - .../thirdPartyPasswordless.test.ts | 122 - ...ctComponentsToCoverAllFirstFactors.test.ts | 72 + test/unit/ssr.test.tsx | 1 + test/with-typescript/src/App.tsx | 679 +- webJsInterfaceSupported.json | 2 +- 477 files changed, 22670 insertions(+), 27037 deletions(-) create mode 100644 examples/with-legacy-2fa/.gitignore create mode 100644 examples/with-legacy-2fa/README.md create mode 100644 examples/with-legacy-2fa/api-server/index.ts create mode 100644 examples/with-legacy-2fa/api-server/secondFactorClaim.ts create mode 100644 examples/with-legacy-2fa/api-server/tsconfig.json create mode 100644 examples/with-legacy-2fa/package.json create mode 100644 examples/with-legacy-2fa/public/favicon.ico create mode 100644 examples/with-legacy-2fa/public/index.html create mode 100644 examples/with-legacy-2fa/public/logo192.png create mode 100644 examples/with-legacy-2fa/public/logo512.png create mode 100644 examples/with-legacy-2fa/public/manifest.json create mode 100644 examples/with-legacy-2fa/public/robots.txt create mode 100644 examples/with-legacy-2fa/src/App.css create mode 100644 examples/with-legacy-2fa/src/App.test.tsx create mode 100644 examples/with-legacy-2fa/src/App.tsx create mode 100644 examples/with-legacy-2fa/src/Footer/index.tsx create mode 100644 examples/with-legacy-2fa/src/Home/CallAPIView.tsx create mode 100644 examples/with-legacy-2fa/src/Home/Logout.tsx create mode 100644 examples/with-legacy-2fa/src/Home/SuccessView.tsx create mode 100644 examples/with-legacy-2fa/src/Home/index.tsx create mode 100644 examples/with-legacy-2fa/src/SecondFactor/index.tsx create mode 100644 examples/with-legacy-2fa/src/index.css create mode 100644 examples/with-legacy-2fa/src/index.tsx create mode 100644 examples/with-legacy-2fa/src/logo.svg create mode 100644 examples/with-legacy-2fa/src/react-app-env.d.ts create mode 100644 examples/with-legacy-2fa/src/reportWebVitals.ts create mode 100644 examples/with-legacy-2fa/src/secondFactorClaim.tsx create mode 100644 examples/with-legacy-2fa/src/setupTests.ts create mode 100644 examples/with-legacy-2fa/test/basic.test.js create mode 100644 examples/with-legacy-2fa/tsconfig.json delete mode 100644 lib/build/SuperTokensBranding.js create mode 100644 lib/build/authCompWrapper.js create mode 100644 lib/build/components/authCompWrapper.d.ts delete mode 100644 lib/build/emailpassword-shared6.js delete mode 100644 lib/build/emailpassword-shared7.js delete mode 100644 lib/build/emailpassword-shared8.js delete mode 100644 lib/build/emailpassword-shared9.js delete mode 100644 lib/build/passwordless-shared2.js delete mode 100644 lib/build/passwordless-shared3.js delete mode 100644 lib/build/recipe/authRecipe/authWidgetWrapper.d.ts rename lib/build/recipe/{thirdpartypasswordless => authRecipe}/componentOverrideContext.d.ts (69%) create mode 100644 lib/build/recipe/authRecipe/components/feature/authPage/authPage.d.ts create mode 100644 lib/build/recipe/authRecipe/components/theme/authPage/authPageComponentList.d.ts create mode 100644 lib/build/recipe/authRecipe/components/theme/authPage/authPageFooter.d.ts create mode 100644 lib/build/recipe/authRecipe/components/theme/authPage/authPageHeader.d.ts create mode 100644 lib/build/recipe/authRecipe/components/theme/authPage/index.d.ts rename lib/build/recipe/{thirdpartyemailpassword/components/themes => authRecipe/components/theme}/themeBase.d.ts (100%) delete mode 100644 lib/build/recipe/emailpassword/components/features/signInAndUp/index.d.ts create mode 100644 lib/build/recipe/emailpassword/components/features/signin/index.d.ts create mode 100644 lib/build/recipe/emailpassword/components/features/signup/index.d.ts rename lib/build/recipe/emailpassword/components/themes/{signInAndUp/signInForm.d.ts => signIn/index.d.ts} (74%) delete mode 100644 lib/build/recipe/emailpassword/components/themes/signInAndUp/index.d.ts delete mode 100644 lib/build/recipe/emailpassword/components/themes/signInAndUp/signIn.d.ts delete mode 100644 lib/build/recipe/emailpassword/components/themes/signInAndUp/signInFooter.d.ts delete mode 100644 lib/build/recipe/emailpassword/components/themes/signInAndUp/signInHeader.d.ts delete mode 100644 lib/build/recipe/emailpassword/components/themes/signInAndUp/signUp.d.ts delete mode 100644 lib/build/recipe/emailpassword/components/themes/signInAndUp/signUpFooter.d.ts delete mode 100644 lib/build/recipe/emailpassword/components/themes/signInAndUp/signUpHeader.d.ts rename lib/build/recipe/emailpassword/components/themes/{signInAndUp/signUpForm.d.ts => signUp/index.d.ts} (78%) create mode 100644 lib/build/recipe/passwordless/components/features/continueWithPasswordless/index.d.ts create mode 100644 lib/build/recipe/passwordless/components/features/linkSent/index.d.ts create mode 100644 lib/build/recipe/passwordless/components/features/signInAndUpEPCombo/index.d.ts create mode 100644 lib/build/recipe/passwordless/components/features/userInputCode/index.d.ts create mode 100644 lib/build/recipe/passwordless/components/themes/continueWithPasswordless/index.d.ts rename lib/build/recipe/passwordless/components/themes/{signInUp/linkSent.d.ts => linkSent/index.d.ts} (52%) delete mode 100644 lib/build/recipe/passwordless/components/themes/signInUp/signInUpFooter.d.ts delete mode 100644 lib/build/recipe/passwordless/components/themes/signInUp/signInUpHeader.d.ts delete mode 100644 lib/build/recipe/passwordless/components/themes/signInUp/userInputCodeForm.d.ts create mode 100644 lib/build/recipe/passwordless/components/themes/signInUpEPCombo/continueWithPasswordlessFooter.d.ts create mode 100644 lib/build/recipe/passwordless/components/themes/signInUpEPCombo/emailForm.d.ts create mode 100644 lib/build/recipe/passwordless/components/themes/signInUpEPCombo/emailOrPhoneForm.d.ts create mode 100644 lib/build/recipe/passwordless/components/themes/signInUpEPCombo/index.d.ts rename lib/build/recipe/passwordless/components/themes/{signInUp => userInputCodeForm}/userInputCodeFormFooter.d.ts (100%) rename lib/build/recipe/passwordless/components/themes/{signInUp => userInputCodeForm}/userInputCodeFormHeader.d.ts (100%) create mode 100644 lib/build/recipe/passwordless/components/themes/userInputCodeForm/userInputCodeFormScreen.d.ts create mode 100644 lib/build/recipe/passwordless/defaultPhoneNumberValidator.d.ts delete mode 100644 lib/build/recipe/thirdparty/components/themes/signInAndUp/signInAndUpHeader.d.ts delete mode 100644 lib/build/recipe/thirdparty/components/themes/signInAndUp/signUpFooter.d.ts delete mode 100644 lib/build/recipe/thirdpartyemailpassword/componentOverrideContext.d.ts delete mode 100644 lib/build/recipe/thirdpartyemailpassword/components/features/signInAndUp/index.d.ts delete mode 100644 lib/build/recipe/thirdpartyemailpassword/components/themes/signInAndUp/header.d.ts delete mode 100644 lib/build/recipe/thirdpartyemailpassword/components/themes/signInAndUp/index.d.ts delete mode 100644 lib/build/recipe/thirdpartyemailpassword/components/themes/translations.d.ts delete mode 100644 lib/build/recipe/thirdpartyemailpassword/functionOverrides.d.ts delete mode 100644 lib/build/recipe/thirdpartyemailpassword/index.d.ts delete mode 100644 lib/build/recipe/thirdpartyemailpassword/prebuiltui.d.ts delete mode 100644 lib/build/recipe/thirdpartyemailpassword/recipe.d.ts delete mode 100644 lib/build/recipe/thirdpartyemailpassword/recipeImplementation/emailPasswordImplementation.d.ts delete mode 100644 lib/build/recipe/thirdpartyemailpassword/recipeImplementation/thirdPartyImplementation.d.ts delete mode 100644 lib/build/recipe/thirdpartyemailpassword/types.d.ts delete mode 100644 lib/build/recipe/thirdpartyemailpassword/utils.d.ts delete mode 100644 lib/build/recipe/thirdpartypasswordless/components/features/signInAndUp/index.d.ts delete mode 100644 lib/build/recipe/thirdpartypasswordless/components/themes/signInUp/header.d.ts delete mode 100644 lib/build/recipe/thirdpartypasswordless/components/themes/signInUp/index.d.ts delete mode 100644 lib/build/recipe/thirdpartypasswordless/components/themes/themeBase.d.ts delete mode 100644 lib/build/recipe/thirdpartypasswordless/components/themes/translations.d.ts delete mode 100644 lib/build/recipe/thirdpartypasswordless/functionOverrides.d.ts delete mode 100644 lib/build/recipe/thirdpartypasswordless/index.d.ts delete mode 100644 lib/build/recipe/thirdpartypasswordless/prebuiltui.d.ts delete mode 100644 lib/build/recipe/thirdpartypasswordless/recipe.d.ts delete mode 100644 lib/build/recipe/thirdpartypasswordless/recipeImplementation/passwordlessImplementation.d.ts delete mode 100644 lib/build/recipe/thirdpartypasswordless/recipeImplementation/thirdPartyImplementation.d.ts delete mode 100644 lib/build/recipe/thirdpartypasswordless/types.d.ts delete mode 100644 lib/build/recipe/thirdpartypasswordless/utils.d.ts delete mode 100644 lib/build/session-shared2.js delete mode 100644 lib/build/thirdparty-shared2.js delete mode 100644 lib/build/thirdpartyemailpassword-shared.js delete mode 100644 lib/build/thirdpartyemailpassword.js delete mode 100644 lib/build/thirdpartyemailpasswordprebuiltui.js delete mode 100644 lib/build/thirdpartypasswordless-shared.js delete mode 100644 lib/build/thirdpartypasswordless.js delete mode 100644 lib/build/thirdpartypasswordlessprebuiltui.js delete mode 100644 lib/build/translations.js rename lib/ts/{recipe/thirdparty/components/themes/signInAndUp/signInAndUpHeader.tsx => components/authCompWrapper.tsx} (56%) delete mode 100644 lib/ts/recipe/authRecipe/authWidgetWrapper.tsx rename lib/ts/recipe/{thirdpartypasswordless => authRecipe}/componentOverrideContext.tsx (69%) create mode 100644 lib/ts/recipe/authRecipe/components/feature/authPage/authPage.tsx rename lib/ts/recipe/{emailpassword/components/themes/signInAndUp/signUpHeader.tsx => authRecipe/components/theme/authPage/authPageComponentList.tsx} (51%) rename lib/ts/recipe/{thirdparty/components/themes/signInAndUp/signUpFooter.tsx => authRecipe/components/theme/authPage/authPageFooter.tsx} (73%) create mode 100644 lib/ts/recipe/authRecipe/components/theme/authPage/authPageHeader.tsx create mode 100644 lib/ts/recipe/authRecipe/components/theme/authPage/index.tsx rename lib/ts/recipe/{thirdpartyemailpassword/components/themes => authRecipe/components/theme}/themeBase.tsx (96%) delete mode 100644 lib/ts/recipe/emailpassword/components/features/signInAndUp/index.tsx create mode 100644 lib/ts/recipe/emailpassword/components/features/signin/index.tsx create mode 100644 lib/ts/recipe/emailpassword/components/features/signup/index.tsx rename lib/ts/recipe/emailpassword/components/themes/{signInAndUp/signInForm.tsx => signIn/index.tsx} (75%) delete mode 100644 lib/ts/recipe/emailpassword/components/themes/signInAndUp/index.tsx delete mode 100644 lib/ts/recipe/emailpassword/components/themes/signInAndUp/signIn.tsx delete mode 100644 lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInFooter.tsx delete mode 100644 lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInHeader.tsx delete mode 100644 lib/ts/recipe/emailpassword/components/themes/signInAndUp/signUp.tsx delete mode 100644 lib/ts/recipe/emailpassword/components/themes/signInAndUp/signUpFooter.tsx rename lib/ts/recipe/emailpassword/components/themes/{signInAndUp/signUpForm.tsx => signUp/index.tsx} (74%) create mode 100644 lib/ts/recipe/passwordless/components/features/continueWithPasswordless/index.tsx create mode 100644 lib/ts/recipe/passwordless/components/features/linkSent/index.tsx create mode 100644 lib/ts/recipe/passwordless/components/features/signInAndUpEPCombo/index.tsx create mode 100644 lib/ts/recipe/passwordless/components/features/userInputCode/index.tsx create mode 100644 lib/ts/recipe/passwordless/components/themes/continueWithPasswordless/index.tsx rename lib/ts/recipe/passwordless/components/themes/{signInUp/linkSent.tsx => linkSent/index.tsx} (84%) delete mode 100644 lib/ts/recipe/passwordless/components/themes/signInUp/signInUpFooter.tsx delete mode 100644 lib/ts/recipe/passwordless/components/themes/signInUp/signInUpHeader.tsx create mode 100644 lib/ts/recipe/passwordless/components/themes/signInUpEPCombo/continueWithPasswordlessFooter.tsx create mode 100644 lib/ts/recipe/passwordless/components/themes/signInUpEPCombo/emailForm.tsx create mode 100644 lib/ts/recipe/passwordless/components/themes/signInUpEPCombo/emailOrPhoneForm.tsx create mode 100644 lib/ts/recipe/passwordless/components/themes/signInUpEPCombo/index.tsx rename lib/ts/recipe/passwordless/components/themes/{signInUp => userInputCodeForm}/userInputCodeFormFooter.tsx (100%) rename lib/ts/recipe/passwordless/components/themes/{signInUp => userInputCodeForm}/userInputCodeFormHeader.tsx (100%) rename lib/ts/recipe/passwordless/components/themes/{signInUp/userInputCodeForm.tsx => userInputCodeForm/userInputCodeFormScreen.tsx} (64%) create mode 100644 lib/ts/recipe/passwordless/defaultPhoneNumberValidator.ts delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/componentOverrideContext.tsx delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/components/features/signInAndUp/index.tsx delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/components/themes/signInAndUp/header.tsx delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/components/themes/signInAndUp/index.tsx delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/components/themes/styles.css delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/components/themes/translations.ts delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/functionOverrides.ts delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/index.ts delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/prebuiltui.tsx delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/recipe.tsx delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/recipeImplementation/emailPasswordImplementation.ts delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/recipeImplementation/thirdPartyImplementation.ts delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/types.ts delete mode 100644 lib/ts/recipe/thirdpartyemailpassword/utils.ts delete mode 100644 lib/ts/recipe/thirdpartypasswordless/components/features/signInAndUp/index.tsx delete mode 100644 lib/ts/recipe/thirdpartypasswordless/components/themes/signInUp/header.tsx delete mode 100644 lib/ts/recipe/thirdpartypasswordless/components/themes/signInUp/index.tsx delete mode 100644 lib/ts/recipe/thirdpartypasswordless/components/themes/styles.css delete mode 100644 lib/ts/recipe/thirdpartypasswordless/components/themes/themeBase.tsx delete mode 100644 lib/ts/recipe/thirdpartypasswordless/components/themes/translations.ts delete mode 100644 lib/ts/recipe/thirdpartypasswordless/functionOverrides.ts delete mode 100644 lib/ts/recipe/thirdpartypasswordless/index.ts delete mode 100644 lib/ts/recipe/thirdpartypasswordless/prebuiltui.tsx delete mode 100644 lib/ts/recipe/thirdpartypasswordless/recipe.tsx delete mode 100644 lib/ts/recipe/thirdpartypasswordless/recipeImplementation/passwordlessImplementation.ts delete mode 100644 lib/ts/recipe/thirdpartypasswordless/recipeImplementation/thirdPartyImplementation.ts delete mode 100644 lib/ts/recipe/thirdpartypasswordless/types.ts delete mode 100644 lib/ts/recipe/thirdpartypasswordless/utils.ts delete mode 100644 recipe/thirdpartyemailpassword/index.d.ts delete mode 100644 recipe/thirdpartyemailpassword/index.js delete mode 100644 recipe/thirdpartyemailpassword/prebuiltui.d.ts delete mode 100644 recipe/thirdpartyemailpassword/prebuiltui.js delete mode 100644 recipe/thirdpartypasswordless/index.d.ts delete mode 100644 recipe/thirdpartypasswordless/index.js delete mode 100644 recipe/thirdpartypasswordless/prebuiltui.d.ts delete mode 100644 recipe/thirdpartypasswordless/prebuiltui.js create mode 100644 stories/allrecipes.stories.tsx create mode 100644 test/unit/authPage.test.tsx delete mode 100644 test/unit/recipe/thirdpartyemailpassword/signInUp.test.tsx delete mode 100644 test/unit/recipe/thirdpartyemailpassword/thirdPartyEmailPassword.test.ts delete mode 100644 test/unit/recipe/thirdpartypasswordless/signInUp.test.tsx delete mode 100644 test/unit/recipe/thirdpartypasswordless/thirdPartyPasswordless.test.ts create mode 100644 test/unit/selectComponentsToCoverAllFirstFactors.test.ts 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 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 zcma);c{J4h9>;%nil|2-o+rCuEF-(I%-F}ijC~o(k~HKAkr0)!FCj~d>`RtpD?8b; zXOC1OD!V*IsqUwzbMF1)-gEDD=A573Z-&G7^LoAC9|WO7Xc0Cx1g^Zu0u_SjAPB3vGa^W|sj)80f#V0@M_CAZTIO(t--xg= z!sii`1giyH7EKL_+Wi0ab<)&E_0KD!3Rp2^HNB*K2@PHCs4PWSA32*-^7d{9nH2_E zmC{C*N*)(vEF1_aMamw2A{ZH5aIDqiabnFdJ|y0%aS|64E$`s2ccV~3lR!u<){eS` z#^Mx6o(iP1Ix%4dv`t@!&Za-K@mTm#vadc{0aWDV*_%EiGK7qMC_(`exc>-$Gb9~W!w_^{*pYRm~G zBN{nA;cm^w$VWg1O^^<6vY`1XCD|s_zv*g*5&V#wv&s#h$xlUilPe4U@I&UXZbL z0)%9Uj&@yd03n;!7do+bfixH^FeZ-Ema}s;DQX2gY+7g0s(9;`8GyvPY1*vxiF&|w z>!vA~GA<~JUqH}d;DfBSi^IT*#lrzXl$fNpq0_T1tA+`A$1?(gLb?e#0>UELvljtQ zK+*74m0jn&)5yk8mLBv;=@}c{t0ztT<v;Avck$S6D`Z)^c0(jiwKhQsn|LDRY&w(Fmi91I7H6S;b0XM{e zXp0~(T@k_r-!jkLwd1_Vre^v$G4|kh4}=Gi?$AaJ)3I+^m|Zyj#*?Kp@w(lQdJZf4 z#|IJW5z+S^e9@(6hW6N~{pj8|NO*>1)E=%?nNUAkmv~OY&ZV;m-%?pQ_11)hAr0oAwILrlsGawpxx4D43J&K=n+p3WLnlDsQ$b(9+4 z?mO^hmV^F8MV{4Lx>(Q=aHhQ1){0d*(e&s%G=i5rq3;t{JC zmgbn5Nkl)t@fPH$v;af26lyhH!k+#}_&aBK4baYPbZy$5aFx4}ka&qxl z$=Rh$W;U)>-=S-0=?7FH9dUAd2(q#4TCAHky!$^~;Dz^j|8_wuKc*YzfdAht@Q&ror?91Dm!N03=4=O!a)I*0q~p0g$Fm$pmr$ zb;wD;STDIi$@M%y1>p&_>%?UP($15gou_ue1u0!4(%81;qcIW8NyxFEvXpiJ|H4wz z*mFT(qVx1FKufG11hByuX%lPk4t#WZ{>8ka2efjY`~;AL6vWyQKpJun2nRiZYDij$ zP>4jQXPaP$UC$yIVgGa)jDV;F0l^n(V=HMRB5)20V7&r$jmk{UUIe zVjKroK}JAbD>B`2cwNQ&GDLx8{pg`7hbA~grk|W6LgiZ`8y`{Iq0i>t!3p2}MS6S+ zO_ruKyAElt)rdS>CtF7j{&6rP-#c=7evGMt7B6`7HG|-(WL`bDUAjyn+k$mx$CH;q2Dz4x;cPP$hW=`pFfLO)!jaCL@V2+F)So3}vg|%O*^T1j>C2lx zsURO-zIJC$^$g2byVbRIo^w>UxK}74^TqUiRR#7s_X$e)$6iYG1(PcW7un-va-S&u zHk9-6Zn&>T==A)lM^D~bk{&rFzCi35>UR!ZjQkdSiNX*-;l4z9j*7|q`TBl~Au`5& z+c)*8?#-tgUR$Zd%Q3bs96w6k7q@#tUn`5rj+r@_sAVVLqco|6O{ILX&U-&-cbVa3 zY?ngHR@%l{;`ri%H*0EhBWrGjv!LE4db?HEWb5mu*t@{kv|XwK8?npOshmzf=vZA@ zVSN9sL~!sn?r(AK)Q7Jk2(|M67Uy3I{eRy z_l&Y@A>;vjkWN5I2xvFFTLX0i+`{qz7C_@bo`ZUzDugfq4+>a3?1v%)O+YTd6@Ul7 zAfLfm=nhZ`)P~&v90$&UcF+yXm9sq!qCx3^9gzIcO|Y(js^Fj)Rvq>nQAHI92ap=P z10A4@prk+AGWCb`2)dQYFuR$|H6iDE8p}9a?#nV2}LBCoCf(Xi2@szia7#gY>b|l!-U`c}@ zLdhvQjc!BdLJvYvzzzngnw51yRYCqh4}$oRCy-z|v3Hc*d|?^Wj=l~18*E~*cR_kU z{XsxM1i{V*4GujHQ3DBpl2w4FgFR48Nma@HPgnyKoIEY-MqmMeY=I<%oG~l!f<+FN z1ZY^;10j4M4#HYXP zw5eJpA_y(>uLQ~OucgxDLuf}fVs272FaMxhn4xnDGIyLXnw>Xsd^J8XhcWIwIoQ9} z%FoSJTAGW(SRGwJwb=@pY7r$uQRK3Zd~XbxU)ts!4XsJrCycrWSI?e!IqwqIR8+Jh zlRjZ`UO1I!BtJR_2~7AbkbSm%XQqxEPkz6BTGWx8e}nQ=w7bZ|eVP4?*Tb!$(R)iC z9)&%bS*u(lXqzitAN)Oo=&Ytn>%Hzjc<5liuPi>zC_nw;Z0AE3Y$Jao_Q90R-gl~5 z_xAb2J%eArrC1CN4G$}-zVvCqF1;H;abAu6G*+PDHSYFx@Tdbfox*uEd3}BUyYY-l zTfEsOqsi#f9^FoLO;ChK<554qkri&Av~SIM*{fEYRE?vH7pTAOmu2pz3X?Wn*!ROX ztd54huAk&mFBemMooL33RV-*1f0Q3_(7hl$<#*|WF9P!;r;4_+X~k~uKEqdzZ$5Al zV63XN@)j$FN#cCD;ek1R#l zv%pGrhB~KWgoCj%GT?%{@@o(AJGt*PG#l3i>lhmb_twKH^EYvacVY-6bsCl5*^~L0 zonm@lk2UvvTKr2RS%}T>^~EYqdL1q4nD%0n&Xqr^cK^`J5W;lRRB^R-O8b&HENO||mo0xaD+S=I8RTlIfVgqN@SXDr2&-)we--K7w= zJVU8?Z+7k9dy;s;^gDkQa`0nz6N{T?(A&Iz)2!DEecLyRa&FI!id#5Z7B*O2=PsR0 zEvc|8{NS^)!d)MDX(97Xw}m&kEO@5jqRaDZ!+%`wYOI<23q|&js`&o4xvjP7D_xv@ z5hEwpsp{HezI9!~6O{~)lLR@oF7?J7i>1|5a~UuoN=q&6N}EJPV_GD`&M*v8Y`^2j zKII*d_@Fi$+i*YEW+Hbzn{iQk~yP z>7N{S4)r*!NwQ`(qcN#8SRQsNK6>{)X12nbF`*7#ecO7I)Q$uZsV+xS4E7aUn+U(K baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 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 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 @@