diff --git a/CHANGELOG.md b/CHANGELOG.md index 581097a47..487d4922c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,104 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## [0.35.0] - 2023-09-XX + +### Overview + +#### Introducing account-linking + +With this release, we are introducing AccountLinking, this will let you: + +- link accounts automatically, +- implement manual account linking flows. + +Check our [guide](https://supertokens.com/docs/thirdpartyemailpassword/common-customizations/account-linking/overview) for more information. + +To use this you'll need compatible versions: + +- Core>=7.0.0 +- supertokens-node>=16.0.0 (support is pending in other backend SDKs) +- supertokens-website>=17.0.3 +- supertokens-web-js>=0.8.0 +- supertokens-auth-react>=0.35.0 + +### Breaking changes + +- Added support for FDI 1.18 (Node SDK>= 16.0.0), but keeping support FDI version1.17 (node >= 15.0.0, golang>=0.13, python>=0.15.0) +- User type has changed across recipes and functions: recipe specific user types have been removed and replaced by a generic one that contains more information +- `createdNewUser` has been renamed to `createdNewRecipeUser` +- `createCode`, `consumeCode`, `createPasswordlessCode` and `consumePasswordlessCode` can now return status: `SIGN_IN_UP_NOT_ALLOWED` +- `signInAndUp` and `thirdPartySignInAndUp` can now return new status: `SIGN_IN_UP_NOT_ALLOWED` +- `sendPasswordResetEmail` can now return `status: "PASSWORD_RESET_NOT_ALLOWED"` +- `signIn` and `emailPasswordSignIn` can now return `SIGN_IN_NOT_ALLOWED` +- `signUp` and `emailPasswordSignUp` can now return `SIGN_UP_NOT_ALLOWED` +- The context param of `getRedirectionURL` gets an optional `user` prop (it's always defined if `createdNewRecipeUser` is set to true) +- Added new language translation keys +- We've added error messages for all of the above error statuses. Please see the new UI [here](https://supertokens.com/docs/thirdpartyemailpassword/common-customizations/account-linking/automatic-account-linking#support-status-codes). You can change the text using the language translation feature + +### Migration + +#### New User structure + +We've added a generic `User` type instead of the old recipe specific ones. The mapping of old props to new in case you are not using account-linking: + +- `user.id` stays `user.id` +- `user.email` becomes `user.emails[0]` +- `user.phoneNumber` becomes `user.phoneNumbers[0]` +- `user.thirdParty` becomes `user.thirdParty[0]` +- `user.timeJoined` is still `user.timeJoined` +- `user.tenantIds` is still `user.tenantIds` + +#### Checking if a user signed up or signed in + +- When calling passwordless consumeCode / social login signinup APIs, you can check if a user signed up by: + +``` + // Here res refers to the result the function/api functions mentioned above. + const isNewUser = res.createdNewRecipeUser && res.user.loginMethods.length === 1; +``` + +- When calling the emailpassword sign up API, you can check if a user signed up by: + +``` + const isNewUser = res.user.loginMethods.length === 1; +``` + +- In `getRedirectionURL` + +``` +EmailPassword.init({ // This looks the same for other recipes + // Other config options. + async getRedirectionURL(context) { + if (context.action === "SUCCESS") { + if (context.isNewRecipeUser && context.user.loginMethods.length === 1) { + // new primary user + } else { + // only a recipe user was created + } + } + return undefined; + } +}) +``` + +- In `onHandleEvent`: + +``` +EmailPassword.init({ // This looks the same for other recipes + // Other config options. + onHandleEvent(context: EmailPasswordOnHandleEventContext) { + if (context.action === "SUCCESS") { + if (context.isNewRecipeUser && context.user.loginMethods.length === 1) { + // new primary user + } else { + // only a recipe user was created + } + } + }, +}) +``` + ## [0.34.2] - 2023-08-27 ### Fixes @@ -40,6 +138,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Only supporting FDI 1.17 - Backend SDKs have to be updated first to a version that supports multi-tenancy for thirdparty - supertokens-node: >= 15.0.0 + - supertokens-golang: >= 0.13.0 + - supertokens-python: >= 0.15.0 - In ThirdParty recipe, - Changed signatures of the functions `getAuthorisationURLWithQueryParamsAndSetState` diff --git a/examples/for-tests-react-16/src/App.js b/examples/for-tests-react-16/src/App.js index e69a5154e..10330bef8 100644 --- a/examples/for-tests-react-16/src/App.js +++ b/examples/for-tests-react-16/src/App.js @@ -605,7 +605,7 @@ function getEmailPasswordConfigs({ disableDefaultUI }) { getRedirectionURL: async (context) => { console.log(`ST_LOGS EMAIL_PASSWORD GET_REDIRECTION_URL ${context.action}`); if (context.action === "SUCCESS") { - setIsNewUserToStorage("emailpassword", context.isNewUser); + setIsNewUserToStorage("emailpassword", context.isNewRecipeUser); return context.redirectToPath || "/dashboard"; } }, @@ -653,6 +653,10 @@ function getThirdPartyPasswordlessConfigs({ staticProviderList, disableDefaultUI name: "Auth0", getRedirectURL: thirdPartyRedirectURL !== null ? () => thirdPartyRedirectURL : undefined, }, + { + id: "mock-provider", + name: "Mock Provider", + }, ]; if (staticProviderList) { const ids = JSON.parse(staticProviderList); @@ -728,7 +732,7 @@ function getThirdPartyPasswordlessConfigs({ staticProviderList, disableDefaultUI getRedirectionURL: async (context) => { console.log(`ST_LOGS THIRDPARTYPASSWORDLESS GET_REDIRECTION_URL ${context.action}`); if (context.action === "SUCCESS") { - setIsNewUserToStorage("thirdpartypasswordless", context.isNewUser); + setIsNewUserToStorage("thirdpartypasswordless", context.isNewRecipeUser); return context.redirectToPath || "/dashboard"; } }, @@ -812,7 +816,7 @@ function getPasswordlessConfigs({ disableDefaultUI }) { getRedirectionURL: async (context) => { console.log(`ST_LOGS PASSWORDLESS GET_REDIRECTION_URL ${context.action}`); if (context.action === "SUCCESS") { - setIsNewUserToStorage("passwordless", context.isNewUser); + setIsNewUserToStorage("passwordless", context.isNewRecipeUser); return context.redirectToPath || "/dashboard"; } }, @@ -855,6 +859,10 @@ function getThirdPartyConfigs({ staticProviderList, disableDefaultUI, thirdParty name: "Auth0", getRedirectURL: thirdPartyRedirectURL !== null ? () => thirdPartyRedirectURL : undefined, }, + { + id: "mock-provider", + name: "Mock Provider", + }, ]; if (staticProviderList) { const ids = JSON.parse(staticProviderList); @@ -874,7 +882,7 @@ function getThirdPartyConfigs({ staticProviderList, disableDefaultUI, thirdParty getRedirectionURL: async (context) => { console.log(`ST_LOGS THIRD_PARTY GET_REDIRECTION_URL ${context.action}`); if (context.action === "SUCCESS") { - setIsNewUserToStorage("thirdparty", context.isNewUser); + setIsNewUserToStorage("thirdparty", context.isNewRecipeUser); return context.redirectToPath || "/dashboard"; } }, @@ -940,6 +948,10 @@ function getThirdPartyEmailPasswordConfigs({ staticProviderList, disableDefaultU name: "Auth0", getRedirectURL: thirdPartyRedirectURL !== null ? () => thirdPartyRedirectURL : undefined, }, + { + id: "mock-provider", + name: "Mock Provider", + }, ]; if (staticProviderList) { const ids = JSON.parse(staticProviderList); @@ -953,7 +965,7 @@ function getThirdPartyEmailPasswordConfigs({ staticProviderList, disableDefaultU getRedirectionURL: async (context) => { console.log(`ST_LOGS THIRD_PARTY_EMAIL_PASSWORD GET_REDIRECTION_URL ${context.action}`); if (context.action === "SUCCESS") { - setIsNewUserToStorage("thirdpartyemailpassword", context.isNewUser); + setIsNewUserToStorage("thirdpartyemailpassword", context.isNewRecipeUser); return context.redirectToPath || "/dashboard"; } }, @@ -1104,8 +1116,8 @@ function getThirdPartyEmailPasswordConfigs({ staticProviderList, disableDefaultU }); } -function setIsNewUserToStorage(recipeName, isNewUser) { - localStorage.setItem("isNewUserCheck", `${recipeName}-${isNewUser}`); +function setIsNewUserToStorage(recipeName, isNewRecipeUser) { + localStorage.setItem("isNewUserCheck", `${recipeName}-${isNewRecipeUser}`); } window.SuperTokens = SuperTokens; diff --git a/examples/for-tests/src/App.js b/examples/for-tests/src/App.js index a453227fe..f65dac002 100644 --- a/examples/for-tests/src/App.js +++ b/examples/for-tests/src/App.js @@ -610,7 +610,7 @@ function getEmailPasswordConfigs({ disableDefaultUI }) { getRedirectionURL: async (context) => { console.log(`ST_LOGS EMAIL_PASSWORD GET_REDIRECTION_URL ${context.action}`); if (context.action === "SUCCESS") { - setIsNewUserToStorage("emailpassword", context.isNewUser); + setIsNewUserToStorage("emailpassword", context.isNewRecipeUser); return context.redirectToPath || "/dashboard"; } }, @@ -658,6 +658,10 @@ function getThirdPartyPasswordlessConfigs({ staticProviderList, disableDefaultUI name: "Auth0", getRedirectURL: thirdPartyRedirectURL !== null ? () => thirdPartyRedirectURL : undefined, }, + { + id: "mock-provider", + name: "Mock Provider", + }, ]; if (staticProviderList) { const ids = JSON.parse(staticProviderList); @@ -745,7 +749,7 @@ function getThirdPartyPasswordlessConfigs({ staticProviderList, disableDefaultUI getRedirectionURL: async (context) => { console.log(`ST_LOGS THIRDPARTYPASSWORDLESS GET_REDIRECTION_URL ${context.action}`); if (context.action === "SUCCESS") { - setIsNewUserToStorage("thirdpartypasswordless", context.isNewUser); + setIsNewUserToStorage("thirdpartypasswordless", context.isNewRecipeUser); return context.redirectToPath || "/dashboard"; } }, @@ -838,7 +842,7 @@ function getPasswordlessConfigs({ disableDefaultUI }) { getRedirectionURL: async (context) => { console.log(`ST_LOGS PASSWORDLESS GET_REDIRECTION_URL ${context.action}`); if (context.action === "SUCCESS") { - setIsNewUserToStorage("passwordless", context.isNewUser); + setIsNewUserToStorage("passwordless", context.isNewRecipeUser); return context.redirectToPath || "/dashboard"; } }, @@ -891,6 +895,10 @@ function getThirdPartyConfigs({ staticProviderList, disableDefaultUI, thirdParty ), }, + { + id: "mock-provider", + name: "Mock Provider", + }, ]; if (staticProviderList) { const ids = JSON.parse(staticProviderList); @@ -922,7 +930,7 @@ function getThirdPartyConfigs({ staticProviderList, disableDefaultUI, thirdParty getRedirectionURL: async (context) => { console.log(`ST_LOGS THIRD_PARTY GET_REDIRECTION_URL ${context.action}`); if (context.action === "SUCCESS") { - setIsNewUserToStorage("thirdparty", context.isNewUser); + setIsNewUserToStorage("thirdparty", context.isNewRecipeUser); return context.redirectToPath || "/dashboard"; } }, @@ -988,6 +996,10 @@ function getThirdPartyEmailPasswordConfigs({ staticProviderList, disableDefaultU name: "Auth0", getRedirectURL: thirdPartyRedirectURL !== null ? () => thirdPartyRedirectURL : undefined, }, + { + id: "mock-provider", + name: "Mock Provider", + }, ]; if (staticProviderList) { const ids = JSON.parse(staticProviderList); @@ -1014,7 +1026,7 @@ function getThirdPartyEmailPasswordConfigs({ staticProviderList, disableDefaultU getRedirectionURL: async (context) => { console.log(`ST_LOGS THIRD_PARTY_EMAIL_PASSWORD GET_REDIRECTION_URL ${context.action}`); if (context.action === "SUCCESS") { - setIsNewUserToStorage("thirdpartyemailpassword", context.isNewUser); + setIsNewUserToStorage("thirdpartyemailpassword", context.isNewRecipeUser); return context.redirectToPath || "/dashboard"; } }, @@ -1165,8 +1177,8 @@ function getThirdPartyEmailPasswordConfigs({ staticProviderList, disableDefaultU }); } -function setIsNewUserToStorage(recipeName, isNewUser) { - localStorage.setItem("isNewUserCheck", `${recipeName}-${isNewUser}`); +function setIsNewUserToStorage(recipeName, isNewRecipeUser) { + localStorage.setItem("isNewUserCheck", `${recipeName}-${isNewRecipeUser}`); } window.SuperTokens = SuperTokens; diff --git a/examples/with-account-linking/README.md b/examples/with-account-linking/README.md index 06201884d..cd4ab4501 100644 --- a/examples/with-account-linking/README.md +++ b/examples/with-account-linking/README.md @@ -1 +1,65 @@ -# Account linking is not supported yet, we are actively working on the feature. +![SuperTokens banner](https://raw.githubusercontent.com/supertokens/supertokens-logo/master/images/Artboard%20%E2%80%93%2027%402x.png) + +# SuperTokens Google one tap Demo app + +This demo app demonstrates the following use cases: + +- Thirdparty Login / Sign-up +- Email Password Login / Sign-up +- Logout +- Session management & Calling APIs +- Account linking + +## Project setup + +Clone the repo, enter the directory, and use `npm` to install the project dependencies: + +```bash +git clone https://github.com/supertokens/supertokens-auth-react +cd supertokens-auth-react/examples/with-account-linking +npm install +cd frontend && npm install && cd ../ +cd backend && npm install && cd ../ +``` + +## Run the demo app + +This compiles and serves the React app and starts the backend API server on port 3001. + +```bash +npm run start +``` + +The app will start on `http://localhost:3000` + +## How it works + +We are adding a new (`/link`) page where the user can add new login methods to their current user, plus enabling automatic account linking. + +### On the frontend + +The demo uses the pre-built UI, but you can always build your own UI instead. + +- We do not need any extra configuration to enable account linking +- To enable manual linking through a custom callback page, we add `getRedirectURL` to the configuration of the social login providers. +- We add a custom page (`/link`) that will: + - Get and show the login methods belonging to the current user + - Show a password form (if available) that calls `/addPassword` to add an email+password login method to the current user. + - Show a phone number form (if available) that calls `/addPhoneNumber` to associate a phone number with the current user. + - Show an "Add Google account" that start a login process through Google +- We add a custom page (`/link/tpcallback/:thirdPartyId`) that will: + - Call `/addThirdPartyUser` through a customized `ThirdPartyEmailPassword.thirdPartySignInAndUp` call + +### On the backend + +- We enable account linking by initializing the recipe and providing a `shouldDoAutomaticAccountLinking` implementation +- We add `/addPassword`, `/addPhoneNumber` and `/addThirdPartyUser` to enable manual linking from the frontend +- We add `/userInfo` so the frontend can list/show the login methods belonging to the current user. + +## Author + +Created with :heart: by the folks at supertokens.com. + +## License + +This project is licensed under the Apache 2.0 license. diff --git a/examples/with-account-linking/backend/config.ts b/examples/with-account-linking/backend/config.ts new file mode 100644 index 000000000..3d03ffb2d --- /dev/null +++ b/examples/with-account-linking/backend/config.ts @@ -0,0 +1,100 @@ +import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword"; +import Session from "supertokens-node/recipe/session"; +import Passwordless from "supertokens-node/recipe/passwordless"; +import AccountLinking from "supertokens-node/recipe/accountlinking"; +import EmailVerification from "supertokens-node/recipe/emailverification"; +import { TypeInput } from "supertokens-node/types"; +import UserMetadata from "supertokens-node/recipe/usermetadata"; +import Dashboard from "supertokens-node/recipe/dashboard"; +import { getUser } from "supertokens-node"; + +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; +} + +export const SuperTokensConfig: TypeInput = { + supertokens: { + // this is the location of the SuperTokens core. + connectionURI: "https://try.supertokens.com", + }, + appInfo: { + appName: "SuperTokens Demo App", + apiDomain: getApiDomain(), + websiteDomain: getWebsiteDomain(), + }, + // recipeList contains all the modules that you want to + // use from SuperTokens. See the full list here: https://supertokens.com/docs/guides + recipeList: [ + EmailVerification.init({ + mode: "REQUIRED", + }), + AccountLinking.init({ + shouldDoAutomaticAccountLinking: async (newAccountInfo, user, _tenantId, context) => { + if (context.doNotLink === true) { + return { + shouldAutomaticallyLink: false, + }; + } + + if (newAccountInfo.recipeUserId !== undefined && user !== undefined) { + let userId = newAccountInfo.recipeUserId.getAsString(); + let hasInfoAssociatedWithUserId = false; // TODO: add your own implementation here. + if (hasInfoAssociatedWithUserId) { + return { + // Alternatively, you can link users but then you should provide an `onAccountLinked` callback + // that implements merging the user of the two users. + shouldAutomaticallyLink: false, + }; + } + } + + return { + shouldAutomaticallyLink: true, + 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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", + }, + ], + }, + }, + { + config: { + thirdPartyId: "github", + clients: [ + { + clientId: "467101b197249757c71f", + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + }, + ], + }, + }, + ], + }), + Passwordless.init({ + contactMethod: "PHONE", + flowType: "USER_INPUT_CODE_AND_MAGIC_LINK", + }), + Session.init(), + Dashboard.init(), + ], +}; diff --git a/examples/with-account-linking/backend/index.ts b/examples/with-account-linking/backend/index.ts new file mode 100644 index 000000000..13b0cdf0e --- /dev/null +++ b/examples/with-account-linking/backend/index.ts @@ -0,0 +1,285 @@ +import express from "express"; +import cors from "cors"; +import supertokens, { getUser, listUsersByAccountInfo } from "supertokens-node"; +import { verifySession } from "supertokens-node/recipe/session/framework/express"; +import { middleware, errorHandler, SessionRequest } from "supertokens-node/framework/express"; +import { getWebsiteDomain, SuperTokensConfig } from "./config"; +import EmailVerification from "supertokens-node/recipe/emailverification"; +import AccountLinking from "supertokens-node/recipe/accountlinking"; +import Session from "supertokens-node/recipe/session"; +import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword"; +import Passwordless from "supertokens-node/recipe/passwordless"; + +supertokens.init(SuperTokensConfig); + +const app = express(); + +app.use( + cors({ + origin: getWebsiteDomain(), + allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], + methods: ["GET", "PUT", "POST", "DELETE"], + credentials: true, + }) +); + +// This exposes all the APIs from SuperTokens to the client. +app.use(middleware()); +app.use(express.json()); + +// 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.get("/userInfo", verifySession(), async (req: SessionRequest, res) => { + const session = req.session!; + const user = await getUser(session.getRecipeUserId().getAsString()); + if (!user) { + throw new Session.Error({ type: Session.Error.UNAUTHORISED, message: "user removed" }); + } + + res.json({ + user: user.toJson(), + }); +}); + +app.post("/addPassword", verifySession(), async (req: SessionRequest, res) => { + const session = req.session!; + // First we check that the current session (and the user it belongs to) can have a user linked to it. + const user = await getUser(session.getRecipeUserId().getAsString()); + if (!user) { + throw new Session.Error({ type: Session.Error.UNAUTHORISED, message: "user removed" }); + } + + const currentLoginMethod = user.loginMethods.find( + (m) => m.recipeUserId.getAsString() === session.getRecipeUserId().getAsString() + ); + if (!currentLoginMethod) { + throw new Error("This should never happen"); + } + + if (currentLoginMethod.recipeId === "emailpassword") { + return res.json({ + status: "GENERAL_ERROR", + message: "This user already logged in using a password", + }); + } + + if (!currentLoginMethod.verified) { + return res.json({ + status: "GENERAL_ERROR", + message: "You can only add a password when logged in using a verified account", + }); + } + + if (currentLoginMethod.email === undefined) { + return res.json({ + status: "GENERAL_ERROR", + message: "You can only add a password to accounts associated with email addresses", + }); + } + + // We then "add the password" by signing up with a new user + let password: string = req.body.password; + const signUpResp = await ThirdPartyEmailPassword.emailPasswordSignUp( + session.getTenantId(), + currentLoginMethod.email, + password + ); + + if (signUpResp.status === "EMAIL_ALREADY_EXISTS_ERROR") { + // In this case the current user has an email that has already signed up + // If they are not linked, the other user can be deleted on the dashboard + // (or merged here if you provide an app specific implementation, but that is a longer/separate topic) + return res.json({ + status: "GENERAL_ERROR", + message: "This user has already signed up using a password.", + }); + } + + // Here we can assume the user in signUpResp is not a primary user since it was just created + // Plus the linkAccounts checks anyway + const newRecipeUserId = signUpResp.user.loginMethods[0].recipeUserId; + + const linkResp = await AccountLinking.linkAccounts(newRecipeUserId, session.getUserId()); + if (linkResp.status !== "OK") { + return res.json({ + status: "GENERAL_ERROR", + message: `Account linking failed (${linkResp.status})`, + }); + } + // if the access token payload contains any information that'd change based on the new account, we'd want to update it here. + + return res.json({ + status: "OK", + user: linkResp.user, + }); +}); + +app.post("/addThirdPartyUser", verifySession(), async (req: SessionRequest, res) => { + // We need this because several functions below require it + const userContext = {}; + const session = req.session!; + // First we check that the current session (and the user it belongs to) can have a user linked to it. + const user = await getUser(session.getRecipeUserId().getAsString()); + if (!user) { + throw new Session.Error({ type: Session.Error.UNAUTHORISED, message: "user removed" }); + } + const loginMethod = user.loginMethods.find( + (m) => m.recipeUserId.getAsString() === session.getRecipeUserId().getAsString() + ); + if (!loginMethod) { + throw new Error("This should never happen"); + } + + if (!loginMethod.verified) { + return res.json({ + status: "GENERAL_ERROR", + message: "You can only add a password when logged in using a verified account", + }); + } + + // We then get get the user info from the third party provider + const provider = await ThirdPartyEmailPassword.thirdPartyGetProvider( + session.getTenantId(), + req.body.thirdPartyId, + req.body.clientType + ); + if (provider === undefined) { + return res.json({ + status: "GENERAL_ERROR", + message: "Unknown thirdparty provider id/client type", + }); + } + + let oAuthTokensToUse; + if ("redirectURIInfo" in req.body && req.body.redirectURIInfo !== undefined) { + oAuthTokensToUse = await provider.exchangeAuthCodeForOAuthTokens({ + redirectURIInfo: req.body.redirectURIInfo, + userContext, + }); + } else if ("oAuthTokens" in req.body && req.body.oAuthTokens !== undefined) { + oAuthTokensToUse = req.body.oAuthTokens; + } else { + throw Error("should never come here"); + } + const tpUserInfo = await provider.getUserInfo({ oAuthTokens: oAuthTokensToUse, userContext }); + let emailInfo = tpUserInfo.email; + if (emailInfo === undefined) { + return res.json({ + status: "NO_EMAIL_GIVEN_BY_PROVIDER", + }); + } + + // In general, we require email verification before linking, but we can skip it in case if the user + // already verified this address in some other way + if (!user.emails.includes(emailInfo.id) && !emailInfo.isVerified) { + return res.json({ + status: "GENERAL_ERROR", + message: "The email of the third-party account doesn't match the current user and is not verified", + }); + } + // We create the new user here + const signUpResp = await ThirdPartyEmailPassword.thirdPartyManuallyCreateOrUpdateUser( + session.getTenantId(), + req.body.thirdPartyId, + tpUserInfo.thirdPartyUserId, + emailInfo.id, + emailInfo.isVerified, + { doNotLink: true } + ); + + if (signUpResp.status !== "OK") { + return res.json(signUpResp); + } + + if (!signUpResp.createdNewRecipeUser) { + // In this case the third party user we tried to add has already signed up + // The other user can be deleted on the dashboard + // (or merged here if you provide an app specific implementation, but that is a longer/separate topic) + return res.json({ + status: "GENERAL_ERROR", + message: "This user has already signed up. Please delete it first.", + }); + } + // Now we can assume the user in signUpResp is not a primary user since it was just created + // Plus the linkAccounts core impl checks anyway + const newRecipeUserId = signUpResp.user.loginMethods[0].recipeUserId; + + const linkResp = await AccountLinking.linkAccounts(newRecipeUserId, session.getUserId()); + if (linkResp.status !== "OK") { + return res.json({ + status: "GENERAL_ERROR", + message: `Account linking failed (${linkResp.status})`, + }); + } + // if the access token payload contains any information that'd change based on the new account, we'd want to update it here. + + return res.json({ + status: "OK", + user: linkResp.user, + }); +}); + +app.post("/addPhoneNumber", verifySession(), async (req: SessionRequest, res) => { + const session = req.session!; + // First we check that the current session (and the user it belongs to) can have a user linked to it. + const user = await getUser(session.getRecipeUserId().getAsString()); + if (!user) { + throw new Session.Error({ type: Session.Error.UNAUTHORISED, message: "user removed" }); + } + const loginMethod = user.loginMethods.find( + (m) => m.recipeUserId.getAsString() === session.getRecipeUserId().getAsString() + ); + if (!loginMethod) { + throw new Error("This should never happen"); + } + + if (!loginMethod.verified) { + return res.json({ + status: "GENERAL_ERROR", + message: "You can only add a phone number when logged in using a verified account", + }); + } + + const phoneNumber = req.body.phoneNumber; + const signUpResp = await Passwordless.signInUp({ + tenantId: session.getTenantId(), + phoneNumber, + userContext: { doNotLink: true }, + }); + + if (signUpResp.createdNewRecipeUser === false) { + return res.json({ + status: "GENERAL_ERROR", + message: "You can only add a phone number to a single user", + }); + } + const newRecipeUserId = signUpResp.user.loginMethods[0].recipeUserId; + + const linkResp = await AccountLinking.linkAccounts(newRecipeUserId, session.getUserId()); + if (linkResp.status !== "OK") { + return res.json({ + status: "GENERAL_ERROR", + message: `Account linking failed (${linkResp.status})`, + }); + } + // if the access token payload contains any information that'd change based on the new account, we'd want to update it here. + + return res.json({ + status: "OK", + user: linkResp.user, + }); +}); + +// In case of session related errors, this error handler +// returns 401 to the client. +app.use(errorHandler()); + +app.listen(3001, () => console.log(`API Server listening on port 3001`)); diff --git a/examples/with-account-linking/backend/package.json b/examples/with-account-linking/backend/package.json new file mode 100644 index 000000000..59a4adeee --- /dev/null +++ b/examples/with-account-linking/backend/package.json @@ -0,0 +1,30 @@ +{ + "name": "supertokens-node", + "version": "0.0.1", + "private": true, + "description": "", + "main": "index.js", + "scripts": { + "start": "npx ts-node-dev --project ./tsconfig.json ./index.ts" + }, + "dependencies": { + "cors": "^2.8.5", + "express": "^4.18.1", + "helmet": "^5.1.0", + "morgan": "^1.10.0", + "npm-run-all": "^4.1.5", + "supertokens-node": "github:supertokens/supertokens-node#account-linking", + "ts-node-dev": "^2.0.0", + "typescript": "^4.7.2" + }, + "devDependencies": { + "@types/cors": "^2.8.12", + "@types/express": "^4.17.17", + "@types/morgan": "^1.9.3", + "@types/node": "^16.11.38", + "nodemon": "^2.0.16" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/examples/with-account-linking/backend/tsconfig.json b/examples/with-account-linking/backend/tsconfig.json new file mode 100644 index 000000000..8a91acaae --- /dev/null +++ b/examples/with-account-linking/backend/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-account-linking/frontend/.env b/examples/with-account-linking/frontend/.env new file mode 100644 index 000000000..7d910f148 --- /dev/null +++ b/examples/with-account-linking/frontend/.env @@ -0,0 +1 @@ +SKIP_PREFLIGHT_CHECK=true \ No newline at end of file diff --git a/examples/with-account-linking/frontend/.gitignore b/examples/with-account-linking/frontend/.gitignore new file mode 100644 index 000000000..4d29575de --- /dev/null +++ b/examples/with-account-linking/frontend/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/examples/with-account-linking/frontend/LICENSE.md b/examples/with-account-linking/frontend/LICENSE.md new file mode 100644 index 000000000..588f27e68 --- /dev/null +++ b/examples/with-account-linking/frontend/LICENSE.md @@ -0,0 +1,192 @@ +Copyright (c) 2020, 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 software except in compliance with the License. A copy +of the License is available below the line. + +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. + +--- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/examples/with-account-linking/frontend/package.json b/examples/with-account-linking/frontend/package.json new file mode 100644 index 000000000..cd6e6e145 --- /dev/null +++ b/examples/with-account-linking/frontend/package.json @@ -0,0 +1,46 @@ +{ + "name": "supertokens-react", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/node": "^16.11.56", + "@types/react": "^18.0.18", + "@types/react-dom": "^18.0.6", + "axios": "^0.21.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.2.1", + "react-scripts": "5.0.1", + "supertokens-auth-react": "github:supertokens/supertokens-auth-react#feat/account-linking", + "typescript": "^4.8.2", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "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" + ] + } +} diff --git a/examples/with-account-linking/frontend/public/favicon.ico b/examples/with-account-linking/frontend/public/favicon.ico new file mode 100644 index 000000000..a11777cc4 Binary files /dev/null and b/examples/with-account-linking/frontend/public/favicon.ico differ diff --git a/examples/with-account-linking/frontend/public/index.html b/examples/with-account-linking/frontend/public/index.html new file mode 100644 index 000000000..6f1f7cb51 --- /dev/null +++ b/examples/with-account-linking/frontend/public/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + React App + + + +
+ + diff --git a/examples/with-account-linking/frontend/public/manifest.json b/examples/with-account-linking/frontend/public/manifest.json new file mode 100644 index 000000000..f01493ff0 --- /dev/null +++ b/examples/with-account-linking/frontend/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-account-linking/frontend/public/robots.txt b/examples/with-account-linking/frontend/public/robots.txt new file mode 100644 index 000000000..e9e57dc4d --- /dev/null +++ b/examples/with-account-linking/frontend/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/examples/with-account-linking/frontend/src/App.css b/examples/with-account-linking/frontend/src/App.css new file mode 100644 index 000000000..8a98a2341 --- /dev/null +++ b/examples/with-account-linking/frontend/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-account-linking/frontend/src/App.tsx b/examples/with-account-linking/frontend/src/App.tsx new file mode 100644 index 000000000..6a8bcd2b7 --- /dev/null +++ b/examples/with-account-linking/frontend/src/App.tsx @@ -0,0 +1,60 @@ +import "./App.css"; +import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; +import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; +import { SessionAuth } from "supertokens-auth-react/recipe/session"; +import { Routes, BrowserRouter as Router, Route } from "react-router-dom"; +import Home from "./Home"; +import { PreBuiltUIList, SuperTokensConfig } from "./config"; +import { LinkingPage } from "./LinkingPage"; +import { LinkingCallbackPage } from "./LinkingCallbackPage"; + +SuperTokens.init(SuperTokensConfig); + +function App() { + return ( + +
+ +
+ + {/* This shows the login UI on "/auth" route */} + {getSuperTokensRoutesForReactRouterDom(require("react-router-dom"), PreBuiltUIList)} + + + + + } + /> + + + + } + /> + only if the user is logged in. + Else it redirects the user to "/auth" */ + + + + } + /> + +
+
+
+
+ ); +} + +export default App; diff --git a/examples/with-account-linking/frontend/src/Home/CallAPIView.tsx b/examples/with-account-linking/frontend/src/Home/CallAPIView.tsx new file mode 100644 index 000000000..6a9d510d4 --- /dev/null +++ b/examples/with-account-linking/frontend/src/Home/CallAPIView.tsx @@ -0,0 +1,15 @@ +import axios from "axios"; +import { getApiDomain } from "../config"; + +export default function CallAPIView() { + async function callAPIClicked() { + 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-account-linking/frontend/src/Home/Home.css b/examples/with-account-linking/frontend/src/Home/Home.css new file mode 100644 index 000000000..228e600d4 --- /dev/null +++ b/examples/with-account-linking/frontend/src/Home/Home.css @@ -0,0 +1,191 @@ +@font-face { + font-family: Menlo; + src: url("../assets/fonts/MenloRegular.ttf"); +} + +.app-container { + font-family: Rubik, sans-serif; +} + +.app-container * { + box-sizing: border-box; +} + +.bold-400 { + font-variation-settings: "wght" 400; +} + +.bold-500 { + font-variation-settings: "wght" 500; +} + +.bold-600 { + font-variation-settings: "wght" 600; +} + +#home-container { + align-items: center; + min-height: 100vh; + background: url("../assets/images/background.png"); + background-size: cover; +} + +.bold-700 { + font-variation-settings: "wght" 700; +} + +.app-container .main-container { + box-shadow: 0px 0px 60px 0px rgba(0, 0, 0, 0.16); + width: min(635px, calc(100% - 24px)); + border-radius: 16px; + margin-block-end: 159px; + background-color: #ffffff; +} + +.main-container .success-title { + line-height: 1; + padding-block: 26px; + background-color: #e7ffed; + text-align: center; + color: #3eb655; + display: flex; + justify-content: center; + align-items: flex-end; + font-size: 20px; +} + +.success-title img.success-icon { + margin-right: 8px; +} + +.main-container .inner-content { + padding-block: 48px; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.inner-content #user-id { + position: relative; + padding: 14px 17px; + border-image-slice: 1; + width: min(430px, calc(100% - 30px)); + margin-inline: auto; + margin-block: 11px 23px; + border-radius: 9px; + line-height: 1; + font-family: Menlo, serif; + cursor: text; +} + +.inner-content #user-id:before { + content: ""; + position: absolute; + inset: 0; + border-radius: 9px; + padding: 2px; + background: linear-gradient(90.31deg, #ff9933 0.11%, #ff3f33 99.82%); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; +} + +.main-container > .top-band, +.main-container > .bottom-band { + border-radius: inherit; +} + +.main-container .top-band { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.main-container .bottom-band { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.main-container .sessionButton { + box-sizing: border-box; + background: #ff9933; + border: 1px solid #ff8a15; + box-shadow: 0px 3px 6px rgba(255, 153, 51, 0.16); + border-radius: 6px; + font-size: 16px; + margin: 0.5em; + text-decoration: none; +} + +.bottom-cta-container { + display: flex; + justify-content: flex-end; + padding-inline: 21px; + background-color: #212d4f; +} + +.bottom-cta-container .view-code { + padding-block: 11px; + color: #bac9f5; + cursor: pointer; + font-size: 14px; +} + +.bottom-links-container { + display: grid; + grid-template-columns: repeat(4, auto); + margin-bottom: 22px; +} + +.bottom-links-container .link { + display: flex; + align-items: center; + margin-inline-end: 68px; + cursor: pointer; +} + +.bottom-links-container .link:last-child { + margin-right: 0; +} + +.truncate { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.separator-line { + max-width: 100%; +} + +.link .link-icon { + width: 15px; + margin-right: 5px; +} + +@media screen and (max-width: 768px) { + .bottom-links-container { + grid-template-columns: repeat(2, 1fr); + column-gap: 64px; + row-gap: 34px; + } + + .bottom-links-container .link { + margin-inline-end: 0; + } + + .separator-line { + max-width: 200px; + } +} + +@media screen and (max-width: 480px) { + #home-container { + justify-content: start; + padding-block-start: 25px; + } + + .app-container .main-container { + margin-block-end: 90px; + } +} diff --git a/examples/with-account-linking/frontend/src/Home/SuccessView.tsx b/examples/with-account-linking/frontend/src/Home/SuccessView.tsx new file mode 100644 index 000000000..5a5417afe --- /dev/null +++ b/examples/with-account-linking/frontend/src/Home/SuccessView.tsx @@ -0,0 +1,78 @@ +import { NavLink, useNavigate } from "react-router-dom"; +import { signOut } from "supertokens-auth-react/recipe/session"; +import { recipeDetails } from "../config"; +import CallAPIView from "./CallAPIView"; +import { BlogsIcon, CelebrateIcon, GuideIcon, SeparatorLine, SignOutIcon } from "../assets/images"; + +interface ILink { + name: string; + onClick: () => void; + icon: string; +} + +export default function SuccessView(props: { userId: string }) { + let userId = props.userId; + + const navigate = useNavigate(); + + async function logoutClicked() { + await signOut(); + navigate("/auth"); + } + + function openLink(url: string) { + window.open(url, "_blank"); + } + + const links: ILink[] = [ + { + name: "Blogs", + onClick: () => openLink("https://supertokens.com/blog"), + icon: BlogsIcon, + }, + { + name: "Documentation", + onClick: () => openLink(recipeDetails.docsLink), + icon: GuideIcon, + }, + { + name: "Sign Out", + onClick: logoutClicked, + icon: SignOutIcon, + }, + ]; + + return ( + <> +
+
+ Login successful Login successful +
+
+
Your userID is:
+
+ {userId} +
+
+ + + + Manage login Methods + +
+
+
+
+ {links.map((link) => ( +
+ {link.name} +
+ {link.name} +
+
+ ))} +
+ separator + + ); +} diff --git a/examples/with-account-linking/frontend/src/Home/index.tsx b/examples/with-account-linking/frontend/src/Home/index.tsx new file mode 100644 index 000000000..0c3f288e8 --- /dev/null +++ b/examples/with-account-linking/frontend/src/Home/index.tsx @@ -0,0 +1,17 @@ +import SuccessView from "./SuccessView"; +import { useSessionContext } from "supertokens-auth-react/recipe/session"; +import "./Home.css"; + +export default function Home() { + const sessionContext = useSessionContext(); + + if (sessionContext.loading === true) { + return null; + } + + return ( +
+ +
+ ); +} diff --git a/examples/with-account-linking/frontend/src/LinkingCallbackPage/index.tsx b/examples/with-account-linking/frontend/src/LinkingCallbackPage/index.tsx new file mode 100644 index 000000000..581ea6fc3 --- /dev/null +++ b/examples/with-account-linking/frontend/src/LinkingCallbackPage/index.tsx @@ -0,0 +1,37 @@ +import ThirdPartyEmailPassword from "supertokens-auth-react/recipe/thirdpartyemailpassword"; +import React from "react"; +import { useNavigate } from "react-router-dom"; +import { useAsyncCallOnMount } from "../useAsyncCallOnMount"; + +export const LinkingCallbackPage: React.FC = () => { + const navigate = useNavigate(); + + useAsyncCallOnMount( + () => + ThirdPartyEmailPassword.thirdPartySignInAndUp({ + options: { + preAPIHook: async (input) => { + const url = new URL(input.url); + url.pathname = "/addThirdPartyUser"; + input.url = url.toString(); + return input; + }, + }, + }), + (resp) => { + if (resp.status === "OK") { + navigate(`/link?success=${encodeURIComponent("Successfully added third-party account")}`); + } else if ("reason" in resp) { + navigate(`/link?error=${encodeURIComponent(resp.reason)}`); + } else { + navigate(`/link?error=${encodeURIComponent(resp.status)}`); + } + } + ); + + return ( +
+ Linking... +
+ ); +}; diff --git a/examples/with-account-linking/frontend/src/LinkingPage/index.tsx b/examples/with-account-linking/frontend/src/LinkingPage/index.tsx new file mode 100644 index 000000000..90d47a34b --- /dev/null +++ b/examples/with-account-linking/frontend/src/LinkingPage/index.tsx @@ -0,0 +1,145 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { NavLink, useLocation } from "react-router-dom"; +import { useSessionContext } from "supertokens-auth-react/recipe/session"; +import { redirectToThirdPartyLogin } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; + +import { getApiDomain } from "../config"; +import "./styles.css"; + +export const LinkingPage: React.FC = () => { + const location = useLocation(); + const sessionContext = useSessionContext(); + + const [userInfo, setUserInfo] = useState(); + + const [error, setError] = useState(new URLSearchParams(location.search).get("error")); + const [success, setSuccess] = useState(new URLSearchParams(location.search).get("success")); + + const [phoneNumber, setPhoneNumber] = useState(); + const [password, setPassword] = useState(); + + const loadUserInfo = useCallback(async () => { + const res = await fetch(`${getApiDomain()}/userInfo`); + setUserInfo(await res.json()); + }, [setUserInfo]); + + const addPassword = useCallback(async () => { + const resp = await fetch(`${getApiDomain()}/addPassword`, { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify({ + password, + }), + }); + + const respBody = await resp.json(); + if (respBody.status !== "OK") { + setSuccess(null); + setError(respBody.reason ?? respBody.message ?? respBody.status); + } else { + setSuccess("Successfully added password"); + setError(null); + } + }, [setError, setSuccess]); + + const addPhoneNumber = useCallback(async () => { + const resp = await fetch(`${getApiDomain()}/addPhoneNumber`, { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify({ + phoneNumber, + }), + }); + + const respBody = await resp.json(); + if (respBody.status !== "OK") { + setError(respBody.reason ?? respBody.message ?? respBody.status); + } else { + setSuccess("Successfully added password"); + } + loadUserInfo(); + }, [setError, setSuccess, loadUserInfo]); + + useEffect(() => { + loadUserInfo(); + }, [loadUserInfo]); + + if (sessionContext.loading === true) { + return null; + } + + 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"); + + return ( +
+ Back + {error &&
{error}
} + {success &&
{success}
} + + {userInfo === undefined ? ( +
Login methods loading...
+ ) : ( +
    + {passwordLoginMethods.map((lm: any) => ( +
    + {lm.recipeId} + {lm.recipeUserId} + Email: {lm.email} +
    + ))} + {thirdPartyLoginMethod.map((lm: any) => ( +
    + {lm.recipeId} + {lm.recipeUserId} + + Provider: {lm.thirdParty.id} ({lm.thirdParty.userId}) - Email: {lm.email} + +
    + ))} + {phoneLoginMethod.map((lm: any) => ( +
    + {lm.recipeId} + {lm.recipeUserId} + Phone number: {lm.phoneNumber} +
    + ))} +
+ )} + {passwordLoginMethods?.length === 0 && ( +
{ + addPassword(); + ev.preventDefault(); + return false; + }}> + setPassword(ev.currentTarget.value)}> + +
+ )} + {phoneLoginMethod?.length === 0 && ( +
{ + addPhoneNumber(); + ev.preventDefault(); + return false; + }}> + setPhoneNumber(ev.currentTarget.value)}> + +
+ )} + {thirdPartyLoginMethod?.length === 0 && ( +
+ +
+ )} +
+ ); +}; diff --git a/examples/with-account-linking/frontend/src/LinkingPage/styles.css b/examples/with-account-linking/frontend/src/LinkingPage/styles.css new file mode 100644 index 000000000..643f6b4dd --- /dev/null +++ b/examples/with-account-linking/frontend/src/LinkingPage/styles.css @@ -0,0 +1,62 @@ +.success-message { + line-height: 1; + padding-block: 26px; + background-color: #e7ffed; + text-align: center; + color: #3eb655; + display: flex; + justify-content: center; + align-items: flex-end; + font-size: 20px; + margin: 8px; + padding: 16px; + border-radius: 16px; +} + +.error-message { + line-height: 1; + padding-block: 26px; + background-color: #ef9a9a; + text-align: center; + color: black; + display: flex; + justify-content: center; + align-items: flex-end; + font-size: 20px; + margin: 8px; + padding: 16px; + border-radius: 16px; +} + +.login-method { + padding: 1em; + background-color: white; + margin: 1.25em; + display: grid; + grid-template-columns: 8em auto; + border-radius: 1em; +} + +.contactInfo { + grid-column: 1 / 3; + margin-top: 0.5em; +} + +.userId { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +form { + margin: 1.25em; +} + +button { + box-sizing: border-box; + border-radius: 6px; + font-size: 16px; + margin: 0.5em; + border: 1px solid black; + text-decoration: none; +} diff --git a/examples/with-account-linking/frontend/src/assets/fonts/MenloRegular.ttf b/examples/with-account-linking/frontend/src/assets/fonts/MenloRegular.ttf new file mode 100644 index 000000000..033dc6d21 Binary files /dev/null and b/examples/with-account-linking/frontend/src/assets/fonts/MenloRegular.ttf differ diff --git a/examples/with-account-linking/frontend/src/assets/images/arrow-right-icon.svg b/examples/with-account-linking/frontend/src/assets/images/arrow-right-icon.svg new file mode 100644 index 000000000..95aa1fec6 --- /dev/null +++ b/examples/with-account-linking/frontend/src/assets/images/arrow-right-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/with-account-linking/frontend/src/assets/images/background.png b/examples/with-account-linking/frontend/src/assets/images/background.png new file mode 100644 index 000000000..2147c15c2 Binary files /dev/null and b/examples/with-account-linking/frontend/src/assets/images/background.png differ diff --git a/examples/with-account-linking/frontend/src/assets/images/blogs-icon.svg b/examples/with-account-linking/frontend/src/assets/images/blogs-icon.svg new file mode 100644 index 000000000..a2fc9dd62 --- /dev/null +++ b/examples/with-account-linking/frontend/src/assets/images/blogs-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/with-account-linking/frontend/src/assets/images/celebrate-icon.svg b/examples/with-account-linking/frontend/src/assets/images/celebrate-icon.svg new file mode 100644 index 000000000..3b40b1efa --- /dev/null +++ b/examples/with-account-linking/frontend/src/assets/images/celebrate-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/with-account-linking/frontend/src/assets/images/guide-icon.svg b/examples/with-account-linking/frontend/src/assets/images/guide-icon.svg new file mode 100644 index 000000000..bd85af72b --- /dev/null +++ b/examples/with-account-linking/frontend/src/assets/images/guide-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/with-account-linking/frontend/src/assets/images/index.ts b/examples/with-account-linking/frontend/src/assets/images/index.ts new file mode 100644 index 000000000..7adf036c4 --- /dev/null +++ b/examples/with-account-linking/frontend/src/assets/images/index.ts @@ -0,0 +1,8 @@ +import SeparatorLine from "./separator-line.svg"; +import ArrowRight from "./arrow-right-icon.svg"; +import SignOutIcon from "./sign-out-icon.svg"; +import GuideIcon from "./guide-icon.svg"; +import BlogsIcon from "./blogs-icon.svg"; +import CelebrateIcon from "./celebrate-icon.svg"; + +export { SeparatorLine, ArrowRight, SignOutIcon, GuideIcon, BlogsIcon, CelebrateIcon }; diff --git a/examples/with-account-linking/frontend/src/assets/images/separator-line.svg b/examples/with-account-linking/frontend/src/assets/images/separator-line.svg new file mode 100644 index 000000000..7127a00dc --- /dev/null +++ b/examples/with-account-linking/frontend/src/assets/images/separator-line.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/with-account-linking/frontend/src/assets/images/sign-out-icon.svg b/examples/with-account-linking/frontend/src/assets/images/sign-out-icon.svg new file mode 100644 index 000000000..6cc4f85fd --- /dev/null +++ b/examples/with-account-linking/frontend/src/assets/images/sign-out-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/with-account-linking/frontend/src/config.tsx b/examples/with-account-linking/frontend/src/config.tsx new file mode 100644 index 000000000..1a6ba9d8a --- /dev/null +++ b/examples/with-account-linking/frontend/src/config.tsx @@ -0,0 +1,61 @@ +import ThirdPartyEmailPassword, { Google, Github, Apple } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; +import EmailVerification from "supertokens-auth-react/recipe/emailverification"; +import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; +import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; +import Session from "supertokens-auth-react/recipe/session"; + +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; +} + +export const SuperTokensConfig = { + appInfo: { + appName: "SuperTokens Demo App", + apiDomain: getApiDomain(), + websiteDomain: getWebsiteDomain(), + }, + // recipeList contains all the modules that you want to + // use from SuperTokens. See the full list here: https://supertokens.com/docs/guides + recipeList: [ + EmailVerification.init({ + mode: "REQUIRED", + }), + ThirdPartyEmailPassword.init({ + signInAndUpFeature: { + providers: [ + Github.init({ + getRedirectURL: (id) => { + if (window.location.pathname.startsWith("/link")) { + return `${getWebsiteDomain()}/link/tpcallback/${id}`; + } + return `${getWebsiteDomain()}/auth/callback/${id}`; + }, + }), + Google.init({ + getRedirectURL: (id) => { + if (window.location.pathname.startsWith("/link")) { + return `${getWebsiteDomain()}/link/tpcallback/${id}`; + } + return `${getWebsiteDomain()}/auth/callback/${id}`; + }, + }), + ], + }, + }), + Session.init(), + ], +}; + +export const recipeDetails = { + docsLink: "https://supertokens.com/docs/thirdpartyemailpassword/introduction", +}; + +export const PreBuiltUIList = [ThirdPartyEmailPasswordPreBuiltUI, EmailVerificationPreBuiltUI]; diff --git a/examples/with-account-linking/frontend/src/index.css b/examples/with-account-linking/frontend/src/index.css new file mode 100644 index 000000000..04146b5e7 --- /dev/null +++ b/examples/with-account-linking/frontend/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-account-linking/frontend/src/index.tsx b/examples/with-account-linking/frontend/src/index.tsx new file mode 100644 index 000000000..399c737cd --- /dev/null +++ b/examples/with-account-linking/frontend/src/index.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; + +const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); +root.render( + + + +); diff --git a/examples/with-account-linking/frontend/src/react-app-env.d.ts b/examples/with-account-linking/frontend/src/react-app-env.d.ts new file mode 100644 index 000000000..6431bc5fc --- /dev/null +++ b/examples/with-account-linking/frontend/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/with-account-linking/frontend/src/useAsyncCallOnMount.ts b/examples/with-account-linking/frontend/src/useAsyncCallOnMount.ts new file mode 100644 index 000000000..a619b8561 --- /dev/null +++ b/examples/with-account-linking/frontend/src/useAsyncCallOnMount.ts @@ -0,0 +1,35 @@ +import { useEffect, useRef } from "react"; + +export const useAsyncCallOnMount = ( + func: () => Promise, + handler: (res: T) => void, + errorHandler?: (err: any) => void +) => { + const signInUpPromise = useRef | undefined>(undefined); + + useEffect(() => { + if (signInUpPromise.current === undefined) { + signInUpPromise.current = func(); + } + const abort = new AbortController(); + + signInUpPromise.current.then( + (resp) => { + if (abort.signal.aborted) { + return; + } + handler(resp); + }, + (err) => { + if (abort.signal.aborted) { + return; + } + if (errorHandler !== undefined) { + errorHandler(err); + } + } + ); + + return () => abort.abort(); + }, [handler, errorHandler]); +}; diff --git a/examples/with-account-linking/frontend/tsconfig.json b/examples/with-account-linking/frontend/tsconfig.json new file mode 100644 index 000000000..c0555cbc6 --- /dev/null +++ b/examples/with-account-linking/frontend/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-account-linking/package.json b/examples/with-account-linking/package.json new file mode 100644 index 000000000..55d8979fe --- /dev/null +++ b/examples/with-account-linking/package.json @@ -0,0 +1,20 @@ +{ + "name": "with-account-linking", + "version": "0.0.1", + "description": "", + "main": "index.js", + "scripts": { + "start:frontend": "cd frontend && npm run start", + "start:frontend-live-demo-app": "cd frontend && npx serve -s build", + "start:backend": "cd backend && npm run start", + "start:backend-live-demo-app": "cd backend && ./startLiveDemoApp.sh", + "start": "npm-run-all --parallel start:frontend start:backend", + "start-live-demo-app": "npx npm-run-all --parallel start:frontend-live-demo-app start:backend-live-demo-app" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "npm-run-all": "^4.1.5" + } +} diff --git a/examples/with-emailpassword-vercel/test/basic.test.js b/examples/with-emailpassword-vercel/test/basic.test.js index 48073408c..ba7efd707 100644 --- a/examples/with-emailpassword-vercel/test/basic.test.js +++ b/examples/with-emailpassword-vercel/test/basic.test.js @@ -90,7 +90,11 @@ describe("SuperTokens Example Basic tests", function () { 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", userId, email); + const tokenInfo = await EmailVerification.createEmailVerificationToken( + "public", + SuperTokensNode.convertToRecipeUserId(userId), + email + ); await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo.token}`); await submitForm(page); diff --git a/examples/with-emailverification-then-password-thirdpartyemailpassword/api-server.js b/examples/with-emailverification-then-password-thirdpartyemailpassword/api-server.js index 0a8761bf7..da97da89e 100644 --- a/examples/with-emailverification-then-password-thirdpartyemailpassword/api-server.js +++ b/examples/with-emailverification-then-password-thirdpartyemailpassword/api-server.js @@ -74,7 +74,9 @@ supertokens.init({ }; } const res = await oI.emailPasswordSignInPOST(input); - await res.session.setClaimValue(RealPasswordClaim, true, input.userContext); + if (res.status === "OK") { + await res.session.setClaimValue(RealPasswordClaim, true, input.userContext); + } return res; }, emailPasswordSignUpPOST: async function (input) { @@ -101,10 +103,11 @@ supertokens.init({ if (signInResponse.status === "WRONG_CREDENTIALS_ERROR") { return response; } else { - await EmailVerification.unverifyEmail(signInResponse.user.id, email); + await EmailVerification.unverifyEmail(signInResponse.recipeUserId, email); response = { status: "OK", user: signInResponse.user, + recipeUserId: signInResponse.recipeUserId, }; } } @@ -112,23 +115,30 @@ supertokens.init({ // we have just created a user with the fake password. // so we mark their session as unusable by the APIs - await Session.createNewSession( + const newSession = await Session.createNewSession( input.options.req, input.options.res, input.tenantId, - user.id, + response.recipeUserId, { - ...RealPasswordClaim.build(user.id, input.tenantId, input.userContext), + ...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 userId = session.getUserId(); + let recipeUserId = session.getRecipeUserId(); let password = input.formFields.filter((f) => f.id === "password")[0].value; if (password === FAKE_PASSWORD) { @@ -137,16 +147,18 @@ supertokens.init({ // now we modify the user's password to the new password + change the session to set RealPasswordClaim to true await ThirdPartyEmailPassword.updateEmailOrPassword({ - userId, + recipeUserId, password, }); await session.setClaimValue(RealPasswordClaim, true); - let user = await ThirdPartyEmailPassword.getUserById(userId); + let user = await supertokens.getUser(recipeUserId.getAsString()); return { status: "OK", user, + recipeUserId, + session, }; } }, 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 7e8505be9..5e65c6b15 100644 --- a/examples/with-emailverification-then-password-thirdpartyemailpassword/test/basic.test.js +++ b/examples/with-emailverification-then-password-thirdpartyemailpassword/test/basic.test.js @@ -84,7 +84,11 @@ describe("SuperTokens Example Basic tests", function () { const userId = await page.evaluate(() => window.__supertokensSessionRecipe.getUserId()); // Create a new token and use it (we don't have access to the originally sent one) - const tokenInfo = await EmailVerification.createEmailVerificationToken("public", userId, email); + const tokenInfo = await EmailVerification.createEmailVerificationToken( + "public", + SuperTokensNode.convertToRecipeUserId(userId), + email + ); await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo.token}`); await submitForm(page); diff --git a/examples/with-emailverification-with-otp/api-server/server.ts b/examples/with-emailverification-with-otp/api-server/server.ts index 4fe454064..1b2837c81 100644 --- a/examples/with-emailverification-with-otp/api-server/server.ts +++ b/examples/with-emailverification-with-otp/api-server/server.ts @@ -22,7 +22,7 @@ supertokens.init({ framework: "express", supertokens: { // TODO: This is a core hosted for demo purposes. You can use this, but make sure to change it to your core instance URI eventually. - connectionURI: "https://try.supertokens.io", + connectionURI: "https://try.supertokens.com", apiKey: "", }, appInfo: { diff --git a/examples/with-hasura-thirdpartyemailpassword/api-server.js b/examples/with-hasura-thirdpartyemailpassword/api-server.js index 183af8171..8258e8f30 100644 --- a/examples/with-hasura-thirdpartyemailpassword/api-server.js +++ b/examples/with-hasura-thirdpartyemailpassword/api-server.js @@ -89,12 +89,12 @@ supertokens.init({ return { ...oI, createNewSession: async function (input) { - let userInfo = await ThirdPartyEmailPassword.getUserById(input.userId); + let userInfo = await supertokens.getUser(input.userId); input.accessTokenPayload = { ...input.accessTokenPayload, "https://hasura.io/jwt/claims": { - "x-hasura-user-id": userInfo.email, + "x-hasura-user-id": userInfo.emails[0], "x-hasura-default-role": "user", "x-hasura-allowed-roles": ["user"], }, diff --git a/examples/with-i18next/test/basic.test.js b/examples/with-i18next/test/basic.test.js index 548e2824c..682fca229 100644 --- a/examples/with-i18next/test/basic.test.js +++ b/examples/with-i18next/test/basic.test.js @@ -97,7 +97,11 @@ describe("SuperTokens Example Basic tests", function () { 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", userId, email); + const tokenInfo = await EmailVerification.createEmailVerificationToken( + "public", + SuperTokensNode.convertToRecipeUserId(userId), + email + ); await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo.token}`); await submitForm(page); diff --git a/examples/with-localstorage/test/basic.test.js b/examples/with-localstorage/test/basic.test.js index 48073408c..ba7efd707 100644 --- a/examples/with-localstorage/test/basic.test.js +++ b/examples/with-localstorage/test/basic.test.js @@ -90,7 +90,11 @@ describe("SuperTokens Example Basic tests", function () { 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", userId, email); + const tokenInfo = await EmailVerification.createEmailVerificationToken( + "public", + SuperTokensNode.convertToRecipeUserId(userId), + email + ); await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo.token}`); await submitForm(page); diff --git a/examples/with-multiple-email-sign-in/api-server/epOverride.ts b/examples/with-multiple-email-sign-in/api-server/epOverride.ts index 3d03c4c14..b37bd0db3 100644 --- a/examples/with-multiple-email-sign-in/api-server/epOverride.ts +++ b/examples/with-multiple-email-sign-in/api-server/epOverride.ts @@ -1,29 +1,27 @@ -import { RecipeInterface } from "supertokens-node/recipe/emailpassword"; +import { APIInterface } from "supertokens-node/recipe/emailpassword"; import { getPrimaryEmailFromInputEmail } from "./emailLinkingMap"; -export function epOverride(oI: RecipeInterface): RecipeInterface { +export function epOverride(oI: APIInterface): APIInterface { return { ...oI, - signIn: async function (input) { - let primaryEmail = getPrimaryEmailFromInputEmail(input.email); - if (primaryEmail !== undefined) { - input.email = primaryEmail; - } - return oI.signIn(input); - }, - signUp: async function (input) { - let primaryEmail = getPrimaryEmailFromInputEmail(input.email); + signInPOST: async function (input) { + const emailField = input.formFields.find((f) => f.id === "email")!; + + let primaryEmail = getPrimaryEmailFromInputEmail(emailField.value); if (primaryEmail !== undefined) { - input.email = primaryEmail; + emailField.value = primaryEmail; } - return oI.signUp(input); + return oI.signInPOST!(input); }, - getUserByEmail: async function (input) { - let primaryEmail = getPrimaryEmailFromInputEmail(input.email); + signUpPOST: async function (input) { + const emailField = input.formFields.find((f) => f.id === "email")!; + + let primaryEmail = getPrimaryEmailFromInputEmail(emailField.value); if (primaryEmail !== undefined) { - input.email = primaryEmail; + emailField.value = primaryEmail; } - return oI.getUserByEmail(input); + + return oI.signUpPOST!(input); }, }; } diff --git a/examples/with-multiple-email-sign-in/api-server/index.ts b/examples/with-multiple-email-sign-in/api-server/index.ts index d92fde8d0..f33112e46 100644 --- a/examples/with-multiple-email-sign-in/api-server/index.ts +++ b/examples/with-multiple-email-sign-in/api-server/index.ts @@ -53,7 +53,7 @@ supertokens.init({ }), EmailPassword.init({ override: { - functions: (oI) => epOverride(oI), + apis: (oI) => epOverride(oI), }, }), Session.init(), @@ -79,7 +79,7 @@ app.use(middleware()); app.post("/add-email", verifySession(), async (req: SessionRequest, res) => { let userId = req.session!.getUserId(); let emailToAdd = req.body.email; - let success = associateNewEmailWithPrimaryEmail(emailToAdd, (await EmailPassword.getUserById(userId))!.email); + let success = associateNewEmailWithPrimaryEmail(emailToAdd, (await supertokens.getUser(userId))!.emails[0]); res.send({ status: success ? "OK" : "INPUT_EMAIL_ASSOCIATED_WITH_ANOTHER_USER", }); diff --git a/examples/with-multiple-email-sign-in/test/basic.test.js b/examples/with-multiple-email-sign-in/test/basic.test.js index 5f3ea42f2..dc99f268f 100644 --- a/examples/with-multiple-email-sign-in/test/basic.test.js +++ b/examples/with-multiple-email-sign-in/test/basic.test.js @@ -90,7 +90,11 @@ describe("SuperTokens Example Basic tests", function () { 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", userId, email); + const tokenInfo = await EmailVerification.createEmailVerificationToken( + "public", + SuperTokensNode.convertToRecipeUserId(userId), + email + ); await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo.token}`); await waitForSTElement(page, "[data-supertokens='button']"); await submitForm(page); @@ -121,7 +125,11 @@ describe("SuperTokens Example Basic tests", function () { await submitForm(page); // Redirected to email verification screen await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); - const tokenInfo2 = await EmailVerification.createEmailVerificationToken("public", userId, email2); + const tokenInfo2 = await EmailVerification.createEmailVerificationToken( + "public", + SuperTokensNode.convertToRecipeUserId(userId), + email2 + ); await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo2.token}`); await waitForSTElement(page, "[data-supertokens='button']"); await submitForm(page); diff --git a/examples/with-netlify/.netlify/edge-functions-import-map.json b/examples/with-netlify/.netlify/edge-functions-import-map.json index b2f6de6db..6ac38f509 100644 --- a/examples/with-netlify/.netlify/edge-functions-import-map.json +++ b/examples/with-netlify/.netlify/edge-functions-import-map.json @@ -1 +1,7 @@ -{ "imports": { "netlify:edge": "https://edge.netlify.com/v1/index.ts" } } +{ + "imports": { + "@netlify/edge-functions": "https://edge.netlify.com/v1/index.ts", + "netlify:edge": "https://edge.netlify.com/v1/index.ts?v=legacy" + }, + "scopes": {} +} diff --git a/examples/with-netlify/test/basic.test.js b/examples/with-netlify/test/basic.test.js index 664487aa9..17eee7d77 100644 --- a/examples/with-netlify/test/basic.test.js +++ b/examples/with-netlify/test/basic.test.js @@ -71,6 +71,7 @@ describe("SuperTokens Example Basic tests", function () { describe("Email Password test", function () { it("Successful signup with credentials", async function () { + await new Promise((res) => setTimeout(res, 1000)); await Promise.all([page.goto(websiteDomain), page.waitForNavigation({ waitUntil: "networkidle0" })]); // redirected to /auth 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 0a38e6b68..48b909fe8 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 @@ -77,18 +77,18 @@ supertokens.init({ thirdPartySignInUp: async function (input) { let resp = await oI.thirdPartySignInUp(input); if (resp.status === "OK") { - if (resp.createdNewUser) { + 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.isNewUser = true; + input.userContext.isNewRecipeUser = true; } } return resp; }, emailPasswordSignUp: async function (input) { - input.userContext.isNewUser = true; + input.userContext.isNewRecipeUser = true; // we set this context here so that // the createNewSession recipe function can // check this and NOT create a new session @@ -105,7 +105,7 @@ supertokens.init({ return { ...oI, createNewSession: async function (input) { - if (input.userContext.isNewUser) { + if (input.userContext.isNewRecipeUser) { // we do not want to create a session for a new user. return { getAccessToken: () => "", @@ -115,6 +115,7 @@ supertokens.init({ getSessionDataFromDatabase: () => null, getTimeCreated: () => -1, getUserId: () => "", + getRecipeUserId: () => input.recipeUserId, revokeSession: () => {}, updateSessionDataInDatabase: () => {}, attachToRequestResponse: () => {}, 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 c89b44de5..458c9e04a 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 @@ -36,7 +36,7 @@ SuperTokens.init({ ThirdPartyEmailPassword.init({ onHandleEvent: (context) => { if (context.action === "SUCCESS") { - if (context.isNewUser) { + 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"); diff --git a/examples/with-one-login-per-subdomain/test/basic.test.js b/examples/with-one-login-per-subdomain/test/basic.test.js index e9019aa93..32412530a 100644 --- a/examples/with-one-login-per-subdomain/test/basic.test.js +++ b/examples/with-one-login-per-subdomain/test/basic.test.js @@ -95,7 +95,11 @@ describe("SuperTokens Example Basic tests", function () { 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", userId, email); + const tokenInfo = await EmailVerification.createEmailVerificationToken( + "public", + SuperTokensNode.convertToRecipeUserId(userId), + email + ); await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo.token}`); await submitForm(page); diff --git a/examples/with-phone-password/api-server/index.ts b/examples/with-phone-password/api-server/index.ts index d0ed7f3a3..4b576262c 100644 --- a/examples/with-phone-password/api-server/index.ts +++ b/examples/with-phone-password/api-server/index.ts @@ -159,6 +159,7 @@ supertokens.init({ // session's payload as PhoneVerifiedClaim: true so that // the user has access to API routes and the frontend UI await session.setClaimValue(PhoneVerifiedClaim, true, input.userContext); + resp.user = (await supertokens.getUser(session.getUserId()))!; } return resp; @@ -187,13 +188,18 @@ supertokens.init({ // we also get the phone number of the user and save it in the // session so that the OTP can be sent to it directly - let userInfo = await EmailPassword.getUserById(input.userId, input.userContext); + let userInfo = await supertokens.getUser(input.userId, input.userContext); return originalImplementation.createNewSession({ ...input, accessTokenPayload: { ...input.accessTokenPayload, - ...PhoneVerifiedClaim.build(input.userId, input.tenantId, input.userContext), - phoneNumber: userInfo?.email, + ...PhoneVerifiedClaim.build( + input.userId, + input.recipeUserId, + input.tenantId, + input.userContext + ), + phoneNumber: userInfo?.emails[0], }, }); } diff --git a/examples/with-phone-password/test/basic.test.js b/examples/with-phone-password/test/basic.test.js index 3d7c04159..084752781 100644 --- a/examples/with-phone-password/test/basic.test.js +++ b/examples/with-phone-password/test/basic.test.js @@ -68,9 +68,9 @@ describe("SuperTokens Example Basic tests", function () { const testOTP = "test123456"; before(async function () { - const user = await EmailPassword.getUserByEmail("public", phoneNumber); - if (user) { - await SuperTokensNode.deleteUser(user.id); + const user = await SuperTokensNode.listUsersByAccountInfo("public", { email: phoneNumber }); + if (user.length > 0) { + await SuperTokensNode.deleteUser(user[0].id); } browser = await puppeteer.launch({ diff --git a/examples/with-supabase/config/backendConfig.ts b/examples/with-supabase/config/backendConfig.ts index 76dadb82b..a8f4dd4f6 100644 --- a/examples/with-supabase/config/backendConfig.ts +++ b/examples/with-supabase/config/backendConfig.ts @@ -74,7 +74,7 @@ export let backendConfig = (): TypeInput => { let response = await originalImplementation.thirdPartySignInUpPOST(input); // check that there is no issue with sign up and that a new user is created - if (response.status === "OK" && response.createdNewUser) { + if (response.status === "OK" && response.createdNewRecipeUser) { // retrieve the supabase_token from the accessTokenPayload const accessTokenPayload = response.session.getAccessTokenPayload(); diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/api-server/index.ts b/examples/with-thirdpartyemailpassword-2fa-passwordless/api-server/index.ts index bb8146fd5..2d3e6eef4 100644 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/api-server/index.ts +++ b/examples/with-thirdpartyemailpassword-2fa-passwordless/api-server/index.ts @@ -185,17 +185,22 @@ supertokens.init({ // 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 Passwordless.getUserById({ - userId: userMetadata.metadata.passwordlessUserId as string, - userContext: input.userContext, - }); - phoneNumber = passwordlessUserInfo?.phoneNumber; + 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.tenantId, input.userContext)), + ...(await SecondFactorClaim.build( + input.userId, + input.recipeUserId, + input.tenantId, + input.userContext + )), phoneNumber, }, }); diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/test/basic.test.js b/examples/with-thirdpartyemailpassword-2fa-passwordless/test/basic.test.js index 9cce15fa1..e5760ff5a 100644 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/test/basic.test.js +++ b/examples/with-thirdpartyemailpassword-2fa-passwordless/test/basic.test.js @@ -139,7 +139,11 @@ describe("SuperTokens Example Basic tests", function () { 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", userId, email); + const tokenInfo = await EmailVerification.createEmailVerificationToken( + "public", + SuperTokensNode.convertToRecipeUserId(userId), + email + ); await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo.token}`); await submitForm(page); diff --git a/examples/with-thirdpartyemailpassword-passwordless/test/basic.test.js b/examples/with-thirdpartyemailpassword-passwordless/test/basic.test.js index d53ab4b62..ab3cf8d03 100644 --- a/examples/with-thirdpartyemailpassword-passwordless/test/basic.test.js +++ b/examples/with-thirdpartyemailpassword-passwordless/test/basic.test.js @@ -100,7 +100,11 @@ describe("SuperTokens Example Basic tests", function () { 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", userId, email); + const tokenInfo = await EmailVerification.createEmailVerificationToken( + "public", + SuperTokensNode.convertToRecipeUserId(userId), + email + ); await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo.token}`); await submitForm(page); diff --git a/examples/with-update-email-post-verification/backend/config.ts b/examples/with-update-email-post-verification/backend/config.ts index 3c838bca3..0ff7093f3 100644 --- a/examples/with-update-email-post-verification/backend/config.ts +++ b/examples/with-update-email-post-verification/backend/config.ts @@ -44,7 +44,7 @@ export const SuperTokensConfig: TypeInput = { // This will update the email of the user to the one // that was just marked as verified by the token. await EmailPassword.updateEmailOrPassword({ - userId: response.user.id, + recipeUserId: response.user.recipeUserId, email: response.user.email, }); } diff --git a/examples/with-update-email-post-verification/backend/index.ts b/examples/with-update-email-post-verification/backend/index.ts index 34a443dfc..7938d48c8 100644 --- a/examples/with-update-email-post-verification/backend/index.ts +++ b/examples/with-update-email-post-verification/backend/index.ts @@ -37,12 +37,12 @@ app.get("/sessioninfo", verifySession(), async (req: SessionRequest, res) => { app.get("/email", verifySession(), async (req: SessionRequest, res) => { let userId = req.session!.getUserId(); - let user = await EmailPassword.getUserById(userId); + let user = await supertokens.getUser(userId); if (user === undefined) { throw new Error("Should never come here"); } - return res.send(user.email); + return res.send(user.emails[0]); }); // This API is called from the frontend when the user enters their new email. @@ -58,7 +58,8 @@ app.post("/change-email", verifySession(), async (req: SessionRequest, res) => { // First we check if the new email is already associated with another user. // If it is, then we throw an error. If it's already associated with this user, // then we return a success response with an appropriate message. - let existingUser = await EmailPassword.getUserByEmail(session.getTenantId(), email); + let users = await supertokens.listUsersByAccountInfo(session.getTenantId(), { email }); + const existingUser = users[0]; if (existingUser !== undefined) { if (existingUser.id === session.getUserId()) { return res.status(200).send("Email already belongs to this account"); @@ -70,13 +71,14 @@ app.post("/change-email", verifySession(), async (req: SessionRequest, res) => { // Then, we check if the email is verified for this user ID or not. // It is important to understand that SuperTokens stores email verification // status based on the user ID AND the email, and not just the email. - let isVerified = await EmailVerification.isEmailVerified(session.getUserId(), email); + let isVerified = await EmailVerification.isEmailVerified(session.getRecipeUserId(), email); if (!isVerified) { // Now we send the email verification link to the user for the new email. const sendEmailRes = await EmailVerification.sendEmailVerificationEmail( session.getTenantId(), session.getUserId(), + session.getRecipeUserId(), email ); @@ -89,7 +91,7 @@ app.post("/change-email", verifySession(), async (req: SessionRequest, res) => { // Since the email is verified, we try and do an update let resp = await EmailPassword.updateEmailOrPassword({ - userId: session.getUserId(), + recipeUserId: session.getRecipeUserId(), email: email, }); diff --git a/examples/with-update-email-post-verification/test/basic.test.js b/examples/with-update-email-post-verification/test/basic.test.js index 55eb0f72f..538dde75f 100644 --- a/examples/with-update-email-post-verification/test/basic.test.js +++ b/examples/with-update-email-post-verification/test/basic.test.js @@ -90,7 +90,11 @@ describe("SuperTokens Example Basic tests", function () { 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", userId, email); + const tokenInfo = await EmailVerification.createEmailVerificationToken( + "public", + SuperTokensNode.convertToRecipeUserId(userId), + email + ); await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo.token}`); await submitForm(page); @@ -131,7 +135,11 @@ describe("SuperTokens Example Basic tests", function () { const alertText = await alertContent; assert(alertText === "Email verification email sent"); - const tokenInfo = await EmailVerification.createEmailVerificationToken("public", userId, newEmail); + const tokenInfo = await EmailVerification.createEmailVerificationToken( + "public", + SuperTokensNode.convertToRecipeUserId(userId), + newEmail + ); await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo.token}`); await submitForm(page); diff --git a/frontendDriverInterfaceSupported.json b/frontendDriverInterfaceSupported.json index 9e871c070..0e254b7bb 100644 --- a/frontendDriverInterfaceSupported.json +++ b/frontendDriverInterfaceSupported.json @@ -1,4 +1,4 @@ { "_comment": "contains a list of frontend-backend interface versions that this package supports", - "versions": ["1.17"] + "versions": ["1.17", "1.18"] } diff --git a/lib/build/SuperTokensBranding.js b/lib/build/SuperTokensBranding.js index 3d960956f..fad0aaeab 100644 --- a/lib/build/SuperTokensBranding.js +++ b/lib/build/SuperTokensBranding.js @@ -45,7 +45,8 @@ var Redirector = function (props) { rid: props.authRecipe.config.recipeId, successRedirectContext: { action: "SUCCESS", - isNewUser: false, + isNewRecipeUser: false, + user: undefined, redirectToPath: genericComponentOverrideContext.getRedirectToPathFromURL(), }, }, diff --git a/lib/build/emailpassword-shared4.js b/lib/build/emailpassword-shared4.js index c96bd2110..e3070936a 100644 --- a/lib/build/emailpassword-shared4.js +++ b/lib/build/emailpassword-shared4.js @@ -56,6 +56,10 @@ var getFunctionOverrides = function (onHandleEvent) { if (response.status === "OK") { onHandleEvent({ action: "RESET_PASSWORD_EMAIL_SENT", + email: input.formFields.find(function (_a) { + var id = _a.id; + return id === "email"; + }).value, userContext: input.userContext, }); } @@ -76,7 +80,7 @@ var getFunctionOverrides = function (onHandleEvent) { if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: true, + isNewRecipeUser: true, user: response.user, userContext: input.userContext, }); @@ -98,7 +102,7 @@ var getFunctionOverrides = function (onHandleEvent) { if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: false, + isNewRecipeUser: false, user: response.user, userContext: input.userContext, }); diff --git a/lib/build/emailpassword-shared6.js b/lib/build/emailpassword-shared6.js index 9b9557670..86d336eb2 100644 --- a/lib/build/emailpassword-shared6.js +++ b/lib/build/emailpassword-shared6.js @@ -16,6 +16,7 @@ var generalError = require("./emailpassword-shared.js"); var STGeneralError = require("supertokens-web-js/utils/error"); var button = require("./emailpassword-shared2.js"); var recipe = require("./session-shared2.js"); +var STGeneralError$1 = require("supertokens-web-js/lib/build/error"); var validators = require("./emailpassword-shared5.js"); var recipe$1 = require("./emailpassword-shared4.js"); @@ -52,6 +53,7 @@ function _interopNamespace(e) { var NormalisedURLPath__default = /*#__PURE__*/ _interopDefault(NormalisedURLPath); var React__namespace = /*#__PURE__*/ _interopNamespace(React); var STGeneralError__default = /*#__PURE__*/ _interopDefault(STGeneralError); +var STGeneralError__default$1 = /*#__PURE__*/ _interopDefault(STGeneralError$1); /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * @@ -250,7 +252,7 @@ var EmailPasswordResetPasswordEmail = function (props) { void 0, void 0, function () { - var validationErrors, emailField; + var validationErrors, emailField, resp; return genericComponentOverrideContext.__generator(this, function (_a) { switch (_a.label) { case 0: @@ -287,7 +289,19 @@ var EmailPasswordResetPasswordEmail = function (props) { }), ]; case 2: - return [2 /*return*/, _a.sent()]; + resp = _a.sent(); + if (resp.status === "PASSWORD_RESET_NOT_ALLOWED") { + return [ + 2 /*return*/, + { + status: "FIELD_ERROR", + formFields: [ + { id: "email", error: resp.reason }, + ], + }, + ]; + } + return [2 /*return*/, resp]; } }); } @@ -615,6 +629,12 @@ var defaultTranslationsEmailPassword = { "Password must contain at least one alphabet": undefined, "Password must contain at least one number": undefined, "Email is invalid": undefined, + "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)": + undefined, + "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)": + undefined, + "Cannot sign in due to security reasons. Please try resetting your password, use a different login method or contact support. (ERR_CODE_008)": + undefined, } ), }; @@ -771,6 +791,8 @@ var SignInForm = uiEntry.withOverride("EmailPasswordSignInForm", function EmailP throw new STGeneralError__default.default( "EMAIL_PASSWORD_SIGN_IN_WRONG_CREDENTIALS_ERROR" ); + } else if (response.status === "SIGN_IN_NOT_ALLOWED") { + throw new STGeneralError__default.default(response.reason); } else { return [2 /*return*/, response]; } @@ -927,7 +949,7 @@ var SignUpForm = uiEntry.withOverride("EmailPasswordSignUpForm", function EmailP onSuccess: props.onSuccess, callAPI: function (formFields) { return genericComponentOverrideContext.__awaiter(_this, void 0, void 0, function () { - var validationErrors; + var validationErrors, res; return genericComponentOverrideContext.__generator(this, function (_a) { switch (_a.label) { case 0: @@ -950,12 +972,18 @@ var SignUpForm = uiEntry.withOverride("EmailPasswordSignUpForm", function EmailP ]; } return [ - 2 /*return*/, + 4 /*yield*/, props.recipeImplementation.signUp({ formFields: formFields, userContext: userContext, }), ]; + case 2: + res = _a.sent(); + if (res.status === "SIGN_UP_NOT_ALLOWED") { + throw new STGeneralError__default$1.default(res.reason); + } + return [2 /*return*/, res]; } }); }); @@ -1149,7 +1177,7 @@ function useChildProps(recipe$1, state, dispatch, history) { ); var userContext = uiEntry.useUserContext(); var onSignInSuccess = React.useCallback( - function () { + function (response) { return genericComponentOverrideContext.__awaiter(_this, void 0, void 0, function () { return genericComponentOverrideContext.__generator(this, function (_a) { return [ @@ -1159,7 +1187,8 @@ function useChildProps(recipe$1, state, dispatch, history) { rid: recipe$1.config.recipeId, successRedirectContext: { action: "SUCCESS", - isNewUser: false, + isNewRecipeUser: false, + user: response.user, redirectToPath: genericComponentOverrideContext.getRedirectToPathFromURL(), }, }, @@ -1173,7 +1202,7 @@ function useChildProps(recipe$1, state, dispatch, history) { [recipe$1, userContext, history] ); var onSignUpSuccess = React.useCallback( - function () { + function (response) { return genericComponentOverrideContext.__awaiter(_this, void 0, void 0, function () { return genericComponentOverrideContext.__generator(this, function (_a) { return [ @@ -1183,7 +1212,8 @@ function useChildProps(recipe$1, state, dispatch, history) { rid: recipe$1.config.recipeId, successRedirectContext: { action: "SUCCESS", - isNewUser: true, + isNewRecipeUser: true, + user: response.user, redirectToPath: genericComponentOverrideContext.getRedirectToPathFromURL(), }, }, diff --git a/lib/build/emailpasswordprebuiltui.js b/lib/build/emailpasswordprebuiltui.js index 9f40e0da2..dbf36ecd1 100644 --- a/lib/build/emailpasswordprebuiltui.js +++ b/lib/build/emailpasswordprebuiltui.js @@ -32,6 +32,7 @@ require("./emailpassword-shared7.js"); require("supertokens-web-js/utils/error"); require("./emailpassword-shared2.js"); require("./emailpassword-shared.js"); +require("supertokens-web-js/lib/build/error"); require("supertokens-web-js/recipe/emailpassword"); require("./authRecipe-shared.js"); diff --git a/lib/build/passwordless-shared2.js b/lib/build/passwordless-shared2.js index 979d2e6a9..06f1fa598 100644 --- a/lib/build/passwordless-shared2.js +++ b/lib/build/passwordless-shared2.js @@ -74,7 +74,7 @@ var getFunctionOverrides = function (onHandleEvent) { } else if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, user: response.user, }); } diff --git a/lib/build/passwordless-shared3.js b/lib/build/passwordless-shared3.js index e46e54f52..de28acb50 100644 --- a/lib/build/passwordless-shared3.js +++ b/lib/build/passwordless-shared3.js @@ -229,6 +229,10 @@ var defaultTranslationsPasswordless = { "Failed to generate a one time code. Please try again": undefined, "Phone number is invalid": undefined, "Email is invalid": undefined, + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_002)": + undefined, + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_003)": + undefined, } ), }; @@ -315,6 +319,18 @@ var LinkClickedScreen = function (props) { }), ]; } + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + return [ + 2 /*return*/, + genericComponentOverrideContext.SuperTokens.getInstanceOrThrow().redirectToAuth({ + history: props.history, + queryParams: { + error: response.reason, + }, + redirectBack: false, + }), + ]; + } if (!(response.status === "OK")) return [3 /*break*/, 3]; return [ 4 /*yield*/, @@ -339,7 +355,8 @@ var LinkClickedScreen = function (props) { rid: props.recipe.config.recipeId, successRedirectContext: { action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, + user: response.user, redirectToPath: loginAttemptInfo === null || loginAttemptInfo === void 0 ? void 0 @@ -599,6 +616,9 @@ var EmailForm = uiEntry.withOverride("PasswordlessEmailForm", function Passwordl ]; case 2: response = _b.sent(); + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + throw new STGeneralError__default.default(response.reason); + } return [2 /*return*/, response]; } }); @@ -2768,6 +2788,7 @@ var EmailOrPhoneForm = uiEntry.withOverride( response, intPhoneNumber, phoneValidationResAfterGuess, + response, ex_1; var _a; return genericComponentOverrideContext.__generator(this, function (_b) { @@ -2798,6 +2819,9 @@ var EmailOrPhoneForm = uiEntry.withOverride( ]; case 3: response = _b.sent(); + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + throw new STGeneralError__default.default(response.reason); + } return [2 /*return*/, response]; case 4: throw new STGeneralError__default.default(emailValidationRes); @@ -2817,6 +2841,9 @@ var EmailOrPhoneForm = uiEntry.withOverride( ]; case 8: response = _b.sent(); + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + throw new STGeneralError__default.default(response.reason); + } return [2 /*return*/, response]; case 9: return [ @@ -2844,7 +2871,11 @@ var EmailOrPhoneForm = uiEntry.withOverride( }), ]; case 13: - return [2 /*return*/, _b.sent()]; + response = _b.sent(); + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + throw new STGeneralError__default.default(response.reason); + } + return [2 /*return*/, response]; case 14: ex_1 = _b.sent(); // General errors from the API can make createCode throw but we want to switch to the phone UI anyway @@ -3420,6 +3451,9 @@ var PhoneForm = uiEntry.withOverride("PasswordlessPhoneForm", function Passwordl ]; case 2: response = _b.sent(); + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + throw new STGeneralError__default.default(response.reason); + } return [2 /*return*/, response]; } }); @@ -3633,7 +3667,13 @@ var UserInputCodeForm = uiEntry.withOverride( ]; case 1: response = _b.sent(); - if (response.status === "OK" || response.status === "RESTART_FLOW_ERROR") { + // We can redirect these statuses, since they all cause a redirection + // and we don't really want to show anything + if ( + response.status === "OK" || + response.status === "RESTART_FLOW_ERROR" || + response.status === "SIGN_IN_UP_NOT_ALLOWED" + ) { return [2 /*return*/, response]; } if (response.status === "INCORRECT_USER_INPUT_CODE_ERROR") { @@ -4036,7 +4076,8 @@ function useChildProps(recipe$1, dispatch, state, callingConsumeCodeRef, userCon rid: recipe$1.config.recipeId, successRedirectContext: { action: "SUCCESS", - isNewUser: result.createdNewUser, + isNewRecipeUser: result.createdNewRecipeUser, + user: result.user, redirectToPath: genericComponentOverrideContext.getRedirectToPathFromURL(), }, }, @@ -4241,9 +4282,9 @@ function getModifiedRecipeImplementation(originalImpl, config, dispatch, calling case 2: _a.sent(); dispatch({ type: "restartFlow", error: "ERROR_SIGN_IN_UP_CODE_CONSUME_RESTART_FLOW" }); - return [3 /*break*/, 5]; + return [3 /*break*/, 7]; case 3: - if (!(res.status === "OK")) return [3 /*break*/, 5]; + if (!(res.status === "SIGN_IN_UP_NOT_ALLOWED")) return [3 /*break*/, 5]; return [ 4 /*yield*/, originalImpl.clearLoginAttemptInfo({ @@ -4252,8 +4293,20 @@ function getModifiedRecipeImplementation(originalImpl, config, dispatch, calling ]; case 4: _a.sent(); - _a.label = 5; + dispatch({ type: "restartFlow", error: res.reason }); + return [3 /*break*/, 7]; case 5: + if (!(res.status === "OK")) return [3 /*break*/, 7]; + return [ + 4 /*yield*/, + originalImpl.clearLoginAttemptInfo({ + userContext: input.userContext, + }), + ]; + case 6: + _a.sent(); + _a.label = 7; + case 7: callingConsumeCodeRef.current = false; return [2 /*return*/, res]; } diff --git a/lib/build/recipe/authRecipe/types.d.ts b/lib/build/recipe/authRecipe/types.d.ts index a2cdfd0fc..9e09ed432 100644 --- a/lib/build/recipe/authRecipe/types.d.ts +++ b/lib/build/recipe/authRecipe/types.d.ts @@ -3,19 +3,23 @@ import type { NormalisedConfig as NormalisedRecipeModuleConfig, UserInput as UserInputRecipeModule, } from "../recipeModule/types"; -export declare type User = { - id: string; - email: string; - timeJoined: number; -}; +import type { User } from "supertokens-web-js/types"; export declare type UserInput = UserInputRecipeModule; export declare type Config = UserInput & RecipeModuleConfig; export declare type NormalisedConfig = NormalisedRecipeModuleConfig; -export declare type GetRedirectionURLContext = { - action: "SUCCESS"; - isNewUser: boolean; - redirectToPath?: string; -}; +export declare type GetRedirectionURLContext = + | { + action: "SUCCESS"; + isNewRecipeUser: true; + user: User; + redirectToPath?: string; + } + | { + action: "SUCCESS"; + isNewRecipeUser: false; + user?: User; + redirectToPath?: string; + }; export declare type OnHandleEventContext = { action: "SESSION_ALREADY_EXISTS"; }; diff --git a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signIn.d.ts b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signIn.d.ts index 46dd93ed6..2b40c960b 100644 --- a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signIn.d.ts +++ b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signIn.d.ts @@ -10,6 +10,6 @@ export declare const SignIn: import("react").ComponentType< config: import("../../../types").NormalisedConfig; signUpClicked?: (() => void) | undefined; forgotPasswordClick: () => void; - onSuccess: () => void; + onSuccess: (result: { user: import("supertokens-web-js/types").User }) => void; } >; diff --git a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signInForm.d.ts b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signInForm.d.ts index 41858dc47..7dbc1321c 100644 --- a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signInForm.d.ts +++ b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signInForm.d.ts @@ -10,7 +10,7 @@ export declare const SignInForm: import("react").ComponentType< config: import("../../../types").NormalisedConfig; signUpClicked?: (() => void) | undefined; forgotPasswordClick: () => void; - onSuccess: () => void; + onSuccess: (result: { user: import("supertokens-web-js/types").User }) => void; } & { header?: JSX.Element | undefined; footer?: JSX.Element | undefined; diff --git a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUp.d.ts b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUp.d.ts index 1a0268aa8..380a6e333 100644 --- a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUp.d.ts +++ b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUp.d.ts @@ -9,6 +9,6 @@ export declare const SignUp: import("react").ComponentType< onError: (error: string) => void; config: import("../../../types").NormalisedConfig; signInClicked?: (() => void) | undefined; - onSuccess: () => void; + onSuccess: (result: { user: import("supertokens-web-js/types").User }) => void; } >; diff --git a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUpForm.d.ts b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUpForm.d.ts index d57524cfe..bbe4a2eb2 100644 --- a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUpForm.d.ts +++ b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUpForm.d.ts @@ -9,7 +9,7 @@ export declare const SignUpForm: import("react").ComponentType< onError: (error: string) => void; config: import("../../../types").NormalisedConfig; signInClicked?: (() => void) | undefined; - onSuccess: () => void; + onSuccess: (result: { user: import("supertokens-web-js/types").User }) => void; } & { header?: JSX.Element | undefined; footer?: JSX.Element | undefined; diff --git a/lib/build/recipe/emailpassword/components/themes/translations.d.ts b/lib/build/recipe/emailpassword/components/themes/translations.d.ts index e0a73064a..6cf2ec356 100644 --- a/lib/build/recipe/emailpassword/components/themes/translations.d.ts +++ b/lib/build/recipe/emailpassword/components/themes/translations.d.ts @@ -57,6 +57,9 @@ export declare const defaultTranslationsEmailPassword: { "Password must contain at least one alphabet": undefined; "Password must contain at least one number": undefined; "Email is invalid": undefined; + "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)": undefined; + "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)": undefined; + "Cannot sign in due to security reasons. Please try resetting your password, use a different login method or contact support. (ERR_CODE_008)": undefined; EMAIL_VERIFICATION_RESEND_SUCCESS: string; EMAIL_VERIFICATION_SEND_TITLE: string; EMAIL_VERIFICATION_SEND_DESC_START: string; diff --git a/lib/build/recipe/emailpassword/index.d.ts b/lib/build/recipe/emailpassword/index.d.ts index b632002d1..ad2c6e085 100644 --- a/lib/build/recipe/emailpassword/index.d.ts +++ b/lib/build/recipe/emailpassword/index.d.ts @@ -2,8 +2,8 @@ import { RecipeInterface } from "supertokens-web-js/recipe/emailpassword"; import { UserInput } from "./types"; import { GetRedirectionURLContext, PreAPIHookContext, OnHandleEventContext } from "./types"; -import type { User } from "../authRecipe/types"; import type { RecipeFunctionOptions } from "supertokens-web-js/recipe/emailpassword"; +import type { User } from "supertokens-web-js/types"; export default class Wrapper { static init( config?: UserInput @@ -23,7 +23,11 @@ export default class Wrapper { userContext?: any; }): Promise< | { - status: "OK" | "RESET_PASSWORD_INVALID_TOKEN_ERROR"; + status: "OK"; + fetchResponse: Response; + } + | { + status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; fetchResponse: Response; } | { @@ -44,7 +48,7 @@ export default class Wrapper { userContext?: any; }): Promise< | { - status: "OK"; + status: "OK" | "PASSWORD_RESET_NOT_ALLOWED"; fetchResponse: Response; } | { @@ -77,6 +81,11 @@ export default class Wrapper { }[]; fetchResponse: Response; } + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } >; static signIn(input: { formFields: { @@ -103,6 +112,11 @@ export default class Wrapper { status: "WRONG_CREDENTIALS_ERROR"; fetchResponse: Response; } + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } >; static doesEmailExist(input: { email: string; options?: RecipeFunctionOptions; userContext?: any }): Promise<{ status: "OK"; diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index 8c03ab29f..d819f58c8 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -22,7 +22,6 @@ import type { import type { GetRedirectionURLContext as AuthRecipeModuleGetRedirectionURLContext, OnHandleEventContext as AuthRecipeModuleOnHandleEventContext, - User, Config as AuthRecipeModuleConfig, NormalisedConfig as NormalisedAuthRecipeModuleConfig, UserInput as AuthRecipeModuleUserInput, @@ -31,6 +30,7 @@ import type React from "react"; import type { Dispatch } from "react"; import type { OverrideableBuilder } from "supertokens-js-override"; import type { RecipeInterface } from "supertokens-web-js/recipe/emailpassword"; +import type { User } from "supertokens-web-js/types"; export declare type ComponentOverrideMap = { EmailPasswordSignIn_Override?: ComponentOverride; EmailPasswordSignInFooter_Override?: ComponentOverride; @@ -122,7 +122,7 @@ export declare type SignInThemeProps = FormThemeBaseProps & { config: NormalisedConfig; signUpClicked?: () => void; forgotPasswordClick: () => void; - onSuccess: () => void; + onSuccess: (result: { user: User }) => void; }; export declare type SignUpThemeProps = FormThemeBaseProps & { recipeImplementation: RecipeInterface; @@ -130,7 +130,7 @@ export declare type SignUpThemeProps = FormThemeBaseProps & { onError: (error: string) => void; config: NormalisedConfig; signInClicked?: () => void; - onSuccess: () => void; + onSuccess: (result: { user: User }) => void; }; export declare type SignInAndUpThemeProps = { signInForm: SignInThemeProps; @@ -172,16 +172,18 @@ export declare type GetRedirectionURLContext = export declare type OnHandleEventContext = | AuthRecipeModuleOnHandleEventContext | { - action: "RESET_PASSWORD_EMAIL_SENT" | "PASSWORD_RESET_SUCCESSFUL"; + action: "RESET_PASSWORD_EMAIL_SENT"; + email: string; + userContext: any; + } + | { + action: "PASSWORD_RESET_SUCCESSFUL"; userContext: any; } | { action: "SUCCESS"; - isNewUser: boolean; - user: { - id: string; - email: string; - }; + isNewRecipeUser: boolean; + user: User; userContext: any; }; export declare type ResetPasswordUsingTokenThemeProps = { diff --git a/lib/build/recipe/passwordless/components/themes/translations.d.ts b/lib/build/recipe/passwordless/components/themes/translations.d.ts index 72eb4b70a..0fed6a0d3 100644 --- a/lib/build/recipe/passwordless/components/themes/translations.d.ts +++ b/lib/build/recipe/passwordless/components/themes/translations.d.ts @@ -55,6 +55,8 @@ export declare const defaultTranslationsPasswordless: { "Failed to generate a one time code. Please try again": undefined; "Phone number is invalid": undefined; "Email is invalid": undefined; + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_002)": undefined; + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_003)": undefined; BRANDING_POWERED_BY_START: string; BRANDING_POWERED_BY_END: string; SOMETHING_WENT_WRONG_ERROR: string; diff --git a/lib/build/recipe/passwordless/index.d.ts b/lib/build/recipe/passwordless/index.d.ts index c8dd212ea..8e8685244 100644 --- a/lib/build/recipe/passwordless/index.d.ts +++ b/lib/build/recipe/passwordless/index.d.ts @@ -3,7 +3,8 @@ import { RecipeInterface } from "supertokens-web-js/recipe/passwordless"; import { UserInput } from "./types"; import { GetRedirectionURLContext, PreAPIHookContext, OnHandleEventContext } from "./types"; import type { RecipeFunctionOptions } from "supertokens-web-js/recipe/passwordless"; -import type { PasswordlessFlowType, PasswordlessUser } from "supertokens-web-js/recipe/passwordless/types"; +import type { PasswordlessFlowType } from "supertokens-web-js/recipe/passwordless/types"; +import type { User } from "supertokens-web-js/types"; export default class Wrapper { static init( config: UserInput @@ -26,13 +27,19 @@ export default class Wrapper { userContext?: any; options?: RecipeFunctionOptions; } - ): Promise<{ - status: "OK"; - deviceId: string; - preAuthSessionId: string; - flowType: PasswordlessFlowType; - fetchResponse: Response; - }>; + ): Promise< + | { + status: "OK"; + deviceId: string; + preAuthSessionId: string; + flowType: PasswordlessFlowType; + fetchResponse: Response; + } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } + >; static resendCode(input?: { userContext?: any; options?: RecipeFunctionOptions }): Promise<{ status: "OK" | "RESTART_FLOW_ERROR"; fetchResponse: Response; @@ -51,8 +58,8 @@ export default class Wrapper { ): Promise< | { status: "OK"; - createdNewUser: boolean; - user: PasswordlessUser; + createdNewRecipeUser: boolean; + user: User; fetchResponse: Response; } | { @@ -65,6 +72,11 @@ export default class Wrapper { status: "RESTART_FLOW_ERROR"; fetchResponse: Response; } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } >; static getLinkCodeFromURL(input?: { userContext?: any }): string; static getPreAuthSessionIdFromURL(input?: { userContext?: any }): string; diff --git a/lib/build/recipe/passwordless/types.d.ts b/lib/build/recipe/passwordless/types.d.ts index 12caca75c..617b38663 100644 --- a/lib/build/recipe/passwordless/types.d.ts +++ b/lib/build/recipe/passwordless/types.d.ts @@ -20,7 +20,8 @@ import type { } from "../authRecipe/types"; import type { Dispatch } from "react"; import type WebJSRecipe from "supertokens-web-js/recipe/passwordless"; -import type { RecipeInterface, PasswordlessUser } from "supertokens-web-js/recipe/passwordless"; +import type { RecipeInterface } from "supertokens-web-js/recipe/passwordless"; +import type { User } from "supertokens-web-js/types"; export declare type PreAndPostAPIHookAction = | "PASSWORDLESS_CREATE_CODE" | "PASSWORDLESS_CONSUME_CODE" @@ -36,8 +37,8 @@ export declare type GetRedirectionURLContext = AuthRecipeModuleGetRedirectionURL export declare type OnHandleEventContext = | { action: "SUCCESS"; - isNewUser: boolean; - user: PasswordlessUser; + isNewRecipeUser: boolean; + user: User; } | { action: "PASSWORDLESS_RESTART_FLOW"; @@ -123,7 +124,7 @@ export declare type UserInput = ( export declare type SignInUpProps = { recipeImplementation: RecipeImplementation; config: NormalisedConfig; - onSuccess?: (result: { createdNewUser: boolean; user: PasswordlessUser }) => void; + onSuccess?: (result: { createdNewRecipeUser: boolean; user: User }) => void; dispatch: Dispatch; featureState: { loginAttemptInfo?: LoginAttemptInfo; @@ -187,7 +188,7 @@ export declare type SignInUpUserInputCodeFormProps = { recipeImplementation: RecipeImplementation; config: NormalisedConfig; loginAttemptInfo: LoginAttemptInfo; - onSuccess?: (result: { createdNewUser: boolean; user: PasswordlessUser }) => void; + onSuccess?: (result: { createdNewRecipeUser: boolean; user: User }) => void; }; export declare type LinkClickedScreenProps = { recipeImplementation: RecipeImplementation; diff --git a/lib/build/recipe/thirdparty/components/themes/translations.d.ts b/lib/build/recipe/thirdparty/components/themes/translations.d.ts index 7ba3735b6..05233577d 100644 --- a/lib/build/recipe/thirdparty/components/themes/translations.d.ts +++ b/lib/build/recipe/thirdparty/components/themes/translations.d.ts @@ -9,6 +9,9 @@ export declare const defaultTranslationsThirdParty: { THIRD_PARTY_PROVIDER_DEFAULT_BTN_START: string; THIRD_PARTY_PROVIDER_DEFAULT_BTN_END: string; THIRD_PARTY_ERROR_NO_EMAIL: string; + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_004)": undefined; + "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)": undefined; + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_006)": undefined; BRANDING_POWERED_BY_START: string; BRANDING_POWERED_BY_END: string; SOMETHING_WENT_WRONG_ERROR: string; diff --git a/lib/build/recipe/thirdparty/index.d.ts b/lib/build/recipe/thirdparty/index.d.ts index 1fa238f4f..043dcba42 100644 --- a/lib/build/recipe/thirdparty/index.d.ts +++ b/lib/build/recipe/thirdparty/index.d.ts @@ -1,5 +1,5 @@ /// -import { RecipeInterface, ThirdPartyUserType as User } from "supertokens-web-js/recipe/thirdparty"; +import { RecipeInterface } from "supertokens-web-js/recipe/thirdparty"; import ActiveDirectory from "./providers/activeDirectory"; import Apple from "./providers/apple"; import Bitbucket from "./providers/bitbucket"; @@ -16,6 +16,7 @@ import Twitter from "./providers/twitter"; import { UserInput, GetRedirectionURLContext, PreAPIHookContext, OnHandleEventContext } from "./types"; import type { StateObject } from "supertokens-web-js/recipe/thirdparty"; import type { RecipeFunctionOptions } from "supertokens-web-js/recipe/thirdpartyemailpassword"; +import type { User } from "supertokens-web-js/types"; export default class Wrapper { static init( config?: UserInput @@ -43,13 +44,18 @@ export default class Wrapper { | { status: "OK"; user: User; - createdNewUser: boolean; + createdNewRecipeUser: boolean; fetchResponse: Response; } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER"; fetchResponse: Response; } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } >; static Apple: typeof Apple; static Bitbucket: typeof Bitbucket; @@ -101,7 +107,6 @@ export { redirectToThirdPartyLogin, ThirdpartyComponentsOverrideProvider, signOut, - User, GetRedirectionURLContext, PreAPIHookContext, OnHandleEventContext, diff --git a/lib/build/recipe/thirdparty/types.d.ts b/lib/build/recipe/thirdparty/types.d.ts index b923ceb60..0a93ccc3d 100644 --- a/lib/build/recipe/thirdparty/types.d.ts +++ b/lib/build/recipe/thirdparty/types.d.ts @@ -16,6 +16,7 @@ import type { import type { OverrideableBuilder } from "supertokens-js-override"; import type ThirdPartyWebJS from "supertokens-web-js/recipe/thirdparty"; import type { StateObject as WebJsStateObject, RecipeInterface } from "supertokens-web-js/recipe/thirdparty"; +import type { User } from "supertokens-web-js/types"; export declare type ComponentOverrideMap = { ThirdPartySignUpFooter_Override?: ComponentOverride; ThirdPartySignInAndUpHeader_Override?: ComponentOverride; @@ -68,11 +69,8 @@ export declare type OnHandleEventContext = | AuthRecipeModuleOnHandleEventContext | { action: "SUCCESS"; - isNewUser: boolean; - user: { - id: string; - email: string; - }; + isNewRecipeUser: boolean; + user: User; userContext: any; }; export declare type SignInAndUpThemeProps = { diff --git a/lib/build/recipe/thirdpartyemailpassword/components/themes/translations.d.ts b/lib/build/recipe/thirdpartyemailpassword/components/themes/translations.d.ts index bb256d116..bd164e96d 100644 --- a/lib/build/recipe/thirdpartyemailpassword/components/themes/translations.d.ts +++ b/lib/build/recipe/thirdpartyemailpassword/components/themes/translations.d.ts @@ -58,6 +58,7 @@ export declare const defaultTranslationsThirdPartyEmailPassword: { "Password must contain at least one alphabet": undefined; "Password must contain at least one number": undefined; "Email is invalid": undefined; + "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)": undefined; EMAIL_VERIFICATION_RESEND_SUCCESS: string; EMAIL_VERIFICATION_SEND_TITLE: string; EMAIL_VERIFICATION_SEND_DESC_START: string; @@ -86,5 +87,8 @@ export declare const defaultTranslationsThirdPartyEmailPassword: { THIRD_PARTY_PROVIDER_DEFAULT_BTN_START: string; THIRD_PARTY_PROVIDER_DEFAULT_BTN_END: string; THIRD_PARTY_ERROR_NO_EMAIL: string; + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_004)": undefined; + "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)": undefined; + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_006)": undefined; }; }; diff --git a/lib/build/recipe/thirdpartyemailpassword/index.d.ts b/lib/build/recipe/thirdpartyemailpassword/index.d.ts index 7b1a767b2..f225db25e 100644 --- a/lib/build/recipe/thirdpartyemailpassword/index.d.ts +++ b/lib/build/recipe/thirdpartyemailpassword/index.d.ts @@ -1,9 +1,5 @@ /// -import { - RecipeInterface, - EmailPasswordUserType as UserType, - ThirdPartyUserType, -} from "supertokens-web-js/recipe/thirdpartyemailpassword"; +import { RecipeInterface } from "supertokens-web-js/recipe/thirdpartyemailpassword"; import { Apple, Google, @@ -21,6 +17,7 @@ import { import { UserInput, GetRedirectionURLContext, PreAPIHookContext, OnHandleEventContext } from "./types"; import type { StateObject } from "supertokens-web-js/recipe/thirdparty"; import type { RecipeFunctionOptions } from "supertokens-web-js/recipe/thirdpartyemailpassword"; +import type { User } from "supertokens-web-js/types"; export default class Wrapper { static init( config?: UserInput @@ -40,7 +37,11 @@ export default class Wrapper { userContext?: any; }): Promise< | { - status: "OK" | "RESET_PASSWORD_INVALID_TOKEN_ERROR"; + status: "OK"; + fetchResponse: Response; + } + | { + status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; fetchResponse: Response; } | { @@ -61,7 +62,7 @@ export default class Wrapper { userContext?: any; }): Promise< | { - status: "OK"; + status: "OK" | "PASSWORD_RESET_NOT_ALLOWED"; fetchResponse: Response; } | { @@ -83,7 +84,7 @@ export default class Wrapper { }): Promise< | { status: "OK"; - user: UserType; + user: User; fetchResponse: Response; } | { @@ -94,6 +95,11 @@ export default class Wrapper { }[]; fetchResponse: Response; } + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } >; static emailPasswordSignIn(input: { formFields: { @@ -105,7 +111,7 @@ export default class Wrapper { }): Promise< | { status: "OK"; - user: UserType; + user: User; fetchResponse: Response; } | { @@ -120,6 +126,11 @@ export default class Wrapper { status: "WRONG_CREDENTIALS_ERROR"; fetchResponse: Response; } + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } >; static doesEmailExist(input: { email: string; options?: RecipeFunctionOptions; userContext?: any }): Promise<{ status: "OK"; @@ -133,14 +144,19 @@ export default class Wrapper { static thirdPartySignInAndUp(input?: { userContext?: any; options?: RecipeFunctionOptions }): Promise< | { status: "OK"; - user: ThirdPartyUserType; - createdNewUser: boolean; + user: User; + createdNewRecipeUser: boolean; fetchResponse: Response; } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER"; fetchResponse: Response; } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } >; static getStateAndOtherInfoFromStorage(input?: { userContext?: any; @@ -218,6 +234,4 @@ export { OnHandleEventContext, UserInput, RecipeInterface, - UserType, - ThirdPartyUserType, }; diff --git a/lib/build/recipe/thirdpartypasswordless/components/themes/translations.d.ts b/lib/build/recipe/thirdpartypasswordless/components/themes/translations.d.ts index dbe4e941e..b24b59e60 100644 --- a/lib/build/recipe/thirdpartypasswordless/components/themes/translations.d.ts +++ b/lib/build/recipe/thirdpartypasswordless/components/themes/translations.d.ts @@ -57,6 +57,8 @@ export declare const defaultTranslationsThirdPartyPasswordless: { "Failed to generate a one time code. Please try again": undefined; "Phone number is invalid": undefined; "Email is invalid": undefined; + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_002)": undefined; + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_003)": undefined; BRANDING_POWERED_BY_START: string; BRANDING_POWERED_BY_END: string; SOMETHING_WENT_WRONG_ERROR: string; @@ -69,5 +71,8 @@ export declare const defaultTranslationsThirdPartyPasswordless: { THIRD_PARTY_PROVIDER_DEFAULT_BTN_START: string; THIRD_PARTY_PROVIDER_DEFAULT_BTN_END: string; THIRD_PARTY_ERROR_NO_EMAIL: string; + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_004)": undefined; + "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)": undefined; + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_006)": undefined; }; }; diff --git a/lib/build/recipe/thirdpartypasswordless/index.d.ts b/lib/build/recipe/thirdpartypasswordless/index.d.ts index 4508e4735..db4806266 100644 --- a/lib/build/recipe/thirdpartypasswordless/index.d.ts +++ b/lib/build/recipe/thirdpartypasswordless/index.d.ts @@ -15,17 +15,14 @@ import { Okta, } from "../thirdparty/"; import { UserInput, GetRedirectionURLContext, PreAPIHookContext, OnHandleEventContext } from "./types"; -import type { StateObject, ThirdPartyUserType as UserType } from "supertokens-web-js/recipe/thirdparty"; -import type { - PasswordlessFlowType, - PasswordlessUser, - RecipeFunctionOptions, -} from "supertokens-web-js/recipe/thirdpartypasswordless"; +import type { StateObject } from "supertokens-web-js/recipe/thirdparty"; +import type { PasswordlessFlowType, RecipeFunctionOptions } from "supertokens-web-js/recipe/thirdpartypasswordless"; +import type { User } from "supertokens-web-js/types"; export default class Wrapper { static init( config: UserInput ): import("../../types").RecipeInitResult< - import("../authRecipe/types").GetRedirectionURLContext, + GetRedirectionURLContext, import("./types").PreAndPostAPIHookAction, OnHandleEventContext, import("./types").NormalisedConfig @@ -37,14 +34,19 @@ export default class Wrapper { static thirdPartySignInAndUp(input?: { userContext?: any; options?: RecipeFunctionOptions }): Promise< | { status: "OK"; - user: UserType; - createdNewUser: boolean; + user: User; + createdNewRecipeUser: boolean; fetchResponse: Response; } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER"; fetchResponse: Response; } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } >; static getThirdPartyStateAndOtherInfoFromStorage(input?: { userContext?: any; @@ -68,13 +70,20 @@ export default class Wrapper { userContext?: any; options?: RecipeFunctionOptions; } - ): Promise<{ - status: "OK"; - deviceId: string; - preAuthSessionId: string; - flowType: PasswordlessFlowType; - fetchResponse: Response; - }>; + ): Promise< + | { + status: "OK"; + deviceId: string; + preAuthSessionId: string; + flowType: PasswordlessFlowType; + fetchResponse: Response; + } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } + >; static resendPasswordlessCode(input?: { userContext?: any; options?: RecipeFunctionOptions }): Promise<{ status: "OK" | "RESTART_FLOW_ERROR"; fetchResponse: Response; @@ -93,8 +102,8 @@ export default class Wrapper { ): Promise< | { status: "OK"; - createdNewUser: boolean; - user: PasswordlessUser; + createdNewRecipeUser: boolean; + user: User; fetchResponse: Response; } | { @@ -107,6 +116,11 @@ export default class Wrapper { status: "RESTART_FLOW_ERROR"; fetchResponse: Response; } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } >; static getPasswordlessLinkCodeFromURL(input?: { userContext?: any }): string; static getPasswordlessPreAuthSessionIdFromURL(input?: { userContext?: any }): string; diff --git a/lib/build/session-shared2.js b/lib/build/session-shared2.js index 2415bbc17..7d78ccc90 100644 --- a/lib/build/session-shared2.js +++ b/lib/build/session-shared2.js @@ -295,7 +295,8 @@ var Session = /** @class */ (function (_super) { rid: Session.RECIPE_ID, successRedirectContext: { action: "SUCCESS", - isNewUser: false, + isNewRecipeUser: false, + user: undefined, }, }; _a.label = 13; diff --git a/lib/build/thirdparty-shared.js b/lib/build/thirdparty-shared.js index fa9fa1e1a..9ecf3f819 100644 --- a/lib/build/thirdparty-shared.js +++ b/lib/build/thirdparty-shared.js @@ -1080,7 +1080,7 @@ var getFunctionOverrides = function (recipeId, onHandleEvent) { if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, user: response.user, userContext: input.userContext, }); diff --git a/lib/build/thirdparty-shared2.js b/lib/build/thirdparty-shared2.js index bf26d1480..c5f17c5db 100644 --- a/lib/build/thirdparty-shared2.js +++ b/lib/build/thirdparty-shared2.js @@ -287,6 +287,17 @@ var defaultTranslationsThirdParty = { THIRD_PARTY_PROVIDER_DEFAULT_BTN_START: "Continue with ", THIRD_PARTY_PROVIDER_DEFAULT_BTN_END: "", THIRD_PARTY_ERROR_NO_EMAIL: "Could not retrieve email. Please try a different method.", + /* + * The following are error messages from our backend SDK. + * These are returned as full messages to preserver compatibilty, but they work just like the keys above. + * They are shown as is by default (setting the value to undefined will display the raw translation key) + */ + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_004)": + undefined, + "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)": + undefined, + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_006)": + undefined, } ), }; @@ -507,6 +518,19 @@ var SignInAndUpCallback$1 = function (props) { }), ]; } + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + return [ + 2 /*return*/, + genericComponentOverrideContext.SuperTokens.getInstanceOrThrow().redirectToAuth({ + history: props.history, + queryParams: { + error: response.status, + message: response.reason, + }, + redirectBack: false, + }), + ]; + } if (response.status === "OK") { stateResponse = props.recipe.webJSRecipe.getStateAndOtherInfoFromStorage({ userContext: userContext, @@ -519,7 +543,8 @@ var SignInAndUpCallback$1 = function (props) { rid: props.recipe.config.recipeId, successRedirectContext: { action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, + user: response.user, redirectToPath: redirectToPath, }, }, diff --git a/lib/build/thirdpartyemailpassword-shared.js b/lib/build/thirdpartyemailpassword-shared.js index b3322dda9..a215a9380 100644 --- a/lib/build/thirdpartyemailpassword-shared.js +++ b/lib/build/thirdpartyemailpassword-shared.js @@ -31,7 +31,7 @@ var getFunctionOverrides = function (recipeId, onHandleEvent) { if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, user: response.user, userContext: input.userContext, }); @@ -90,6 +90,10 @@ var getFunctionOverrides = function (recipeId, onHandleEvent) { if (response.status === "OK") { onHandleEvent({ action: "RESET_PASSWORD_EMAIL_SENT", + email: input.formFields.find(function (_a) { + var id = _a.id; + return id === "email"; + }).value, userContext: input.userContext, }); } @@ -110,7 +114,7 @@ var getFunctionOverrides = function (recipeId, onHandleEvent) { if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: true, + isNewRecipeUser: true, user: response.user, userContext: input.userContext, }); @@ -132,7 +136,7 @@ var getFunctionOverrides = function (recipeId, onHandleEvent) { if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: false, + isNewRecipeUser: false, user: response.user, userContext: input.userContext, }); diff --git a/lib/build/thirdpartyemailpasswordprebuiltui.js b/lib/build/thirdpartyemailpasswordprebuiltui.js index 66e50379f..091b64450 100644 --- a/lib/build/thirdpartyemailpasswordprebuiltui.js +++ b/lib/build/thirdpartyemailpasswordprebuiltui.js @@ -33,6 +33,7 @@ require("./emailpassword-shared7.js"); require("supertokens-web-js/utils/error"); require("./emailpassword-shared5.js"); require("./emailpassword-shared2.js"); +require("supertokens-web-js/lib/build/error"); require("./emailpassword-shared4.js"); require("supertokens-web-js/recipe/emailpassword"); require("./authRecipe-shared.js"); diff --git a/lib/build/thirdpartypasswordless-shared.js b/lib/build/thirdpartypasswordless-shared.js index 299c78801..ef7d96ef0 100644 --- a/lib/build/thirdpartypasswordless-shared.js +++ b/lib/build/thirdpartypasswordless-shared.js @@ -31,7 +31,7 @@ var getFunctionOverrides = function (recipeId, onHandleEvent) { if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, user: response.user, userContext: input.userContext, }); @@ -110,7 +110,7 @@ var getFunctionOverrides = function (recipeId, onHandleEvent) { } else if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, user: response.user, }); } diff --git a/lib/build/version.d.ts b/lib/build/version.d.ts index 2da25307c..1e2536ff4 100644 --- a/lib/build/version.d.ts +++ b/lib/build/version.d.ts @@ -1 +1 @@ -export declare const package_version = "0.34.2"; +export declare const package_version = "0.35.0"; diff --git a/lib/ts/recipe/authRecipe/authWidgetWrapper.tsx b/lib/ts/recipe/authRecipe/authWidgetWrapper.tsx index 55b4be1c2..052000fe9 100644 --- a/lib/ts/recipe/authRecipe/authWidgetWrapper.tsx +++ b/lib/ts/recipe/authRecipe/authWidgetWrapper.tsx @@ -77,7 +77,8 @@ const Redirector = = UserInputRecipeModule; @@ -31,11 +26,19 @@ export type Config = UserInput & RecipeModuleConfig; export type NormalisedConfig = NormalisedRecipeModuleConfig; -export type GetRedirectionURLContext = { - action: "SUCCESS"; - isNewUser: boolean; - redirectToPath?: string; -}; +export type GetRedirectionURLContext = + | { + action: "SUCCESS"; + isNewRecipeUser: true; + user: User; + redirectToPath?: string; + } + | { + action: "SUCCESS"; + isNewRecipeUser: false; + user?: User; + redirectToPath?: string; + }; export type OnHandleEventContext = { action: "SESSION_ALREADY_EXISTS"; diff --git a/lib/ts/recipe/emailpassword/components/features/signInAndUp/index.tsx b/lib/ts/recipe/emailpassword/components/features/signInAndUp/index.tsx index fc6cb65d9..86def01ab 100644 --- a/lib/ts/recipe/emailpassword/components/features/signInAndUp/index.tsx +++ b/lib/ts/recipe/emailpassword/components/features/signInAndUp/index.tsx @@ -41,6 +41,7 @@ import type { } from "../../../types"; import type { Dispatch } from "react"; import type { RecipeInterface } from "supertokens-web-js/recipe/emailpassword"; +import type { User } from "supertokens-web-js/types"; export const useFeatureReducer = (recipe: Recipe | undefined) => { return React.useReducer( @@ -111,35 +112,43 @@ export function useChildProps( const recipeImplementation = useMemo(() => recipe && getModifiedRecipeImplementation(recipe.webJSRecipe), [recipe]); const userContext = useUserContext(); - const onSignInSuccess = useCallback(async (): Promise => { - return Session.getInstanceOrThrow().validateGlobalClaimsAndHandleSuccessRedirection( - { - rid: recipe!.config.recipeId, - successRedirectContext: { - action: "SUCCESS", - isNewUser: false, - redirectToPath: getRedirectToPathFromURL(), + const onSignInSuccess = useCallback( + async (response: { user: User }): Promise => { + return Session.getInstanceOrThrow().validateGlobalClaimsAndHandleSuccessRedirection( + { + rid: recipe!.config.recipeId, + successRedirectContext: { + action: "SUCCESS", + isNewRecipeUser: false, + user: response.user, + redirectToPath: getRedirectToPathFromURL(), + }, }, - }, - userContext, - history - ); - }, [recipe, userContext, history]); + userContext, + history + ); + }, + [recipe, userContext, history] + ); - const onSignUpSuccess = useCallback(async (): Promise => { - return Session.getInstanceOrThrow().validateGlobalClaimsAndHandleSuccessRedirection( - { - rid: recipe!.config.recipeId, - successRedirectContext: { - action: "SUCCESS", - isNewUser: true, - redirectToPath: getRedirectToPathFromURL(), + const onSignUpSuccess = useCallback( + async (response: { user: User }): Promise => { + return Session.getInstanceOrThrow().validateGlobalClaimsAndHandleSuccessRedirection( + { + rid: recipe!.config.recipeId, + successRedirectContext: { + action: "SUCCESS", + isNewRecipeUser: true, + user: response.user, + redirectToPath: getRedirectToPathFromURL(), + }, }, - }, - userContext, - history - ); - }, [recipe, userContext, history]); + userContext, + history + ); + }, + [recipe, userContext, history] + ); return useMemo(() => { if (recipe === undefined || recipeImplementation === undefined) { diff --git a/lib/ts/recipe/emailpassword/components/themes/resetPasswordUsingToken/resetPasswordEmail.tsx b/lib/ts/recipe/emailpassword/components/themes/resetPasswordUsingToken/resetPasswordEmail.tsx index bd2c3bb36..75451f204 100644 --- a/lib/ts/recipe/emailpassword/components/themes/resetPasswordUsingToken/resetPasswordEmail.tsx +++ b/lib/ts/recipe/emailpassword/components/themes/resetPasswordUsingToken/resetPasswordEmail.tsx @@ -105,10 +105,17 @@ const EmailPasswordResetPasswordEmail: React.FC = (props) => { setEmailFieldValue(emailField.value); } - return await props.recipeImplementation.sendPasswordResetEmail({ + const resp = await props.recipeImplementation.sendPasswordResetEmail({ formFields, userContext, }); + if (resp.status === "PASSWORD_RESET_NOT_ALLOWED") { + return { + status: "FIELD_ERROR", + formFields: [{ id: "email", error: resp.reason }], + }; + } + return resp; }} showLabels={true} validateOnBlur={true} diff --git a/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInForm.tsx b/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInForm.tsx index b0cdb8f56..2f7cc2dde 100644 --- a/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInForm.tsx +++ b/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInForm.tsx @@ -62,6 +62,8 @@ export const SignInForm = withOverride( }); if (response.status === "WRONG_CREDENTIALS_ERROR") { throw new STGeneralError("EMAIL_PASSWORD_SIGN_IN_WRONG_CREDENTIALS_ERROR"); + } else if (response.status === "SIGN_IN_NOT_ALLOWED") { + throw new STGeneralError(response.reason); } else { return response; } diff --git a/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signUpForm.tsx b/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signUpForm.tsx index 1c69829dd..e7df1ceba 100644 --- a/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signUpForm.tsx +++ b/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signUpForm.tsx @@ -15,6 +15,8 @@ /* * Imports. */ +import STGeneralError from "supertokens-web-js/lib/build/error"; + import { withOverride } from "../../../../../components/componentOverride/withOverride"; import { useUserContext } from "../../../../../usercontext"; import { validateForm } from "../../../../../utils"; @@ -56,10 +58,15 @@ export const SignUpForm = withOverride( }; } - return props.recipeImplementation.signUp({ + const res = await props.recipeImplementation.signUp({ formFields, userContext, }); + + if (res.status === "SIGN_UP_NOT_ALLOWED") { + throw new STGeneralError(res.reason); + } + return res; }} validateOnBlur={true} showLabels={true} diff --git a/lib/ts/recipe/emailpassword/components/themes/translations.ts b/lib/ts/recipe/emailpassword/components/themes/translations.ts index 7d16fb5b6..048a6940a 100644 --- a/lib/ts/recipe/emailpassword/components/themes/translations.ts +++ b/lib/ts/recipe/emailpassword/components/themes/translations.ts @@ -80,5 +80,11 @@ export const defaultTranslationsEmailPassword = { "Password must contain at least one alphabet": undefined, "Password must contain at least one number": undefined, "Email is invalid": undefined, + "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)": + undefined, + "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)": + undefined, + "Cannot sign in due to security reasons. Please try resetting your password, use a different login method or contact support. (ERR_CODE_008)": + undefined, }, }; diff --git a/lib/ts/recipe/emailpassword/functionOverrides.ts b/lib/ts/recipe/emailpassword/functionOverrides.ts index 3e01f10ce..06227e9c0 100644 --- a/lib/ts/recipe/emailpassword/functionOverrides.ts +++ b/lib/ts/recipe/emailpassword/functionOverrides.ts @@ -28,6 +28,7 @@ export const getFunctionOverrides = if (response.status === "OK") { onHandleEvent({ action: "RESET_PASSWORD_EMAIL_SENT", + email: input.formFields.find(({ id }) => id === "email")!.value, userContext: input.userContext, }); } @@ -40,7 +41,7 @@ export const getFunctionOverrides = if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: true, + isNewRecipeUser: true, user: response.user, userContext: input.userContext, }); @@ -54,7 +55,7 @@ export const getFunctionOverrides = if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: false, + isNewRecipeUser: false, user: response.user, userContext: input.userContext, }); diff --git a/lib/ts/recipe/emailpassword/index.ts b/lib/ts/recipe/emailpassword/index.ts index e44cfb821..6f5423b40 100644 --- a/lib/ts/recipe/emailpassword/index.ts +++ b/lib/ts/recipe/emailpassword/index.ts @@ -22,8 +22,8 @@ import EmailPassword from "./recipe"; import { UserInput } from "./types"; import { GetRedirectionURLContext, PreAPIHookContext, OnHandleEventContext } from "./types"; -import type { User } from "../authRecipe/types"; import type { RecipeFunctionOptions } from "supertokens-web-js/recipe/emailpassword"; +import type { User } from "supertokens-web-js/types"; export default class Wrapper { static init(config?: UserInput) { @@ -45,7 +45,11 @@ export default class Wrapper { userContext?: any; }): Promise< | { - status: "OK" | "RESET_PASSWORD_INVALID_TOKEN_ERROR"; + status: "OK"; + fetchResponse: Response; + } + | { + status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; fetchResponse: Response; } | { @@ -72,7 +76,7 @@ export default class Wrapper { userContext?: any; }): Promise< | { - status: "OK"; + status: "OK" | "PASSWORD_RESET_NOT_ALLOWED"; fetchResponse: Response; } | { @@ -111,6 +115,11 @@ export default class Wrapper { }[]; fetchResponse: Response; } + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } > { return EmailPassword.getInstanceOrThrow().webJSRecipe.signUp({ ...input, @@ -143,6 +152,11 @@ export default class Wrapper { status: "WRONG_CREDENTIALS_ERROR"; fetchResponse: Response; } + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } > { return EmailPassword.getInstanceOrThrow().webJSRecipe.signIn({ ...input, diff --git a/lib/ts/recipe/emailpassword/types.ts b/lib/ts/recipe/emailpassword/types.ts index df43f112e..253329077 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -37,7 +37,6 @@ import type { import type { GetRedirectionURLContext as AuthRecipeModuleGetRedirectionURLContext, OnHandleEventContext as AuthRecipeModuleOnHandleEventContext, - User, Config as AuthRecipeModuleConfig, NormalisedConfig as NormalisedAuthRecipeModuleConfig, UserInput as AuthRecipeModuleUserInput, @@ -46,6 +45,7 @@ import type React from "react"; import type { Dispatch } from "react"; import type { OverrideableBuilder } from "supertokens-js-override"; import type { RecipeInterface } from "supertokens-web-js/recipe/emailpassword"; +import type { User } from "supertokens-web-js/types"; export type ComponentOverrideMap = { EmailPasswordSignIn_Override?: ComponentOverride; @@ -245,7 +245,7 @@ export type SignInThemeProps = FormThemeBaseProps & { config: NormalisedConfig; signUpClicked?: () => void; forgotPasswordClick: () => void; - onSuccess: () => void; + onSuccess: (result: { user: User }) => void; }; export type SignUpThemeProps = FormThemeBaseProps & { @@ -254,7 +254,7 @@ export type SignUpThemeProps = FormThemeBaseProps & { onError: (error: string) => void; config: NormalisedConfig; signInClicked?: () => void; - onSuccess: () => void; + onSuccess: (result: { user: User }) => void; }; export type SignInAndUpThemeProps = { @@ -342,13 +342,21 @@ export type OnHandleEventContext = /* * On Handle Event actions */ - action: "RESET_PASSWORD_EMAIL_SENT" | "PASSWORD_RESET_SUCCESSFUL"; + action: "RESET_PASSWORD_EMAIL_SENT"; + email: string; + userContext: any; + } + | { + /* + * On Handle Event actions + */ + action: "PASSWORD_RESET_SUCCESSFUL"; userContext: any; } | { action: "SUCCESS"; - isNewUser: boolean; - user: { id: string; email: string }; + isNewRecipeUser: boolean; + user: User; userContext: any; }; diff --git a/lib/ts/recipe/passwordless/components/features/linkClickedScreen/index.tsx b/lib/ts/recipe/passwordless/components/features/linkClickedScreen/index.tsx index d79c2991c..99181dee9 100644 --- a/lib/ts/recipe/passwordless/components/features/linkClickedScreen/index.tsx +++ b/lib/ts/recipe/passwordless/components/features/linkClickedScreen/index.tsx @@ -86,6 +86,16 @@ const LinkClickedScreen: React.FC = (props) => { }); } + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + return SuperTokens.getInstanceOrThrow().redirectToAuth({ + history: props.history, + queryParams: { + error: response.reason, + }, + redirectBack: false, + }); + } + if (response.status === "OK") { const loginAttemptInfo = await props.recipe.webJSRecipe.getLoginAttemptInfo({ @@ -99,7 +109,8 @@ const LinkClickedScreen: React.FC = (props) => { rid: props.recipe.config.recipeId, successRedirectContext: { action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, + user: response.user, redirectToPath: loginAttemptInfo?.redirectToPath, }, }, diff --git a/lib/ts/recipe/passwordless/components/features/signInAndUp/index.tsx b/lib/ts/recipe/passwordless/components/features/signInAndUp/index.tsx index 831b70a13..49eb6f92f 100644 --- a/lib/ts/recipe/passwordless/components/features/signInAndUp/index.tsx +++ b/lib/ts/recipe/passwordless/components/features/signInAndUp/index.tsx @@ -35,7 +35,8 @@ import type { FeatureBaseProps } from "../../../../../types"; import type Recipe from "../../../recipe"; import type { AdditionalLoginAttemptInfoProperties, ComponentOverrideMap } from "../../../types"; import type { PasswordlessSignInUpAction, SignInUpState, SignInUpChildProps, NormalisedConfig } from "../../../types"; -import type { RecipeInterface, PasswordlessUser } from "supertokens-web-js/recipe/passwordless"; +import type { RecipeInterface } from "supertokens-web-js/recipe/passwordless"; +import type { User } from "supertokens-web-js/types"; export const useSuccessInAnotherTabChecker = ( state: SignInUpState, @@ -214,13 +215,14 @@ export function useChildProps( return undefined; } return { - onSuccess: (result: { createdNewUser: boolean; user: PasswordlessUser }) => { + onSuccess: (result: { createdNewRecipeUser: boolean; user: User }) => { return SessionRecipe.getInstanceOrThrow().validateGlobalClaimsAndHandleSuccessRedirection( { rid: recipe.config.recipeId, successRedirectContext: { action: "SUCCESS", - isNewUser: result.createdNewUser, + isNewRecipeUser: result.createdNewRecipeUser, + user: result.user, redirectToPath: getRedirectToPathFromURL(), }, }, @@ -367,6 +369,12 @@ function getModifiedRecipeImplementation( }); dispatch({ type: "restartFlow", error: "ERROR_SIGN_IN_UP_CODE_CONSUME_RESTART_FLOW" }); + } else if (res.status === "SIGN_IN_UP_NOT_ALLOWED") { + await originalImpl.clearLoginAttemptInfo({ + userContext: input.userContext, + }); + + dispatch({ type: "restartFlow", error: res.reason }); } else if (res.status === "OK") { await originalImpl.clearLoginAttemptInfo({ userContext: input.userContext, diff --git a/lib/ts/recipe/passwordless/components/themes/signInUp/emailForm.tsx b/lib/ts/recipe/passwordless/components/themes/signInUp/emailForm.tsx index 3055e1165..451b60535 100644 --- a/lib/ts/recipe/passwordless/components/themes/signInUp/emailForm.tsx +++ b/lib/ts/recipe/passwordless/components/themes/signInUp/emailForm.tsx @@ -61,6 +61,10 @@ export const EmailForm = withOverride( userContext, }); + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + throw new STGeneralError(response.reason); + } + return response; }} validateOnBlur={false} diff --git a/lib/ts/recipe/passwordless/components/themes/signInUp/emailOrPhoneForm.tsx b/lib/ts/recipe/passwordless/components/themes/signInUp/emailOrPhoneForm.tsx index fbade3bfc..8e7bd9cb6 100644 --- a/lib/ts/recipe/passwordless/components/themes/signInUp/emailOrPhoneForm.tsx +++ b/lib/ts/recipe/passwordless/components/themes/signInUp/emailOrPhoneForm.tsx @@ -83,6 +83,10 @@ export const EmailOrPhoneForm = withOverride( userContext, }); + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + throw new STGeneralError(response.reason); + } + return response; } else { throw new STGeneralError(emailValidationRes); @@ -95,6 +99,9 @@ export const EmailOrPhoneForm = withOverride( userContext, }); + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + throw new STGeneralError(response.reason); + } return response; } @@ -108,10 +115,15 @@ export const EmailOrPhoneForm = withOverride( const phoneValidationResAfterGuess = await props.config.validatePhoneNumber(intPhoneNumber); if (phoneValidationResAfterGuess === undefined) { try { - return await props.recipeImplementation.createCode({ + const response = await props.recipeImplementation.createCode({ phoneNumber: intPhoneNumber, userContext, }); + + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + throw new STGeneralError(response.reason); + } + return response; } catch (ex) { // General errors from the API can make createCode throw but we want to switch to the phone UI anyway setValue("emailOrPhone", intPhoneNumber); diff --git a/lib/ts/recipe/passwordless/components/themes/signInUp/phoneForm.tsx b/lib/ts/recipe/passwordless/components/themes/signInUp/phoneForm.tsx index 81f75c4aa..f081908da 100644 --- a/lib/ts/recipe/passwordless/components/themes/signInUp/phoneForm.tsx +++ b/lib/ts/recipe/passwordless/components/themes/signInUp/phoneForm.tsx @@ -77,6 +77,10 @@ export const PhoneForm = withOverride( userContext, }); + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + throw new STGeneralError(response.reason); + } + return response; }} validateOnBlur={false} diff --git a/lib/ts/recipe/passwordless/components/themes/signInUp/userInputCodeForm.tsx b/lib/ts/recipe/passwordless/components/themes/signInUp/userInputCodeForm.tsx index 57c772e06..7ce45d203 100644 --- a/lib/ts/recipe/passwordless/components/themes/signInUp/userInputCodeForm.tsx +++ b/lib/ts/recipe/passwordless/components/themes/signInUp/userInputCodeForm.tsx @@ -137,7 +137,13 @@ export const UserInputCodeForm = withOverride( userContext, }); - if (response.status === "OK" || response.status === "RESTART_FLOW_ERROR") { + // We can redirect these statuses, since they all cause a redirection + // and we don't really want to show anything + if ( + response.status === "OK" || + response.status === "RESTART_FLOW_ERROR" || + response.status === "SIGN_IN_UP_NOT_ALLOWED" + ) { return response; } diff --git a/lib/ts/recipe/passwordless/components/themes/translations.ts b/lib/ts/recipe/passwordless/components/themes/translations.ts index 26cc2ffdc..0fa66ae81 100644 --- a/lib/ts/recipe/passwordless/components/themes/translations.ts +++ b/lib/ts/recipe/passwordless/components/themes/translations.ts @@ -79,5 +79,9 @@ export const defaultTranslationsPasswordless = { "Failed to generate a one time code. Please try again": undefined, "Phone number is invalid": undefined, "Email is invalid": undefined, + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_002)": + undefined, + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_003)": + undefined, }, }; diff --git a/lib/ts/recipe/passwordless/functionOverrides.ts b/lib/ts/recipe/passwordless/functionOverrides.ts index b5b299a12..372cf858b 100644 --- a/lib/ts/recipe/passwordless/functionOverrides.ts +++ b/lib/ts/recipe/passwordless/functionOverrides.ts @@ -43,7 +43,7 @@ export const getFunctionOverrides = } else if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, user: response.user, }); } diff --git a/lib/ts/recipe/passwordless/index.ts b/lib/ts/recipe/passwordless/index.ts index 93f5c8e8c..d4ae09bec 100644 --- a/lib/ts/recipe/passwordless/index.ts +++ b/lib/ts/recipe/passwordless/index.ts @@ -23,7 +23,8 @@ import { UserInput } from "./types"; import { GetRedirectionURLContext, PreAPIHookContext, OnHandleEventContext } from "./types"; import type { RecipeFunctionOptions } from "supertokens-web-js/recipe/passwordless"; -import type { PasswordlessFlowType, PasswordlessUser } from "supertokens-web-js/recipe/passwordless/types"; +import type { PasswordlessFlowType } from "supertokens-web-js/recipe/passwordless/types"; +import type { User } from "supertokens-web-js/types"; export default class Wrapper { static init(config: UserInput) { @@ -40,13 +41,19 @@ export default class Wrapper { input: | { email: string; userContext?: any; options?: RecipeFunctionOptions } | { phoneNumber: string; userContext?: any; options?: RecipeFunctionOptions } - ): Promise<{ - status: "OK"; - deviceId: string; - preAuthSessionId: string; - flowType: PasswordlessFlowType; - fetchResponse: Response; - }> { + ): Promise< + | { + status: "OK"; + deviceId: string; + preAuthSessionId: string; + flowType: PasswordlessFlowType; + fetchResponse: Response; + } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + } + > { return Passwordless.getInstanceOrThrow().webJSRecipe.createCode({ ...input, userContext: getNormalisedUserContext(input?.userContext), @@ -77,8 +84,8 @@ export default class Wrapper { ): Promise< | { status: "OK"; - createdNewUser: boolean; - user: PasswordlessUser; + createdNewRecipeUser: boolean; + user: User; fetchResponse: Response; } | { @@ -88,6 +95,7 @@ export default class Wrapper { fetchResponse: Response; } | { status: "RESTART_FLOW_ERROR"; fetchResponse: Response } + | { status: "SIGN_IN_UP_NOT_ALLOWED"; reason: string; fetchResponse: Response } > { return Passwordless.getInstanceOrThrow().webJSRecipe.consumeCode({ ...input, diff --git a/lib/ts/recipe/passwordless/types.ts b/lib/ts/recipe/passwordless/types.ts index 5598ff13b..bd78c63ce 100644 --- a/lib/ts/recipe/passwordless/types.ts +++ b/lib/ts/recipe/passwordless/types.ts @@ -35,7 +35,8 @@ import type { } from "../authRecipe/types"; import type { Dispatch } from "react"; import type WebJSRecipe from "supertokens-web-js/recipe/passwordless"; -import type { RecipeInterface, PasswordlessUser } from "supertokens-web-js/recipe/passwordless"; +import type { RecipeInterface } from "supertokens-web-js/recipe/passwordless"; +import type { User } from "supertokens-web-js/types"; export type PreAndPostAPIHookAction = | "PASSWORDLESS_CREATE_CODE" @@ -66,8 +67,8 @@ export type GetRedirectionURLContext = AuthRecipeModuleGetRedirectionURLContext; export type OnHandleEventContext = | { action: "SUCCESS"; - isNewUser: boolean; - user: PasswordlessUser; + isNewRecipeUser: boolean; + user: User; } | { action: "PASSWORDLESS_RESTART_FLOW"; @@ -187,7 +188,7 @@ export type UserInput = ( export type SignInUpProps = { recipeImplementation: RecipeImplementation; config: NormalisedConfig; - onSuccess?: (result: { createdNewUser: boolean; user: PasswordlessUser }) => void; + onSuccess?: (result: { createdNewRecipeUser: boolean; user: User }) => void; dispatch: Dispatch; featureState: { loginAttemptInfo?: LoginAttemptInfo; @@ -257,7 +258,7 @@ export type SignInUpUserInputCodeFormProps = { recipeImplementation: RecipeImplementation; config: NormalisedConfig; loginAttemptInfo: LoginAttemptInfo; - onSuccess?: (result: { createdNewUser: boolean; user: PasswordlessUser }) => void; + onSuccess?: (result: { createdNewRecipeUser: boolean; user: User }) => void; }; export type LinkClickedScreenProps = { diff --git a/lib/ts/recipe/session/recipe.tsx b/lib/ts/recipe/session/recipe.tsx index 81ce43874..46f1ffb96 100644 --- a/lib/ts/recipe/session/recipe.tsx +++ b/lib/ts/recipe/session/recipe.tsx @@ -169,7 +169,8 @@ export default class Session extends RecipeModule = (props) => { }); } + if (response.status === "SIGN_IN_UP_NOT_ALLOWED") { + return SuperTokens.getInstanceOrThrow().redirectToAuth({ + history: props.history, + queryParams: { + error: response.status, + message: response.reason, + }, + redirectBack: false, + }); + } + if (response.status === "OK") { const stateResponse = props.recipe.webJSRecipe.getStateAndOtherInfoFromStorage({ userContext, @@ -65,7 +76,8 @@ const SignInAndUpCallback: React.FC = (props) => { rid: props.recipe.config.recipeId, successRedirectContext: { action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, + user: response.user, redirectToPath, }, }, diff --git a/lib/ts/recipe/thirdparty/components/themes/translations.ts b/lib/ts/recipe/thirdparty/components/themes/translations.ts index c9ed3258e..052ac5537 100644 --- a/lib/ts/recipe/thirdparty/components/themes/translations.ts +++ b/lib/ts/recipe/thirdparty/components/themes/translations.ts @@ -15,5 +15,19 @@ export const defaultTranslationsThirdParty = { THIRD_PARTY_PROVIDER_DEFAULT_BTN_END: "", THIRD_PARTY_ERROR_NO_EMAIL: "Could not retrieve email. Please try a different method.", + + /* + * The following are error messages from our backend SDK. + * These are returned as full messages to preserver compatibilty, but they work just like the keys above. + * They are shown as is by default (setting the value to undefined will display the raw translation key) + */ + + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_004)": + undefined, + "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)": + undefined, + + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_006)": + undefined, }, }; diff --git a/lib/ts/recipe/thirdparty/functionOverrides.ts b/lib/ts/recipe/thirdparty/functionOverrides.ts index 2070af3e4..5c5637cc3 100644 --- a/lib/ts/recipe/thirdparty/functionOverrides.ts +++ b/lib/ts/recipe/thirdparty/functionOverrides.ts @@ -14,7 +14,7 @@ export const getFunctionOverrides = if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, user: response.user, userContext: input.userContext, }); diff --git a/lib/ts/recipe/thirdparty/index.ts b/lib/ts/recipe/thirdparty/index.ts index 996e1bece..1ca8e57da 100644 --- a/lib/ts/recipe/thirdparty/index.ts +++ b/lib/ts/recipe/thirdparty/index.ts @@ -18,7 +18,7 @@ */ // /!\ ThirdParty must be imported before any of the providers to prevent circular dependencies. -import { RecipeInterface, ThirdPartyUserType as User } from "supertokens-web-js/recipe/thirdparty"; +import { RecipeInterface } from "supertokens-web-js/recipe/thirdparty"; import { getNormalisedUserContext } from "../../utils"; @@ -42,6 +42,7 @@ import { redirectToThirdPartyLogin as UtilsRedirectToThirdPartyLogin } from "./u import type { StateObject } from "supertokens-web-js/recipe/thirdparty"; import type { RecipeFunctionOptions } from "supertokens-web-js/recipe/thirdpartyemailpassword"; +import type { User } from "supertokens-web-js/types"; export default class Wrapper { /* @@ -98,13 +99,18 @@ export default class Wrapper { | { status: "OK"; user: User; - createdNewUser: boolean; + createdNewRecipeUser: boolean; fetchResponse: Response; } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER"; fetchResponse: Response; } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } > { return ThirdParty.getInstanceOrThrow().webJSRecipe.signInAndUp({ ...input, @@ -159,7 +165,6 @@ export { redirectToThirdPartyLogin, ThirdpartyComponentsOverrideProvider, signOut, - User, GetRedirectionURLContext, PreAPIHookContext, OnHandleEventContext, diff --git a/lib/ts/recipe/thirdparty/types.ts b/lib/ts/recipe/thirdparty/types.ts index cc93bb2b3..6be339670 100644 --- a/lib/ts/recipe/thirdparty/types.ts +++ b/lib/ts/recipe/thirdparty/types.ts @@ -31,6 +31,7 @@ import type { import type { OverrideableBuilder } from "supertokens-js-override"; import type ThirdPartyWebJS from "supertokens-web-js/recipe/thirdparty"; import type { StateObject as WebJsStateObject, RecipeInterface } from "supertokens-web-js/recipe/thirdparty"; +import type { User } from "supertokens-web-js/types"; export type ComponentOverrideMap = { ThirdPartySignUpFooter_Override?: ComponentOverride; @@ -123,8 +124,8 @@ export type OnHandleEventContext = | AuthRecipeModuleOnHandleEventContext | { action: "SUCCESS"; - isNewUser: boolean; - user: { id: string; email: string }; + isNewRecipeUser: boolean; + user: User; userContext: any; }; diff --git a/lib/ts/recipe/thirdpartyemailpassword/functionOverrides.ts b/lib/ts/recipe/thirdpartyemailpassword/functionOverrides.ts index 66b511aa1..0a3653072 100644 --- a/lib/ts/recipe/thirdpartyemailpassword/functionOverrides.ts +++ b/lib/ts/recipe/thirdpartyemailpassword/functionOverrides.ts @@ -14,7 +14,7 @@ export const getFunctionOverrides = if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, user: response.user, userContext: input.userContext, }); @@ -58,6 +58,7 @@ export const getFunctionOverrides = if (response.status === "OK") { onHandleEvent({ action: "RESET_PASSWORD_EMAIL_SENT", + email: input.formFields.find(({ id }) => id === "email")!.value, userContext: input.userContext, }); } @@ -70,7 +71,7 @@ export const getFunctionOverrides = if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: true, + isNewRecipeUser: true, user: response.user, userContext: input.userContext, }); @@ -84,7 +85,7 @@ export const getFunctionOverrides = if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: false, + isNewRecipeUser: false, user: response.user, userContext: input.userContext, }); diff --git a/lib/ts/recipe/thirdpartyemailpassword/index.ts b/lib/ts/recipe/thirdpartyemailpassword/index.ts index 146aac166..f352812c9 100644 --- a/lib/ts/recipe/thirdpartyemailpassword/index.ts +++ b/lib/ts/recipe/thirdpartyemailpassword/index.ts @@ -12,11 +12,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { - RecipeInterface, - EmailPasswordUserType as UserType, - ThirdPartyUserType, -} from "supertokens-web-js/recipe/thirdpartyemailpassword"; +import { RecipeInterface } from "supertokens-web-js/recipe/thirdpartyemailpassword"; import { getNormalisedUserContext } from "../../utils"; import { @@ -41,6 +37,7 @@ import { UserInput, GetRedirectionURLContext, PreAPIHookContext, OnHandleEventCo import type { StateObject } from "supertokens-web-js/recipe/thirdparty"; import type { RecipeFunctionOptions } from "supertokens-web-js/recipe/thirdpartyemailpassword"; +import type { User } from "supertokens-web-js/types"; export default class Wrapper { static init(config?: UserInput) { @@ -62,7 +59,11 @@ export default class Wrapper { userContext?: any; }): Promise< | { - status: "OK" | "RESET_PASSWORD_INVALID_TOKEN_ERROR"; + status: "OK"; + fetchResponse: Response; + } + | { + status: "RESET_PASSWORD_INVALID_TOKEN_ERROR"; fetchResponse: Response; } | { @@ -89,7 +90,7 @@ export default class Wrapper { userContext?: any; }): Promise< | { - status: "OK"; + status: "OK" | "PASSWORD_RESET_NOT_ALLOWED"; fetchResponse: Response; } | { @@ -117,7 +118,7 @@ export default class Wrapper { }): Promise< | { status: "OK"; - user: UserType; + user: User; fetchResponse: Response; } | { @@ -128,6 +129,11 @@ export default class Wrapper { }[]; fetchResponse: Response; } + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } > { return ThirdPartyEmailPassword.getInstanceOrThrow().webJSRecipe.emailPasswordSignUp({ ...input, @@ -145,7 +151,7 @@ export default class Wrapper { }): Promise< | { status: "OK"; - user: UserType; + user: User; fetchResponse: Response; } | { @@ -160,6 +166,11 @@ export default class Wrapper { status: "WRONG_CREDENTIALS_ERROR"; fetchResponse: Response; } + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } > { return ThirdPartyEmailPassword.getInstanceOrThrow().webJSRecipe.emailPasswordSignIn({ ...input, @@ -208,14 +219,19 @@ export default class Wrapper { static async thirdPartySignInAndUp(input?: { userContext?: any; options?: RecipeFunctionOptions }): Promise< | { status: "OK"; - user: ThirdPartyUserType; - createdNewUser: boolean; + user: User; + createdNewRecipeUser: boolean; fetchResponse: Response; } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER"; fetchResponse: Response; } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } > { return ThirdPartyEmailPassword.getInstanceOrThrow().webJSRecipe.thirdPartySignInAndUp({ ...input, @@ -305,6 +321,4 @@ export { OnHandleEventContext, UserInput, RecipeInterface, - UserType, - ThirdPartyUserType, }; diff --git a/lib/ts/recipe/thirdpartypasswordless/functionOverrides.ts b/lib/ts/recipe/thirdpartypasswordless/functionOverrides.ts index 62f386f12..de3737de5 100644 --- a/lib/ts/recipe/thirdpartypasswordless/functionOverrides.ts +++ b/lib/ts/recipe/thirdpartypasswordless/functionOverrides.ts @@ -14,7 +14,7 @@ export const getFunctionOverrides = if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, user: response.user, userContext: input.userContext, }); @@ -73,7 +73,7 @@ export const getFunctionOverrides = } else if (response.status === "OK") { onHandleEvent({ action: "SUCCESS", - isNewUser: response.createdNewUser, + isNewRecipeUser: response.createdNewRecipeUser, user: response.user, }); } diff --git a/lib/ts/recipe/thirdpartypasswordless/index.ts b/lib/ts/recipe/thirdpartypasswordless/index.ts index 6efc183cd..7cb24c69c 100644 --- a/lib/ts/recipe/thirdpartypasswordless/index.ts +++ b/lib/ts/recipe/thirdpartypasswordless/index.ts @@ -35,12 +35,9 @@ import { RecipeComponentsOverrideContextProvider } from "./componentOverrideCont import ThirdPartyPasswordless from "./recipe"; import { UserInput, GetRedirectionURLContext, PreAPIHookContext, OnHandleEventContext } from "./types"; -import type { StateObject, ThirdPartyUserType as UserType } from "supertokens-web-js/recipe/thirdparty"; -import type { - PasswordlessFlowType, - PasswordlessUser, - RecipeFunctionOptions, -} from "supertokens-web-js/recipe/thirdpartypasswordless"; +import type { StateObject } from "supertokens-web-js/recipe/thirdparty"; +import type { PasswordlessFlowType, RecipeFunctionOptions } from "supertokens-web-js/recipe/thirdpartypasswordless"; +import type { User } from "supertokens-web-js/types"; export default class Wrapper { static init(config: UserInput) { @@ -76,14 +73,19 @@ export default class Wrapper { static async thirdPartySignInAndUp(input?: { userContext?: any; options?: RecipeFunctionOptions }): Promise< | { status: "OK"; - user: UserType; - createdNewUser: boolean; + user: User; + createdNewRecipeUser: boolean; fetchResponse: Response; } | { status: "NO_EMAIL_GIVEN_BY_PROVIDER"; fetchResponse: Response; } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } > { return ThirdPartyPasswordless.getInstanceOrThrow().webJSRecipe.thirdPartySignInAndUp({ ...input, @@ -119,13 +121,20 @@ export default class Wrapper { input: | { email: string; userContext?: any; options?: RecipeFunctionOptions } | { phoneNumber: string; userContext?: any; options?: RecipeFunctionOptions } - ): Promise<{ - status: "OK"; - deviceId: string; - preAuthSessionId: string; - flowType: PasswordlessFlowType; - fetchResponse: Response; - }> { + ): Promise< + | { + status: "OK"; + deviceId: string; + preAuthSessionId: string; + flowType: PasswordlessFlowType; + fetchResponse: Response; + } + | { + status: "SIGN_IN_UP_NOT_ALLOWED"; + reason: string; + fetchResponse: Response; + } + > { return ThirdPartyPasswordless.getInstanceOrThrow().webJSRecipe.createPasswordlessCode({ ...input, userContext: getNormalisedUserContext(input.userContext), @@ -156,8 +165,8 @@ export default class Wrapper { ): Promise< | { status: "OK"; - createdNewUser: boolean; - user: PasswordlessUser; + createdNewRecipeUser: boolean; + user: User; fetchResponse: Response; } | { @@ -167,6 +176,7 @@ export default class Wrapper { fetchResponse: Response; } | { status: "RESTART_FLOW_ERROR"; fetchResponse: Response } + | { status: "SIGN_IN_UP_NOT_ALLOWED"; reason: string; fetchResponse: Response } > { return ThirdPartyPasswordless.getInstanceOrThrow().webJSRecipe.consumePasswordlessCode({ ...input, diff --git a/lib/ts/version.ts b/lib/ts/version.ts index 32897ee40..6da45ed4c 100644 --- a/lib/ts/version.ts +++ b/lib/ts/version.ts @@ -12,4 +12,4 @@ * License for the specific language governing permissions and limitations * under the License. */ -export const package_version = "0.34.2"; +export const package_version = "0.35.0"; diff --git a/package-lock.json b/package-lock.json index 0737d9dad..2a901d8ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "supertokens-auth-react", - "version": "0.34.2", + "version": "0.35.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "supertokens-auth-react", - "version": "0.34.2", + "version": "0.35.0", "license": "Apache-2.0", "dependencies": { "intl-tel-input": "^17.0.19", @@ -52,9 +52,9 @@ "jsdom-global": "3.0.2", "madge": "^5.0.1", "mkdirp": "^1.0.4", - "mocha": "6.1.4", - "mocha-junit-reporter": "^2.0.2", - "mocha-multi": "1.1.6", + "mocha": "^10.2.0", + "mocha-junit-reporter": "^2.2.1", + "mocha-multi": "^1.1.7", "mocha-split-tests": "github:rishabhpoddar/mocha-split-tests", "npm-run-all": "^4.1.5", "postcss": "^8.4.19", @@ -88,7 +88,7 @@ "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0", - "supertokens-web-js": "^0.7.2" + "supertokens-web-js": "github:supertokens/supertokens-web-js#0.8" } }, "eslint": { @@ -4903,9 +4903,9 @@ } }, "node_modules/ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true, "engines": { "node": ">=6" @@ -5070,25 +5070,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.reduce": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", - "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array.prototype.tosorted": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", @@ -6003,15 +5984,6 @@ "integrity": "sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==", "dev": true }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -6904,12 +6876,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -8017,13 +7983,10 @@ } }, "node_modules/flat": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", - "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, - "dependencies": { - "is-buffer": "~2.0.3" - }, "bin": { "flat": "cli.js" } @@ -8074,29 +8037,6 @@ "rimraf": "bin.js" } }, - "node_modules/flat/node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=4" - } - }, "node_modules/flatted": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", @@ -8467,15 +8407,6 @@ "node": ">=0.6.8" } }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -8900,15 +8831,6 @@ "resolved": "https://registry.npmjs.org/intl-tel-input/-/intl-tel-input-17.0.21.tgz", "integrity": "sha512-TfyPxLe41QZPOf6RqBxRE2dpQ0FThB/PBD/gRbxVhGW7IuYg30QD90x/vjmEo4vkZw7j8etxpVcjIZVRcG+Otw==" }, - "node_modules/invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -9168,6 +9090,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -11857,18 +11788,6 @@ "node": ">=6" } }, - "node_modules/lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "dependencies": { - "invert-kv": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -11994,15 +11913,80 @@ "dev": true }, "node_modules/log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "dependencies": { - "chalk": "^2.0.1" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/loglevel": { @@ -12216,18 +12200,6 @@ "tmpl": "1.0.5" } }, - "node_modules/map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "dependencies": { - "p-defer": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/marked": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", @@ -12287,20 +12259,6 @@ "is-buffer": "~1.1.6" } }, - "node_modules/mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "dependencies": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -12417,41 +12375,43 @@ "dev": true }, "node_modules/mocha": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", - "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dev": true, "dependencies": { - "ansi-colors": "3.2.3", + "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "2.2.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "ms": "2.1.1", - "node-environment-flags": "1.0.5", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.2.2", - "yargs-parser": "13.0.0", - "yargs-unparser": "1.5.0" + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" }, "bin": { "_mocha": "bin/_mocha", - "mocha": "bin/mocha" + "mocha": "bin/mocha.js" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" } }, "node_modules/mocha-junit-reporter": { @@ -12498,9 +12458,9 @@ } }, "node_modules/mocha-multi": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/mocha-multi/-/mocha-multi-1.1.6.tgz", - "integrity": "sha512-hMVmd9C1h4PEiFNwHxn8aa5/EgGLg0UswdOrlrq1Y8ieKmot8hZLYaiESIgg/He3E4oxwaXPWT1V1PJ0qNJlUQ==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/mocha-multi/-/mocha-multi-1.1.7.tgz", + "integrity": "sha512-SXZRgHy0XiRTASyOp0p6fjOkdj+R62L6cqutnYyQOvIjNznJuUwzykxctypeRiOwPd+gfn4yt3NRulMQyI8Tzg==", "dev": true, "dependencies": { "debug": "^4.1.1", @@ -12513,7 +12473,7 @@ "node": ">=6.0.0" }, "peerDependencies": { - "mocha": ">=2.2.0 <7 || ^9" + "mocha": ">=2.2.0 <7 || >=9" } }, "node_modules/mocha-split-tests": { @@ -12553,80 +12513,97 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "node_modules/mocha/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/mocha/node_modules/cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "dependencies": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/mocha/node_modules/cliui/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "node_modules/mocha/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=4" + "node": ">=7.0.0" } }, - "node_modules/mocha/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } + "node_modules/mocha/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/mocha/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true, "engines": { "node": ">=0.3.1" } }, - "node_modules/mocha/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/mocha/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "locate-path": "^3.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mocha/node_modules/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -12638,297 +12615,173 @@ }, "engines": { "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/mocha/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=4" + "node": "*" } }, "node_modules/mocha/node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "node_modules/mocha/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mocha/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=10" } }, - "node_modules/mocha/node_modules/minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", - "dev": true - }, - "node_modules/mocha/node_modules/mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "minimist": "0.0.8" - }, - "bin": { - "mkdirp": "bin/cmd.js" + "balanced-match": "^1.0.0" } }, "node_modules/mocha/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/mocha/node_modules/object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "node_modules/mocha/node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true, - "dependencies": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">= 0.4" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/mocha/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "dependencies": { - "p-try": "^2.0.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mocha/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/mocha/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/mocha/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/mocha/node_modules/string-width/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mocha/node_modules/string-width/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/mocha/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=4" - } - }, - "node_modules/mocha/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/mocha/node_modules/supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=6" - } - }, - "node_modules/mocha/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" + "node": ">=10" }, - "bin": { - "which": "bin/which" + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/mocha/node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", - "dev": true, - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mocha/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mocha/node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "dependencies": { - "number-is-nan": "^1.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mocha/node_modules/wrap-ansi/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "node": ">=10" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/mocha/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "node_modules/mocha/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, "node_modules/mocha/node_modules/yargs": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", - "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "dependencies": { - "cliui": "^4.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.0.0" + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" } }, "node_modules/module-definition": { @@ -13068,16 +12921,6 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "node_modules/node-environment-flags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", - "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", - "dev": true, - "dependencies": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, "node_modules/node-fetch": { "version": "2.6.12", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", @@ -13269,15 +13112,6 @@ "node": ">=8" } }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/nwsapi": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", @@ -13376,25 +13210,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz", - "integrity": "sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==", - "dev": true, - "dependencies": { - "array.prototype.reduce": "^1.0.5", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.21.2", - "safe-array-concat": "^1.0.0" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object.hasown": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", @@ -13555,22 +13370,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/ora/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ora/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -13595,187 +13394,37 @@ "node": ">=8" } }, - "node_modules/os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true, - "dependencies": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - }, "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/os-locale/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=4.8" - } - }, - "node_modules/os-locale/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "node": ">=10" }, - "engines": { - "node": ">=6" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-locale/node_modules/get-stream": { + "node_modules/p-locate": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/os-locale/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-locale/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/os-locale/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/os-locale/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-locale/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-locale/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" + "p-limit": "^2.2.0" }, "engines": { "node": ">=8" @@ -15163,6 +14812,15 @@ "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==", "dev": true }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -16089,6 +15747,15 @@ "semver": "bin/semver" } }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -16515,15 +16182,6 @@ "node": ">=8" } }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -16585,19 +16243,19 @@ "integrity": "sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg==" }, "node_modules/supertokens-web-js": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/supertokens-web-js/-/supertokens-web-js-0.7.2.tgz", - "integrity": "sha512-8TQFfuDzOkdbP/br7AHI8PJaslAYOVmHKT9rxb+v2cuFXYbZzHKoZeY12C1hepqHED8Cbt+rnIIVb2VUYlzd/w==", + "version": "0.8.0", + "resolved": "git+ssh://git@github.com/supertokens/supertokens-web-js.git#3f2549680195dc660326eff9df13b150ff9fcbbd", + "license": "Apache-2.0", "peer": true, "dependencies": { "supertokens-js-override": "0.0.4", - "supertokens-website": "^17.0.1" + "supertokens-website": "^17.0.3" } }, "node_modules/supertokens-website": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/supertokens-website/-/supertokens-website-17.0.2.tgz", - "integrity": "sha512-ki19f8Bl24iypXOtnCj/ebMA7hi0WxCe1f0fItGXkOV0fVAkRCYVqAKf7wiLQ3Lwhc+us4cgOQwRLIvctLrrUg==", + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/supertokens-website/-/supertokens-website-17.0.3.tgz", + "integrity": "sha512-+5qMuii8mp3TEPX5HKLt48A4gzKM8cmnoYmFeoecLeylfY3mEAeL7AbAVjCp6On/s/xvvH6Q22fOEoCFf5Tu/g==", "peer": true, "dependencies": { "browser-tabs-lock": "^1.3.0", @@ -17708,58 +17366,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2" - } - }, - "node_modules/wide-align/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/word-wrap": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", @@ -17769,6 +17375,12 @@ "node": ">=0.10.0" } }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, "node_modules/wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", @@ -17958,50 +17570,69 @@ } }, "node_modules/yargs-parser": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", - "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "engines": { + "node": ">=10" } }, "node_modules/yargs-unparser": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", - "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "dependencies": { - "flat": "^4.1.0", - "lodash": "^4.17.11", - "yargs": "^12.0.5" + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/yargs-unparser/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yargs-unparser/node_modules/cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true, - "dependencies": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" } }, - "node_modules/yargs-unparser/node_modules/find-up": { + "node_modules/yargs/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/yargs/node_modules/find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", @@ -18013,13 +17644,7 @@ "node": ">=6" } }, - "node_modules/yargs-unparser/node_modules/get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "node_modules/yargs-unparser/node_modules/is-fullwidth-code-point": { + "node_modules/yargs/node_modules/is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", @@ -18028,7 +17653,7 @@ "node": ">=4" } }, - "node_modules/yargs-unparser/node_modules/locate-path": { + "node_modules/yargs/node_modules/locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", @@ -18041,7 +17666,7 @@ "node": ">=6" } }, - "node_modules/yargs-unparser/node_modules/p-limit": { + "node_modules/yargs/node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", @@ -18056,7 +17681,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yargs-unparser/node_modules/p-locate": { + "node_modules/yargs/node_modules/p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", @@ -18068,7 +17693,7 @@ "node": ">=6" } }, - "node_modules/yargs-unparser/node_modules/path-exists": { + "node_modules/yargs/node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", @@ -18077,256 +17702,50 @@ "node": ">=4" } }, - "node_modules/yargs-unparser/node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true - }, - "node_modules/yargs-unparser/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "dependencies": { + "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "strip-ansi": "^5.1.0" }, "engines": { - "node": ">=4" + "node": ">=6" } }, - "node_modules/yargs-unparser/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "node_modules/yargs/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "dependencies": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" }, "engines": { - "node": ">=4" + "node": ">=6" } }, - "node_modules/yargs-unparser/node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", + "node_modules/yargs/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } }, - "node_modules/yargs-unparser/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargs-unparser/node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargs-unparser/node_modules/wrap-ansi/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargs-unparser/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargs-unparser/node_modules/yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "dependencies": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "node_modules/yargs-unparser/node_modules/yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/yargs/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/yargs/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" } }, "node_modules/yocto-queue": { @@ -21706,9 +21125,9 @@ } }, "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, "ansi-escapes": { @@ -21828,19 +21247,6 @@ "es-shim-unscopables": "^1.0.0" } }, - "array.prototype.reduce": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", - "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - } - }, "array.prototype.tosorted": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", @@ -22482,12 +21888,6 @@ "integrity": "sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==", "dev": true }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "dev": true - }, "collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -23152,12 +22552,6 @@ "which-typed-array": "^1.1.10" } }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, "es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -24012,21 +23406,10 @@ } }, "flat": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", - "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true - } - } + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true }, "flat-cache": { "version": "2.0.1", @@ -24339,12 +23722,6 @@ "temp": "~0.4.0" } }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -24650,12 +24027,6 @@ "resolved": "https://registry.npmjs.org/intl-tel-input/-/intl-tel-input-17.0.21.tgz", "integrity": "sha512-TfyPxLe41QZPOf6RqBxRE2dpQ0FThB/PBD/gRbxVhGW7IuYg30QD90x/vjmEo4vkZw7j8etxpVcjIZVRcG+Otw==" }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, "is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -24828,6 +24199,12 @@ "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", "dev": true }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, "is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -26834,15 +26211,6 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -26946,12 +26314,58 @@ "dev": true }, "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { - "chalk": "^2.0.1" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "loglevel": { @@ -27113,15 +26527,6 @@ "tmpl": "1.0.5" } }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, "marked": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", @@ -27165,17 +26570,6 @@ "is-buffer": "~1.1.6" } }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -27259,99 +26653,101 @@ "dev": true }, "mocha": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", - "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dev": true, "requires": { - "ansi-colors": "3.2.3", + "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "2.2.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "ms": "2.1.1", - "node-environment-flags": "1.0.5", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.2.2", - "yargs-parser": "13.0.0", - "yargs-unparser": "1.5.0" + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - } + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "color-name": "~1.1.4" } }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" } }, "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -27360,231 +26756,126 @@ "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true - }, "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "^5.0.0" } }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dev": true, "requires": { - "minimist": "0.0.8" + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + } } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "p-limit": "^3.0.2" } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^5.0.1" } }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true - }, "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { - "isexe": "^2.0.0" + "has-flag": "^4.0.0" } }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, "yargs": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", - "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^4.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.0.0" + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } } } @@ -27620,9 +26911,9 @@ } }, "mocha-multi": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/mocha-multi/-/mocha-multi-1.1.6.tgz", - "integrity": "sha512-hMVmd9C1h4PEiFNwHxn8aa5/EgGLg0UswdOrlrq1Y8ieKmot8hZLYaiESIgg/He3E4oxwaXPWT1V1PJ0qNJlUQ==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/mocha-multi/-/mocha-multi-1.1.7.tgz", + "integrity": "sha512-SXZRgHy0XiRTASyOp0p6fjOkdj+R62L6cqutnYyQOvIjNznJuUwzykxctypeRiOwPd+gfn4yt3NRulMQyI8Tzg==", "dev": true, "requires": { "debug": "^4.1.1", @@ -27760,16 +27051,6 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "node-environment-flags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", - "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, "node-fetch": { "version": "2.6.12", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", @@ -27916,14 +27197,8 @@ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "path-key": "^3.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "dev": true + "path-key": "^3.0.0" + } }, "nwsapi": { "version": "2.2.7", @@ -27993,19 +27268,6 @@ "es-abstract": "^1.20.4" } }, - "object.getownpropertydescriptors": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz", - "integrity": "sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==", - "dev": true, - "requires": { - "array.prototype.reduce": "^1.0.5", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.21.2", - "safe-array-concat": "^1.0.0" - } - }, "object.hasown": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", @@ -28121,16 +27383,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -28151,125 +27403,12 @@ } } }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -29196,6 +28335,15 @@ "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==", "dev": true }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -29870,6 +29018,15 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -30212,12 +29369,6 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "dev": true - }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -30263,19 +29414,18 @@ "integrity": "sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg==" }, "supertokens-web-js": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/supertokens-web-js/-/supertokens-web-js-0.7.2.tgz", - "integrity": "sha512-8TQFfuDzOkdbP/br7AHI8PJaslAYOVmHKT9rxb+v2cuFXYbZzHKoZeY12C1hepqHED8Cbt+rnIIVb2VUYlzd/w==", + "version": "git+ssh://git@github.com/supertokens/supertokens-web-js.git#3f2549680195dc660326eff9df13b150ff9fcbbd", + "from": "supertokens-web-js@github:supertokens/supertokens-web-js#0.8", "peer": true, "requires": { "supertokens-js-override": "0.0.4", - "supertokens-website": "^17.0.1" + "supertokens-website": "^17.0.3" } }, "supertokens-website": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/supertokens-website/-/supertokens-website-17.0.2.tgz", - "integrity": "sha512-ki19f8Bl24iypXOtnCj/ebMA7hi0WxCe1f0fItGXkOV0fVAkRCYVqAKf7wiLQ3Lwhc+us4cgOQwRLIvctLrrUg==", + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/supertokens-website/-/supertokens-website-17.0.3.tgz", + "integrity": "sha512-+5qMuii8mp3TEPX5HKLt48A4gzKM8cmnoYmFeoecLeylfY3mEAeL7AbAVjCp6On/s/xvvH6Q22fOEoCFf5Tu/g==", "peer": true, "requires": { "browser-tabs-lock": "^1.3.0", @@ -31119,54 +30269,18 @@ "has-tostringtag": "^1.0.0" } }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, "word-wrap": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, "wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", @@ -31409,199 +30523,34 @@ } }, "yargs-parser": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", - "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true }, "yargs-unparser": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", - "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.11", - "yargs": "^12.0.5" + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { + "decamelize": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true } } }, diff --git a/package.json b/package.json index cfb235e6c..0e38373a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "supertokens-auth-react", - "version": "0.34.2", + "version": "0.35.0", "description": "ReactJS SDK that provides login functionality with SuperTokens.", "main": "./index.js", "engines": { @@ -46,9 +46,9 @@ "jsdom-global": "3.0.2", "madge": "^5.0.1", "mkdirp": "^1.0.4", - "mocha": "6.1.4", - "mocha-junit-reporter": "^2.0.2", - "mocha-multi": "1.1.6", + "mocha": "^10.2.0", + "mocha-junit-reporter": "^2.2.1", + "mocha-multi": "^1.1.7", "mocha-split-tests": "github:rishabhpoddar/mocha-split-tests", "npm-run-all": "^4.1.5", "postcss": "^8.4.19", @@ -83,7 +83,7 @@ "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0", - "supertokens-web-js": "^0.7.2" + "supertokens-web-js": "github:supertokens/supertokens-web-js#0.8" }, "scripts": { "init": "bash ./init.sh", diff --git a/test/end-to-end/accountlinking.test.js b/test/end-to-end/accountlinking.test.js new file mode 100644 index 000000000..9e53cf47f --- /dev/null +++ b/test/end-to-end/accountlinking.test.js @@ -0,0 +1,690 @@ +/* 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 + */ + +import assert from "assert"; +import puppeteer from "puppeteer"; +import { + clearBrowserCookiesWithoutAffectingConsole, + clickOnProviderButton, + getUserIdWithFetch, + getLogoutButton, + setInputValues, + submitForm, + waitForSTElement, + getPasswordlessDevice, + setPasswordlessFlowType, + getFeatureFlags, + isReact16, + setAccountLinkingConfig, + signUp, + toggleSignInSignUp, + getInputAdornmentsSuccess, + getInputAdornmentsError, + getFieldErrors, + assertProviders, + getGeneralError, + getLatestURLWithToken, + submitFormReturnRequestAndResponse, + getTextByDataSupertokens, + sendEmailResetPasswordSuccessMessage, + changeEmail, +} from "../helpers"; +import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_IN_UP_API, RESET_PASSWORD_API } from "../constants"; + +/* + * Tests. + */ +describe("SuperTokens Account linking", function () { + let browser; + let page; + let consoleLogs; + + before(async function () { + const features = await getFeatureFlags(); + if (!features.includes("accountlinking")) { + this.skip(); + } + }); + + describe("Recipe combination tests", () => { + before(async function () { + await fetch(`${TEST_SERVER_BASE_URL}/beforeeach`, { + method: "POST", + }).catch(console.error); + + await fetch(`${TEST_SERVER_BASE_URL}/startst`, { + method: "POST", + }).catch(console.error); + + browser = await puppeteer.launch({ + args: ["--no-sandbox", "--disable-setuid-sandbox"], + headless: true, + }); + page = await browser.newPage(); + page.on("console", (consoleObj) => { + const log = consoleObj.text(); + if (log.startsWith("ST_LOGS")) { + consoleLogs.push(log); + } + }); + }); + + after(async function () { + await browser.close(); + await fetch(`${TEST_SERVER_BASE_URL}/after`, { + method: "POST", + }).catch(console.error); + + await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { + method: "POST", + }).catch(console.error); + }); + + beforeEach(async function () { + consoleLogs = []; + consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); + await setPasswordlessFlowType("EMAIL_OR_PHONE", "USER_INPUT_CODE_AND_MAGIC_LINK"); + await Promise.all([ + page.goto( + `${TEST_CLIENT_BASE_URL}/auth/?authRecipe=thirdpartypasswordless&passwordlessContactMethodType=EMAIL_OR_PHONE` + ), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await page.evaluate(() => window.localStorage.removeItem("mode")); + }); + + afterEach(async function () { + await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); + }); + + describe("account consolidation", () => { + const loginTypes = [ + ["passwordless", tryPasswordlessSignInUp], + ["thirdparty", tryThirdPartySignInUp], + ["emailpassword", tryEmailPasswordSignUp], + ]; + + for (const login1 of loginTypes) { + for (const login2 of loginTypes) { + if (login1 !== login2) { + const doLogin1 = login1[1]; + const doLogin2 = login2[1]; + + it(`should work for ${login1[0]} - ${login2[0]} w/ email verification not required`, async () => { + const email = `test-user+${Date.now()}@supertokens.com`; + await setAccountLinkingConfig(true, true, false); + // 1. Sign up with login method 1 + await doLogin1(page, email); + + await Promise.all([ + page.waitForSelector(".sessionInfo-user-id"), + page.waitForNetworkIdle(), + ]); + const userId1 = await getUserIdWithFetch(page); + + // 2. Log out + await logOut(page); + + // 3. Sign in with other login method + await doLogin2(page, email, true); + await Promise.all([ + page.waitForSelector(".sessionInfo-user-id"), + page.waitForNetworkIdle(), + ]); + + const thirdPartyUserId = await getUserIdWithFetch(page); + + // 4. Compare userIds + assert.strictEqual(thirdPartyUserId, userId1); + }); + + if (login2[0] !== "emailpassword") { + it(`should work for ${login1[0]} - ${login2[0]} w/ email verification required`, async () => { + await page.evaluate(() => window.localStorage.setItem("mode", "REQUIRED")); + + const email = `test-user+${Date.now()}@supertokens.com`; + await setAccountLinkingConfig(true, true, true); + // 1. Sign up with login method 1 + await doLogin1(page, email); + if (login1[0] === "emailpassword") { + await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); + await new Promise((res) => setTimeout(res, 250)); + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(latestURLWithToken), + ]); + await Promise.all([ + submitForm(page), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + } + + await Promise.all([ + page.waitForSelector(".sessionInfo-user-id"), + page.waitForNetworkIdle(), + ]); + const userId1 = await getUserIdWithFetch(page); + + // 2. Log out + await logOut(page); + + // 3. Sign in with other login method + await doLogin2(page, email, true); + await Promise.all([ + page.waitForSelector(".sessionInfo-user-id"), + page.waitForNetworkIdle(), + ]); + + const thirdPartyUserId = await getUserIdWithFetch(page); + + // 4. Compare userIds + assert.strictEqual(thirdPartyUserId, userId1); + }); + } else { + it(`should work for ${login1[0]} - password reset (invite link flow) w/ email verification required`, async () => { + await page.evaluate(() => window.localStorage.setItem("mode", "REQUIRED")); + + const email = `test-user+${Date.now()}@supertokens.com`; + await setAccountLinkingConfig(true, true, true); + // 1. Sign up with login method 1 + await doLogin1(page, email); + if (login1[0] === "emailpassword") { + await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); + await new Promise((res) => setTimeout(res, 250)); + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(latestURLWithToken), + ]); + await Promise.all([ + submitForm(page), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + } + + await Promise.all([ + page.waitForSelector(".sessionInfo-user-id"), + page.waitForNetworkIdle(), + ]); + const userId1 = await getUserIdWithFetch(page); + + // 2. Log out + await logOut(page); + + // 3. Sign in with other login method + await tryEmailPasswordResetPassword(page, email); + await Promise.all([ + page.waitForSelector(".sessionInfo-user-id"), + page.waitForNetworkIdle(), + ]); + + const thirdPartyUserId = await getUserIdWithFetch(page); + + // 4. Compare userIds + assert.strictEqual(thirdPartyUserId, userId1); + }); + } + } + } + } + }); + + describe("conflicting accounts", () => { + it("should not allow sign up w/ emailpassword in case of conflict", async function () { + const email = `test-user+${Date.now()}@supertokens.com`; + + await setAccountLinkingConfig(true, true, true); + // 1. Sign up with credentials + await tryPasswordlessSignInUp(page, email); + + await Promise.all([page.waitForSelector(".sessionInfo-user-id"), page.waitForNetworkIdle()]); + + // 2. Log out + await logOut(page); + + await waitForSTElement(page, `input[name=emailOrPhone]`); + + // 3. Try sign up with email password + await tryEmailPasswordSignUp(page, email); + + const successAdornments = await getInputAdornmentsSuccess(page); + assert.strictEqual(successAdornments.length, 4); + + const errorAdornments = await getInputAdornmentsError(page); + assert.strictEqual(errorAdornments.length, 0); + + assert.strictEqual(new URL(page.url()).pathname, "/auth/"); + assert.strictEqual( + await getGeneralError(page), + "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)" + ); + }); + + it("should not allow sign in w/ an unverified emailpassword user in case of conflict", async function () { + const email = `test-user+${Date.now()}@supertokens.com`; + + await setAccountLinkingConfig(true, false); + // 1. Sign up without account linking with an unverified tp user & log out + await tryEmailPasswordSignUp(page, email); + await logOut(page); + + await setAccountLinkingConfig(true, true, false); + // 2. Sign up with passwordless to create a primary user + await tryPasswordlessSignInUp(page, email); + + await Promise.all([page.waitForSelector(".sessionInfo-user-id"), page.waitForNetworkIdle()]); + + // 3. Log out + await logOut(page); + + await waitForSTElement(page, `input[name=emailOrPhone]`); + + await setAccountLinkingConfig(true, true, true); + // 4. Try sign in with emailpassword + + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth/?authRecipe=emailpassword`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await setInputValues(page, [ + { name: "email", value: email }, + { name: "password", value: "Asdf12.." }, + ]); + + await submitForm(page); + assert.strictEqual(new URL(page.url()).pathname, "/auth/"); + assert.strictEqual( + await getGeneralError(page), + "Cannot sign in due to security reasons. Please try resetting your password, use a different login method or contact support. (ERR_CODE_008)" + ); + }); + + it("should not allow sign up w/ an unverified thirdparty user in case of conflict", async function () { + const email = `test-user+${Date.now()}@supertokens.com`; + + await setAccountLinkingConfig(true, true, true); + // 1. Sign up with credentials + await tryPasswordlessSignInUp(page, email); + + await Promise.all([page.waitForSelector(".sessionInfo-user-id"), page.waitForNetworkIdle()]); + + // 2. Log out + await logOut(page); + + await waitForSTElement(page, `input[name=emailOrPhone]`); + + // 3. Try sign up with third party + await tryThirdPartySignInUp(page, email, false); + + assert.strictEqual(new URL(page.url()).pathname, "/auth/"); + assert.strictEqual( + await getGeneralError(page), + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_006)" + ); + }); + + it("should not allow using thirdparty sign in with changed email in case of conflict", async function () { + const email = `test-user+${Date.now()}@supertokens.com`; + const email2 = `test-user-2+${Date.now()}@supertokens.com`; + + await setAccountLinkingConfig(true, true, true); + // 1. Sign up with credentials + + await tryPasswordlessSignInUp(page, email); + + await Promise.all([page.waitForSelector(".sessionInfo-user-id"), page.waitForNetworkIdle()]); + + // 2. Log out + await logOut(page); + + await waitForSTElement(page, `input[name=emailOrPhone]`); + + // 3. Sign up with third party + await tryThirdPartySignInUp(page, email2, false); + await Promise.all([page.waitForSelector(".sessionInfo-user-id"), page.waitForNetworkIdle()]); + + // 4. Log out + await logOut(page); + + // 5. Sign in with changed email address (thirdPartyUserId matching the previous sign in) + + await tryThirdPartySignInUp(page, email, false, email2); + + assert.strictEqual(new URL(page.url()).pathname, "/auth/"); + assert.strictEqual( + await getGeneralError(page), + "Cannot sign in / up because new email cannot be applied to existing account. Please contact support. (ERR_CODE_005)" + ); + }); + + it("should not allow sign in w/ an unverified thirdparty user in case of conflict", async function () { + const email = `test-user+${Date.now()}@supertokens.com`; + + await setAccountLinkingConfig(true, false); + // 1. Sign up without account linking with an unverified tp user & log out + await tryThirdPartySignInUp(page, email, false); + await logOut(page); + + await setAccountLinkingConfig(true, true, false); + // 2. Sign up with passwordless to create a primary user + await tryPasswordlessSignInUp(page, email); + + await Promise.all([page.waitForSelector(".sessionInfo-user-id"), page.waitForNetworkIdle()]); + + // 3. Log out + await logOut(page); + + await waitForSTElement(page, `input[name=emailOrPhone]`); + + await setAccountLinkingConfig(true, true, true); + // 4. Try sign in with third party + await tryThirdPartySignInUp(page, email, false); + + assert.strictEqual(new URL(page.url()).pathname, "/auth/"); + assert.strictEqual( + await getGeneralError(page), + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_004)" + ); + }); + + it("should not allow sign up w/ passwordless if it conflicts with an unverified user", async function () { + const email = `test-user+${Date.now()}@supertokens.com`; + + await setAccountLinkingConfig(true, false); + // 1. Sign up without account linking with an unverified tp user & log out + await tryEmailPasswordSignUp(page, email); + await logOut(page); + + await setAccountLinkingConfig(true, true, true); + // 2. Sign up with passwordless + await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth/?authRecipe=passwordless`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + await setInputValues(page, [{ name: "emailOrPhone", value: email }]); + await submitForm(page); + + assert.strictEqual( + await getGeneralError(page), + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_002)" + ); + assert.strictEqual(new URL(page.url()).pathname, "/auth/"); + }); + + it("should not allow sign up w/ passwordless after changing the email if it conflicts with an unverified user", async function () { + const email = `test-user+${Date.now()}@supertokens.com`; + const email2 = `test-user-2+${Date.now()}@supertokens.com`; + + await setAccountLinkingConfig(true, false); + // 1. Sign up without account linking with an unverified tp user & log out + await tryEmailPasswordSignUp(page, email); + await logOut(page); + + // 2. Sign up with passwordless + await tryPasswordlessSignInUp(page, email2); + await Promise.all([page.waitForSelector(".sessionInfo-user-id"), page.waitForNetworkIdle()]); + const accessTokenPayload = await page.evaluate(() => + window.__supertokensSessionRecipe.getAccessTokenPayloadSecurely() + ); + const userId = accessTokenPayload.sub; + await logOut(page); + + await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth/?authRecipe=passwordless`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await changeEmail("passwordless", userId, email); + await setAccountLinkingConfig(true, true, true); + + await setInputValues(page, [{ name: "emailOrPhone", value: email }]); + await submitForm(page); + + assert.strictEqual( + await getGeneralError(page), + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_003)" + ); + assert.strictEqual(new URL(page.url()).pathname, "/auth/"); + }); + + it("should not allow sign up w/ passwordless if it conflicts with an unverified user", async function () { + const email = `test-user+${Date.now()}@supertokens.com`; + + await setAccountLinkingConfig(true, false); + // 1. Sign up without account linking with an unverified tp user & log out + await tryEmailPasswordSignUp(page, email); + await logOut(page); + + await setAccountLinkingConfig(true, true, true); + // 2. Sign in with passwordless + await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth/?authRecipe=passwordless`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + await setInputValues(page, [{ name: "emailOrPhone", value: email }]); + await submitForm(page); + + assert.strictEqual( + await getGeneralError(page), + "Cannot sign in / up due to security reasons. Please try a different login method or contact support. (ERR_CODE_002)" + ); + assert.strictEqual(new URL(page.url()).pathname, "/auth/"); + }); + + it("should not allow password reset if it conflicts with an unverified user", async function () { + const email = `test-user+${Date.now()}@supertokens.com`; + const email2 = `test-user2+${Date.now()}@supertokens.com`; + + await setAccountLinkingConfig(true, true, false); + // 1. Sign up without account linking with an unverified tp user & log out + await tryThirdPartySignInUp(page, email2, false); + await logOut(page); + + // 2. Sign up with an EP user that will be linked to the first one + await tryEmailPasswordSignUp(page, email2); + await logOut(page); + + // 3. Change the email of the initial tp user + await tryThirdPartySignInUp(page, email, false, email2); + await logOut(page); + + await setAccountLinkingConfig(true, false, false); + // 4. Add a recipe level user + await tryEmailPasswordSignUp(page, email); + await logOut(page); + + await setAccountLinkingConfig(true, true, true); + // 5. Try resetting the password of the recipe leve user + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth/reset-password?authRecipe=emailpassword`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + await setInputValues(page, [{ name: "email", value: email }]); + + await submitForm(page); + + await new Promise((res) => setTimeout(res, 200)); + assert.deepStrictEqual(await getFieldErrors(page), [ + "Reset password link was not created because of account take over risk. Please contact support. (ERR_CODE_001)", + ]); + assert.strictEqual(new URL(page.url()).pathname, "/auth/reset-password"); + }); + + it("should allow sign in w/ a verified emailpassword user in case of conflict", async function () { + const email = `test-user+${Date.now()}@supertokens.com`; + await page.evaluate(() => window.localStorage.setItem("mode", "REQUIRED")); + + await setAccountLinkingConfig(true, false); + // 1. Sign up without account linking with an unverified tp user & log out + await tryEmailPasswordSignUp(page, email); + await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); + + await new Promise((res) => setTimeout(res, 250)); + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(latestURLWithToken), + ]); + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + + await logOut(page); + + await setAccountLinkingConfig(true, true, false); + // 2. Sign up with passwordless to create a primary user + await tryPasswordlessSignInUp(page, email); + + await Promise.all([page.waitForSelector(".sessionInfo-user-id"), page.waitForNetworkIdle()]); + + // 3. Log out + await logOut(page); + + await waitForSTElement(page, `input[name=emailOrPhone]`); + + await setAccountLinkingConfig(true, true, true); + // 4. Try sign in with emailpassword + + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth/?authRecipe=emailpassword`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await setInputValues(page, [ + { name: "email", value: email }, + { name: "password", value: "Asdf12.." }, + ]); + + await submitForm(page); + await Promise.all([page.waitForSelector(".sessionInfo-user-id"), page.waitForNetworkIdle()]); + }); + }); + }); +}); + +async function tryEmailPasswordSignUp(page, email) { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth/?authRecipe=emailpassword`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + await toggleSignInSignUp(page); + + await setInputValues(page, [ + { name: "email", value: email }, + { name: "password", value: "Asdf12.." }, + { name: "name", value: "asdf" }, + { name: "age", value: "20" }, + ]); + + await submitForm(page); + await new Promise((res) => setTimeout(res, 250)); +} + +async function tryPasswordlessSignInUp(page, email) { + await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth/?authRecipe=passwordless`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + await setInputValues(page, [{ name: "emailOrPhone", value: email }]); + await submitForm(page); + + await waitForSTElement(page, "[data-supertokens~=input][name=userInputCode]"); + + const loginAttemptInfo = JSON.parse( + await page.evaluate(() => localStorage.getItem("supertokens-passwordless-loginAttemptInfo")) + ); + const device = await getPasswordlessDevice(loginAttemptInfo); + await setInputValues(page, [{ name: "userInputCode", value: device.codes[0].userInputCode }]); + await submitForm(page); +} + +async function tryThirdPartySignInUp(page, email, isVerified = true, userId = email) { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth/?authRecipe=thirdparty`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + await assertProviders(page); + + await clickOnProviderButton(page, "Mock Provider"); + const url = new URL(page.url()); + assert.strictEqual(url.pathname, `/mockProvider/auth`); + assert.ok(url.searchParams.get("state")); + + await Promise.all([ + page.goto( + `${TEST_CLIENT_BASE_URL}/auth/callback/mock-provider?code=asdf&email=${encodeURIComponent( + email + )}&userId=${encodeURIComponent(userId)}&isVerified=${isVerified}&state=${url.searchParams.get("state")}` + ), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); +} + +async function tryEmailPasswordResetPassword(page, email) { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth/reset-password?authRecipe=emailpassword`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + await setInputValues(page, [{ name: "email", value: email }]); + + await submitForm(page); + const successMessage = await sendEmailResetPasswordSuccessMessage(page); + + assert.deepStrictEqual( + successMessage, + `A password reset email has been sent to ${email}, if it exists in our system. Resend or change email` + ); + // Get valid token. + const latestURLWithToken = await getLatestURLWithToken(); + await page.goto(latestURLWithToken); + + // Submit new password + await setInputValues(page, [ + { name: "password", value: "Asdf12.." }, + { name: "confirm-password", value: "Asdf12.." }, + ]); + await submitFormReturnRequestAndResponse(page, RESET_PASSWORD_API); + + const title = await getTextByDataSupertokens(page, "headerTitle"); + assert.deepStrictEqual(title, "Success!"); + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + + await setInputValues(page, [ + { name: "email", value: email }, + { name: "password", value: "Asdf12.." }, + ]); + + await submitForm(page); + await new Promise((res) => setTimeout(res, 250)); +} + +async function logOut(page) { + await Promise.all([page.waitForSelector(".sessionInfo-user-id"), page.waitForNetworkIdle()]); + const logoutButton = await getLogoutButton(page); + await Promise.all([logoutButton.click(), page.waitForNavigation({ waitUntil: "networkidle0" })]); + await waitForSTElement(page); +} diff --git a/test/end-to-end/emailverification.test.js b/test/end-to-end/emailverification.test.js index 551ad4781..66746fa05 100644 --- a/test/end-to-end/emailverification.test.js +++ b/test/end-to-end/emailverification.test.js @@ -17,8 +17,6 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import puppeteer from "puppeteer"; import fetch from "isomorphic-fetch"; @@ -52,15 +50,14 @@ import { waitForSTElement, isGeneralErrorSupported, setGeneralErrorToLocalStorage, + isAccountLinkingSupported, } from "../helpers"; -// Run the tests in a DOM environment. -require("jsdom-global")(); - describe("SuperTokens Email Verification", function () { let browser; let page; let consoleLogs; + let accountLinkingSupported; before(async function () { await fetch(`${TEST_SERVER_BASE_URL}/beforeeach`, { @@ -73,6 +70,8 @@ describe("SuperTokens Email Verification", function () { args: ["--no-sandbox", "--disable-setuid-sandbox"], headless: true, }); + accountLinkingSupported = await isAccountLinkingSupported(); + page = await browser.newPage(); await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), @@ -126,7 +125,7 @@ describe("SuperTokens Email Verification", function () { ]); await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); - await fetch(`${TEST_SERVER_BASE_URL}/deleteUser`, { + await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/deleteUser`, { method: "POST", headers: [["content-type", "application/json"]], body: JSON.stringify({ @@ -183,7 +182,7 @@ describe("SuperTokens Email Verification", function () { let pathname = await page.evaluate(() => window.location.pathname); assert.deepStrictEqual(pathname, "/auth/verify-email"); - await fetch(`${TEST_SERVER_BASE_URL}/deleteUser`, { + await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/deleteUser`, { method: "POST", headers: [["content-type", "application/json"]], body: JSON.stringify({ @@ -257,9 +256,13 @@ describe("SuperTokens Email Verification", function () { "ST_LOGS SESSION OVERRIDE GET_USER_ID", "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", - "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS SESSION OVERRIDE GET_JWT_PAYLOAD_SECURELY", + ...(accountLinkingSupported + ? [] + : [ + "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS SESSION OVERRIDE GET_JWT_PAYLOAD_SECURELY", + ]), "ST_LOGS EMAIL_VERIFICATION OVERRIDE SEND_VERIFICATION_EMAIL", "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS SEND_VERIFY_EMAIL", "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT VERIFY_EMAIL_SENT", @@ -343,9 +346,13 @@ describe("SuperTokens Email Verification", function () { "ST_LOGS SESSION OVERRIDE GET_USER_ID", "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", - "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS SESSION OVERRIDE GET_JWT_PAYLOAD_SECURELY", + ...(accountLinkingSupported + ? [] + : [ + "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS SESSION OVERRIDE GET_JWT_PAYLOAD_SECURELY", + ]), "ST_LOGS EMAIL_VERIFICATION OVERRIDE SEND_VERIFICATION_EMAIL", "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS SEND_VERIFY_EMAIL", "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT VERIFY_EMAIL_SENT", @@ -489,9 +496,13 @@ describe("SuperTokens Email Verification", function () { "ST_LOGS SESSION OVERRIDE GET_USER_ID", "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", - "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS SESSION OVERRIDE GET_JWT_PAYLOAD_SECURELY", + ...(accountLinkingSupported + ? [] + : [ + "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS SESSION OVERRIDE GET_JWT_PAYLOAD_SECURELY", + ]), "ST_LOGS EMAIL_VERIFICATION OVERRIDE SEND_VERIFICATION_EMAIL", "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS SEND_VERIFY_EMAIL", "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT VERIFY_EMAIL_SENT", diff --git a/test/end-to-end/generalerror.test.js b/test/end-to-end/generalerror.test.js index c9acfbadf..31ae80548 100644 --- a/test/end-to-end/generalerror.test.js +++ b/test/end-to-end/generalerror.test.js @@ -42,8 +42,6 @@ import { setGeneralErrorToLocalStorage, } from "../helpers"; -// Run the tests in a DOM environment. -require("jsdom-global")(); import { TEST_SERVER_BASE_URL, SIGN_UP_API, diff --git a/test/end-to-end/getRedirectionURL.test.js b/test/end-to-end/getRedirectionURL.test.js index 2f2cdf656..735fa3556 100644 --- a/test/end-to-end/getRedirectionURL.test.js +++ b/test/end-to-end/getRedirectionURL.test.js @@ -17,8 +17,7 @@ import { isPasswordlessSupported, isThirdPartyPasswordlessSupported, } from "../helpers"; -// Run the tests in a DOM environment. -require("jsdom-global")(); + import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, @@ -27,7 +26,7 @@ import { } from "../constants"; describe("getRedirectionURL Tests", function () { - describe("Test that isNewUser is passed correctly", function () { + describe("Test that isNewRecipeUser is passed correctly", function () { describe("Email Password Recipe", function () { let browser; let page; @@ -70,7 +69,7 @@ describe("getRedirectionURL Tests", function () { await clearBrowserCookiesWithoutAffectingConsole(page, []); }); - it("Test that isNewUser is true when signing up", async function () { + it("Test that isNewRecipeUser is true when signing up", async function () { await toggleSignInSignUp(page); await defaultSignUp(page); const newUserCheck = await page.evaluate(() => localStorage.getItem("isNewUserCheck")); @@ -117,7 +116,7 @@ describe("getRedirectionURL Tests", function () { await page.evaluate(() => localStorage.removeItem("isNewUserCheck")); }); - it("Test that isNewUser works correctly", async function () { + it("Test that isNewRecipeUser works correctly", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth`), page.waitForNavigation({ waitUntil: "networkidle0" }), @@ -175,14 +174,14 @@ describe("getRedirectionURL Tests", function () { await page.evaluate(() => localStorage.removeItem("isNewUserCheck")); }); - it("Test that isNewUser is true when signing up with email", async function () { + it("Test that isNewRecipeUser is true when signing up with email", async function () { await toggleSignInSignUp(page); await defaultSignUp(page, "thirdpartyemailpassword"); const newUserCheck = await page.evaluate(() => localStorage.getItem("isNewUserCheck")); assert.equal(newUserCheck, "thirdpartyemailpassword-true"); }); - it("Test that isNewUser works correctly when signing up with auth 0", async function () { + it("Test that isNewRecipeUser works correctly when signing up with auth 0", async function () { await assertProviders(page); await clickOnProviderButton(page, "Auth0"); await Promise.all([ @@ -220,10 +219,10 @@ describe("getRedirectionURL Tests", function () { method: "POST", headers: [["content-type", "application/json"]], body: JSON.stringify({ - configUpdates: [ - { key: "passwordless_code_lifetime", value: 4000 }, - { key: "passwordless_max_code_input_attempts", value: 3 }, - ], + coreConfig: { + passwordless_code_lifetime: 4000, + passwordless_max_code_input_attempts: 3, + }, }), }).catch(console.error); @@ -264,7 +263,7 @@ describe("getRedirectionURL Tests", function () { await page.evaluate(() => localStorage.removeItem("isNewUserCheck")); }); - it("Test that isNewUser is passed correctly", async function () { + it("Test that isNewRecipeUser is passed correctly", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth`), page.waitForNavigation({ waitUntil: "networkidle0" }), @@ -309,10 +308,10 @@ describe("getRedirectionURL Tests", function () { method: "POST", headers: [["content-type", "application/json"]], body: JSON.stringify({ - configUpdates: [ - { key: "passwordless_code_lifetime", value: 4000 }, - { key: "passwordless_max_code_input_attempts", value: 3 }, - ], + coreConfig: { + passwordless_code_lifetime: 4000, + passwordless_max_code_input_attempts: 3, + }, }), }).catch(console.error); @@ -354,7 +353,7 @@ describe("getRedirectionURL Tests", function () { await page.evaluate(() => localStorage.removeItem("isNewUserCheck")); }); - it("Test that isNewUser is passed correctly", async function () { + it("Test that isNewRecipeUser is passed correctly", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth`), page.waitForNavigation({ waitUntil: "networkidle0" }), @@ -375,7 +374,7 @@ describe("getRedirectionURL Tests", function () { assert.equal(newUserCheck, "thirdpartypasswordless-true"); }); - it("Test that isNewUser works correctly when signing up with auth 0", async function () { + it("Test that isNewRecipeUser works correctly when signing up with auth 0", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth`), page.waitForNavigation({ waitUntil: "networkidle0" }), diff --git a/test/end-to-end/multitenancy.dynamic_login_methods.test.js b/test/end-to-end/multitenancy.dynamic_login_methods.test.js index ee811fe7d..04ded6bf8 100644 --- a/test/end-to-end/multitenancy.dynamic_login_methods.test.js +++ b/test/end-to-end/multitenancy.dynamic_login_methods.test.js @@ -17,8 +17,6 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import puppeteer from "puppeteer"; import fetch from "isomorphic-fetch"; @@ -38,6 +36,9 @@ import { clickOnProviderButton, loginWithAuth0, isMultitenancySupported, + isAccountLinkingSupported, + isMultitenancyManagementEndpointsSupported, + setupTenant, } from "../helpers"; import { TEST_CLIENT_BASE_URL, @@ -48,9 +49,7 @@ import { SOMETHING_WENT_WRONG_ERROR, } from "../constants"; -// Run the tests in a DOM environment. -require("jsdom-global")(); - +let connectionURI; /* * Tests. */ @@ -60,7 +59,7 @@ describe("SuperTokens Multitenancy dynamic login methods", function () { let pageCrashed; before(async function () { - const isSupported = await isMultitenancySupported(); + const isSupported = (await isMultitenancySupported()) && (await isMultitenancyManagementEndpointsSupported()); if (!isSupported) { this.skip(); } @@ -71,10 +70,13 @@ describe("SuperTokens Multitenancy dynamic login methods", function () { method: "POST", }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { + const startSTResp = await fetch(`${TEST_SERVER_BASE_URL}/startst`, { method: "POST", }).catch(console.error); + assert.strictEqual(startSTResp.status, 200); + connectionURI = await startSTResp.text(); + page = await browser.newPage(); pageCrashed = false; page.on("console", (c) => { @@ -112,7 +114,9 @@ describe("SuperTokens Multitenancy dynamic login methods", function () { }); after(async function () { - await browser.close(); + if (browser !== undefined) { + await browser.close(); + } }); it("Renders correct signup form with emailpassword when core list of providers is empty", async function () { @@ -129,12 +133,18 @@ describe("SuperTokens Multitenancy dynamic login methods", function () { page.waitForNavigation({ waitUntil: "networkidle0" }), ]); const providers = await getProvidersLabels(page); - compareArrayContents(providers, [ - "Continue with Github", - "Continue with Google", - "Continue with Facebook", - "Continue with Auth0", - ]); + compareArrayContents( + providers, + (await isAccountLinkingSupported()) + ? [ + "Continue with Github", + "Continue with Google", + "Continue with Facebook", + "Continue with Auth0", + "Continue with Mock Provider", + ] + : ["Continue with Github", "Continue with Google", "Continue with Facebook", "Continue with Auth0"] + ); const inputNames = await getInputNames(page); assert.deepStrictEqual(inputNames, ["email", "password"]); }); @@ -370,12 +380,18 @@ describe("SuperTokens Multitenancy dynamic login methods", function () { ]); const providers = await getProvidersLabels(page); - compareArrayContents(providers, [ - "Continue with Github", - "Continue with Google", - "Continue with Facebook", - "Continue with Auth0", - ]); + compareArrayContents( + providers, + (await isAccountLinkingSupported()) + ? [ + "Continue with Github", + "Continue with Google", + "Continue with Facebook", + "Continue with Auth0", + "Continue with Mock Provider", + ] + : ["Continue with Github", "Continue with Google", "Continue with Facebook", "Continue with Auth0"] + ); assert.strictEqual(await getProviderLogoCount(page), 3); }); @@ -951,42 +967,8 @@ function clearDynamicLoginMethodsSettings(page) { }); } -export async function enableDynamicLoginMethods(page, mockLoginMethods, tenantId = "public", app = "public") { - let coreResp = await fetch(`http://localhost:9000/appid-${app}/recipe/multitenancy/tenant`, { - method: "PUT", - headers: new Headers([ - ["content-type", "application/json"], - ["rid", "multitenancy"], - ]), - body: JSON.stringify({ - tenantId, - emailPasswordEnabled: mockLoginMethods.emailPassword?.enabled === true, - thirdPartyEnabled: mockLoginMethods.thirdParty?.enabled === true, - passwordlessEnabled: mockLoginMethods.passwordless?.enabled === true, - coreConfig: {}, - }), - }); - assert.strictEqual(coreResp.status, 200); - - for (const provider of mockLoginMethods["thirdParty"]?.providers) { - coreResp = await fetch(`http://localhost:9000/appid-${app}/${tenantId}/recipe/multitenancy/config/thirdparty`, { - method: "PUT", - headers: new Headers([ - ["content-type", "application/json"], - ["rid", "multitenancy"], - ]), - body: JSON.stringify({ - skipValidation: true, - config: { - ...providerConfigs[provider.id.split("-")[0]], - thirdPartyId: provider.id, - name: provider.name, - }, - }), - }); - - assert.strictEqual(coreResp.status, 200); - } +export async function enableDynamicLoginMethods(page, mockLoginMethods, tenantId = "public") { + await setupTenant(tenantId, mockLoginMethods); return page.evaluate(() => { window.localStorage.setItem("usesDynamicLoginMethods", "true"); @@ -1002,66 +984,3 @@ export async function enableDynamicLoginMethods(page, mockLoginMethods, tenantId function compareArrayContents(actual, expected) { return assert.deepStrictEqual(actual.sort(), expected.sort()); } - -const providerConfigs = { - 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", - }, - }, - ], - }, - github: { - clients: [ - { - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - clientId: "467101b197249757c71f", - }, - ], - }, - google: { - clients: [ - { - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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", - }, - ], - }, - auth0: { - // this contains info about forming the authorisation redirect URL without the state params and without the redirect_uri param - authorizationEndpoint: `https://${process.env.AUTH0_DOMAIN}/authorize`, - authorizationEndpointQueryParams: { - scope: "openid profile email email_verified", - }, - jwksURI: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`, - tokenEndpoint: `https://${process.env.AUTH0_DOMAIN}/oauth/token`, - clients: [ - { - clientId: process.env.AUTH0_CLIENT_ID, - clientSecret: process.env.AUTH0_CLIENT_SECRET, - }, - ], - userInfoMap: { - fromIdTokenPayload: { - userId: "sub", - email: "email", - emailVerified: "email_verified", - }, - }, - }, - test: { - // We add a client since it's required - clients: [ - { - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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", - }, - ], - }, -}; diff --git a/test/end-to-end/multitenancy.mock.test.js b/test/end-to-end/multitenancy.mock.test.js index 83b132d50..5783342e7 100644 --- a/test/end-to-end/multitenancy.mock.test.js +++ b/test/end-to-end/multitenancy.mock.test.js @@ -17,8 +17,6 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import puppeteer from "puppeteer"; import fetch from "isomorphic-fetch"; @@ -34,9 +32,6 @@ import { import { TEST_CLIENT_BASE_URL, DEFAULT_WEBSITE_BASE_PATH, ST_ROOT_SELECTOR } from "../constants"; import { before } from "mocha"; -// Run the tests in a DOM environment. -require("jsdom-global")(); - /* * Tests. */ diff --git a/test/end-to-end/multitenancy.tenant_interactions.test.js b/test/end-to-end/multitenancy.tenant_interactions.test.js index e384e3e70..ab6958e6e 100644 --- a/test/end-to-end/multitenancy.tenant_interactions.test.js +++ b/test/end-to-end/multitenancy.tenant_interactions.test.js @@ -40,6 +40,11 @@ import { getTextByDataSupertokens, getVerificationEmailErrorTitle, isMultitenancySupported, + isMultitenancyManagementEndpointsSupported, + setupTenant, + addUserToTenant, + removeUserFromTenant, + removeTenant, } from "../helpers"; import { TEST_CLIENT_BASE_URL, @@ -50,9 +55,6 @@ import { TEST_APPLICATION_SERVER_BASE_URL, } from "../constants"; -// Run the tests in a DOM environment. -require("jsdom-global")(); - /* * Tests. */ @@ -62,7 +64,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { let pageCrashed; before(async function () { - const isSupported = await isMultitenancySupported(); + const isSupported = (await isMultitenancySupported()) && (await isMultitenancyManagementEndpointsSupported()); if (!isSupported) { this.skip(); } @@ -122,13 +124,15 @@ describe("SuperTokens Multitenancy tenant interactions", function () { }); after(async function () { - await browser.close(); + if (browser !== undefined) { + await browser.close(); + } }); describe("without user sharing", () => { it("should not allow sign into user created on public when using a custom tenants", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -136,7 +140,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -166,7 +170,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should not allow sign into user created on custom tenant when using public", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -174,7 +178,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -204,7 +208,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should allow sign up on custom tenant after signing up on public", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -212,7 +216,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -241,7 +245,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should allow sign up on public tenant after signing up on a custom", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -249,7 +253,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -281,7 +285,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { describe("with user sharing", () => { it("should allow sign into user created on public when using a custom tenants", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -289,7 +293,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -305,7 +309,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { const email = await epSignUp(page); const userId = await getUserIdWithFetch(page); - addUserToTenant("public", "customer1", userId); + addUserToTenant("customer1", userId); await clearBrowserCookiesWithoutAffectingConsole(page, []); await setTenantId(page, "customer1"); @@ -320,7 +324,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should allow sign into user created on custom tenant when using public", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -328,7 +332,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -345,7 +349,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { const email = await epSignUp(page); const userId = await getUserIdWithFetch(page); - addUserToTenant("public", "public", userId); + addUserToTenant("public", userId); await clearBrowserCookiesWithoutAffectingConsole(page, []); await setTenantId(page, "public"); @@ -360,7 +364,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should not allow sign up on custom tenant after signing up on public", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -368,7 +372,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -383,7 +387,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { ]); const email = await epSignUp(page); const userId = await getUserIdWithFetch(page); - addUserToTenant("public", "customer1", userId); + addUserToTenant("customer1", userId); await clearBrowserCookiesWithoutAffectingConsole(page, []); @@ -399,7 +403,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should not allow sign up on public tenant after signing up on a custom", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -407,7 +411,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -425,7 +429,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { let pathname = await page.evaluate(() => window.location.pathname); assert.deepStrictEqual(pathname, "/dashboard"); const userId = await getUserIdWithFetch(page); - addUserToTenant("public", "public", userId); + addUserToTenant("public", userId); await clearBrowserCookiesWithoutAffectingConsole(page, []); @@ -443,7 +447,15 @@ describe("SuperTokens Multitenancy tenant interactions", function () { describe("with removed user", () => { it("should not allow sign in", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { + emailPassword: { enabled: true }, + passwordless: { enabled: false }, + thirdParty: { + enabled: true, + providers: [], + }, + }); + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -460,7 +472,8 @@ describe("SuperTokens Multitenancy tenant interactions", function () { const email = await epSignUp(page); const userId = await getUserIdWithFetch(page); - removeUserFromTenant("public", "public", userId); + await addUserToTenant("customer1", userId); + await removeUserFromTenant("public", userId); await clearBrowserCookiesWithoutAffectingConsole(page, []); @@ -474,7 +487,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should allow sign up", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -482,7 +495,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -498,7 +511,8 @@ describe("SuperTokens Multitenancy tenant interactions", function () { const email = await epSignUp(page); const userId = await getUserIdWithFetch(page); - removeUserFromTenant("public", "public", userId); + await addUserToTenant("customer1", userId); + await removeUserFromTenant("public", userId); await clearBrowserCookiesWithoutAffectingConsole(page, []); await Promise.all([ @@ -513,7 +527,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should log out on refresh", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -521,7 +535,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -537,7 +551,8 @@ describe("SuperTokens Multitenancy tenant interactions", function () { await epSignUp(page); const userId = await getUserIdWithFetch(page); - removeUserFromTenant("public", "public", userId); + await addUserToTenant("customer1", userId); + await removeUserFromTenant("public", userId); await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/dashboard`), @@ -553,7 +568,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { describe("with removed tenant", () => { it("should not allow sign in", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -576,13 +591,13 @@ describe("SuperTokens Multitenancy tenant interactions", function () { page.waitForNavigation({ waitUntil: "networkidle0" }), ]); - await removeTenant("public", "customer1"); + await removeTenant("customer1"); await epSignIn(page, email, SOMETHING_WENT_WRONG_ERROR); }); it("should not allow sign up", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -596,13 +611,13 @@ describe("SuperTokens Multitenancy tenant interactions", function () { page.goto(`${TEST_CLIENT_BASE_URL}${DEFAULT_WEBSITE_BASE_PATH}`), page.waitForNavigation({ waitUntil: "networkidle0" }), ]); - await removeTenant("public", "customer1"); + await removeTenant("customer1"); await epSignUp(page, undefined, [], SOMETHING_WENT_WRONG_ERROR); }); it("should crash if dynamic login methods is enabled", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -613,7 +628,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { await enableDynamicLoginMethods(page); await setTenantId(page, "customer1"); - await removeTenant("public", "customer1"); + await removeTenant("customer1"); await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}${DEFAULT_WEBSITE_BASE_PATH}`), @@ -627,7 +642,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should keep the session active if even if dynamic login methods is enabled", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -644,7 +659,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { ]); await epSignUp(page); - await removeTenant("public", "customer1"); + await removeTenant("customer1"); let getDynamicLoginMethodsCalled = false; await page.setRequestInterception(true); @@ -676,10 +691,10 @@ describe("SuperTokens Multitenancy tenant interactions", function () { } }); - it.skip("should revoke magic links on removed tenants", async function () { + it("should revoke magic links on removed tenants", async function () { await setPasswordlessFlowType("EMAIL_OR_PHONE", "USER_INPUT_CODE_AND_MAGIC_LINK"); await setEnabledRecipes(page, ["passwordless"]); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: false }, passwordless: { enabled: true }, thirdParty: { @@ -706,13 +721,15 @@ describe("SuperTokens Multitenancy tenant interactions", function () { ); const device = await getPasswordlessDevice(loginAttemptInfo); - await removeTenant("public", "customer1"); + await removeTenant("customer1"); await setTenantId(page, "public"); - await page.goto(device.codes[0].urlWithLinkCode); + await Promise.all([ + page.goto(device.codes[0].urlWithLinkCode), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); assert.strictEqual(await getGeneralError(page), SOMETHING_WENT_WRONG_ERROR); - await waitForSTElement(page, "[data-supertokens~=input][name=emailOrPhone]"); }); }); @@ -723,7 +740,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should work using OTP on the public tenants", async function () { await setEnabledRecipes(page, ["passwordless"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: false }, passwordless: { enabled: true }, thirdParty: { @@ -756,7 +773,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should work using magic links on the public tenants", async function () { await setEnabledRecipes(page, ["passwordless"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: false }, passwordless: { enabled: true }, thirdParty: { @@ -788,7 +805,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should work using OTP on a custom tenants", async function () { await setEnabledRecipes(page, ["passwordless"]); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: false }, passwordless: { enabled: true }, thirdParty: { @@ -822,7 +839,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should work using magic links on a custom tenants", async function () { await setEnabledRecipes(page, ["passwordless"]); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: false }, passwordless: { enabled: true }, thirdParty: { @@ -855,7 +872,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should work using magic links on a custom tenants even if the current app has the wrong tenant id", async function () { await setEnabledRecipes(page, ["passwordless"]); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: false }, passwordless: { enabled: true }, thirdParty: { @@ -889,7 +906,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should work using OTP on a custom tenants even if the current app has the wrong tenant id", async function () { await setEnabledRecipes(page, ["passwordless"]); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: false }, passwordless: { enabled: true }, thirdParty: { @@ -933,7 +950,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { describe("password reset links", () => { it("should reset password only on the tenant the link was created customer1 -> public", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -941,7 +958,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -985,7 +1002,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should reset password only on the tenant the link was created", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -993,7 +1010,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -1035,9 +1052,9 @@ describe("SuperTokens Multitenancy tenant interactions", function () { await epSignIn(page, email); }); - it("should be revoked when removing tenants", async function () { + it.skip("should be revoked when removing tenants", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -1045,7 +1062,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -1068,7 +1085,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { await submitForm(page); await sendEmailResetPasswordSuccessMessage(page); const latestURLWithToken = await getLatestURLWithToken(); - await removeTenant("public", "customer1"); + await removeTenant("customer1"); await page.goto(latestURLWithToken); const newPassword = "NEW_Str0ngP@ssw0rd"; @@ -1077,14 +1094,14 @@ describe("SuperTokens Multitenancy tenant interactions", function () { { name: "confirm-password", value: newPassword }, ]); await submitForm(page); - assert.strictEqual(await getGeneralError(page), SOMETHING_WENT_WRONG_ERROR); + assert.strictEqual(await getGeneralError(page), "Invalid password reset token"); }); }); describe("email verification links", () => { it("should verify email only on the tenant in the link", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -1092,7 +1109,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -1145,7 +1162,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should be revoked when removing tenants", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -1153,7 +1170,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { providers: [], }, }); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -1171,7 +1188,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { await new Promise((res) => setTimeout(res, 1000)); const latestURLWithToken = await getLatestURLWithToken(); - await removeTenant("public", "customer1"); + await removeTenant("customer1"); await page.goto(latestURLWithToken); assert.strictEqual(await getVerificationEmailErrorTitle(page), "!\nSomething went wrong"); @@ -1181,7 +1198,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { describe("AllowedDomainsClaim", () => { it("should return the right value on a custom tenant", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "customer1", { + await setupTenant("customer1", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -1220,7 +1237,7 @@ describe("SuperTokens Multitenancy tenant interactions", function () { it("should return the right value on the default tenant", async function () { await setEnabledRecipes(page, ["emailpassword"]); - await setupTenant("public", "public", { + await setupTenant("public", { emailPassword: { enabled: true }, passwordless: { enabled: false }, thirdParty: { @@ -1310,66 +1327,6 @@ export async function enableDynamicLoginMethods(page) { }); } -async function setupTenant(appId, tenantId, mockLoginMethods) { - let coreResp = await fetch(`http://localhost:9000/appid-${appId}/recipe/multitenancy/tenant`, { - method: "PUT", - headers: new Headers([ - ["content-type", "application/json"], - ["rid", "multitenancy"], - ]), - body: JSON.stringify({ - tenantId, - emailPasswordEnabled: mockLoginMethods.emailPassword?.enabled === true, - thirdPartyEnabled: mockLoginMethods.thirdParty?.enabled === true, - passwordlessEnabled: mockLoginMethods.passwordless?.enabled === true, - coreConfig: {}, - }), - }); - assert.strictEqual(coreResp.status, 200); -} - -async function addUserToTenant(appId, tenantId, userId) { - let coreResp = await fetch(`http://localhost:9000/appid-${appId}/${tenantId}/recipe/multitenancy/tenant/user`, { - method: "POST", - headers: new Headers([ - ["content-type", "application/json"], - ["rid", "multitenancy"], - ]), - body: JSON.stringify({ - userId, - }), - }); - assert.strictEqual(coreResp.status, 200); -} - -async function removeUserFromTenant(appId, tenantId, userId) { - let coreResp = await fetch( - `http://localhost:9000/appid-${appId}/${tenantId}/recipe/multitenancy/tenant/user/remove`, - { - method: "POST", - headers: new Headers([ - ["content-type", "application/json"], - ["rid", "multitenancy"], - ]), - body: JSON.stringify({ - userId, - }), - } - ); - assert.strictEqual(coreResp.status, 200); -} - -async function removeTenant(appId, tenantId) { - let coreResp = await fetch(`http://localhost:9000/appid-${appId}/recipe/multitenancy/tenant/remove`, { - method: "POST", - headers: new Headers([["rid", "multitenancy"]]), - body: JSON.stringify({ - tenantId, - }), - }); - assert.strictEqual(coreResp.status, 200); -} - async function epSignUp(page, email, fieldErrors, generalError, emailVerificationRequired) { const link = await getSignInOrSignUpSwitchLink(page); const linkText = await link.evaluate((e) => e.textContent); diff --git a/test/end-to-end/passwordless.test.js b/test/end-to-end/passwordless.test.js index ef7f55efc..401542fb3 100644 --- a/test/end-to-end/passwordless.test.js +++ b/test/end-to-end/passwordless.test.js @@ -17,8 +17,6 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import puppeteer from "puppeteer"; import fetch from "isomorphic-fetch"; @@ -37,10 +35,9 @@ import { isGeneralErrorSupported, setGeneralErrorToLocalStorage, getInputField, + isAccountLinkingSupported, } from "../helpers"; -// Run the tests in a DOM environment. -require("jsdom-global")(); import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, @@ -535,10 +532,12 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe }); function getTestCases(contactMethod, inputName, contactInfo) { + let accountLinkingSupported; describe(`UserInputCode`, function () { before(async function () { ({ browser, page } = await initBrowser(contactMethod, consoleLogs, authRecipe)); await setPasswordlessFlowType(contactMethod, "USER_INPUT_CODE"); + accountLinkingSupported = await isAccountLinkingSupported(); }); after(async function () { @@ -1039,6 +1038,7 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe before(async function () { ({ browser, page } = await initBrowser(contactMethod, consoleLogs, authRecipe)); await setPasswordlessFlowType(contactMethod, "MAGIC_LINK"); + accountLinkingSupported = await isAccountLinkingSupported(); }); after(async function () { @@ -1470,6 +1470,7 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe `ST_LOGS ${logId} OVERRIDE GET_LOGIN_ATTEMPT_INFO`, `ST_LOGS ${logId} OVERRIDE CONSUME_CODE`, `ST_LOGS ${logId} PRE_API_HOOKS PASSWORDLESS_CONSUME_CODE`, + ...(accountLinkingSupported ? [`ST_LOGS ${logId} ON_HANDLE_EVENT PASSWORDLESS_RESTART_FLOW`] : []), `ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH`, ...signInUpPageLoadLogs, ]); @@ -1545,6 +1546,7 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe before(async function () { ({ browser, page } = await initBrowser(contactMethod, consoleLogs, authRecipe)); await setPasswordlessFlowType(contactMethod, "USER_INPUT_CODE_AND_MAGIC_LINK"); + accountLinkingSupported = await isAccountLinkingSupported(); }); after(async function () { @@ -2130,10 +2132,10 @@ async function initBrowser(contactMethod, consoleLogs, authRecipe, { defaultCoun method: "POST", headers: [["content-type", "application/json"]], body: JSON.stringify({ - configUpdates: [ - { key: "passwordless_code_lifetime", value: 4000 }, - { key: "passwordless_max_code_input_attempts", value: 3 }, - ], + coreConfig: { + passwordless_code_lifetime: 4000, + passwordless_max_code_input_attempts: 3, + }, }), }).catch(console.error); diff --git a/test/end-to-end/refresherrors.test.js b/test/end-to-end/refresherrors.test.js index d36000842..06909e4cf 100644 --- a/test/end-to-end/refresherrors.test.js +++ b/test/end-to-end/refresherrors.test.js @@ -21,8 +21,6 @@ import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, getTextInDashboardNoAuth, screenshotOnFailure } from "../helpers"; -// Run the tests in a DOM environment. -require("jsdom-global")(); import { TEST_CLIENT_BASE_URL } from "../constants"; describe("Refresh errors", function () { diff --git a/test/end-to-end/resetpasswordusingtoken.test.js b/test/end-to-end/resetpasswordusingtoken.test.js index 1bb1d6813..0d5b14dc1 100644 --- a/test/end-to-end/resetpasswordusingtoken.test.js +++ b/test/end-to-end/resetpasswordusingtoken.test.js @@ -17,8 +17,6 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import puppeteer from "puppeteer"; import fetch from "isomorphic-fetch"; @@ -56,9 +54,6 @@ import { getResetPasswordSuccessBackToSignInButton, } from "../helpers"; -// Run the tests in a DOM environment. -require("jsdom-global")(); - /* * Tests. */ diff --git a/test/end-to-end/routing.test.js b/test/end-to-end/routing.test.js index 1aee0aadc..55c4e3fea 100644 --- a/test/end-to-end/routing.test.js +++ b/test/end-to-end/routing.test.js @@ -17,15 +17,10 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import { spawn } from "child_process"; import puppeteer from "puppeteer"; -// Run the tests in a DOM environment. -require("jsdom-global")(); - import { TEST_CLIENT_BASE_URL, DEFAULT_WEBSITE_BASE_PATH } from "../constants"; import { getSubmitFormButtonLabel, diff --git a/test/end-to-end/signin-rrdv5.test.js b/test/end-to-end/signin-rrdv5.test.js index 83c1f6a22..5a5b441df 100644 --- a/test/end-to-end/signin-rrdv5.test.js +++ b/test/end-to-end/signin-rrdv5.test.js @@ -17,8 +17,6 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import puppeteer from "puppeteer"; import { @@ -55,9 +53,6 @@ import { import fetch from "isomorphic-fetch"; import { SOMETHING_WENT_WRONG_ERROR } from "../constants"; -// Run the tests in a DOM environment. -require("jsdom-global")(); - import { EMAIL_EXISTS_API, SIGN_IN_API, TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_OUT_API } from "../constants"; /* diff --git a/test/end-to-end/signin-rrdv6.test.js b/test/end-to-end/signin-rrdv6.test.js index 978f4ba63..51c22c61f 100644 --- a/test/end-to-end/signin-rrdv6.test.js +++ b/test/end-to-end/signin-rrdv6.test.js @@ -17,8 +17,6 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import puppeteer from "puppeteer"; import { @@ -55,9 +53,6 @@ import { import fetch from "isomorphic-fetch"; import { SOMETHING_WENT_WRONG_ERROR } from "../constants"; -// Run the tests in a DOM environment. -require("jsdom-global")(); - import { EMAIL_EXISTS_API, SIGN_IN_API, TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_OUT_API } from "../constants"; /* diff --git a/test/end-to-end/signin.test.js b/test/end-to-end/signin.test.js index 1ed83048a..e5b00ecab 100644 --- a/test/end-to-end/signin.test.js +++ b/test/end-to-end/signin.test.js @@ -17,8 +17,6 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import puppeteer from "puppeteer"; import { @@ -58,9 +56,6 @@ import { import fetch from "isomorphic-fetch"; import { SOMETHING_WENT_WRONG_ERROR } from "../constants"; -// Run the tests in a DOM environment. -require("jsdom-global")(); - import { EMAIL_EXISTS_API, SIGN_IN_API, TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_OUT_API } from "../constants"; /* diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index 117458a14..3804da90c 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -17,8 +17,6 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import fetch from "isomorphic-fetch"; import puppeteer from "puppeteer"; @@ -42,8 +40,6 @@ import { waitForSTElement, } from "../helpers"; -// Run the tests in a DOM environment. -require("jsdom-global")(); import { EMAIL_EXISTS_API, SIGN_UP_API, diff --git a/test/end-to-end/thirdparty.test.js b/test/end-to-end/thirdparty.test.js index 5489a04e4..c1d176566 100644 --- a/test/end-to-end/thirdparty.test.js +++ b/test/end-to-end/thirdparty.test.js @@ -17,8 +17,6 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import fetch from "isomorphic-fetch"; import puppeteer from "puppeteer"; @@ -35,8 +33,6 @@ import { clickOnProviderButtonWithoutWaiting, } from "../helpers"; -// Run the tests in a DOM environment. -require("jsdom-global")(); import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_IN_UP_API, GET_AUTH_URL_API } from "../constants"; describe("SuperTokens Third Party", function () { diff --git a/test/end-to-end/thirdpartyemailpassword.test.js b/test/end-to-end/thirdpartyemailpassword.test.js index 2411fba07..798908687 100644 --- a/test/end-to-end/thirdpartyemailpassword.test.js +++ b/test/end-to-end/thirdpartyemailpassword.test.js @@ -17,8 +17,6 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import fetch from "isomorphic-fetch"; import puppeteer from "puppeteer"; @@ -43,6 +41,8 @@ import { waitFor, getFieldErrors, clickOnProviderButtonWithoutWaiting, + getFeatureFlags, + setEnabledRecipes, } from "../helpers"; import { TEST_CLIENT_BASE_URL, @@ -54,9 +54,6 @@ import { GET_AUTH_URL_API, } from "../constants"; -// Run the tests in a DOM environment. -require("jsdom-global")(); - /* * Tests. */ @@ -371,6 +368,22 @@ describe("SuperTokens Third Party Email Password", function () { const url = new URL(res[0].url()); assert.strictEqual(url.searchParams.get("clientType"), "test-web"); }); + + it("should handle no providers enabled on the backend", async function () { + if (!(await getFeatureFlags()).includes("recipeConfig")) { + this.skip(); + } + await assertProviders(page); + await setEnabledRecipes(["thirdpartyemailpassword"], []); + + await Promise.all([ + page.waitForResponse( + (response) => response.url().startsWith(GET_AUTH_URL_API) && response.status() === 400 + ), + clickOnProviderButtonWithoutWaiting(page, "Auth0"), + ]); + assert.strictEqual(await getGeneralError(page), "Something went wrong. Please try again."); + }); }); describe("Third Party callback error tests", function () { diff --git a/test/end-to-end/thirdpartypasswordless.test.js b/test/end-to-end/thirdpartypasswordless.test.js index be53d2cf5..9474ba8ba 100644 --- a/test/end-to-end/thirdpartypasswordless.test.js +++ b/test/end-to-end/thirdpartypasswordless.test.js @@ -17,8 +17,6 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import puppeteer from "puppeteer"; import { @@ -34,14 +32,15 @@ import { setPasswordlessFlowType, getFeatureFlags, isReact16, + assertProviders, + setEnabledRecipes, + clickOnProviderButtonWithoutWaiting, + getGeneralError, } from "../helpers"; -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_IN_UP_API } from "../constants"; +import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_IN_UP_API, GET_AUTH_URL_API } from "../constants"; import { getThirdPartyTestCases } from "./thirdparty.test"; import { getPasswordlessTestCases } from "./passwordless.test"; -// Run the tests in a DOM environment. -require("jsdom-global")(); - /* * Tests. */ @@ -210,6 +209,22 @@ describe("SuperTokens Third Party Passwordless", function () { "ST_LOGS SESSION OVERRIDE GET_USER_ID", ]); }); + + it("should handle no providers enabled on the backend", async function () { + if (!(await getFeatureFlags()).includes("recipeConfig")) { + this.skip(); + } + await assertProviders(page); + await setEnabledRecipes(["thirdpartypasswordless"], []); + + await Promise.all([ + page.waitForResponse( + (response) => response.url().startsWith(GET_AUTH_URL_API) && response.status() === 400 + ), + clickOnProviderButtonWithoutWaiting(page, "Auth0"), + ]); + assert.strictEqual(await getGeneralError(page), "Something went wrong. Please try again."); + }); }); describe("Third Party specific", function () { diff --git a/test/end-to-end/userContext.test.js b/test/end-to-end/userContext.test.js index fe1ee8c92..9bd66287d 100644 --- a/test/end-to-end/userContext.test.js +++ b/test/end-to-end/userContext.test.js @@ -12,8 +12,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; + import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, @@ -35,9 +34,6 @@ import { } from "../constants"; import assert from "assert"; -// Run the tests in a DOM environment. -require("jsdom-global")(); - describe("SuperTokens userContext with UI components test", function () { let browser; let page; diff --git a/test/end-to-end/userroles.test.js b/test/end-to-end/userroles.test.js index 607d08ab1..f4ac7b811 100644 --- a/test/end-to-end/userroles.test.js +++ b/test/end-to-end/userroles.test.js @@ -32,8 +32,6 @@ import { waitForText, } from "../helpers"; -// Run the tests in a DOM environment. -require("jsdom-global")(); import { TEST_APPLICATION_SERVER_BASE_URL, TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL } from "../constants"; describe("User Roles in the frontend", function () { diff --git a/test/helpers.js b/test/helpers.js index 925b5d4b2..8274127f6 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -254,7 +254,7 @@ export async function getInputAdornmentsError(page) { document .querySelector(ST_ROOT_SELECTOR) .shadowRoot.querySelectorAll("[data-supertokens~='inputAdornmentError']"), - (i) => i.name + (i) => i.textContent ), { ST_ROOT_SELECTOR } ); @@ -509,6 +509,7 @@ export async function assertProviders(page) { "Continue with Apple", "Continue with Custom", "Continue with Auth0", + "Continue with Mock Provider", ]); } @@ -593,11 +594,7 @@ export async function defaultSignUp(page, rid = "emailpassword") { rid ); } -export async function signUp(page, fields, postValues, rid = "emailpassword") { - if (postValues === undefined) { - postValues = JSON.stringify({ formFields: fields.map((v) => ({ id: v.name, value: v.value })) }); - } - +export async function signUp(page, fields, postValues = undefined, rid = "emailpassword") { // Set values. await setInputValues(page, fields); const successAdornments = await getInputAdornmentsSuccess(page); @@ -614,7 +611,9 @@ export async function signUp(page, fields, postValues, rid = "emailpassword") { assert.strictEqual(hasEmailExistMethodBeenCalled, false); assert.strictEqual(request.headers().rid, rid); - assert.strictEqual(request.postData(), postValues); + if (postValues !== undefined) { + assert.strictEqual(request.postData(), postValues); + } assert.strictEqual(response.status, "OK"); await page.setRequestInterception(false); @@ -704,12 +703,12 @@ export async function screenshotOnFailure(ctx, browser) { continue; } - const title = ctx.currentTest + let title = ctx.currentTest .fullTitle() .split(/\W/) .filter((a) => a.length !== 0) - .join("_") - .substring(0, 20); + .join("_"); + title = title.substring(title.length - 30); await pages[i].screenshot({ path: path.join(screenshotRoot, testFileName, `${title}-tab_${i}-${Date.now()}.png`), }); @@ -744,6 +743,44 @@ export function setPasswordlessFlowType(contactMethod, flowType) { }); } +export function setAccountLinkingConfig(enabled, shouldAutomaticallyLink, shouldRequireVerification) { + return fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/test/setAccountLinkingConfig`, { + method: "POST", + headers: [["content-type", "application/json"]], + body: JSON.stringify({ + enabled, + shouldAutoLink: { + shouldAutomaticallyLink, + shouldRequireVerification, + }, + }), + }); +} + +export function changeEmail(rid, recipeUserId, email, phoneNumber) { + return fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/changeEmail`, { + method: "POST", + headers: [["content-type", "application/json"]], + body: JSON.stringify({ + rid, + recipeUserId, + email, + phoneNumber, + }), + }); +} + +export function setEnabledRecipes(enabledRecipes, enabledProviders) { + return fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/test/setEnabledRecipes`, { + method: "POST", + headers: [["content-type", "application/json"]], + body: JSON.stringify({ + enabledRecipes, + enabledProviders, + }), + }); +} + export function isReact16() { return process.env.IS_REACT_16 === "true"; } @@ -807,6 +844,24 @@ export async function isMultitenancySupported() { return true; } +export async function isMultitenancyManagementEndpointsSupported() { + const features = await getFeatureFlags(); + if (!features.includes("multitenancyManagementEndpoints")) { + return false; + } + + return true; +} + +export async function isAccountLinkingSupported() { + const features = await getFeatureFlags(); + if (!features.includes("accountlinking")) { + return false; + } + + return true; +} + /** * For example setGeneralErrorToLocalStorage("EMAIL_PASSWORD", "EMAIL_PASSWORD_SIGN_UP", page) to * set for signUp in email password @@ -821,3 +876,121 @@ export async function setGeneralErrorToLocalStorage(recipeName, action, page) { export async function getTestEmail() { return `john.doe+${Date.now()}@supertokens.io`; } + +export async function setupTenant(tenantId, loginMethods) { + const body = { + tenantId, + loginMethods, + }; + if (loginMethods.thirdParty?.providers) { + body.loginMethods.thirdParty.providers = loginMethods.thirdParty.providers.map((provider) => ({ + ...testProviderConfigs[provider.id.split("-")[0]], + thirdPartyId: provider.id, + name: provider.name, + })); + } + let coreResp = await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/setupTenant`, { + method: "POST", + headers: new Headers([["content-type", "application/json"]]), + body: JSON.stringify(body), + }); + assert.strictEqual(coreResp.status, 200); +} + +export async function addUserToTenant(tenantId, recipeUserId) { + let coreResp = await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/addUserToTenant`, { + method: "POST", + headers: new Headers([["content-type", "application/json"]]), + body: JSON.stringify({ + tenantId, + recipeUserId, + }), + }); + assert.strictEqual(coreResp.status, 200); +} + +export async function removeUserFromTenant(tenantId, recipeUserId) { + let coreResp = await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/removeUserFromTenant`, { + method: "POST", + headers: new Headers([["content-type", "application/json"]]), + body: JSON.stringify({ + tenantId, + recipeUserId, + }), + }); + assert.strictEqual(coreResp.status, 200); +} + +export async function removeTenant(tenantId) { + let coreResp = await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/removeTenant`, { + method: "POST", + headers: new Headers([["content-type", "application/json"]]), + body: JSON.stringify({ + tenantId, + }), + }); + assert.strictEqual(coreResp.status, 200); +} + +const testProviderConfigs = { + 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", + }, + }, + ], + }, + github: { + clients: [ + { + clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", + clientId: "467101b197249757c71f", + }, + ], + }, + google: { + clients: [ + { + clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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", + }, + ], + }, + auth0: { + // this contains info about forming the authorisation redirect URL without the state params and without the redirect_uri param + authorizationEndpoint: `https://${process.env.AUTH0_DOMAIN}/authorize`, + authorizationEndpointQueryParams: { + scope: "openid profile email email_verified", + }, + jwksURI: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`, + tokenEndpoint: `https://${process.env.AUTH0_DOMAIN}/oauth/token`, + clients: [ + { + clientId: process.env.AUTH0_CLIENT_ID, + clientSecret: process.env.AUTH0_CLIENT_SECRET, + }, + ], + userInfoMap: { + fromIdTokenPayload: { + userId: "sub", + email: "email", + emailVerified: "email_verified", + }, + }, + }, + test: { + // We add a client since it's required + clients: [ + { + clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", + clientSecret: "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", + }, + ], + }, +}; diff --git a/test/server/index.js b/test/server/index.js index 1b2c45409..e05e753d1 100644 --- a/test/server/index.js +++ b/test/server/index.js @@ -44,6 +44,7 @@ let { customAuth0Provider, maxVersion, stopST, + mockThirdPartyProvider, } = require("./utils"); let { version: nodeSDKVersion } = require("supertokens-node/lib/build/version"); const fetch = require("isomorphic-fetch"); @@ -89,6 +90,15 @@ try { multitenancySupported = false; } +let AccountLinking, AccountLinkingRaw, accountLinkingSupported; +try { + AccountLinkingRaw = require("supertokens-node/lib/build/recipe/accountlinking/recipe").default; + AccountLinking = require("supertokens-node/recipe/accountlinking"); + accountLinkingSupported = true; +} catch (ex) { + accountLinkingSupported = false; +} + let generalErrorSupported; if (maxVersion(nodeSDKVersion, "9.9.9") === "9.9.9") { @@ -98,7 +108,7 @@ if (maxVersion(nodeSDKVersion, "9.9.9") === "9.9.9") { generalErrorSupported = true; } -const providers = [ +const fullProviderList = [ { config: { thirdPartyId: "google", @@ -133,18 +143,34 @@ const providers = [ }, }, customAuth0Provider(), + mockThirdPartyProvider, ]; let urlencodedParser = bodyParser.urlencoded({ limit: "20mb", extended: true, parameterLimit: 20000 }); let jsonParser = bodyParser.json({ limit: "20mb" }); let app = express(); + +const originalSend = app.response.send; +app.response.send = function sendOverWrite(body) { + originalSend.call(this, body); + this.__custombody__ = body; +}; + morgan.token("body", function (req, res) { - return JSON.stringify(req.body && req.body["formFields"]); + return JSON.stringify(req.body); +}); + +morgan.token("res-body", function (req, res) { + return typeof res.__custombody__ ? res.__custombody__ : JSON.stringify(res.__custombody__); }); -app.use(morgan("[:date[iso]] :url :method :status :response-time ms - :res[content-length] - :body")); + app.use(urlencodedParser); app.use(jsonParser); + +app.use(morgan("[:date[iso]] :url :method :body", { immediate: true })); +app.use(morgan("[:date[iso]] :url :method :status :response-time ms - :res[content-length] :res-body")); + app.use(cookieParser()); const WEB_PORT = process.env.WEB_PORT || 3031; @@ -184,6 +210,13 @@ const formFields = (process.env.MIN_FIELDS && []) || [ optional: true, }, ]; + +let connectionURI = "http://localhost:9000"; +let passwordlessConfig = {}; +let accountLinkingConfig = {}; +let enabledProviders = undefined; +let enabledRecipes = undefined; + initST(); app.use( @@ -202,30 +235,37 @@ app.get("/ping", async (req, res) => { }); app.post("/startst", async (req, res) => { - if (req.body && req.body.configUpdates) { - for (const update of req.body.configUpdates) { - await setKeyValueInConfig(update.key, update.value); - } + try { + connectionURI = await startST(req.body); + console.log("Connection URI: " + connectionURI); + const OPAQUE_KEY_WITH_ALL_FEATURES_ENABLED = + "N2yITHflaFS4BPm7n0bnfFCjP4sJoTERmP0J=kXQ5YONtALeGnfOOe2rf2QZ0mfOh0aO3pBqfF-S0jb0ABpat6pySluTpJO6jieD6tzUOR1HrGjJO=50Ob3mHi21tQHJ"; + + await fetch(`${connectionURI}/ee/license`, { + method: "PUT", + headers: { + "content-type": "application/json; charset=utf-8", + }, + body: JSON.stringify({ + licenseKey: OPAQUE_KEY_WITH_ALL_FEATURES_ENABLED, + }), + }); + initST(); + res.send(connectionURI + ""); + } catch (err) { + console.log(err); + res.status(500).send(err.toString()); } - let pid = await startST(); - const OPAQUE_KEY_WITH_MULTITENANCY_FEATURE = - "ijaleljUd2kU9XXWLiqFYv5br8nutTxbyBqWypQdv2N-BocoNriPrnYQd0NXPm8rVkeEocN9ayq0B7c3Pv-BTBIhAZSclXMlgyfXtlwAOJk=9BfESEleW6LyTov47dXu"; - - await fetch(`http://localhost:9000/ee/license`, { - method: "PUT", - headers: { - "content-type": "application/json; charset=utf-8", - }, - body: JSON.stringify({ - licenseKey: OPAQUE_KEY_WITH_MULTITENANCY_FEATURE, - }), - }); - res.send(pid + ""); }); app.post("/beforeeach", async (req, res) => { deviceStore = new Map(); + accountLinkingConfig = {}; + passwordlessConfig = {}; + enabledProviders = undefined; + enabledRecipes = undefined; + await killAllST(); await setupST(); res.send(); @@ -255,6 +295,7 @@ app.get("/sessioninfo", verifySession(), async (req, res, next) => { res.send({ sessionHandle: session.getHandle(), userId: session.getUserId(), + recipeUserId: accountLinkingSupported ? session.getRecipeUserId().getAsString() : session.getUserId(), accessTokenPayload, sessionData, }); @@ -264,16 +305,46 @@ app.get("/sessioninfo", verifySession(), async (req, res, next) => { }); app.post("/deleteUser", async (req, res) => { - if (req.body.rid !== "emailpassword") { - res.status(400).send({ message: "Not implemented" }); + if (!accountLinkingSupported) { + const user = await EmailPassword.getUserByEmail("public", req.body.email); + return res.send(await SuperTokens.deleteUser(user.id)); + } + + const users = await SuperTokens.listUsersByAccountInfo("public", req.body); + res.send(await SuperTokens.deleteUser(users[0].id)); +}); + +app.post("/changeEmail", async (req, res) => { + let resp; + if (req.body.rid === "emailpassword") { + resp = await EmailPassword.updateEmailOrPassword({ + recipeUserId: convertToRecipeUserIdIfAvailable(req.body.recipeUserId), + email: req.body.email, + tenantIdForPasswordPolicy: req.body.tenantId, + }); + } else if (req.body.rid === "thirdparty") { + const user = await SuperTokens.getUser({ userId: req.body.recipeUserId }); + const loginMethod = user.loginMethod.find((lm) => lm.recipeUserId.getAsString() === req.body.recipeUserId); + resp = await ThirdParty.manuallyCreateOrUpdateUser( + req.body.tenantId, + loginMethod.thirdParty.id, + loginMethod.thirdParty.userId, + req.body.email, + false + ); + } else if (req.body.rid === "passwordless") { + resp = await Passwordless.updateUser({ + recipeUserId: convertToRecipeUserIdIfAvailable(req.body.recipeUserId), + email: req.body.email, + phoneNumber: req.body.phoneNumber, + }); } - const user = await EmailPassword.getUserByEmail("public", req.body.email); - res.send(await SuperTokens.deleteUser(user.id)); + res.json(resp); }); app.get("/unverifyEmail", verifySession(), async (req, res) => { let session = req.session; - await EmailVerification.unverifyEmail(session.getUserId()); + await EmailVerification.unverifyEmail(accountLinkingSupported ? session.getRecipeUserId() : session.getUserId()); await session.fetchAndSetClaim(EmailVerification.EmailVerificationClaim); res.send({ status: "OK" }); }); @@ -316,30 +387,82 @@ app.get("/token", async (_, res) => { }); }); +app.post("/setupTenant", async (req, res) => { + const { tenantId, loginMethods, coreConfig } = req.body; + let coreResp = await Multitenancy.createOrUpdateTenant(tenantId, { + emailPasswordEnabled: loginMethods.emailPassword?.enabled === true, + thirdPartyEnabled: loginMethods.thirdParty?.enabled === true, + passwordlessEnabled: loginMethods.passwordless?.enabled === true, + coreConfig, + }); + + if (loginMethods.thirdParty.providers !== undefined) { + for (const provider of loginMethods.thirdParty.providers) { + await Multitenancy.createOrUpdateThirdPartyConfig(tenantId, provider); + } + } + res.send(coreResp); +}); + +app.post("/addUserToTenant", async (req, res) => { + const { tenantId, recipeUserId } = req.body; + let coreResp = await Multitenancy.associateUserToTenant(tenantId, convertToRecipeUserIdIfAvailable(recipeUserId)); + res.send(coreResp); +}); + +app.post("/removeUserFromTenant", async (req, res) => { + const { tenantId, recipeUserId } = req.body; + let coreResp = await Multitenancy.disassociateUserFromTenant( + tenantId, + convertToRecipeUserIdIfAvailable(recipeUserId) + ); + res.send(coreResp); +}); + +app.post("/removeTenant", async (req, res) => { + const { tenantId } = req.body; + let coreResp = await Multitenancy.deleteTenant(tenantId); + res.send(coreResp); +}); + app.post("/test/setFlow", (req, res) => { - initST({ - passwordlessConfig: { - contactMethod: req.body.contactMethod, - flowType: req.body.flowType, - - emailDelivery: { - override: (oI) => { - return { - ...oI, - sendEmail: saveCode, - }; - }, + passwordlessConfig = { + contactMethod: req.body.contactMethod, + flowType: req.body.flowType, + + emailDelivery: { + override: (oI) => { + return { + ...oI, + sendEmail: saveCode, + }; }, - smsDelivery: { - override: (oI) => { - return { - ...oI, - sendSms: saveCode, - }; - }, + }, + smsDelivery: { + override: (oI) => { + return { + ...oI, + sendSms: saveCode, + }; }, }, - }); + }; + initST(); + res.sendStatus(200); +}); + +app.post("/test/setAccountLinkingConfig", (req, res) => { + accountLinkingConfig = { + ...req.body, + }; + initST(); + res.sendStatus(200); +}); + +app.post("/test/setEnabledRecipes", (req, res) => { + enabledRecipes = req.body.enabledRecipes; + enabledProviders = req.body.enabledProviders; + initST(); res.sendStatus(200); }); @@ -368,8 +491,15 @@ app.get("/test/featureFlags", (req, res) => { if (multitenancySupported) { available.push("multitenancy"); + available.push("multitenancyManagementEndpoints"); } + if (accountLinkingSupported) { + available.push("accountlinking"); + } + + available.push("recipeConfig"); + res.send({ available, }); @@ -402,13 +532,12 @@ server.listen(process.env.NODE_PORT === undefined ? 8080 : process.env.NODE_PORT } catch (e) {} await setupST(); - const pid = await startST(); - console.log(`Application started on http://localhost:${process.env.NODE_PORT | 8080}`); - console.log(`processId: ${pid}`); + connectionURI = await startST(); + console.log("Connection URI: " + connectionURI); } })(process.env.START === "true"); -function initST({ passwordlessConfig } = {}) { +function initST() { if (process.env.TEST_MODE) { if (userRolesSupported) { UserRolesRaw.reset(); @@ -426,6 +555,10 @@ function initST({ passwordlessConfig } = {}) { MultitenancyRaw.reset(); } + if (accountLinkingSupported) { + AccountLinkingRaw.reset(); + } + EmailVerificationRaw.reset(); EmailPasswordRaw.reset(); ThirdPartyRaw.reset(); @@ -436,276 +569,296 @@ function initST({ passwordlessConfig } = {}) { } const recipeList = [ - EmailVerification.init({ - mode: "OPTIONAL", - emailDelivery: { - override: (oI) => { - return { - ...oI, - sendEmail: async (input) => { - console.log(input.emailVerifyLink); - latestURLWithToken = input.emailVerifyLink; - }, - }; + [ + "emailverification", + EmailVerification.init({ + mode: "OPTIONAL", + emailDelivery: { + override: (oI) => { + return { + ...oI, + sendEmail: async (input) => { + latestURLWithToken = input.emailVerifyLink; + }, + }; + }, }, - }, - override: { - apis: (oI) => { - return { - ...oI, - generateEmailVerifyTokenPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API email verification code", - }; - } - return oI.generateEmailVerifyTokenPOST(input); - }, - verifyEmailPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API email verify", - }; - } - return oI.verifyEmailPOST(input); - }, - }; + override: { + apis: (oI) => { + return { + ...oI, + generateEmailVerifyTokenPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API email verification code", + }; + } + return oI.generateEmailVerifyTokenPOST(input); + }, + verifyEmailPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API email verify", + }; + } + return oI.verifyEmailPOST(input); + }, + }; + }, }, - }, - }), - EmailPassword.init({ - override: { - apis: (oI) => { - return { - ...oI, - passwordResetPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API reset password consume", - }; - } - return oI.passwordResetPOST(input); - }, - generatePasswordResetTokenPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API reset password", - }; - } - return oI.generatePasswordResetTokenPOST(input); - }, - emailExistsGET: async function (input) { - let generalError = input.options.req.getKeyValueFromQuery("generalError"); - if (generalError === "true") { - return { - status: "GENERAL_ERROR", - message: "general error from API email exists", - }; - } - return oI.emailExistsGET(input); - }, - signUpPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API sign up", - }; - } - return oI.signUpPOST(input); - }, - signInPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - let message = "general error from API sign in"; - - if (body.generalErrorMessage !== undefined) { - message = body.generalErrorMessage; + }), + ], + [ + "emailpassword", + EmailPassword.init({ + override: { + apis: (oI) => { + return { + ...oI, + passwordResetPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API reset password consume", + }; } + return oI.passwordResetPOST(input); + }, + generatePasswordResetTokenPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API reset password", + }; + } + return oI.generatePasswordResetTokenPOST(input); + }, + emailExistsGET: async function (input) { + let generalError = input.options.req.getKeyValueFromQuery("generalError"); + if (generalError === "true") { + return { + status: "GENERAL_ERROR", + message: "general error from API email exists", + }; + } + return oI.emailExistsGET(input); + }, + signUpPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API sign up", + }; + } + return oI.signUpPOST(input); + }, + signInPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + let message = "general error from API sign in"; - return { - status: "GENERAL_ERROR", - message, - }; - } - return oI.signInPOST(input); - }, - }; + if (body.generalErrorMessage !== undefined) { + message = body.generalErrorMessage; + } + + return { + status: "GENERAL_ERROR", + message, + }; + } + return oI.signInPOST(input); + }, + }; + }, }, - }, - signUpFeature: { - formFields, - }, - emailDelivery: { - override: (oI) => { - return { - ...oI, - sendEmail: async (input) => { - console.log(input.passwordResetLink); - latestURLWithToken = input.passwordResetLink; - }, - }; + signUpFeature: { + formFields, }, - }, - }), - ThirdParty.init({ - signInAndUpFeature: { - providers, - }, - override: { - apis: (originalImplementation) => { - return { - ...originalImplementation, - authorisationUrlGET: async function (input) { - let generalErrorFromQuery = input.options.req.getKeyValueFromQuery("generalError"); - if (generalErrorFromQuery === "true") { - return { - status: "GENERAL_ERROR", - message: "general error from API authorisation url get", - }; - } - - return originalImplementation.authorisationUrlGET(input); - }, - signInUpPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API sign in up", - }; - } - - return originalImplementation.signInUpPOST(input); - }, - }; + emailDelivery: { + override: (oI) => { + return { + ...oI, + sendEmail: async (input) => { + console.log(input.passwordResetLink); + latestURLWithToken = input.passwordResetLink; + }, + }; + }, }, - }, - }), - ThirdPartyEmailPassword.init({ - signUpFeature: { - formFields, - }, - emailDelivery: { - override: (oI) => { - return { - ...oI, - sendEmail: async (input) => { - console.log(input.passwordResetLink); - latestURLWithToken = input.passwordResetLink; - }, - }; + }), + ], + [ + "thirdparty", + ThirdParty.init({ + signInAndUpFeature: { + providers: + enabledProviders !== undefined + ? fullProviderList.filter(({ config }) => enabledProviders.includes(config.thirdPartyId)) + : fullProviderList, }, - }, - providers, - override: { - apis: (originalImplementation) => { - return { - ...originalImplementation, - emailPasswordSignUpPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API sign up", - }; - } - - return originalImplementation.emailPasswordSignUpPOST(input); - }, - passwordResetPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API reset password consume", - }; - } - return originalImplementation.passwordResetPOST(input); - }, - generatePasswordResetTokenPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API reset password", - }; - } - return originalImplementation.generatePasswordResetTokenPOST(input); - }, - emailPasswordEmailExistsGET: async function (input) { - let generalError = input.options.req.getKeyValueFromQuery("generalError"); - if (generalError === "true") { - return { - status: "GENERAL_ERROR", - message: "general error from API email exists", - }; - } - return originalImplementation.emailPasswordEmailExistsGET(input); - }, - emailPasswordSignInPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API sign in", - }; - } - return originalImplementation.emailPasswordSignInPOST(input); - }, - authorisationUrlGET: async function (input) { - let generalErrorFromQuery = input.options.req.getKeyValueFromQuery("generalError"); - if (generalErrorFromQuery === "true") { - return { - status: "GENERAL_ERROR", - message: "general error from API authorisation url get", - }; - } - - return originalImplementation.authorisationUrlGET(input); - }, - thirdPartySignInUpPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API sign in up", - }; - } - - return originalImplementation.thirdPartySignInUpPOST(input); - }, - }; + override: { + apis: (originalImplementation) => { + return { + ...originalImplementation, + authorisationUrlGET: async function (input) { + let generalErrorFromQuery = input.options.req.getKeyValueFromQuery("generalError"); + if (generalErrorFromQuery === "true") { + return { + status: "GENERAL_ERROR", + message: "general error from API authorisation url get", + }; + } + + return originalImplementation.authorisationUrlGET(input); + }, + signInUpPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API sign in up", + }; + } + + return originalImplementation.signInUpPOST(input); + }, + }; + }, }, - }, - }), - Session.init({ - override: { - apis: function (originalImplementation) { - return { - ...originalImplementation, - signOutPOST: async (input) => { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from signout API", - }; - } - return originalImplementation.signOutPOST(input); - }, - }; + }), + ], + [ + "thirdpartyemailpassword", + ThirdPartyEmailPassword.init({ + signUpFeature: { + formFields, }, - }, - }), + emailDelivery: { + override: (oI) => { + return { + ...oI, + sendEmail: async (input) => { + console.log(input.passwordResetLink); + latestURLWithToken = input.passwordResetLink; + }, + }; + }, + }, + providers: + enabledProviders !== undefined + ? fullProviderList.filter((config) => enabledProviders.includes(config.config)) + : fullProviderList, + override: { + apis: (originalImplementation) => { + return { + ...originalImplementation, + emailPasswordSignUpPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API sign up", + }; + } + + return originalImplementation.emailPasswordSignUpPOST(input); + }, + passwordResetPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API reset password consume", + }; + } + return originalImplementation.passwordResetPOST(input); + }, + generatePasswordResetTokenPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API reset password", + }; + } + return originalImplementation.generatePasswordResetTokenPOST(input); + }, + emailPasswordEmailExistsGET: async function (input) { + let generalError = input.options.req.getKeyValueFromQuery("generalError"); + if (generalError === "true") { + return { + status: "GENERAL_ERROR", + message: "general error from API email exists", + }; + } + return originalImplementation.emailPasswordEmailExistsGET(input); + }, + emailPasswordSignInPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API sign in", + }; + } + return originalImplementation.emailPasswordSignInPOST(input); + }, + authorisationUrlGET: async function (input) { + let generalErrorFromQuery = input.options.req.getKeyValueFromQuery("generalError"); + if (generalErrorFromQuery === "true") { + return { + status: "GENERAL_ERROR", + message: "general error from API authorisation url get", + }; + } + + return originalImplementation.authorisationUrlGET(input); + }, + thirdPartySignInUpPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API sign in up", + }; + } + + return originalImplementation.thirdPartySignInUpPOST(input); + }, + }; + }, + }, + }), + ], + [ + "session", + Session.init({ + override: { + apis: function (originalImplementation) { + return { + ...originalImplementation, + signOutPOST: async (input) => { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from signout API", + }; + } + return originalImplementation.signOutPOST(input); + }, + }; + }, + }, + }), + ], ]; passwordlessConfig = { @@ -730,7 +883,8 @@ function initST({ passwordlessConfig } = {}) { ...passwordlessConfig, }; if (passwordlessSupported) { - recipeList.push( + recipeList.push([ + "passwordless", Passwordless.init({ ...passwordlessConfig, override: { @@ -770,15 +924,19 @@ function initST({ passwordlessConfig } = {}) { }; }, }, - }) - ); + }), + ]); } if (thirdPartyPasswordlessSupported) { - recipeList.push( + recipeList.push([ + "thirdpartypasswordless", ThirdPartyPasswordless.init({ ...passwordlessConfig, - providers, + providers: + enabledProviders !== undefined + ? fullProviderList.filter((config) => enabledProviders.includes(config.config)) + : fullProviderList, override: { apis: (originalImplementation) => { return { @@ -838,23 +996,46 @@ function initST({ passwordlessConfig } = {}) { }; }, }, - }) - ); + }), + ]); } if (userRolesSupported) { - recipeList.push(UserRoles.init()); + recipeList.push(["userroles", UserRoles.init()]); } if (multitenancySupported) { - recipeList.push( + recipeList.push([ + "multitenancy", Multitenancy.init({ getAllowedDomainsForTenantId: (tenantId) => [ `${tenantId}.example.com`, websiteDomain.replace(/https?:\/\/([^:\/]*).*/, "$1"), ], - }) - ); + }), + ]); + } + + accountLinkingConfig = { + enabled: false, + shouldAutoLink: { + ...accountLinkingConfig?.shouldAutoLink, + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + }, + ...accountLinkingConfig, + }; + if (accountLinkingSupported) { + if (accountLinkingConfig.enabled) { + recipeList.push([ + "accountlinking", + AccountLinking.init({ + shouldDoAutomaticAccountLinking: () => ({ + ...accountLinkingConfig.shouldAutoLink, + }), + }), + ]); + } } SuperTokens.init({ @@ -864,8 +1045,18 @@ function initST({ passwordlessConfig } = {}) { websiteDomain, }, supertokens: { - connectionURI: "http://localhost:9000", + connectionURI, }, - recipeList, + recipeList: + enabledRecipes !== undefined + ? recipeList.filter(([key]) => enabledRecipes.includes(key)).map(([_key, recipeFunc]) => recipeFunc) + : recipeList.map(([_key, recipeFunc]) => recipeFunc), }); } + +function convertToRecipeUserIdIfAvailable(id) { + if (SuperTokens.convertToRecipeUserId !== undefined) { + return SuperTokens.convertToRecipeUserId(id); + } + return id; +} diff --git a/test/server/package-lock.json b/test/server/package-lock.json index 0da214688..3eeeb3944 100644 --- a/test/server/package-lock.json +++ b/test/server/package-lock.json @@ -15,100 +15,7 @@ "dotenv": "^8.2.0", "express": "4.17.1", "morgan": "^1.10.0", - "supertokens-node": "latest" - } - }, - "node_modules/@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.34", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.34.tgz", - "integrity": "sha512-fvr49XlCGoUj2Pp730AItckfjat4WNb0lb3kfrLWffd+RLeoGAMsq7UOy04PAPtoL01uKwcp6u8nhzpgpDYr3w==", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/jsonwebtoken": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz", - "integrity": "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" - }, - "node_modules/@types/node": { - "version": "18.16.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", - "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "node_modules/@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", - "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", - "dependencies": { - "@types/mime": "*", - "@types/node": "*" + "supertokens-node": "^15.2.0" } }, "node_modules/accepts": { @@ -224,17 +131,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/co-body": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.1.0.tgz", - "integrity": "sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==", - "dependencies": { - "inflation": "^2.0.0", - "qs": "^6.5.2", - "raw-body": "^2.3.3", - "type-is": "^1.6.16" - } - }, "node_modules/content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -247,9 +143,9 @@ } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } @@ -291,6 +187,19 @@ "node": ">= 0.10" } }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + }, "node_modules/dayjs": { "version": "1.11.7", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", @@ -579,20 +488,6 @@ "node": ">= 0.10" } }, - "node_modules/jose": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz", - "integrity": "sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg==", - "dependencies": { - "@panva/asn1.js": "^1.0.0" - }, - "engines": { - "node": ">=10.13.0 < 13 || >=13.7.0" - }, - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", @@ -623,43 +518,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/jwks-rsa": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.1.5.tgz", - "integrity": "sha512-IODtn1SwEm7n6GQZnQLY0oxKDrMh7n/jRH1MzE8mlxWMrh2NnMyOsXTebu8vJ1qCpmuTJcL4DdiE0E4h8jnwsA==", - "dependencies": { - "@types/express": "^4.17.14", - "@types/jsonwebtoken": "^8.5.9", - "debug": "^4.3.4", - "jose": "^2.0.6", - "limiter": "^1.1.5", - "lru-memoizer": "^2.1.4" - }, - "engines": { - "node": ">=10 < 13 || >=14" - } - }, - "node_modules/jwks-rsa/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/jwks-rsa/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -674,39 +532,11 @@ "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.13.tgz", "integrity": "sha512-b74iyWmwb4GprAUPjPkJ11GTC7KX4Pd3onpJfKxYyY8y9Rbb4ERY47LvCMEDM09WD3thiLDMXtkfDK/AX+zT7Q==" }, - "node_modules/limiter": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", - "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" - }, - "node_modules/lru-cache": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", - "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", - "dependencies": { - "pseudomap": "^1.0.1", - "yallist": "^2.0.0" - } - }, - "node_modules/lru-memoizer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz", - "integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==", - "dependencies": { - "lodash.clonedeep": "^4.5.0", - "lru-cache": "~4.0.0" - } - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -794,6 +624,25 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/nodemailer": { "version": "6.7.8", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.8.tgz", @@ -850,6 +699,14 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/pkce-challenge": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-3.1.0.tgz", + "integrity": "sha512-bQ/0XPZZ7eX+cdAkd61uYWpfMhakH3NeteUF1R8GNa+LMqX8QFAkbCLqq+AYAns1/ueACBu/BMWhrlKGrdvGZg==", + "dependencies": { + "crypto-js": "^4.1.1" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -862,11 +719,6 @@ "node": ">= 0.10" } }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" - }, "node_modules/psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -1031,76 +883,22 @@ "integrity": "sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg==" }, "node_modules/supertokens-node": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/supertokens-node/-/supertokens-node-14.1.1.tgz", - "integrity": "sha512-BFcKyZs0pPe8GyKaSIcBzwJ25bU2jvVB/JKP+C1TZy8ngFNd/stETbQWVQ5sszsUb0GuxkKduDzkgxw5AYtfTA==", + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/supertokens-node/-/supertokens-node-15.2.0.tgz", + "integrity": "sha512-lsiO77FHIiSP3gmS07YFVYRctNVchoW4+JkDfaiTdfSpSBoX+VDWCHwl7mbH47nuodIGz1JzYQuXEiaO26o7HA==", "dependencies": { - "axios": "0.21.4", - "body-parser": "1.20.1", - "co-body": "6.1.0", + "content-type": "^1.0.5", "cookie": "0.4.0", + "cross-fetch": "^3.1.6", "debug": "^4.3.3", + "inflation": "^2.0.0", "jose": "^4.13.1", - "jsonwebtoken": "^9.0.0", - "jwks-rsa": "^2.0.5", "libphonenumber-js": "^1.9.44", "nodemailer": "^6.7.2", + "pkce-challenge": "^3.0.0", "psl": "1.8.0", "supertokens-js-override": "^0.0.4", - "twilio": "^4.7.2", - "verify-apple-id-token": "^3.0.1" - } - }, - "node_modules/supertokens-node/node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, - "node_modules/supertokens-node/node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/supertokens-node/node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/supertokens-node/node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/supertokens-node/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" + "twilio": "^4.7.2" } }, "node_modules/supertokens-node/node_modules/cookie": { @@ -1127,43 +925,6 @@ } } }, - "node_modules/supertokens-node/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/supertokens-node/node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/supertokens-node/node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/supertokens-node/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "node_modules/supertokens-node/node_modules/jose": { "version": "4.14.4", "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", @@ -1177,66 +938,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/supertokens-node/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/supertokens-node/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/supertokens-node/node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/supertokens-node/node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/supertokens-node/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/supertokens-node/node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, "node_modules/toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -1245,6 +946,11 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/twilio": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/twilio/-/twilio-4.10.0.tgz", @@ -1330,68 +1036,20 @@ "node": ">= 0.8" } }, - "node_modules/verify-apple-id-token": { + "node_modules/webidl-conversions": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/verify-apple-id-token/-/verify-apple-id-token-3.0.1.tgz", - "integrity": "sha512-q91pG1e52TpEzXldMirWYNWcSQC4WuzgG0y/ZnBhzjfk0pSxi4YlGh5OTVRlodBenayGHfSDn5VseG9QDuqOew==", - "dependencies": { - "jsonwebtoken": "^9.0.0", - "jwks-rsa": "^3.0.0" - } - }, - "node_modules/verify-apple-id-token/node_modules/@types/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/verify-apple-id-token/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, - "node_modules/verify-apple-id-token/node_modules/jose": { - "version": "4.14.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", - "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/verify-apple-id-token/node_modules/jwks-rsa": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", - "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dependencies": { - "@types/express": "^4.17.14", - "@types/jsonwebtoken": "^9.0.0", - "debug": "^4.3.4", - "jose": "^4.10.4", - "limiter": "^1.1.5", - "lru-memoizer": "^2.1.4" - }, - "engines": { - "node": ">=14" + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, - "node_modules/verify-apple-id-token/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/xmlbuilder": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", @@ -1399,104 +1057,9 @@ "engines": { "node": ">=6.0" } - }, - "node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" } }, "dependencies": { - "@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" - }, - "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.34", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.34.tgz", - "integrity": "sha512-fvr49XlCGoUj2Pp730AItckfjat4WNb0lb3kfrLWffd+RLeoGAMsq7UOy04PAPtoL01uKwcp6u8nhzpgpDYr3w==", - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "@types/jsonwebtoken": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz", - "integrity": "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==", - "requires": { - "@types/node": "*" - } - }, - "@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" - }, - "@types/node": { - "version": "18.16.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", - "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==" - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", - "requires": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "@types/serve-static": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", - "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", - "requires": { - "@types/mime": "*", - "@types/node": "*" - } - }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1586,17 +1149,6 @@ "get-intrinsic": "^1.0.2" } }, - "co-body": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.1.0.tgz", - "integrity": "sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==", - "requires": { - "inflation": "^2.0.0", - "qs": "^6.5.2", - "raw-body": "^2.3.3", - "type-is": "^1.6.16" - } - }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -1606,9 +1158,9 @@ } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "cookie": { "version": "0.3.1", @@ -1638,6 +1190,19 @@ "vary": "^1" } }, + "cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "requires": { + "node-fetch": "^2.6.12" + } + }, + "crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + }, "dayjs": { "version": "1.11.7", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", @@ -1854,14 +1419,6 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, - "jose": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.6.tgz", - "integrity": "sha512-FVoPY7SflDodE4lknJmbAHSUjLCzE2H1F6MS0RYKMQ8SR+lNccpMf8R4eqkNYyyUjR5qZReOzZo5C5YiHOCjjg==", - "requires": { - "@panva/asn1.js": "^1.0.0" - } - }, "jsonwebtoken": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", @@ -1890,34 +1447,6 @@ "safe-buffer": "^5.0.1" } }, - "jwks-rsa": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.1.5.tgz", - "integrity": "sha512-IODtn1SwEm7n6GQZnQLY0oxKDrMh7n/jRH1MzE8mlxWMrh2NnMyOsXTebu8vJ1qCpmuTJcL4DdiE0E4h8jnwsA==", - "requires": { - "@types/express": "^4.17.14", - "@types/jsonwebtoken": "^8.5.9", - "debug": "^4.3.4", - "jose": "^2.0.6", - "limiter": "^1.1.5", - "lru-memoizer": "^2.1.4" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, "jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -1932,39 +1461,11 @@ "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.13.tgz", "integrity": "sha512-b74iyWmwb4GprAUPjPkJ11GTC7KX4Pd3onpJfKxYyY8y9Rbb4ERY47LvCMEDM09WD3thiLDMXtkfDK/AX+zT7Q==" }, - "limiter": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", - "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" - }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" - }, - "lru-cache": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", - "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", - "requires": { - "pseudomap": "^1.0.1", - "yallist": "^2.0.0" - } - }, - "lru-memoizer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz", - "integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==", - "requires": { - "lodash.clonedeep": "^4.5.0", - "lru-cache": "~4.0.0" - } - }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2027,6 +1528,14 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, "nodemailer": { "version": "6.7.8", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.8.tgz", @@ -2065,6 +1574,14 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "pkce-challenge": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-3.1.0.tgz", + "integrity": "sha512-bQ/0XPZZ7eX+cdAkd61uYWpfMhakH3NeteUF1R8GNa+LMqX8QFAkbCLqq+AYAns1/ueACBu/BMWhrlKGrdvGZg==", + "requires": { + "crypto-js": "^4.1.1" + } + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2074,11 +1591,6 @@ "ipaddr.js": "1.9.1" } }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" - }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -2217,73 +1729,24 @@ "integrity": "sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg==" }, "supertokens-node": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/supertokens-node/-/supertokens-node-14.1.1.tgz", - "integrity": "sha512-BFcKyZs0pPe8GyKaSIcBzwJ25bU2jvVB/JKP+C1TZy8ngFNd/stETbQWVQ5sszsUb0GuxkKduDzkgxw5AYtfTA==", + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/supertokens-node/-/supertokens-node-15.2.0.tgz", + "integrity": "sha512-lsiO77FHIiSP3gmS07YFVYRctNVchoW4+JkDfaiTdfSpSBoX+VDWCHwl7mbH47nuodIGz1JzYQuXEiaO26o7HA==", "requires": { - "axios": "0.21.4", - "body-parser": "1.20.1", - "co-body": "6.1.0", + "content-type": "^1.0.5", "cookie": "0.4.0", + "cross-fetch": "^3.1.6", "debug": "^4.3.3", + "inflation": "^2.0.0", "jose": "^4.13.1", - "jsonwebtoken": "^9.0.0", - "jwks-rsa": "^2.0.5", "libphonenumber-js": "^1.9.44", "nodemailer": "^6.7.2", + "pkce-challenge": "^3.0.0", "psl": "1.8.0", "supertokens-js-override": "^0.0.4", - "twilio": "^4.7.2", - "verify-apple-id-token": "^3.0.1" + "twilio": "^4.7.2" }, "dependencies": { - "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "requires": { - "follow-redirects": "^1.14.0" - } - }, - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, "cookie": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", @@ -2297,33 +1760,6 @@ "ms": "2.1.2" } }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "jose": { "version": "4.14.4", "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", @@ -2333,48 +1769,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" } } }, @@ -2383,6 +1777,11 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "twilio": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/twilio/-/twilio-4.10.0.tgz", @@ -2449,65 +1848,24 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, - "verify-apple-id-token": { + "webidl-conversions": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/verify-apple-id-token/-/verify-apple-id-token-3.0.1.tgz", - "integrity": "sha512-q91pG1e52TpEzXldMirWYNWcSQC4WuzgG0y/ZnBhzjfk0pSxi4YlGh5OTVRlodBenayGHfSDn5VseG9QDuqOew==", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "requires": { - "jsonwebtoken": "^9.0.0", - "jwks-rsa": "^3.0.0" - }, - "dependencies": { - "@types/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==", - "requires": { - "@types/node": "*" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "jose": { - "version": "4.14.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", - "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==" - }, - "jwks-rsa": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", - "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", - "requires": { - "@types/express": "^4.17.14", - "@types/jsonwebtoken": "^9.0.0", - "debug": "^4.3.4", - "jose": "^4.10.4", - "limiter": "^1.1.5", - "lru-memoizer": "^2.1.4" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, "xmlbuilder": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==" - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" } } } diff --git a/test/server/package.json b/test/server/package.json index 67b48cf74..a529f9e0d 100644 --- a/test/server/package.json +++ b/test/server/package.json @@ -15,6 +15,6 @@ "dotenv": "^8.2.0", "express": "4.17.1", "morgan": "^1.10.0", - "supertokens-node": "latest" + "supertokens-node": "^15.2.0" } } diff --git a/test/server/utils.js b/test/server/utils.js index 12b53b78a..c80268199 100644 --- a/test/server/utils.js +++ b/test/server/utils.js @@ -14,6 +14,7 @@ */ const { exec } = require("child_process"); let fs = require("fs"); +let assert = require("assert"); let axios = require("axios").default; module.exports.executeCommand = async function (cmd) { @@ -94,7 +95,18 @@ module.exports.killAllST = async function () { } }; -module.exports.startST = async function (host = "localhost", port = 9000) { +module.exports.startST = async function (config = {}) { + const host = config.host ?? "localhost"; + const port = config.port ?? 9000; + + const notUsingTestApp = + process.env.REAL_DB_TEST !== "true" || host !== "localhost" || port !== 9000 || config.noApp === true; + if (config.coreConfig && notUsingTestApp) { + for (const [k, v] of Object.entries(config.coreConfig)) { + await module.exports.setKeyValueInConfig(k, v); + } + } + return new Promise(async (resolve, reject) => { let installationPath = process.env.INSTALL_PATH; let pidsBefore = await getListOfPids(); @@ -106,7 +118,8 @@ module.exports.startST = async function (host = "localhost", port = 9000) { ` && java -Djava.security.egd=file:/dev/urandom -classpath "./core/*:./plugin-interface/*" io.supertokens.Main ./ DEV host=` + host + " port=" + - port + port + + " test_mode" ) .catch((err) => { if (!returned) { @@ -130,7 +143,53 @@ module.exports.startST = async function (host = "localhost", port = 9000) { } else { if (!returned) { returned = true; - resolve(nonIntersection[0]); + console.log(`Application started on http://localhost:${process.env.NODE_PORT | 8080}`); + console.log(`processId: ${nonIntersection[0]}`); + + if (notUsingTestApp) { + return resolve(`http://${host}:${port}`); + } + + try { + // Math.random is an unsafe random but it doesn't actually matter here + // const appId = configs.appId ?? `testapp-${Date.now()}-${Math.floor(Math.random() * 1000)}`; + const appId = config.appId ?? `testapp`; + + await module.exports.removeAppAndTenants(host, port, appId); + + const OPAQUE_KEY_WITH_MULTITENANCY_FEATURE = + "ijaleljUd2kU9XXWLiqFYv5br8nutTxbyBqWypQdv2N-BocoNriPrnYQd0NXPm8rVkeEocN9ayq0B7c3Pv-BTBIhAZSclXMlgyfXtlwAOJk=9BfESEleW6LyTov47dXu"; + + await fetch(`http://${host}:${port}/ee/license`, { + method: "PUT", + headers: { + "content-type": "application/json; charset=utf-8", + }, + body: JSON.stringify({ + licenseKey: OPAQUE_KEY_WITH_MULTITENANCY_FEATURE, + }), + }); + + // Create app + const createAppResp = await fetch(`http://${host}:${port}/recipe/multitenancy/app`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + appId, + emailPasswordEnabled: true, + thirdPartyEnabled: true, + passwordlessEnabled: true, + coreConfig: config.coreConfig, + }), + }); + const respBody = await createAppResp.json(); + assert.strictEqual(respBody.status, "OK"); + resolve(`http://${host}:${port}/appid-${appId}`); + } catch (err) { + reject(err); + } } } } @@ -141,6 +200,81 @@ module.exports.startST = async function (host = "localhost", port = 9000) { }); }; +module.exports.removeAppAndTenants = async function (host, port, appId) { + const tenantsResp = await fetch(`http://${host}:${port}/appid-${appId}/recipe/multitenancy/tenant/list`); + if (tenantsResp.status === 401) { + const updateAppResp = await fetch(`http://${host}:${port}/recipe/multitenancy/app`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + appId, + coreConfig: { api_keys: null }, + }), + }); + assert.strictEqual(updateAppResp.status, 200); + await module.exports.removeAppAndTenants(host, port, appId); + } else if (tenantsResp.status === 200) { + const tenants = (await tenantsResp.json()).tenants; + for (const t of tenants) { + if (t.tenantId !== "public") { + await fetch(`http://${host}:${port}/appid-${appId}/recipe/multitenancy/tenant/remove`, { + method: "POST", + headers: { + "content-type": "application/json; charset=utf-8", + }, + body: JSON.stringify({ + tenantId: t.tenantId, + }), + }); + } + } + + const removeResp = await fetch(`http://${host}:${port}/recipe/multitenancy/app/remove`, { + method: "POST", + headers: { + "content-type": "application/json; charset=utf-8", + }, + body: JSON.stringify({ + appId, + }), + }); + const removeRespBody = await removeResp.json(); + assert.strictEqual(removeRespBody.status, "OK"); + } +}; + +const WEB_PORT = process.env.WEB_PORT || 3031; +const websiteDomain = `http://localhost:${WEB_PORT}`; +module.exports.mockThirdPartyProvider = { + config: { + name: "Mock Provider", + thirdPartyId: "mock-provider", + authorizationEndpoint: `${websiteDomain}/mockProvider/auth`, + tokenEndpoint: `${websiteDomain}/mockProvider/token`, + clients: [ + { + clientId: "supertokens", + clientSecret: "", + }, + ], + }, + override: (oI) => ({ + ...oI, + exchangeAuthCodeForOAuthTokens: ({ redirectURIInfo }) => redirectURIInfo.redirectURIQueryParams, + getUserInfo: ({ oAuthTokens }) => { + return { + thirdPartyUserId: oAuthTokens.userId ?? "user", + email: { + id: oAuthTokens.email ?? "email@test.com", + isVerified: oAuthTokens.isVerified !== "false", + }, + rawUserInfoFromProvider: {}, + }; + }, + }), +}; /** * * @returns {import("supertokens-node/lib/build/recipe/thirdparty/types").ProviderConfig} diff --git a/test/unit/recipe/emailpassword/signInUp.test.tsx b/test/unit/recipe/emailpassword/signInUp.test.tsx index 20e1b12ea..f2d5d5a29 100644 --- a/test/unit/recipe/emailpassword/signInUp.test.tsx +++ b/test/unit/recipe/emailpassword/signInUp.test.tsx @@ -73,7 +73,7 @@ describe("EmailPassword.SignInAndUp", () => { expect(MockSession.validateGlobalClaimsAndHandleSuccessRedirection).toHaveBeenCalledWith( { rid: "emailpassword", - successRedirectContext: { action: "SUCCESS", isNewUser: false, redirectToPath: undefined }, + successRedirectContext: { action: "SUCCESS", isNewRecipeUser: false, redirectToPath: undefined }, }, {}, undefined diff --git a/test/unit/recipe/passwordless/signInUp.test.tsx b/test/unit/recipe/passwordless/signInUp.test.tsx index 0192e9c72..f5cb153b5 100644 --- a/test/unit/recipe/passwordless/signInUp.test.tsx +++ b/test/unit/recipe/passwordless/signInUp.test.tsx @@ -74,7 +74,7 @@ describe("Passwordless.SingInUp", () => { expect(MockSession.validateGlobalClaimsAndHandleSuccessRedirection).toHaveBeenCalledWith( { rid: "passwordless", - successRedirectContext: { action: "SUCCESS", isNewUser: false, redirectToPath: undefined }, + successRedirectContext: { action: "SUCCESS", isNewRecipeUser: false, redirectToPath: undefined }, }, {}, undefined diff --git a/test/unit/recipe/thirdparty/signInUp.test.tsx b/test/unit/recipe/thirdparty/signInUp.test.tsx index 1e500a4b7..165450be8 100644 --- a/test/unit/recipe/thirdparty/signInUp.test.tsx +++ b/test/unit/recipe/thirdparty/signInUp.test.tsx @@ -78,7 +78,7 @@ describe("ThirdParty.SignInAndUp", () => { expect(MockSession.validateGlobalClaimsAndHandleSuccessRedirection).toHaveBeenCalledWith( { rid: "thirdparty", - successRedirectContext: { action: "SUCCESS", isNewUser: false, redirectToPath: undefined }, + successRedirectContext: { action: "SUCCESS", isNewRecipeUser: false, redirectToPath: undefined }, }, {}, undefined diff --git a/test/unit/recipe/thirdpartyemailpassword/signInUp.test.tsx b/test/unit/recipe/thirdpartyemailpassword/signInUp.test.tsx index ff563c285..f3783cb97 100644 --- a/test/unit/recipe/thirdpartyemailpassword/signInUp.test.tsx +++ b/test/unit/recipe/thirdpartyemailpassword/signInUp.test.tsx @@ -73,7 +73,7 @@ describe("ThirdPartyEmailPassword.SignInAndUp", () => { expect(MockSession.validateGlobalClaimsAndHandleSuccessRedirection).toHaveBeenCalledWith( { rid: "thirdpartyemailpassword", - successRedirectContext: { action: "SUCCESS", isNewUser: false, redirectToPath: undefined }, + successRedirectContext: { action: "SUCCESS", isNewRecipeUser: false, redirectToPath: undefined }, }, {}, undefined diff --git a/test/unit/recipe/thirdpartypasswordless/signInUp.test.tsx b/test/unit/recipe/thirdpartypasswordless/signInUp.test.tsx index a2a4e5ba2..807150958 100644 --- a/test/unit/recipe/thirdpartypasswordless/signInUp.test.tsx +++ b/test/unit/recipe/thirdpartypasswordless/signInUp.test.tsx @@ -74,7 +74,7 @@ describe("ThirdPartyPasswordless.SignInAndUp", () => { expect(MockSession.validateGlobalClaimsAndHandleSuccessRedirection).toHaveBeenCalledWith( { rid: "thirdpartypasswordless", - successRedirectContext: { action: "SUCCESS", isNewUser: false, redirectToPath: undefined }, + successRedirectContext: { action: "SUCCESS", isNewRecipeUser: false, redirectToPath: undefined }, }, {}, undefined diff --git a/test/visual/basic.test.js b/test/visual/basic.test.js index 9e209d1df..c6b778a4c 100644 --- a/test/visual/basic.test.js +++ b/test/visual/basic.test.js @@ -17,8 +17,6 @@ * Imports */ -/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */ -import regeneratorRuntime from "regenerator-runtime"; import assert from "assert"; import puppeteer from "puppeteer"; import fetch from "isomorphic-fetch"; diff --git a/test/with-typescript/src/App.tsx b/test/with-typescript/src/App.tsx index 523586b13..5a3113718 100644 --- a/test/with-typescript/src/App.tsx +++ b/test/with-typescript/src/App.tsx @@ -377,13 +377,28 @@ function getEmailPasswordConfigs() { }, }, - onHandleEvent(context: EmailPasswordOnHandleEventContext) {}, + onHandleEvent(context: EmailPasswordOnHandleEventContext) { + if (context.action === "SUCCESS") { + if (context.isNewRecipeUser && context.user.loginMethods.length === 1) { + // new primary user + } else { + // only a recipe user was created + } + } + }, async preAPIHook(context: EmailPasswordPreAPIHookContext) { return context; }, async getRedirectionURL(context: EmailPasswordGetRedirectionURLContext) { + if (context.action === "SUCCESS") { + if (context.isNewRecipeUser && context.user.loginMethods.length === 1) { + // new primary user + } else { + // only a recipe user was created + } + } return undefined; }, override: { @@ -518,7 +533,7 @@ Passwordless.init({ // in this case, they are usually redirected to the main app } else if (context.action === "SUCCESS") { let user = context.user; - if (context.isNewUser) { + if (context.isNewRecipeUser) { // sign up success } else { // sign in success