diff --git a/packages/backend/src/routes/auth.ts b/packages/backend/src/routes/auth.ts index 2c736310..90111485 100644 --- a/packages/backend/src/routes/auth.ts +++ b/packages/backend/src/routes/auth.ts @@ -5,6 +5,16 @@ import { authenticateUser, createUser } from '../turnkey-backend'; const router = Router(); +router.post('/accesscode', async (req, res) => { + console.log('Access Code', req.body); + const { accesscode } = req.body; + if (accesscode === '444444') { + return res.send({ isValid: true }); + } else { + return res.sendStatus(204); + } +}); + // // Turnkey // diff --git a/packages/frontend/src/pages/auth/AccessCode.tsx b/packages/frontend/src/pages/auth/AccessCode.tsx new file mode 100644 index 00000000..e35bd893 --- /dev/null +++ b/packages/frontend/src/pages/auth/AccessCode.tsx @@ -0,0 +1,94 @@ +import React, { useState } from 'react'; + +import { Button } from 'components/shared/Button'; +import { + ArrowRightCircleFilledIcon, + LoaderIcon, +} from 'components/shared/CustomIcon'; +import { WavyBorder } from 'components/shared/WavyBorder'; +import { Input } from 'components/shared/Input'; +import { verifyAccessCode } from 'utils/accessCode'; + +type AccessMethod = 'accesscode' | 'passkey'; + +type Err = { type: AccessMethod; message: string }; + +type AccessCodeProps = { + onCorrectAccessCode: () => void; +}; + +export const AccessCode: React.FC = ({ + onCorrectAccessCode, +}) => { + const [accessCode, setAccessCode] = useState(''); + const [error, setError] = useState(); + const [accessMethod, setAccessMethod] = useState(false); + + async function validateAccessCode() { + setAccessMethod('accesscode'); + try { + const isValidAccessCode = await verifyAccessCode(accessCode); + if (isValidAccessCode) { + localStorage.setItem('accessCode', accessCode); + onCorrectAccessCode(); + } else { + setError({ + type: 'accesscode', + message: 'Invalid access code', + }); + } + } catch (err: any) { + setError({ type: 'accesscode', message: err.message }); + } + } + + const loading = accessMethod; + const isValidAccessCodeLength = accessCode.length === 6; + + return ( +
+
+
+ Access Code +
+
+ +
+
+
+ setAccessCode(e.target.value)} + disabled={!!loading} + /> +
+ + {error && error.type === 'accesscode' && ( +
+
+
+ Error: {error.message} +
+
+
+ )} +
+
+
+ ); +}; diff --git a/packages/frontend/src/pages/auth/AccessSignUp.tsx b/packages/frontend/src/pages/auth/AccessSignUp.tsx new file mode 100644 index 00000000..d91fb167 --- /dev/null +++ b/packages/frontend/src/pages/auth/AccessSignUp.tsx @@ -0,0 +1,20 @@ +import React, { useState } from 'react'; + +import { AccessCode } from './AccessCode'; +import { SignUp } from './SignUp'; + +type AccessSignUpProps = { + onDone: () => void; +}; + +export const AccessSignUp: React.FC = ({ onDone }) => { + const [isValidAccessCode, setIsValidAccessCode] = useState( + !!localStorage.getItem('accessCode'), + ); + + return isValidAccessCode ? ( + + ) : ( + setIsValidAccessCode(true)} /> + ); +}; diff --git a/packages/frontend/src/pages/auth/SignUp.tsx b/packages/frontend/src/pages/auth/SignUp.tsx index 581e9595..f5564e46 100644 --- a/packages/frontend/src/pages/auth/SignUp.tsx +++ b/packages/frontend/src/pages/auth/SignUp.tsx @@ -21,6 +21,7 @@ import { turnkeySignin, turnkeySignup, } from 'utils/turnkey-frontend'; +import { verifyAccessCode } from 'utils/accessCode'; type Provider = 'google' | 'github' | 'apple' | 'email'; @@ -111,6 +112,29 @@ export const SignUp = ({ onDone }: Props) => { const loading = provider; const emailValid = /.@./.test(email); + useEffect(() => { + const validateAccessCode = async () => { + const accessCode = localStorage.getItem('accessCode'); + if (!accessCode) { + redirectToSignup(); + return; + } + + try { + await verifyAccessCode(accessCode); + } catch (err: any) { + redirectToSignup(); + } + }; + + const redirectToSignup = () => { + localStorage.removeItem('accessCode'); + window.location.href = '/signup'; + }; + + validateAccessCode(); + }, []); + return (
diff --git a/packages/frontend/src/pages/auth/SnowballAuth.tsx b/packages/frontend/src/pages/auth/SnowballAuth.tsx index 69cb7987..dd5abf1a 100644 --- a/packages/frontend/src/pages/auth/SnowballAuth.tsx +++ b/packages/frontend/src/pages/auth/SnowballAuth.tsx @@ -1,14 +1,15 @@ +import React, { useEffect, useState } from 'react'; + import { snowball } from 'utils/use-snowball'; import { Login } from './Login'; -import { SignUp } from './SignUp'; -import { useEffect, useState } from 'react'; import { Done } from './Done'; +import { AccessSignUp } from './AccessSignUp'; type Screen = 'login' | 'signup' | 'success'; const DASHBOARD_URL = '/'; -export const SnowballAuth = () => { +export const SnowballAuth: React.FC = () => { const path = window.location.pathname; const [screen, setScreen] = useState( path === '/login' ? 'login' : 'signup', @@ -30,7 +31,7 @@ export const SnowballAuth = () => { if (screen === 'signup') { return ( - { setScreen('success'); }} diff --git a/packages/frontend/src/utils/accessCode.ts b/packages/frontend/src/utils/accessCode.ts new file mode 100644 index 00000000..d5e1b68f --- /dev/null +++ b/packages/frontend/src/utils/accessCode.ts @@ -0,0 +1,28 @@ +import { baseUrl } from './constants'; + +export async function verifyAccessCode( + accesscode: string, +): Promise { + const res = await fetch(`${baseUrl}/auth/accesscode`, { + method: 'POST', + body: JSON.stringify({ + accesscode, + }), + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }); + // If API returns a non-empty 200, this is a valid access code. + if (res.status == 200) { + const isValid = (await res.json()).isValid as boolean; + console.log('isValid', isValid); + return isValid; + } else if (res.status === 204) { + return null; + } else { + throw new Error( + `Unexpected response from access code endpoint: ${res.status}: ${await res.text()}`, + ); + } +}