diff --git a/package-lock.json b/package-lock.json index 25c01a4f8..f6501d28d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7984,6 +7984,13 @@ "@types/node": "*" } }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/debounce": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.4.tgz", @@ -11603,6 +11610,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/crypto-random-string": { "version": "2.0.0", "license": "MIT", @@ -17829,6 +17842,15 @@ "npm": ">=6" } }, + "node_modules/jssha": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz", + "integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "license": "MIT", @@ -25120,6 +25142,15 @@ "node": ">=0.6" } }, + "node_modules/totp-generator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/totp-generator/-/totp-generator-1.0.0.tgz", + "integrity": "sha512-Iu/1Lk60/MH8FE+5cDWPiGbwKK1hxzSq+KT9oSqhZ1BEczGIKGcN50bP0WMLiIZKRg7t29iWLxw6f81TICQdoA==", + "license": "MIT", + "dependencies": { + "jssha": "^3.3.1" + } + }, "node_modules/tough-cookie": { "version": "4.1.3", "license": "BSD-3-Clause", @@ -27226,14 +27257,17 @@ "@corbado/connect-react": "*", "aws-sdk": "^2.1646.0", "axios": "^1.7.3", + "crypto-js": "^4.2.0", "jsonwebtoken": "^9.0.2", "jwks-rsa": "^3.1.0", "next": "14.2.4", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "totp-generator": "^1.0.0" }, "devDependencies": { "@tailwindcss/forms": "^0.5.7", + "@types/crypto-js": "^4.2.2", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/packages/react/src/components/authentication/AuthFlow.tsx b/packages/react/src/components/authentication/AuthFlow.tsx index 9b7eecc00..67125e0b1 100644 --- a/packages/react/src/components/authentication/AuthFlow.tsx +++ b/packages/react/src/components/authentication/AuthFlow.tsx @@ -40,7 +40,11 @@ import ErrorPopup from '../ui/errors/ErrorPopup'; import { FreemiumBadge } from '../ui/FreemiumBadge'; import { LoadingSpinner } from '../ui/LoadingSpinner'; -export const AuthFlow: FC = () => { +type Props = { + initialAutoFocus: boolean; +}; + +export const AuthFlow: FC = ({ initialAutoFocus }) => { const { isDevMode, customerSupportEmail } = useErrorHandling(); const { currentScreen, initState } = useFlowHandler(); const [loading, setLoading] = useState(false); @@ -74,9 +78,19 @@ export const AuthFlow: FC = () => { switch (currentScreen.block.type) { case BlockTypes.LoginInit: - return ; + return ( + + ); case BlockTypes.SignupInit: - return ; + return ( + + ); case BlockTypes.EmailVerify: switch (currentScreen.screen) { case ScreenNames.EmailLinkSent: diff --git a/packages/react/src/components/authentication/login-init/LoginForm.tsx b/packages/react/src/components/authentication/login-init/LoginForm.tsx index 8d36c8e5d..f778bc626 100644 --- a/packages/react/src/components/authentication/login-init/LoginForm.tsx +++ b/packages/react/src/components/authentication/login-init/LoginForm.tsx @@ -11,9 +11,16 @@ export interface LoginFormProps { socialLoadingInProgress: boolean | undefined; loading: boolean; setLoading: (loading: boolean) => void; + initialAutoFocus: boolean; } -export const LoginForm: FC = ({ block, loading, socialLoadingInProgress, setLoading }) => { +export const LoginForm: FC = ({ + block, + loading, + socialLoadingInProgress, + setLoading, + initialAutoFocus, +}) => { const { t } = useTranslation('translation', { keyPrefix: `login.login-init.login-init` }); const [textField, setTextField] = useState(null); @@ -33,10 +40,13 @@ export const LoginForm: FC = ({ block, loading, socialLoadingInP setTextField({ value: block.data.loginIdentifier, translatedError: block.data.loginIdentifierError }); - if (textFieldRef.current) { + if (!textFieldRef.current) { + return; + } + if (initialAutoFocus) { textFieldRef.current.focus(); - textFieldRef.current.value = block.data.loginIdentifier ? block.data.loginIdentifier : ''; } + textFieldRef.current.value = block.data.loginIdentifier ? block.data.loginIdentifier : ''; }, [block]); const submitButtonText = useMemo(() => t('button_submit'), [t]); @@ -73,7 +83,7 @@ export const LoginForm: FC = ({ block, loading, socialLoadingInP initialCountry='US' initialPhoneNumber={textField?.value} errorMessage={textField?.translatedError} - autoFocus={true} + autoFocus={initialAutoFocus} onChange={setPhoneInput} /> ); @@ -94,7 +104,7 @@ export const LoginForm: FC = ({ block, loading, socialLoadingInP name='username' type='username' autoComplete='username webauthn' - autoFocus={true} + autoFocus={initialAutoFocus} {...commonProps} /> ); @@ -108,7 +118,7 @@ export const LoginForm: FC = ({ block, loading, socialLoadingInP name='email' type='email' autoComplete='username webauthn' - autoFocus={true} + autoFocus={initialAutoFocus} {...commonProps} /> ); diff --git a/packages/react/src/screens/auth-blocks/login-init/LoginInit.tsx b/packages/react/src/screens/auth-blocks/login-init/LoginInit.tsx index a418e2c81..e820d6618 100644 --- a/packages/react/src/screens/auth-blocks/login-init/LoginInit.tsx +++ b/packages/react/src/screens/auth-blocks/login-init/LoginInit.tsx @@ -8,7 +8,7 @@ import { Header, SecondaryButton, SocialLoginButtons, SubHeader, Text } from '.. import { LastIdentifier } from '../../../components/authentication/login-init/LastIdentifier'; import { LoginForm } from '../../../components/authentication/login-init/LoginForm'; -export const LoginInit = ({ block }: { block: LoginInitBlock }) => { +export const LoginInit = ({ block, initialAutoFocus }: { block: LoginInitBlock; initialAutoFocus: boolean }) => { const { t } = useTranslation('translation', { keyPrefix: `login.login-init.login-init` }); const [loading, setLoading] = useState(false); const [socialLoadingInProgress, setSocialLoadingInProgress] = useState(undefined); @@ -105,6 +105,7 @@ export const LoginInit = ({ block }: { block: LoginInitBlock }) => { loading={loading} socialLoadingInProgress={socialLoadingInProgress} setLoading={setLoading} + initialAutoFocus={initialAutoFocus} /> )} { +export const SignupInit = ({ block, initialAutoFocus }: { block: SignupInitBlock; initialAutoFocus: boolean }) => { const { t } = useTranslation('translation', { keyPrefix: `signup.signup-init.signup-init` }); const [loading, setLoading] = useState(false); @@ -82,7 +81,7 @@ export const SignupInit = ({ block }: { block: SignupInitBlock }) => { ref.current.value = value || ''; } - if (!hasFocusField.current) { + if (!hasFocusField.current && initialAutoFocus) { ref.current.focus(); hasFocusField.current = true; } @@ -98,7 +97,7 @@ export const SignupInit = ({ block }: { block: SignupInitBlock }) => { const userName = block.data.userName; const email = block.data.email; const phone = block.data.phone; - const foucsPhoneField = !!(phone && !email && !userName && !fullName); + const foucsPhoneField = !!(phone && !email && !userName && !fullName) && initialAutoFocus; if (foucsPhoneField) { hasFocusField.current = true; diff --git a/packages/react/src/screens/core/CorbadoAuth.tsx b/packages/react/src/screens/core/CorbadoAuth.tsx index a326c58c3..d4b2d83f6 100644 --- a/packages/react/src/screens/core/CorbadoAuth.tsx +++ b/packages/react/src/screens/core/CorbadoAuth.tsx @@ -6,14 +6,19 @@ import React from 'react'; import { AuthFlow } from '../../components'; import FlowHandlerProvider from '../../contexts/FlowHandlerProvider'; -const CorbadoAuth: FC = ({ onLoggedIn, handleNavigationEvents, initialBlock }) => { +const CorbadoAuth: FC = ({ + onLoggedIn, + handleNavigationEvents, + initialBlock, + initialAutoFocus = true, +}) => { return ( - + ); }; diff --git a/packages/react/src/screens/core/Login.tsx b/packages/react/src/screens/core/Login.tsx index 53e7b101d..59b700c83 100644 --- a/packages/react/src/screens/core/Login.tsx +++ b/packages/react/src/screens/core/Login.tsx @@ -6,7 +6,12 @@ import React from 'react'; import { AuthFlow } from '../../components'; import FlowHandlerProvider from '../../contexts/FlowHandlerProvider'; -const Login: FC = ({ handleNavigationEvents, onLoggedIn, navigateToSignUp }) => { +const Login: FC = ({ + handleNavigationEvents, + onLoggedIn, + navigateToSignUp, + initialAutoFocus = true, +}) => { return ( = ({ handleNavigationEvents, onLoggedIn, nav onChangeFlow={navigateToSignUp} initialFlowType={AuthType.Login} > - + ); }; diff --git a/packages/react/src/screens/core/SignUp.tsx b/packages/react/src/screens/core/SignUp.tsx index 0db6d82f2..b6e372b69 100644 --- a/packages/react/src/screens/core/SignUp.tsx +++ b/packages/react/src/screens/core/SignUp.tsx @@ -6,7 +6,12 @@ import React from 'react'; import { AuthFlow } from '../../components'; import FlowHandlerProvider from '../../contexts/FlowHandlerProvider'; -const SignUp: FC = ({ handleNavigationEvents, onSignedUp, navigateToLogin }) => { +const SignUp: FC = ({ + handleNavigationEvents, + onSignedUp, + navigateToLogin, + initialAutoFocus = true, +}) => { return ( = ({ handleNavigationEvents, onSignedUp, n onChangeFlow={navigateToLogin} initialFlowType={AuthType.SignUp} > - + ); }; diff --git a/packages/types/src/component.ts b/packages/types/src/component.ts index 9c2594530..07427394c 100644 --- a/packages/types/src/component.ts +++ b/packages/types/src/component.ts @@ -33,6 +33,7 @@ export interface CorbadoAuthConfig { onLoggedIn: () => void; handleNavigationEvents?: boolean; initialBlock?: 'signup-init' | 'login-init'; + initialAutoFocus?: boolean; } /** @@ -46,6 +47,7 @@ export interface CorbadoSignUpConfig { onSignedUp: () => void; navigateToLogin?: () => void; handleNavigationEvents?: boolean; + initialAutoFocus?: boolean; } /** @@ -59,4 +61,5 @@ export interface CorbadoLoginConfig { onLoggedIn: () => void; navigateToSignUp?: () => void; handleNavigationEvents?: boolean; + initialAutoFocus?: boolean; }