diff --git a/examples/with-thirdparty-2fa-passwordless/README.md b/examples/with-thirdparty-2fa-passwordless/README.md index 2d2569330..2851798af 100644 --- a/examples/with-thirdparty-2fa-passwordless/README.md +++ b/examples/with-thirdparty-2fa-passwordless/README.md @@ -1,3 +1,3 @@ -# For 2FA demo please refer to [this demo](https://github.com/supertokens/supertokens-auth-react/blob/master/examples/with-thirdpartyemailpassword-2fa-passwordless). +# For 2FA demo please refer to [this demo](https://github.com/supertokens/supertokens-auth-react/blob/master/examples/with-multifactorauth). If you would like to specifically see this demo app, you can find the code in the commit history. diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/.gitignore b/examples/with-thirdpartyemailpassword-2fa-passwordless/.gitignore deleted file mode 100644 index 4d29575de..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# 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-thirdpartyemailpassword-2fa-passwordless/README.md b/examples/with-thirdpartyemailpassword-2fa-passwordless/README.md index 6f19fdd87..2851798af 100644 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/README.md +++ b/examples/with-thirdpartyemailpassword-2fa-passwordless/README.md @@ -1,59 +1,3 @@ -![SuperTokens banner](https://raw.githubusercontent.com/supertokens/supertokens-logo/master/images/Artboard%20%E2%80%93%2027%402x.png) +# For 2FA demo please refer to [this demo](https://github.com/supertokens/supertokens-auth-react/blob/master/examples/with-multifactorauth). -# SuperTokens ThirdPartyEmailPassword + Passwordless 2fa demo - -This demo app demonstrates how we can implement sign in with third party providers or email password with email verification and then add SMS OTP based 2FA on top of that. - -## 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-thirdpartyemailpassword-2fa-passwordless -npm install -``` - -## 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` - -If you would like to modify the website (http://localhost:3000) or the API server (http://localhost:3001) URL: - -- Change the `apiPort` or `apiDomain` values in `api-server/index.ts` -- Change the `apiPort` or `apiUrl` values in `src/App.tsx` -- Change the `websitePort` or `websiteDomain` values in `api-server/index.ts` -- Change the `websitePort` or `websiteUrl` values in `src/App.tsx` - -## Sending emails and SMS - -The demo app, by default sends email and SMS by contacting https://api.supertokens.com servers. The SMS sending via this service is rate limited. You can configure the demo app to use your own SMTP server and use your own Twilio account credentials. You can also override how the emails and SMS are sent to take full control of their design and how they are sent. - -## Project structure & Parameters - -- The frontend code is located in the `src` folder. -- The backend API is in the `api-server/index.ts` file. - -## Production build - -```bash -npm run build && npm run start -``` - -## Current drawbacks: - -- No way of passing on redirectToPath across the 2 factors. - -## Author - -Created with :heart: by the folks at supertokens.com. - -## License - -This project is licensed under the Apache 2.0 license. +If you would like to specifically see this demo app, you can find the code in the commit history. diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/api-server/index.ts b/examples/with-thirdpartyemailpassword-2fa-passwordless/api-server/index.ts deleted file mode 100644 index 2d3e6eef4..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/api-server/index.ts +++ /dev/null @@ -1,246 +0,0 @@ -import express from "express"; -import cors from "cors"; -import supertokens from "supertokens-node"; -import Session from "supertokens-node/recipe/session"; -import { verifySession } from "supertokens-node/recipe/session/framework/express"; -import { middleware, errorHandler, SessionRequest } from "supertokens-node/framework/express"; -import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword"; -import Passwordless from "supertokens-node/recipe/passwordless"; -import UserMetadata from "supertokens-node/recipe/usermetadata"; -import { SecondFactorClaim } from "./secondFactorClaim"; -import EmailVerification from "supertokens-node/recipe/emailverification"; -import Dashboard from "supertokens-node/recipe/dashboard"; - -require("dotenv").config(); - -const apiPort = process.env.REACT_APP_API_PORT || 3001; -const apiDomain = process.env.REACT_APP_API_URL || `http://localhost:${apiPort}`; -const websitePort = process.env.REACT_APP_WEBSITE_PORT || 3000; -const websiteDomain = process.env.REACT_APP_WEBSITE_URL || `http://localhost:${websitePort}`; - -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.com", - apiKey: "", - }, - appInfo: { - appName: "SuperTokens Demo App", - apiDomain, - websiteDomain, - }, - recipeList: [ - EmailVerification.init({ - mode: "REQUIRED", - }), - ThirdPartyEmailPassword.init({ - providers: [ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - { - config: { - thirdPartyId: "google", - clients: [ - { - clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com", - clientSecret: "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", - }, - ], - }, - }, - { - config: { - thirdPartyId: "github", - clients: [ - { - clientId: "467101b197249757c71f", - clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd", - }, - ], - }, - }, - { - config: { - thirdPartyId: "apple", - clients: [ - { - clientId: "4398792-io.supertokens.example.service", - additionalConfig: { - keyId: "7M48Y4RYDL", - privateKey: - "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----", - teamId: "YWQCXGJRJL", - }, - }, - ], - }, - }, - ], - }), - UserMetadata.init(), - Passwordless.init({ - contactMethod: "PHONE", - flowType: "USER_INPUT_CODE", - smsDelivery: { - override: (oI) => { - return { - ...oI, - sendSms: async function (input) { - console.log(input); - }, - }; - }, - }, - override: { - apis: (oI) => { - return { - ...oI, - createCodePOST: async function (input) { - if (oI.createCodePOST === undefined) { - throw new Error("Should never come here"); - } - /** - * - * We want to make sure that the OTP being generated is for the - * same number that belongs to this user. - */ - - // We remove claim checking here, since this needs to be callable without the second factor completed - let session = await Session.getSession(input.options.req, input.options.res, { - overrideGlobalClaimValidators: () => [], - }); - if (session === undefined) { - throw new Error("Should never come here"); - } - - let phoneNumber: string = session.getAccessTokenPayload().phoneNumber; - - if (phoneNumber !== undefined) { - if (!("phoneNumber" in input) || input.phoneNumber !== phoneNumber) { - throw new Error("Should never come here"); - } - } - - return oI.createCodePOST(input); - }, - - consumeCodePOST: async function (input) { - if (oI.consumeCodePOST === undefined) { - throw new Error("Should never come here"); - } - // we should already have a session here since this is called - // after phone password login - // We remove claim checking here, since this needs to be callable without the second factor completed - let session = await Session.getSession(input.options.req, input.options.res, { - overrideGlobalClaimValidators: () => [], - }); - if (session === undefined) { - throw new Error("Should never come here"); - } - - // we add the session to the user context so that the createNewSession - // function doesn't create a new session - input.userContext.session = session; - let resp = await oI.consumeCodePOST(input); - - if (resp.status === "OK") { - // OTP verification was successful. We can now mark the - // session's payload as SecondFactorClaim: true so that - // the user has access to API routes and the frontend UI - await resp.session.setClaimValue(SecondFactorClaim, true); - - // we associate the passwordless user ID with the thirdpartyemailpassword - // user ID, so that later on, we can fetch the phone number. - await UserMetadata.updateUserMetadata(session.getUserId(), { - passwordlessUserId: resp.user.id, - }); - } - - return resp; - }, - }; - }, - }, - }), - Session.init({ - override: { - functions: (originalImplementation) => { - return { - ...originalImplementation, - getGlobalClaimValidators: (input) => [ - ...input.claimValidatorsAddedByOtherRecipes, - SecondFactorClaim.validators.hasValue(true), - ], - createNewSession: async function (input) { - if (input.userContext.session !== undefined) { - /** - * This will be true for passwordless login. - */ - return input.userContext.session; - } - let userMetadata = await UserMetadata.getUserMetadata(input.userId); - let phoneNumber: string | undefined = undefined; - if (userMetadata.metadata.passwordlessUserId !== undefined) { - // we alreay have a phone number associated with this user, - // so we will add it to the access token payload so that - // we can send an OTP to it without asking the end user. - let passwordlessUserInfo = await supertokens.getUser( - userMetadata.metadata.passwordlessUserId as string, - input.userContext - ); - phoneNumber = passwordlessUserInfo?.phoneNumbers[0]; - } - return originalImplementation.createNewSession({ - ...input, - accessTokenPayload: { - ...input.accessTokenPayload, - ...(await SecondFactorClaim.build( - input.userId, - input.recipeUserId, - input.tenantId, - input.userContext - )), - phoneNumber, - }, - }); - }, - }; - }, - }, - }), - Dashboard.init(), - ], -}); - -const app = express(); - -app.use( - cors({ - origin: websiteDomain, - allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()], - methods: ["GET", "PUT", "POST", "DELETE"], - credentials: true, - }) -); - -app.use(middleware()); - -// An example API that requires session verification -app.get("/sessioninfo", verifySession(), async (req: SessionRequest, res) => { - let session = req.session!; - res.send({ - sessionHandle: session.getHandle(), - userId: session.getUserId(), - accessTokenPayload: session.getAccessTokenPayload(), - }); -}); - -app.use(errorHandler()); - -app.use((err: any, req: any, res: any, next: any) => { - console.log(err); - res.status(500).send("Internal error: " + err.message); -}); - -app.listen(apiPort, () => console.log(`API Server listening on port ${apiPort}`)); diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/api-server/secondFactorClaim.ts b/examples/with-thirdpartyemailpassword-2fa-passwordless/api-server/secondFactorClaim.ts deleted file mode 100644 index 2e8d0d4bb..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/api-server/secondFactorClaim.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { BooleanClaim } from "supertokens-node/recipe/session/claims"; - -export const SecondFactorClaim = new BooleanClaim({ - fetchValue: () => false, - key: "2fa-completed", -}); diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/api-server/tsconfig.json b/examples/with-thirdpartyemailpassword-2fa-passwordless/api-server/tsconfig.json deleted file mode 100644 index 8a91acaae..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/api-server/tsconfig.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "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-thirdpartyemailpassword-2fa-passwordless/package.json b/examples/with-thirdpartyemailpassword-2fa-passwordless/package.json deleted file mode 100644 index 93d608e5e..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/package.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "with-thirdpartyemailpassword-2fa-passwordless", - "version": "0.1.0", - "private": true, - "dependencies": { - "@testing-library/jest-dom": "^5.16.4", - "@testing-library/react": "^13.3.0", - "@testing-library/user-event": "^13.5.0", - "@types/cors": "^2.8.12", - "@types/jest": "^27.5.2", - "@types/node": "^16.11.38", - "@types/react": "^18.0.10", - "@types/react-dom": "^18.0.5", - "axios": "^0.27.2", - "cors": "^2.8.5", - "dotenv": "^16.0.1", - "express": "^4.18.1", - "helmet": "^5.1.0", - "morgan": "^1.10.0", - "npm-run-all": "^4.1.5", - "react": "^18.1.0", - "react-dom": "^18.1.0", - "react-router-dom": "^6.3.0", - "react-scripts": "^5.0.1", - "supertokens-auth-react": "latest", - "supertokens-node": "latest", - "ts-node-dev": "^2.0.0", - "typescript": "^4.7.2", - "web-vitals": "^2.1.4" - }, - "scripts": { - "start": "npm-run-all --parallel spa api-server", - "api-server": "npx ts-node-dev --project api-server/tsconfig.json api-server/index.ts", - "spa": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "devDependencies": { - "nodemon": "^2.0.16" - } -} diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/public/favicon.ico b/examples/with-thirdpartyemailpassword-2fa-passwordless/public/favicon.ico deleted file mode 100644 index a11777cc4..000000000 Binary files a/examples/with-thirdpartyemailpassword-2fa-passwordless/public/favicon.ico and /dev/null differ diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/public/index.html b/examples/with-thirdpartyemailpassword-2fa-passwordless/public/index.html deleted file mode 100644 index ce6d2bcbe..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/public/index.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - React App - - - -
- - diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/public/logo192.png b/examples/with-thirdpartyemailpassword-2fa-passwordless/public/logo192.png deleted file mode 100644 index fc44b0a37..000000000 Binary files a/examples/with-thirdpartyemailpassword-2fa-passwordless/public/logo192.png and /dev/null differ diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/public/logo512.png b/examples/with-thirdpartyemailpassword-2fa-passwordless/public/logo512.png deleted file mode 100644 index a4e47a654..000000000 Binary files a/examples/with-thirdpartyemailpassword-2fa-passwordless/public/logo512.png and /dev/null differ diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/public/manifest.json b/examples/with-thirdpartyemailpassword-2fa-passwordless/public/manifest.json deleted file mode 100644 index f01493ff0..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "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-thirdpartyemailpassword-2fa-passwordless/public/robots.txt b/examples/with-thirdpartyemailpassword-2fa-passwordless/public/robots.txt deleted file mode 100644 index e9e57dc4d..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/App.css b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/App.css deleted file mode 100644 index 8a98a2341..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/App.css +++ /dev/null @@ -1,27 +0,0 @@ -.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-thirdpartyemailpassword-2fa-passwordless/src/App.test.tsx b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/App.test.tsx deleted file mode 100644 index 9b2f4d07b..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/App.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; -import { render, screen } from "@testing-library/react"; -import App from "./App"; - -test("renders learn react link", () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/App.tsx b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/App.tsx deleted file mode 100644 index faefd9ceb..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/App.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import React, { useEffect } from "react"; -import { EmailVerificationClaim } from "supertokens-auth-react/recipe/emailverification"; -import "./App.css"; -import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react"; -import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui"; -import ThirdPartyEmailPassword from "supertokens-auth-react/recipe/thirdpartyemailpassword"; -import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui"; -import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui"; -import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; -import EmailVerification from "supertokens-auth-react/recipe/emailverification"; -import Session, { SessionAuth, useSessionContext } from "supertokens-auth-react/recipe/session"; -import Home from "./Home"; -import { Routes, BrowserRouter as Router, Route } from "react-router-dom"; -import Footer from "./Footer"; -import Passwordless, { PasswordlessComponentsOverrideProvider } from "supertokens-auth-react/recipe/passwordless"; -import SecondFactor from "./SecondFactor"; -import { SecondFactorClaim } from "./secondFactorClaim"; -import { useNavigate } from "react-router-dom"; - -export function getApiDomain() { - const apiPort = process.env.REACT_APP_API_PORT || 3001; - const apiUrl = process.env.REACT_APP_API_URL || `http://localhost:${apiPort}`; - return apiUrl; -} - -export function getWebsiteDomain() { - const websitePort = process.env.REACT_APP_WEBSITE_PORT || 3000; - const websiteUrl = process.env.REACT_APP_WEBSITE_URL || `http://localhost:${websitePort}`; - return websiteUrl; -} - -SuperTokens.init({ - appInfo: { - appName: "SuperTokens Demo App", // TODO: Your app name - apiDomain: getApiDomain(), // TODO: Change to your app's API domain - websiteDomain: getWebsiteDomain(), // TODO: Change to your app's website domain - }, - getRedirectionURL: async (ctx) => { - if (ctx.action === "TO_AUTH") { - return "/auth?rid=thirdpartyemailpassword"; - } - }, - recipeList: [ - EmailVerification.init({ - mode: "REQUIRED", - }), - ThirdPartyEmailPassword.init({ - signInAndUpFeature: { - providers: [ - ThirdPartyEmailPassword.Github.init(), - ThirdPartyEmailPassword.Google.init(), - ThirdPartyEmailPassword.Apple.init(), - ], - }, - }), - Passwordless.init({ - signInUpFeature: { - disableDefaultUI: true, - }, - contactMethod: "PHONE", - }), - Session.init({ - override: { - functions: (oI) => ({ - ...oI, - getGlobalClaimValidators: ({ claimValidatorsAddedByOtherRecipes }) => { - return [SecondFactorClaim.validators.isTrue(), ...claimValidatorsAddedByOtherRecipes]; - }, - }), - }, - }), - ], -}); - -function App() { - return ( - - { - return ( -
- Second factor auth -
- ); - }, - // we override the component which shows the change phone number button - PasswordlessUserInputCodeFormFooter_Override: ({ - DefaultComponent, - ...props - }: { - DefaultComponent: any; - }) => { - const session = useSessionContext(); - - if (session.loading !== true && session.accessTokenPayload.phoneNumber === undefined) { - // this will show the change phone number button - return ; - } - - // this will hide the change phone number button - return null; - }, - }}> -
-
- - {/* This shows the login UI on "/auth" route */} - {getSuperTokensRoutesForReactRouterDom(require("react-router-dom"), [ - ThirdPartyEmailPasswordPreBuiltUI, - EmailVerificationPreBuiltUI, - PasswordlessPreBuiltUI, - ])} - - - - } - /> - - - - } - /> - -
-
-
-
-
- ); -} - -export default function AppWithRouter() { - return ( - - - - ); -} diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Footer/index.tsx b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Footer/index.tsx deleted file mode 100644 index 90f4d8eb5..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Footer/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -export default function Footer() { - return ( -
- React Demo app. Made with ❤️ using supertokens.com -
- ); -} diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Home/CallAPIView.tsx b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Home/CallAPIView.tsx deleted file mode 100644 index 140336a54..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Home/CallAPIView.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import axios from "axios"; -import { getApiDomain } from "../App"; - -export default function CallAPIView() { - async function callAPIClicked() { - // this will also automatically refresh the session if needed - let response = await axios.get(getApiDomain() + "/sessioninfo"); - window.alert("Session Information:\n" + JSON.stringify(response.data, null, 2)); - } - - return ( -
- Call API -
- ); -} diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Home/Logout.tsx b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Home/Logout.tsx deleted file mode 100644 index b6dece96a..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Home/Logout.tsx +++ /dev/null @@ -1,32 +0,0 @@ -export default function Logout(props: { logoutClicked: () => void }) { - let logoutClicked = props.logoutClicked; - - return ( -
-
- SIGN OUT -
-
- ); -} diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Home/SuccessView.tsx b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Home/SuccessView.tsx deleted file mode 100644 index 2c2b67915..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Home/SuccessView.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import CallAPIView from "./CallAPIView"; - -export default function SuccessView(props: { userId: string }) { - let userId = props.userId; - - return ( -
- - 🥳🎉 - - Login successful -
-
- Your user ID is -
- {userId} -
-
-
-
- -
-
-
- ------------------------------------ -
-
-
- - ); -} diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Home/index.tsx b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Home/index.tsx deleted file mode 100644 index 071751bf7..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/Home/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { useEffect } from "react"; -import Logout from "./Logout"; -import SuccessView from "./SuccessView"; -import { useSessionContext } from "supertokens-auth-react/recipe/session"; -import { useNavigate } from "react-router-dom"; -import { signOut } from "supertokens-auth-react/recipe/thirdpartyemailpassword"; - -export default function Home() { - const session = useSessionContext(); - const navigate = useNavigate(); - - async function logoutClicked() { - await signOut(); - navigate("/auth"); - } - - if (session.loading) { - return null; - } - - return ( -
- - -
- ); -} diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/SecondFactor/index.tsx b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/SecondFactor/index.tsx deleted file mode 100644 index 5c0881a93..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/SecondFactor/index.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { redirectToAuth } from "supertokens-auth-react"; -import Passwordless from "supertokens-auth-react/recipe/passwordless"; -import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui"; -import Session, { useSessionContext } from "supertokens-auth-react/recipe/session"; - -const CustomSignInUpTheme: typeof PasswordlessPreBuiltUI.SignInUpTheme = (props) => { - let [showDefaultUI, setShowDefaultUI] = useState(false); - const session = useSessionContext(); - - useEffect(() => { - let aborting = false; - async function effect() { - if (session.loading === true) { - return; - } - - const phoneNumber = session.accessTokenPayload.phoneNumber; - // If don't have a phone number we show the default UI (which should be the phone form) - if (phoneNumber === undefined) { - setShowDefaultUI(true); - } else { - // we start the OTP flow if it's not started already - if (props.featureState.loginAttemptInfo === undefined) { - await props.recipeImplementation.createCode({ phoneNumber, userContext: props.userContext }); - } - - if (aborting) { - return; - } - // if we have an OTP flow (or we just started one) we show the default UI which should be the OTP input - setShowDefaultUI(true); - } - } - effect(); - return () => { - aborting = true; - }; - }, []); - - // If this was active, we'd not show the OTP screen because it'd detect an active session. - props.featureState.successInAnotherTab = false; - - if (showDefaultUI) { - return ; - } - return <>; -}; - -export default function SecondFactor() { - const session = useSessionContext(); - const navigate = useNavigate(); - - return ( -
- - { - // @ts-ignore We ignore the error about missing props, since they'll be set by the feature component - - } - - -
{ - await Passwordless.clearLoginAttemptInfo(); - await Session.signOut(); - redirectToAuth({ redirectBack: false }); - }} - style={{ - cursor: "pointer", - color: "blue", - textDecoration: "underline", - }}> - Login with another account -
-
- ); -} diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/index.css b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/index.css deleted file mode 100644 index 04146b5e7..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/index.css +++ /dev/null @@ -1,11 +0,0 @@ -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-thirdpartyemailpassword-2fa-passwordless/src/index.tsx b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/index.tsx deleted file mode 100644 index ba09b3613..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import "./index.css"; -import App from "./App"; -import reportWebVitals from "./reportWebVitals"; - -const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); -root.render( - - - -); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/logo.svg b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/logo.svg deleted file mode 100644 index 9dfc1c058..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/react-app-env.d.ts b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5fc..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/reportWebVitals.ts b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/reportWebVitals.ts deleted file mode 100644 index 0677b6701..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/reportWebVitals.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ReportHandler } from "web-vitals"; - -const reportWebVitals = (onPerfEntry?: ReportHandler) => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/secondFactorClaim.tsx b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/secondFactorClaim.tsx deleted file mode 100644 index fe1479d36..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/secondFactorClaim.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { BooleanClaim } from "supertokens-auth-react/recipe/session"; - -export const SecondFactorClaim = new BooleanClaim({ - id: "2fa-completed", - refresh: async () => { - // This is something we have no way of refreshing, so this is a no-op - }, - onFailureRedirection: () => "/second-factor", -}); diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/setupTests.ts b/examples/with-thirdpartyemailpassword-2fa-passwordless/src/setupTests.ts deleted file mode 100644 index 1dd407a63..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/src/setupTests.ts +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import "@testing-library/jest-dom"; diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/test/basic.test.js b/examples/with-thirdpartyemailpassword-2fa-passwordless/test/basic.test.js deleted file mode 100644 index e5760ff5a..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/test/basic.test.js +++ /dev/null @@ -1,166 +0,0 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. - * - * This software is licensed under the Apache License, Version 2.0 (the - * "License") as published by the Apache Software Foundation. - * - * You may not use this file except in compliance with the License. You may - * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -/* - * Imports - */ - -const assert = require("assert"); -const puppeteer = require("puppeteer"); -const fetch = require("isomorphic-fetch"); -const { - getTestEmail, - setInputValues, - submitForm, - toggleSignInSignUp, - waitForSTElement, -} = require("../../../test/exampleTestHelpers"); - -const SuperTokensNode = require("supertokens-node"); -const Session = require("supertokens-node/recipe/session"); -const Passwordless = require("supertokens-node/recipe/passwordless"); -const EmailVerification = require("supertokens-node/recipe/emailverification"); -const ThirdPartyEmailPassword = require("supertokens-node/recipe/thirdpartyemailpassword"); - -// Run the tests in a DOM environment. -require("jsdom-global")(); - -const apiDomain = "http://localhost:3001"; -const websiteDomain = "http://localhost:3000"; -SuperTokensNode.init({ - supertokens: { - // We are running these tests without running a local ST instance - connectionURI: "https://try.supertokens.com", - }, - appInfo: { - // These largely shouldn't matter except for creating links which we can change anyway - apiDomain: apiDomain, - websiteDomain: websiteDomain, - appName: "testNode", - }, - recipeList: [ - Passwordless.init({ - contactMethod: "EMAIL_OR_PHONE", - flowType: "USER_INPUT_CODE_AND_MAGIC_LINK", - }), - EmailVerification.init({ - mode: "OPTIONAL", - }), - ThirdPartyEmailPassword.init(), - Session.init(), - ], -}); - -describe("SuperTokens Example Basic tests", function () { - let browser; - let page; - const email = getTestEmail(); - // maybe we could/should randomize this.. - const phoneNumber = "+18004444444"; - const testPW = "Str0ngP@ssw0rd"; - const testOTP = "test123456"; - - before(async function () { - browser = await puppeteer.launch({ - args: ["--no-sandbox", "--disable-setuid-sandbox"], - headless: true, - }); - page = await browser.newPage(); - }); - - after(async function () { - await browser.close(); - }); - - describe("Email Password test", function () { - it("Successful signup with credentials", async function () { - await Promise.all([page.goto(websiteDomain), page.waitForNavigation({ waitUntil: "networkidle0" })]); - - // redirected to /auth - await toggleSignInSignUp(page); - await setInputValues(page, [ - { name: "email", value: email }, - { name: "password", value: testPW }, - ]); - await submitForm(page); - - // Sent the otp - await waitForSTElement(page, "[name=phoneNumber_text]"); - assert.strictEqual(page.url(), websiteDomain + "/second-factor"); - - // Attempt reloading Home - await Promise.all([page.goto(websiteDomain), page.waitForNavigation({ waitUntil: "networkidle0" })]); - // Redirect back to OTP screen - await waitForSTElement(page, "[name=phoneNumber_text]"); - await setInputValues(page, [ - { - name: "phoneNumber_text", - value: phoneNumber, - }, - ]); - await submitForm(page); - - // Redirected to email verification screen (OTP screen from passwordless w/ overrides) - await waitForSTElement(page, "[name=userInputCode]"); - // Attempt reloading Home - await Promise.all([page.goto(websiteDomain), page.waitForNavigation({ waitUntil: "networkidle0" })]); - await waitForSTElement(page, "[name=userInputCode]"); - - const loginAttemptInfo = JSON.parse( - await page.evaluate(() => localStorage.getItem("supertokens-passwordless-loginAttemptInfo")) - ); - // Create a new OTP and use it (we don't have access to the originally sent one) - await Passwordless.createNewCodeForDevice({ - tenantId: "public", - deviceId: loginAttemptInfo.deviceId, - userInputCode: testOTP, - }); - await setInputValues(page, [{ name: "userInputCode", value: testOTP }]); - await submitForm(page); - - const userId = await page.evaluate(() => window.__supertokensSessionRecipe.getUserId()); - - // We get to the email verification screen - await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); - // Attempt reloading Home - await Promise.all([page.goto(websiteDomain), page.waitForNavigation({ waitUntil: "networkidle0" })]); - await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); - - // Create a new token and use it (we don't have access to the originally sent one) - const tokenInfo = await EmailVerification.createEmailVerificationToken( - "public", - SuperTokensNode.convertToRecipeUserId(userId), - email - ); - await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo.token}`); - await submitForm(page); - - const callApiBtn = await page.waitForSelector(".sessionButton"); - - let setAlertContent; - let alertContent = new Promise((res) => (setAlertContent = res)); - page.on("dialog", async (dialog) => { - setAlertContent(dialog.message()); - await dialog.dismiss(); - }); - await callApiBtn.click(); - - const alertText = await alertContent; - assert(alertText.startsWith("Session Information:")); - const sessionInfo = JSON.parse(alertText.replace(/^Session Information:/, "")); - assert.strictEqual(sessionInfo.userId, userId); - }); - }); -}); diff --git a/examples/with-thirdpartyemailpassword-2fa-passwordless/tsconfig.json b/examples/with-thirdpartyemailpassword-2fa-passwordless/tsconfig.json deleted file mode 100644 index c0555cbc6..000000000 --- a/examples/with-thirdpartyemailpassword-2fa-passwordless/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "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"] -}