From 6b660e52cd6cf46274cb83d6eb190784e6a29031 Mon Sep 17 00:00:00 2001 From: Ollie Elmgren <111321901+ollie-stytch@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:07:00 +0100 Subject: [PATCH] Add Passkeys SDK UI Recipe and Page (#161) * Add passkeys SDK UI recipe and page * Add views enum logic & success view * Add views enum logic & success view --- components/Passkeys/LoginWithPasskeys.tsx | 33 +++ components/Passkeys/PasskeyRegistration.tsx | 187 +++++++++++++ lib/loginProduct.ts | 5 + lib/recipeData.tsx | 36 ++- package-lock.json | 279 +++----------------- package.json | 4 +- pages/recipes/passkeys/profile.tsx | 65 +++++ public/passkeys-icon.svg | 4 + 8 files changed, 360 insertions(+), 253 deletions(-) create mode 100644 components/Passkeys/LoginWithPasskeys.tsx create mode 100644 components/Passkeys/PasskeyRegistration.tsx create mode 100644 pages/recipes/passkeys/profile.tsx create mode 100644 public/passkeys-icon.svg diff --git a/components/Passkeys/LoginWithPasskeys.tsx b/components/Passkeys/LoginWithPasskeys.tsx new file mode 100644 index 0000000..da61123 --- /dev/null +++ b/components/Passkeys/LoginWithPasskeys.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import {OTPMethods, Products, StytchError, StytchEvent, StytchLoginConfig} from '@stytch/vanilla-js'; +import {StytchLogin, useStytchUser} from '@stytch/nextjs'; +import {useRouter} from 'next/router'; + +const loginConfig: StytchLoginConfig = { + sessionOptions: { + sessionDurationMinutes: 60, + }, + products: [Products.passkeys, Products.otp], + otpOptions: { + expirationMinutes: 10, + methods: [OTPMethods.Email], + }, +}; + +const callbackConfig = { + onEvent: (message: StytchEvent) => console.log(message), + onError: (error: StytchError) => console.log(error), +} + +const LoginWithPasskeys = () => { + const { user } = useStytchUser(); + const router = useRouter(); + + if (user) { + router.push('/recipes/passkeys/profile'); + } + + return ; +}; + +export default LoginWithPasskeys; diff --git a/components/Passkeys/PasskeyRegistration.tsx b/components/Passkeys/PasskeyRegistration.tsx new file mode 100644 index 0000000..79ee4d3 --- /dev/null +++ b/components/Passkeys/PasskeyRegistration.tsx @@ -0,0 +1,187 @@ +import React, {useEffect, useState} from 'react'; +import {AuthenticationFactor, Products, StytchError, StytchEvent, StytchEventType} from '@stytch/vanilla-js'; +import {StytchPasskeyRegistration, useStytch, useStytchSession, useStytchUser} from '@stytch/nextjs'; + +const styles: Record = { + registerButton: { + margin: 'auto' + } +}; + +enum StepUpType { + email = "email", + webauthn = "webauthn", +} + +const StepUp = ({ type }: { type: StepUpType }) => { + const [inputValue, setInputValue] = useState(""); + const [methodID, setMethodID] = useState(""); + const { user } = useStytchUser(); + const [error, setError] = useState(""); + const stytch = useStytch(); + + const validateOTPButtonClick = () => { + stytch.otps.authenticate(inputValue, methodID, { + session_duration_minutes: 30, + }).catch((e) => { + setError("Error occurred validating OTP: " + e); + }); + }; + + const handleSendOTPButtonClick = () => { + stytch.otps.email + .send(user?.emails?.at(0)?.email as string, { + expiration_minutes: 5, + }) + .then((resp) => { + setMethodID(resp.method_id); + }) + .catch((e) => { + setError("Error occurred sending email: " + e); + }); + }; + + const handleInputChange = (event: any) => { + setInputValue(event.target.value); + }; + + if (type === StepUpType.webauthn) { + return ( + <> +

You need to step up {type} before creating Passkeys!

+ + + ); + } + + return ( +
+

You need to step up {type} before creating Passkeys!

+ +
+
+ +
+ + {error} +
+ ); +}; + + +enum PasskeyRegViews { + Start = "START", + Register = "REGISTER", + Success = "SUCCESS", + StepUpWebAuthn = "STEP_UP_WEBAUTHN", + StepUpEmail = "STEP_UP_EMAIL", +} + +const PasskeyRegistration = () => { + const [displayView, setDisplayView] = useState(PasskeyRegViews.Start); + const { session } = useStytchSession(); + const { user, isInitialized } = useStytchUser(); + + useEffect(() => { + const sessionHasPasskeyFactor = session?.authentication_factors?.some( + (factor: AuthenticationFactor) => factor.delivery_method === "webauthn_registration", + ); + const sessionHasEmailFactor = session?.authentication_factors?.some( + (factor: AuthenticationFactor) => factor.delivery_method === "email", + ); + const displayPasskeyStepUp = sessionHasEmailFactor && !sessionHasPasskeyFactor && user?.webauthn_registrations?.length! > 0; + const displayEmailStepUp = !sessionHasEmailFactor && sessionHasPasskeyFactor; + if (displayEmailStepUp) { + setDisplayView(PasskeyRegViews.StepUpEmail); + } else if (displayPasskeyStepUp){ + setDisplayView(PasskeyRegViews.StepUpWebAuthn); + } + + // If the user authenticates succesfully on the step-up page we should navigate to the registration view + if (displayView === PasskeyRegViews.StepUpEmail || displayView === PasskeyRegViews.StepUpWebAuthn + && !displayEmailStepUp && !displayPasskeyStepUp) { + setDisplayView(PasskeyRegViews.Register); + } + + },[session, user]); + + const callbackConfig = { + onEvent: (message: StytchEvent) => { + console.log(message) + if (message.type === StytchEventType.PasskeySkip) { + alert("We just return to the start here, but you can do whatever you want!"); + setDisplayView(PasskeyRegViews.Start); + } + if (message.type === StytchEventType.PasskeyDone) { + setDisplayView(PasskeyRegViews.Success); + } + }, + onError: (error: StytchError) => console.log(error), + } + + return ( + <> + {displayView === PasskeyRegViews.Start && ( + + )} + {displayView === PasskeyRegViews.StepUpWebAuthn && ( + + )} + {displayView === PasskeyRegViews.StepUpEmail && ( + + )} + {displayView === PasskeyRegViews.Register && ( + + )} + {displayView === PasskeyRegViews.Success && ( +
+

Passkey created!

+

+ You can now use your Passkey to sign in to your account. +

+ +
+ )} + + ); +}; + +export default PasskeyRegistration; diff --git a/lib/loginProduct.ts b/lib/loginProduct.ts index 1f835d2..bf81515 100644 --- a/lib/loginProduct.ts +++ b/lib/loginProduct.ts @@ -4,6 +4,7 @@ import smsIcon from '/public/sms-icon.svg'; import webauthnIcon from '/public/webauthn-icon.svg'; import web3Icon from '/public/web3-icon.svg'; import passwordsIcon from '/public/passwords-icon.svg'; +import passkeysIcon from 'public/passkeys-icon.svg'; import { LoginProduct } from './types'; const LoginProducts: Record = { @@ -31,6 +32,10 @@ const LoginProducts: Record = { icon: passwordsIcon, name: 'Passwords', }, + PASSKEYS: { + icon: passkeysIcon, + name: 'Passkeys', + } }; export default LoginProducts; diff --git a/lib/recipeData.tsx b/lib/recipeData.tsx index 155a897..e291759 100644 --- a/lib/recipeData.tsx +++ b/lib/recipeData.tsx @@ -6,6 +6,8 @@ import LoginWithStytchSDKUI from '../components/LoginWithStytchSDKUI'; import LoginWithPasswords from '../components/Passwords/LoginWithPasswords'; import LoginProducts from './loginProduct'; import LoginWithOneTap from '../components/LoginWithOneTapSDKUI'; +import LoginWithPasskeys from "../components/Passkeys/LoginWithPasskeys"; +import {OTPMethods, Products, StytchLoginConfig} from "@stytch/vanilla-js"; export const Recipes: Record = { REACT: { @@ -127,7 +129,7 @@ const trigger = useCallback(async () => { id: 'passwords', title: 'Passwords', details: - 'Build an email/password authentication experience including passwords resets, password strength checking, and magic links using prebuilt Stytch UI components.', + 'Build an email/password authentication experience including passwords resets, password strength checking, and magic links using prebuilt Stytch UI components.', description: ``, instructions: `To the right you'll see the Stytch UI configured for password login. Enter a new email address and you will be prompted to create an account with a secure password.`, component: , @@ -152,14 +154,42 @@ const LoginWithPasswords = () => { if (user) { router.push('/profile'); } +return ;`, + }, + PASSKEYS: { + id: 'passkeys', + title: 'Passkeys', + details: + 'Build an email/passkey authentication experience including passkey registrations and email OTPs using prebuilt Stytch UI components.', + description: ``, + instructions: 'To the right you\'ll see the Stytch UI configured for Email OTP and Passkey login. Continue with email to create an account. Then, once logged in, use the Passkey Registration SDK to create a passkey for your account.', + component: , + products: [LoginProducts.PASSKEYS], + code: `const loginConfig: StytchLoginConfig = + sessionOptions: { + sessionDurationMinutes: 60, + }, + products: [Products.passkeys, Products.otp], + otpOptions: { + expirationMinutes: 10, + methods: [OTPMethods.Email], + }, +}; +const LoginWithPasskeys = () => { + const { user } = useStytchUser(); + const router = useRouter(); + + if (user) { + router.push('/recipes/passkeys/profile'); + } return ;`, }, -ONETAP: { + ONETAP: { id: 'onetap', title: 'Floating Google One Tap', details: - 'Render Google One Tap in a floating manner on your webpages, and nudge users down the login/signup flow from anywhere in your user experience.', + 'Render Google One Tap in a floating manner on your webpages, and nudge users down the login/signup flow from anywhere in your user experience.', description: `This authentication method can be used as a standalone login/signup method, or paired with other login methods such as email magic links.`, instructions: `Google One Tap is powered through an iframe that Google provides compared to the traditional OAuth flow of redirecting the user to a separate Google page. As a result, the user can click directly on their desired account to login or create an account - hence, a “One Tap” experience. In the top right hand corner of this page you'll see the Stytch UI configured for Google One Tap if you have any active Chrome sessions in your browser.`, component: , diff --git a/package-lock.json b/package-lock.json index 1bd670b..1c21699 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "0.1.0", "dependencies": { "@github/webauthn-json": "^2.0.1", - "@stytch/nextjs": "11.0.0", - "@stytch/vanilla-js": "2.2.0", + "@stytch/nextjs": "^14.0.0", + "@stytch/vanilla-js": "^3.2.2", "cookies": "^0.8.0", "next": "^12.3.1", "react": "^18.2.0", @@ -932,12 +932,9 @@ } }, "node_modules/@github/webauthn-json": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@github/webauthn-json/-/webauthn-json-2.0.1.tgz", - "integrity": "sha512-9vjpjK3Qfd5FgvdueWYOGnR3TadG1dLQ2vHoL+un5JutH1fqR4LZaOWqHWGLmZz7NZodGle53GdXF9yhq4JWgA==", - "dependencies": { - "rome": "^0.9.2-next" - }, + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@github/webauthn-json/-/webauthn-json-2.1.1.tgz", + "integrity": "sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==", "bin": { "webauthn-json": "dist/bin/main.js" } @@ -1270,96 +1267,6 @@ "@babel/runtime": "^7.13.10" } }, - "node_modules/@rometools/cli-darwin-arm64": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/cli-darwin-arm64/-/cli-darwin-arm64-0.9.2-next.tgz", - "integrity": "sha512-7i/3sRwCsz5QzGarYpiggInciSO99mH2Qx3LZ8Mf+ia1jPWBaVtGiN+GimFZEcFnYhJ6EXMNYlAuX1nJyaRSVQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rometools/cli-darwin-x64": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/cli-darwin-x64/-/cli-darwin-x64-0.9.2-next.tgz", - "integrity": "sha512-Sy0cgqW86PT0TuyAgNHpalRXrKM5WZPnDtHKVS2QXA5Ad01dhQxPHl5SiF12ussJztHWjWDmMKAso/Uopz8ENw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rometools/cli-linux-arm64": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/cli-linux-arm64/-/cli-linux-arm64-0.9.2-next.tgz", - "integrity": "sha512-WdhmT4sx1iGbjME8krEsYLmkgzJlNwv9yEjwIyhpAe87QngYF4w7KwdZeCAp28jkI8cp5vGYjTAHtbARRMUAcQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rometools/cli-linux-x64": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/cli-linux-x64/-/cli-linux-x64-0.9.2-next.tgz", - "integrity": "sha512-Pgq0srYfXqgZ/Zlv2a3BzHhh+dewddWRrSWqqhjxcLLEm5IPVqfVmq0fUbNMZYm9M7DOJbUmcMdU6lPFdV044w==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rometools/cli-win32-arm64": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/cli-win32-arm64/-/cli-win32-arm64-0.9.2-next.tgz", - "integrity": "sha512-6+5R/IzJKxIXMefjQ03/5D+fKBSsynV9LvF3Ovtr0vsSfA3SdjRfsOlkH/bPOrWcLT96Fkdq8ATWd5Ah2NBQ6A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rometools/cli-win32-x64": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/cli-win32-x64/-/cli-win32-x64-0.9.2-next.tgz", - "integrity": "sha512-cFg4mGdkWcVajk+mKWjt5g/RBaxvCrJ8qBseLSIWEh8jHfpSTbOFc5Z/Mkv7QY1WWOEUBr0bX71iqZqfh3jfOA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rometools/wasm-bundler": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/wasm-bundler/-/wasm-bundler-0.9.2-next.tgz", - "integrity": "sha512-sa2rpam4spyijJMGfqrxttN8QX67OyZjAb+JyFB5FGfNvd075nFdTBSuM177tgbbSwmt3n2MY23EV1aCLiGTmg==", - "optional": true - }, - "node_modules/@rometools/wasm-nodejs": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/wasm-nodejs/-/wasm-nodejs-0.9.2-next.tgz", - "integrity": "sha512-yUTxjYZfrqXQrszRUVK/lUfRJOD5g9wfZXeRBJ4FJllGCy1CvwwR9bS09oSLIZd8zMCC02XMBBW+OZJ1b6rZrA==", - "optional": true - }, - "node_modules/@rometools/wasm-web": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/wasm-web/-/wasm-web-0.9.2-next.tgz", - "integrity": "sha512-iZpgD4n5f2tMeSwlF50k/d8RGBkdoi7wX9YtYTXFks7mN3Z5OU7IHC1LvY28yAv0EELwMFYaaZIvhIs0jozzKw==", - "optional": true - }, "node_modules/@rushstack/eslint-patch": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.4.tgz", @@ -1367,32 +1274,31 @@ "dev": true }, "node_modules/@stytch/core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@stytch/core/-/core-1.2.0.tgz", - "integrity": "sha512-BMAaWzQZ73cvtdfWBdtxcjhUFtNqyg0AlbUh4w/NpNhIpn6pYoDRv2cdE43/g4pGDE5PaTbm0rp8d96g4lh5zw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@stytch/core/-/core-1.4.1.tgz", + "integrity": "sha512-MCge+QdjB5JOsd++n48YuLrlHsM4K1ycfDYHd2QdB8iUv8kXzr8eBFK1jn28W0FMheouU0DnefdhyNGexK9Uig==", "dependencies": { "uuid": "8.3.2" } }, "node_modules/@stytch/nextjs": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@stytch/nextjs/-/nextjs-11.0.0.tgz", - "integrity": "sha512-hUjvp+aebdIa8BwyvWXA1ENo5p9mzbFGePb1epua2Y7D0cke1dFk94jn3t5H0pMMDsUcm+yzos+bhD8LacnjHg==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@stytch/nextjs/-/nextjs-14.0.0.tgz", + "integrity": "sha512-hkTD/oXKGOac9tlafZGwg5sypTIb/QYYJUy+p9/+MrEQQ3PhXjxpvtOkKwO/T5DOT0K702lIVDarokNS0ueNcA==", "peerDependencies": { - "@stytch/vanilla-js": "^2.2.0", + "@stytch/vanilla-js": "^3.2.0", "react": ">= 17.0.2", "react-dom": ">= 17.0.2" } }, "node_modules/@stytch/vanilla-js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@stytch/vanilla-js/-/vanilla-js-2.2.0.tgz", - "integrity": "sha512-NdME50IXiKOVOdZCq+y/LB1fnXwfepRfZBtwkJ85FM497+QtX5+9NAi7qNVeat024fqAORbil0r0MeM53bwrDg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@stytch/vanilla-js/-/vanilla-js-3.2.2.tgz", + "integrity": "sha512-uxrFwJfs9XCnCXfZWYMvRXxK0hzprzBbANnuTFENintdl3RucIKGb7TxIH7ixEMzKeXrNiKHx6EloEf4Eor/VQ==", "dependencies": { - "@github/webauthn-json": "1.0.3", + "@github/webauthn-json": "2.1.1", "@radix-ui/react-tabs": "0.1.5", - "@stytch/core": "1.2.0", - "awesome-phonenumber": "3.2.0", + "@stytch/core": "1.4.1", "esbuild": "0.19.2", "js-cookie": "3.0.1", "lodash.merge": "4.6.2", @@ -1404,14 +1310,6 @@ "uuid": "8.3.2" } }, - "node_modules/@stytch/vanilla-js/node_modules/@github/webauthn-json": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@github/webauthn-json/-/webauthn-json-1.0.3.tgz", - "integrity": "sha512-hD4cCudZUvJGR3CPF9Ugz4XZ1uOK2Sgn5D6CDKxxGhW/m3yQ5nu9744TtGhroDa5rXCJGNjw0/S/tnluyFilHQ==", - "bin": { - "webauthn-json": "dist/bin/main.js" - } - }, "node_modules/@stytch/vanilla-js/node_modules/@radix-ui/react-collection": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-0.1.4.tgz", @@ -2068,14 +1966,6 @@ "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", "dev": true }, - "node_modules/awesome-phonenumber": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/awesome-phonenumber/-/awesome-phonenumber-3.2.0.tgz", - "integrity": "sha512-FvO80NlYiYrpi3QR2B4fZwGeTgDb/MMYKNe2M9y7eDSmuy/7Gg9ifhmaO2z+p7eKZaNJGEx19Br4gAoSqSQyHA==", - "engines": { - "node": ">=12" - } - }, "node_modules/axe-core": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz", @@ -4596,29 +4486,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rome": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/rome/-/rome-0.9.2-next.tgz", - "integrity": "sha512-ppc7Jg3oZfmVXvs28OynJBWa26dy8201QNH3vY8RlfdzxblLOb9+ovPgcmwceSURZMNz/HS+aTpuHW8T06ciHA==", - "hasInstallScript": true, - "bin": { - "rome": "bin/rome" - }, - "engines": { - "node": ">=14.*" - }, - "optionalDependencies": { - "@rometools/cli-darwin-arm64": "0.9.2-next", - "@rometools/cli-darwin-x64": "0.9.2-next", - "@rometools/cli-linux-arm64": "0.9.2-next", - "@rometools/cli-linux-x64": "0.9.2-next", - "@rometools/cli-win32-arm64": "0.9.2-next", - "@rometools/cli-win32-x64": "0.9.2-next", - "@rometools/wasm-bundler": "0.9.2-next", - "@rometools/wasm-nodejs": "0.9.2-next", - "@rometools/wasm-web": "0.9.2-next" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5796,12 +5663,9 @@ } }, "@github/webauthn-json": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@github/webauthn-json/-/webauthn-json-2.0.1.tgz", - "integrity": "sha512-9vjpjK3Qfd5FgvdueWYOGnR3TadG1dLQ2vHoL+un5JutH1fqR4LZaOWqHWGLmZz7NZodGle53GdXF9yhq4JWgA==", - "requires": { - "rome": "^0.9.2-next" - } + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@github/webauthn-json/-/webauthn-json-2.1.1.tgz", + "integrity": "sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==" }, "@humanwhocodes/config-array": { "version": "0.10.7", @@ -5986,60 +5850,6 @@ "@babel/runtime": "^7.13.10" } }, - "@rometools/cli-darwin-arm64": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/cli-darwin-arm64/-/cli-darwin-arm64-0.9.2-next.tgz", - "integrity": "sha512-7i/3sRwCsz5QzGarYpiggInciSO99mH2Qx3LZ8Mf+ia1jPWBaVtGiN+GimFZEcFnYhJ6EXMNYlAuX1nJyaRSVQ==", - "optional": true - }, - "@rometools/cli-darwin-x64": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/cli-darwin-x64/-/cli-darwin-x64-0.9.2-next.tgz", - "integrity": "sha512-Sy0cgqW86PT0TuyAgNHpalRXrKM5WZPnDtHKVS2QXA5Ad01dhQxPHl5SiF12ussJztHWjWDmMKAso/Uopz8ENw==", - "optional": true - }, - "@rometools/cli-linux-arm64": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/cli-linux-arm64/-/cli-linux-arm64-0.9.2-next.tgz", - "integrity": "sha512-WdhmT4sx1iGbjME8krEsYLmkgzJlNwv9yEjwIyhpAe87QngYF4w7KwdZeCAp28jkI8cp5vGYjTAHtbARRMUAcQ==", - "optional": true - }, - "@rometools/cli-linux-x64": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/cli-linux-x64/-/cli-linux-x64-0.9.2-next.tgz", - "integrity": "sha512-Pgq0srYfXqgZ/Zlv2a3BzHhh+dewddWRrSWqqhjxcLLEm5IPVqfVmq0fUbNMZYm9M7DOJbUmcMdU6lPFdV044w==", - "optional": true - }, - "@rometools/cli-win32-arm64": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/cli-win32-arm64/-/cli-win32-arm64-0.9.2-next.tgz", - "integrity": "sha512-6+5R/IzJKxIXMefjQ03/5D+fKBSsynV9LvF3Ovtr0vsSfA3SdjRfsOlkH/bPOrWcLT96Fkdq8ATWd5Ah2NBQ6A==", - "optional": true - }, - "@rometools/cli-win32-x64": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/cli-win32-x64/-/cli-win32-x64-0.9.2-next.tgz", - "integrity": "sha512-cFg4mGdkWcVajk+mKWjt5g/RBaxvCrJ8qBseLSIWEh8jHfpSTbOFc5Z/Mkv7QY1WWOEUBr0bX71iqZqfh3jfOA==", - "optional": true - }, - "@rometools/wasm-bundler": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/wasm-bundler/-/wasm-bundler-0.9.2-next.tgz", - "integrity": "sha512-sa2rpam4spyijJMGfqrxttN8QX67OyZjAb+JyFB5FGfNvd075nFdTBSuM177tgbbSwmt3n2MY23EV1aCLiGTmg==", - "optional": true - }, - "@rometools/wasm-nodejs": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/wasm-nodejs/-/wasm-nodejs-0.9.2-next.tgz", - "integrity": "sha512-yUTxjYZfrqXQrszRUVK/lUfRJOD5g9wfZXeRBJ4FJllGCy1CvwwR9bS09oSLIZd8zMCC02XMBBW+OZJ1b6rZrA==", - "optional": true - }, - "@rometools/wasm-web": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/@rometools/wasm-web/-/wasm-web-0.9.2-next.tgz", - "integrity": "sha512-iZpgD4n5f2tMeSwlF50k/d8RGBkdoi7wX9YtYTXFks7mN3Z5OU7IHC1LvY28yAv0EELwMFYaaZIvhIs0jozzKw==", - "optional": true - }, "@rushstack/eslint-patch": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.4.tgz", @@ -6047,28 +5857,27 @@ "dev": true }, "@stytch/core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@stytch/core/-/core-1.2.0.tgz", - "integrity": "sha512-BMAaWzQZ73cvtdfWBdtxcjhUFtNqyg0AlbUh4w/NpNhIpn6pYoDRv2cdE43/g4pGDE5PaTbm0rp8d96g4lh5zw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@stytch/core/-/core-1.4.1.tgz", + "integrity": "sha512-MCge+QdjB5JOsd++n48YuLrlHsM4K1ycfDYHd2QdB8iUv8kXzr8eBFK1jn28W0FMheouU0DnefdhyNGexK9Uig==", "requires": { "uuid": "8.3.2" } }, "@stytch/nextjs": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@stytch/nextjs/-/nextjs-11.0.0.tgz", - "integrity": "sha512-hUjvp+aebdIa8BwyvWXA1ENo5p9mzbFGePb1epua2Y7D0cke1dFk94jn3t5H0pMMDsUcm+yzos+bhD8LacnjHg==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@stytch/nextjs/-/nextjs-14.0.0.tgz", + "integrity": "sha512-hkTD/oXKGOac9tlafZGwg5sypTIb/QYYJUy+p9/+MrEQQ3PhXjxpvtOkKwO/T5DOT0K702lIVDarokNS0ueNcA==", "requires": {} }, "@stytch/vanilla-js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@stytch/vanilla-js/-/vanilla-js-2.2.0.tgz", - "integrity": "sha512-NdME50IXiKOVOdZCq+y/LB1fnXwfepRfZBtwkJ85FM497+QtX5+9NAi7qNVeat024fqAORbil0r0MeM53bwrDg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@stytch/vanilla-js/-/vanilla-js-3.2.2.tgz", + "integrity": "sha512-uxrFwJfs9XCnCXfZWYMvRXxK0hzprzBbANnuTFENintdl3RucIKGb7TxIH7ixEMzKeXrNiKHx6EloEf4Eor/VQ==", "requires": { - "@github/webauthn-json": "1.0.3", + "@github/webauthn-json": "2.1.1", "@radix-ui/react-tabs": "0.1.5", - "@stytch/core": "1.2.0", - "awesome-phonenumber": "3.2.0", + "@stytch/core": "1.4.1", "esbuild": "0.19.2", "js-cookie": "3.0.1", "lodash.merge": "4.6.2", @@ -6080,11 +5889,6 @@ "uuid": "8.3.2" }, "dependencies": { - "@github/webauthn-json": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@github/webauthn-json/-/webauthn-json-1.0.3.tgz", - "integrity": "sha512-hD4cCudZUvJGR3CPF9Ugz4XZ1uOK2Sgn5D6CDKxxGhW/m3yQ5nu9744TtGhroDa5rXCJGNjw0/S/tnluyFilHQ==" - }, "@radix-ui/react-collection": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-0.1.4.tgz", @@ -6570,11 +6374,6 @@ "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", "dev": true }, - "awesome-phonenumber": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/awesome-phonenumber/-/awesome-phonenumber-3.2.0.tgz", - "integrity": "sha512-FvO80NlYiYrpi3QR2B4fZwGeTgDb/MMYKNe2M9y7eDSmuy/7Gg9ifhmaO2z+p7eKZaNJGEx19Br4gAoSqSQyHA==" - }, "axe-core": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz", @@ -8416,22 +8215,6 @@ "glob": "^7.1.3" } }, - "rome": { - "version": "0.9.2-next", - "resolved": "https://registry.npmjs.org/rome/-/rome-0.9.2-next.tgz", - "integrity": "sha512-ppc7Jg3oZfmVXvs28OynJBWa26dy8201QNH3vY8RlfdzxblLOb9+ovPgcmwceSURZMNz/HS+aTpuHW8T06ciHA==", - "requires": { - "@rometools/cli-darwin-arm64": "0.9.2-next", - "@rometools/cli-darwin-x64": "0.9.2-next", - "@rometools/cli-linux-arm64": "0.9.2-next", - "@rometools/cli-linux-x64": "0.9.2-next", - "@rometools/cli-win32-arm64": "0.9.2-next", - "@rometools/cli-win32-x64": "0.9.2-next", - "@rometools/wasm-bundler": "0.9.2-next", - "@rometools/wasm-nodejs": "0.9.2-next", - "@rometools/wasm-web": "0.9.2-next" - } - }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/package.json b/package.json index 48a99fb..261f428 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ ], "dependencies": { "@github/webauthn-json": "^2.0.1", - "@stytch/nextjs": "11.0.0", - "@stytch/vanilla-js": "2.2.0", + "@stytch/nextjs": "^14.0.0", + "@stytch/vanilla-js": "^3.2.2", "cookies": "^0.8.0", "next": "^12.3.1", "react": "^18.2.0", diff --git a/pages/recipes/passkeys/profile.tsx b/pages/recipes/passkeys/profile.tsx new file mode 100644 index 0000000..bfcadc7 --- /dev/null +++ b/pages/recipes/passkeys/profile.tsx @@ -0,0 +1,65 @@ +import React, { useEffect } from 'react'; +import { useRouter } from 'next/router'; +import {useStytchUser, useStytch, useStytchSession, StytchPasskeyRegistration} from '@stytch/nextjs'; +import CodeBlock from '../../../components/common/CodeBlock'; +import PasskeyRegistration from "../../../components/Passkeys/PasskeyRegistration"; + +const Profile = () => { + const { user, isInitialized } = useStytchUser(); + const stytch = useStytch(); + const router = useRouter(); + + useEffect(() => { + if (isInitialized && !user) { + router.replace('/'); + } + }, [user, isInitialized, router]); + + const signOut = async () => { + await stytch.session.revoke(); + }; + + return ( +
+
+

Welcome to your profile!

+

Below is your Stytch user object.

+ + + +
+
+ +
+
+ ); +}; + +const styles: Record = { + container: { + display: 'flex', + margin: '48px 24px', + justifyContent: 'center', + gap: '48px', + flexWrap: 'wrap', + }, + details: { + backgroundColor: '#FFF', + padding: '24px', + flexBasis: '450px', + flexGrow: 1, + flexShrink: 1, + }, + registrationDetails: { + backgroundColor: '#FFF', + display: 'flex', + padding: '24px', + width: '500px', + flexShrink: 1, + justifyContent: 'center', + }, +}; + +export default Profile; diff --git a/public/passkeys-icon.svg b/public/passkeys-icon.svg new file mode 100644 index 0000000..7f1743e --- /dev/null +++ b/public/passkeys-icon.svg @@ -0,0 +1,4 @@ + + + +