diff --git a/src/frontend/src/components/SplashScreen.tsx b/src/frontend/src/components/SplashScreen.tsx index 79bb9992ac..8c8e783d57 100644 --- a/src/frontend/src/components/SplashScreen.tsx +++ b/src/frontend/src/components/SplashScreen.tsx @@ -1,4 +1,5 @@ import { BackgroundImage } from '@mantine/core'; +import { useEffect } from 'react'; import { generateUrl } from '../functions/urls'; import { useServerApiState } from '../states/ApiState'; @@ -7,10 +8,20 @@ import { useServerApiState } from '../states/ApiState'; */ export default function SplashScreen({ children -}: { +}: Readonly<{ children: React.ReactNode; -}) { - const [server] = useServerApiState((state) => [state.server]); +}>) { + const [server, fetchServerApiState] = useServerApiState((state) => [ + state.server, + state.fetchServerApiState + ]); + + // Fetch server data on mount if no server data is present + useEffect(() => { + if (server.server === null) { + fetchServerApiState(); + } + }, [server]); if (server.customize?.splash) { return ( diff --git a/src/frontend/src/components/forms/AuthenticationForm.tsx b/src/frontend/src/components/forms/AuthenticationForm.tsx index 982b386265..a615d728c8 100644 --- a/src/frontend/src/components/forms/AuthenticationForm.tsx +++ b/src/frontend/src/components/forms/AuthenticationForm.tsx @@ -7,7 +7,6 @@ import { Loader, PasswordInput, Stack, - Text, TextInput } from '@mantine/core'; import { useForm } from '@mantine/form'; @@ -329,47 +328,3 @@ export function RegistrationForm() { ); } - -export function ModeSelector({ - loginMode, - changePage -}: Readonly<{ - loginMode: boolean; - changePage: (state: string) => void; -}>) { - const [sso_registration, registration_enabled] = useServerApiState( - (state) => [state.sso_registration_enabled, state.registration_enabled] - ); - const both_reg_enabled = - registration_enabled() || sso_registration() || false; - - if (both_reg_enabled === false) return null; - return ( - - {loginMode ? ( - <> - Don't have an account?{' '} - changePage('register')} - > - Register - - - ) : ( - changePage('login')} - > - Go back to login - - )} - - ); -} diff --git a/src/frontend/src/components/forms/InstanceOptions.tsx b/src/frontend/src/components/forms/InstanceOptions.tsx index 091887275d..7b1606d869 100644 --- a/src/frontend/src/components/forms/InstanceOptions.tsx +++ b/src/frontend/src/components/forms/InstanceOptions.tsx @@ -1,13 +1,5 @@ import { Trans, t } from '@lingui/macro'; -import { - ActionIcon, - Divider, - Group, - Paper, - Select, - Table, - Text -} from '@mantine/core'; +import { ActionIcon, Divider, Group, Select, Table, Text } from '@mantine/core'; import { useToggle } from '@mantine/hooks'; import { IconApi, @@ -18,11 +10,11 @@ import { IconServerSpark } from '@tabler/icons-react'; +import { Wrapper } from '../../pages/Auth/Layout'; import { useServerApiState } from '../../states/ApiState'; import { useLocalState } from '../../states/LocalState'; import type { HostList } from '../../states/states'; import { EditButton } from '../buttons/EditButton'; -import { StylishText } from '../items/StylishText'; import { HostOptionsForm } from './HostOptionsForm'; export function InstanceOptions({ @@ -54,46 +46,42 @@ export function InstanceOptions({ } return ( - <> - - {t`Select Server`} - - - + + } + /> + - {HostListEdit ? ( - <> - - - Edit host options - - - - ) : ( - <> - - - - )} - - + {HostListEdit ? ( + <> + + + Edit host options + + + + ) : ( + <> + + + + )} + ); } diff --git a/src/frontend/src/components/items/LanguageToggle.tsx b/src/frontend/src/components/items/LanguageToggle.tsx index 75248c2260..5874683afa 100644 --- a/src/frontend/src/components/items/LanguageToggle.tsx +++ b/src/frontend/src/components/items/LanguageToggle.tsx @@ -22,6 +22,7 @@ export function LanguageToggle() { onClick={() => toggle.toggle()} size='lg' variant='transparent' + aria-label='Language toggle' > diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index c94b3cec09..b714bf2e92 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -1,5 +1,5 @@ import { t } from '@lingui/macro'; -import { notifications } from '@mantine/notifications'; +import { notifications, showNotification } from '@mantine/notifications'; import axios from 'axios'; import type { AxiosRequestConfig } from 'axios'; import type { Location, NavigateFunction } from 'react-router-dom'; @@ -358,3 +358,172 @@ export function authApi( // use normal api return api(url, requestConfig); } + +export const getTotpSecret = async (setTotpQr: any) => { + await authApi(apiUrl(ApiEndpoints.auth_totp), undefined, 'get').catch( + (err) => { + if (err.status == 404 && err.response.data.meta.secret) { + setTotpQr(err.response.data.meta); + } else { + const msg = err.response.data.errors[0].message; + showNotification({ + title: t`Failed to set up MFA`, + message: msg, + color: 'red' + }); + } + } + ); +}; + +export function handleVerifyTotp( + value: string, + navigate: NavigateFunction, + location: Location +) { + return () => { + authApi(apiUrl(ApiEndpoints.auth_totp), undefined, 'post', { + code: value + }).then(() => { + followRedirect(navigate, location?.state); + }); + }; +} + +export function handlePasswordReset( + key: string | null, + password: string, + navigate: NavigateFunction +) { + function success() { + notifications.show({ + title: t`Password set`, + message: t`The password was set successfully. You can now login with your new password`, + color: 'green', + autoClose: false + }); + navigate('/login'); + } + + function passwordError(values: any) { + notifications.show({ + title: t`Reset failed`, + message: values?.errors.map((e: any) => e.message).join('\n'), + color: 'red' + }); + } + + // Set password with call to backend + api + .post( + apiUrl(ApiEndpoints.user_reset_set), + { + key: key, + password: password + }, + { headers: { Authorization: '' } } + ) + .then((val) => { + if (val.status === 200) { + success(); + } else { + passwordError(val.data); + } + }) + .catch((err) => { + if (err.response?.status === 400) { + passwordError(err.response.data); + } else if (err.response?.status === 401) { + success(); + } else { + passwordError(err.response.data); + } + }); +} + +export function handleVerifyEmail( + key: string | undefined, + navigate: NavigateFunction +) { + // Set password with call to backend + api + .post(apiUrl(ApiEndpoints.auth_email_verify), { + key: key + }) + .then((val) => { + if (val.status === 200) { + navigate('/login'); + } + }); +} + +export function handleChangePassword( + pwd1: string, + pwd2: string, + current: string, + navigate: NavigateFunction +) { + const { clearUserState } = useUserState.getState(); + + function passwordError(values: any) { + let message: any = + values?.new_password || + values?.new_password2 || + values?.new_password1 || + values?.current_password || + values?.error || + t`Password could not be changed`; + + // If message is array + if (!Array.isArray(message)) { + message = [message]; + } + + message.forEach((msg: string) => { + notifications.show({ + title: t`Error`, + message: msg, + color: 'red' + }); + }); + } + + // check if passwords match + if (pwd1 !== pwd2) { + passwordError({ new_password2: t`The two password fields didn’t match` }); + return; + } + + // Set password with call to backend + api + .post(apiUrl(ApiEndpoints.auth_pwd_change), { + current_password: current, + new_password: pwd2 + }) + .then((val) => { + passwordError(val.data); + }) + .catch((err) => { + if (err.status === 401) { + notifications.show({ + title: t`Password Changed`, + message: t`The password was set successfully. You can now login with your new password`, + color: 'green', + autoClose: false + }); + clearUserState(); + clearCsrfCookie(); + navigate('/login'); + } else { + // compile errors + const errors: { [key: string]: string[] } = {}; + for (const val of err.response.data.errors) { + if (!errors[val.param]) { + errors[val.param] = []; + } + errors[val.param].push(val.message); + } + passwordError(errors); + } + }); +} diff --git a/src/frontend/src/pages/Auth/ChangePassword.tsx b/src/frontend/src/pages/Auth/ChangePassword.tsx index 0c2d47a329..9a78c45a08 100644 --- a/src/frontend/src/pages/Auth/ChangePassword.tsx +++ b/src/frontend/src/pages/Auth/ChangePassword.tsx @@ -1,8 +1,6 @@ import { Trans, t } from '@lingui/macro'; import { Button, - Center, - Container, Divider, Group, Paper, @@ -11,18 +9,11 @@ import { Text } from '@mantine/core'; import { useForm } from '@mantine/form'; -import { notifications } from '@mantine/notifications'; import { useNavigate } from 'react-router-dom'; - -import { api } from '../../App'; -import SplashScreen from '../../components/SplashScreen'; import { StylishText } from '../../components/items/StylishText'; -import { ProtectedRoute } from '../../components/nav/Layout'; -import { LanguageContext } from '../../contexts/LanguageContext'; -import { ApiEndpoints } from '../../enums/ApiEndpoints'; -import { clearCsrfCookie } from '../../functions/auth'; -import { apiUrl } from '../../states/ApiState'; +import { handleChangePassword } from '../../functions/auth'; import { useUserState } from '../../states/UserState'; +import { Wrapper } from './Layout'; export default function Set_Password() { const simpleForm = useForm({ @@ -36,123 +27,57 @@ export default function Set_Password() { const user = useUserState(); const navigate = useNavigate(); - function passwordError(values: any) { - let message: any = - values?.new_password || - values?.new_password2 || - values?.new_password1 || - values?.current_password || - values?.error || - t`Password could not be changed`; - - // If message is array - if (!Array.isArray(message)) { - message = [message]; - } - - message.forEach((msg: string) => { - notifications.show({ - title: t`Error`, - message: msg, - color: 'red' - }); - }); - } - - function handleSet() { - const { clearUserState } = useUserState.getState(); - - // check if passwords match - if (simpleForm.values.new_password1 !== simpleForm.values.new_password2) { - passwordError({ new_password2: t`The two password fields didn’t match` }); - return; - } - - // Set password with call to backend - api - .post(apiUrl(ApiEndpoints.auth_pwd_change), { - current_password: simpleForm.values.current_password, - new_password: simpleForm.values.new_password2 - }) - .then((val) => { - passwordError(val.data); - }) - .catch((err) => { - if (err.status === 401) { - notifications.show({ - title: t`Password Changed`, - message: t`The password was set successfully. You can now login with your new password`, - color: 'green', - autoClose: false - }); - clearUserState(); - clearCsrfCookie(); - navigate('/login'); - } else { - // compile errors - const errors: { [key: string]: string[] } = {}; - for (const val of err.response.data.errors) { - if (!errors[val.param]) { - errors[val.param] = []; - } - errors[val.param].push(val.message); - } - passwordError(errors); - } - }); - } - return ( - - - -
- - - - {t`Reset Password`} - - {user.username() && ( - - - {t`Username`} - {user.username()} - - - )} - - - - - - - - - - -
-
-
-
+ + {user.username() && ( + + + {t`User`} + {user.username()} + + + )} + + + + + + + + ); } diff --git a/src/frontend/src/pages/Auth/Layout.tsx b/src/frontend/src/pages/Auth/Layout.tsx new file mode 100644 index 0000000000..05675750f0 --- /dev/null +++ b/src/frontend/src/pages/Auth/Layout.tsx @@ -0,0 +1,74 @@ +import { Trans } from '@lingui/macro'; +import { + Button, + Center, + Container, + Divider, + Group, + Loader, + Paper, + Stack +} from '@mantine/core'; +import { Outlet, useNavigate } from 'react-router-dom'; +import SplashScreen from '../../components/SplashScreen'; +import { StylishText } from '../../components/items/StylishText'; +import { doLogout } from '../../functions/auth'; + +export default function Layout() { + return ( + +
+
+ + + +
+
+
+ ); +} + +export function Wrapper({ + children, + titleText, + logOff = false, + loader = false, + smallPadding = false +}: Readonly<{ + children?: React.ReactNode; + titleText: string; + logOff?: boolean; + loader?: boolean; + smallPadding?: boolean; +}>) { + const navigate = useNavigate(); + + return ( + + + {titleText} + + {loader && ( + + + + )} + {children} + {logOff && ( + <> + + + + )} + + + ); +} diff --git a/src/frontend/src/pages/Auth/Logged-In.tsx b/src/frontend/src/pages/Auth/LoggedIn.tsx similarity index 50% rename from src/frontend/src/pages/Auth/Logged-In.tsx rename to src/frontend/src/pages/Auth/LoggedIn.tsx index cff65b773f..d4feef1a10 100644 --- a/src/frontend/src/pages/Auth/Logged-In.tsx +++ b/src/frontend/src/pages/Auth/LoggedIn.tsx @@ -1,15 +1,14 @@ -import { Trans } from '@lingui/macro'; -import { Card, Container, Group, Loader, Stack, Text } from '@mantine/core'; +import { t } from '@lingui/macro'; import { useDebouncedCallback } from '@mantine/hooks'; import { useEffect } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { checkLoginState } from '../../functions/auth'; +import { Wrapper } from './Layout'; export default function Logged_In() { const navigate = useNavigate(); const location = useLocation(); - const checkLoginStateDebounced = useDebouncedCallback(checkLoginState, 300); useEffect(() => { @@ -17,19 +16,6 @@ export default function Logged_In() { }, [navigate]); return ( - - - - - - Checking if you are already logged in - - - - - - - - + ); } diff --git a/src/frontend/src/pages/Auth/Login.tsx b/src/frontend/src/pages/Auth/Login.tsx index 67a032ab03..28e18697da 100644 --- a/src/frontend/src/pages/Auth/Login.tsx +++ b/src/frontend/src/pages/Auth/Login.tsx @@ -1,18 +1,12 @@ -import { t } from '@lingui/macro'; -import { Center, Container, Divider, Paper, Text } from '@mantine/core'; -import { useDisclosure, useToggle } from '@mantine/hooks'; +import { Trans, t } from '@lingui/macro'; +import { Anchor, Divider, Text } from '@mantine/core'; +import { useToggle } from '@mantine/hooks'; import { useEffect, useMemo } from 'react'; import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'; import { setApiDefaults } from '../../App'; -import SplashScreen from '../../components/SplashScreen'; import { AuthFormOptions } from '../../components/forms/AuthFormOptions'; -import { - AuthenticationForm, - ModeSelector, - RegistrationForm -} from '../../components/forms/AuthenticationForm'; +import { AuthenticationForm } from '../../components/forms/AuthenticationForm'; import { InstanceOptions } from '../../components/forms/InstanceOptions'; -import { StylishText } from '../../components/items/StylishText'; import { defaultHostKey } from '../../defaults/defaultHostList'; import { checkLoginState, @@ -21,6 +15,7 @@ import { } from '../../functions/auth'; import { useServerApiState } from '../../states/ApiState'; import { useLocalState } from '../../states/LocalState'; +import { Wrapper } from './Layout'; export default function Login() { const [hostKey, setHost, hostList] = useLocalState((state) => [ @@ -35,35 +30,29 @@ export default function Login() { const hostname = hostList[hostKey] === undefined ? t`No selection` : hostList[hostKey]?.name; const [hostEdit, setHostEdit] = useToggle([false, true] as const); - const [loginMode, setMode] = useDisclosure(true); const navigate = useNavigate(); const location = useLocation(); const [searchParams] = useSearchParams(); - - useEffect(() => { - if (location.pathname === '/register') { - setMode.close(); - } else { - setMode.open(); - } - }, [location]); + const [sso_registration, registration_enabled] = useServerApiState( + (state) => [state.sso_registration_enabled, state.registration_enabled] + ); + const both_reg_enabled = + registration_enabled() || sso_registration() || false; const LoginMessage = useMemo(() => { const val = server.customize?.login_message; - if (val) { - return ( - <> - - - - dangerouslySetInnerHTML={{ __html: val }} - /> - - - ); - } - return null; + if (val == undefined) return null; + return ( + <> + + + + dangerouslySetInnerHTML={{ __html: val }} + /> + + + ); }, [server.customize]); // Data manipulation functions @@ -94,54 +83,37 @@ export default function Login() { } }, []); - // Fetch server data on mount if no server data is present - useEffect(() => { - if (server.server === null) { - fetchServerApiState(); - } - }, [server]); - - // Main rendering block return ( - -
-
- - {hostEdit ? ( - - ) : ( - <> - - - {loginMode ? t`Login` : t`Register`} - - - {loginMode ? : } - navigate(`/${newPage}`)} - /> - {LoginMessage} - - - + <> + {hostEdit ? ( + + ) : ( + <> + + + {both_reg_enabled === false && ( + + Don't have an account?{' '} + navigate('/register')} + > + Register + + )} - -
-
-
+ {LoginMessage} +
+ + + )} + ); } diff --git a/src/frontend/src/pages/Auth/Logout.tsx b/src/frontend/src/pages/Auth/Logout.tsx index 967564d738..e50f3f2bf8 100644 --- a/src/frontend/src/pages/Auth/Logout.tsx +++ b/src/frontend/src/pages/Auth/Logout.tsx @@ -1,9 +1,8 @@ -import { Trans } from '@lingui/macro'; -import { Card, Container, Group, Loader, Stack, Text } from '@mantine/core'; import { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { doLogout } from '../../functions/auth'; +import { Wrapper } from './Layout'; /* Expose a route for explicit logout via URL */ export default function Logout() { @@ -13,20 +12,5 @@ export default function Logout() { doLogout(navigate); }, []); - return ( - - - - - - Logging out - - - - - - - - - ); + return ; } diff --git a/src/frontend/src/pages/Auth/MFA.tsx b/src/frontend/src/pages/Auth/MFA.tsx new file mode 100644 index 0000000000..a4f377e603 --- /dev/null +++ b/src/frontend/src/pages/Auth/MFA.tsx @@ -0,0 +1,35 @@ +import { Trans, t } from '@lingui/macro'; +import { Button, TextInput } from '@mantine/core'; +import { useForm } from '@mantine/form'; +import { useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { handleMfaLogin } from '../../functions/auth'; +import { Wrapper } from './Layout'; + +export default function Mfa() { + const simpleForm = useForm({ initialValues: { code: '' } }); + const navigate = useNavigate(); + const location = useLocation(); + const [loginError, setLoginError] = useState(undefined); + + return ( + + + + + ); +} diff --git a/src/frontend/src/pages/Auth/MFALogin.tsx b/src/frontend/src/pages/Auth/MFALogin.tsx deleted file mode 100644 index 7bed8b29f1..0000000000 --- a/src/frontend/src/pages/Auth/MFALogin.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Trans, t } from '@lingui/macro'; -import { - Button, - Center, - Container, - Paper, - Stack, - TextInput -} from '@mantine/core'; -import { useForm } from '@mantine/form'; -import { useLocation, useNavigate } from 'react-router-dom'; - -import { useState } from 'react'; -import SplashScreen from '../../components/SplashScreen'; -import { StylishText } from '../../components/items/StylishText'; -import { LanguageContext } from '../../contexts/LanguageContext'; -import { handleMfaLogin } from '../../functions/auth'; - -export default function MFALogin() { - const simpleForm = useForm({ initialValues: { code: '' } }); - const navigate = useNavigate(); - const location = useLocation(); - const [loginError, setLoginError] = useState(undefined); - - return ( - - -
- - - - {t`Multi-Factor Login`} - - - - - - - -
-
-
- ); -} diff --git a/src/frontend/src/pages/Auth/MFASetup.tsx b/src/frontend/src/pages/Auth/MFASetup.tsx index c4c7901a55..7564ef6cb0 100644 --- a/src/frontend/src/pages/Auth/MFASetup.tsx +++ b/src/frontend/src/pages/Auth/MFASetup.tsx @@ -1,12 +1,10 @@ import { Trans, t } from '@lingui/macro'; -import { Button, Center, Container, Stack, Title } from '@mantine/core'; -import { showNotification } from '@mantine/notifications'; +import { getTotpSecret, handleVerifyTotp } from '../../functions/auth'; +import { Wrapper } from './Layout'; + +import { Button } from '@mantine/core'; import { useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { LanguageContext } from '../../contexts/LanguageContext'; -import { ApiEndpoints } from '../../enums/ApiEndpoints'; -import { authApi, doLogout, followRedirect } from '../../functions/auth'; -import { apiUrl } from '../../states/ApiState'; import { QrRegistrationForm } from '../Index/Settings/AccountSettings/QrRegistrationForm'; export default function MFASetup() { @@ -16,61 +14,24 @@ export default function MFASetup() { const [totpQr, setTotpQr] = useState<{ totp_url: string; secret: string }>(); const [value, setValue] = useState(''); - const registerTotp = async () => { - await authApi(apiUrl(ApiEndpoints.auth_totp), undefined, 'get').catch( - (err) => { - if (err.status == 404 && err.response.data.meta.secret) { - setTotpQr(err.response.data.meta); - } else { - const msg = err.response.data.errors[0].message; - showNotification({ - title: t`Failed to set up MFA`, - message: msg, - color: 'red' - }); - } - } - ); - }; - useEffect(() => { - if (!totpQr) { - registerTotp(); - } - }, [totpQr]); + getTotpSecret(setTotpQr); + }, []); return ( - -
- - - - <Trans>MFA Setup Required</Trans> - - - - - - -
-
+ + + + ); } diff --git a/src/frontend/src/pages/Auth/Register.tsx b/src/frontend/src/pages/Auth/Register.tsx new file mode 100644 index 0000000000..f8a810354c --- /dev/null +++ b/src/frontend/src/pages/Auth/Register.tsx @@ -0,0 +1,27 @@ +import { Trans, t } from '@lingui/macro'; +import { Anchor, Text } from '@mantine/core'; +import { useNavigate } from 'react-router-dom'; +import { RegistrationForm } from '../../components/forms/AuthenticationForm'; +import {} from '../../functions/auth'; +import { Wrapper } from './Layout'; + +export default function Register() { + const navigate = useNavigate(); + + return ( + + + + navigate('/login')} + > + Go back to login + + + + ); +} diff --git a/src/frontend/src/pages/Auth/Reset.tsx b/src/frontend/src/pages/Auth/Reset.tsx index 2529c7644e..95d3f480f3 100644 --- a/src/frontend/src/pages/Auth/Reset.tsx +++ b/src/frontend/src/pages/Auth/Reset.tsx @@ -1,48 +1,29 @@ import { Trans, t } from '@lingui/macro'; -import { - Button, - Center, - Container, - Stack, - TextInput, - Title -} from '@mantine/core'; +import { Button, TextInput } from '@mantine/core'; import { useForm } from '@mantine/form'; import { useNavigate } from 'react-router-dom'; - -import { LanguageContext } from '../../contexts/LanguageContext'; import { handleReset } from '../../functions/auth'; +import { Wrapper } from './Layout'; export default function Reset() { const simpleForm = useForm({ initialValues: { email: '' } }); const navigate = useNavigate(); return ( - -
- - - - <Trans>Reset password</Trans> - - - - - - - -
-
+ + + + ); } diff --git a/src/frontend/src/pages/Auth/ResetPassword.tsx b/src/frontend/src/pages/Auth/ResetPassword.tsx index c47bb96f42..845e346dfe 100644 --- a/src/frontend/src/pages/Auth/ResetPassword.tsx +++ b/src/frontend/src/pages/Auth/ResetPassword.tsx @@ -1,114 +1,47 @@ import { Trans, t } from '@lingui/macro'; -import { - Button, - Center, - Container, - PasswordInput, - Stack, - Title -} from '@mantine/core'; +import { Button, PasswordInput } from '@mantine/core'; import { useForm } from '@mantine/form'; import { notifications } from '@mantine/notifications'; import { useEffect } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; -import { api } from '../../App'; -import { LanguageContext } from '../../contexts/LanguageContext'; -import { ApiEndpoints } from '../../enums/ApiEndpoints'; -import { apiUrl } from '../../states/ApiState'; +import { handlePasswordReset } from '../../functions/auth'; +import { Wrapper } from './Layout'; export default function ResetPassword() { const simpleForm = useForm({ initialValues: { password: '' } }); const [searchParams] = useSearchParams(); const navigate = useNavigate(); - const key = searchParams.get('key'); - function invalidKey() { - notifications.show({ - title: t`Key invalid`, - message: t`You need to provide a valid key to set a new password. Check your inbox for a reset link.`, - color: 'red' - }); - navigate('/login'); - } - - function success() { - notifications.show({ - title: t`Password set`, - message: t`The password was set successfully. You can now login with your new password`, - color: 'green', - autoClose: false - }); - navigate('/login'); - } - - function passwordError(values: any) { - notifications.show({ - title: t`Reset failed`, - message: values?.errors.map((e: any) => e.message).join('\n'), - color: 'red' - }); - } - + // make sure we have a key useEffect(() => { - // make sure we have a key if (!key) { - invalidKey(); + notifications.show({ + title: t`Key invalid`, + message: t`You need to provide a valid key to set a new password. Check your inbox for a reset link.`, + color: 'red', + autoClose: false + }); } }, [key]); - function handleSet() { - // Set password with call to backend - api - .post( - apiUrl(ApiEndpoints.user_reset_set), - { - key: key, - password: simpleForm.values.password - }, - { headers: { Authorization: '' } } - ) - .then((val) => { - if (val.status === 200) { - success(); - } else { - passwordError(val.data); - } - }) - .catch((err) => { - if (err.response?.status === 400) { - passwordError(err.response.data); - } else if (err.response?.status === 401) { - success(); - } else { - passwordError(err.response.data); - } - }); - } - return ( - -
- - - - <Trans>Set new password</Trans> - - - - - - - -
-
+ + + + ); } diff --git a/src/frontend/src/pages/Auth/VerifyEmail.tsx b/src/frontend/src/pages/Auth/VerifyEmail.tsx index c6e098252d..64fc040a69 100644 --- a/src/frontend/src/pages/Auth/VerifyEmail.tsx +++ b/src/frontend/src/pages/Auth/VerifyEmail.tsx @@ -1,61 +1,33 @@ import { Trans, t } from '@lingui/macro'; -import { Button, Center, Container, Stack, Title } from '@mantine/core'; +import { Button } from '@mantine/core'; import { notifications } from '@mantine/notifications'; import { useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { api } from '../../App'; -import { LanguageContext } from '../../contexts/LanguageContext'; -import { ApiEndpoints } from '../../enums/ApiEndpoints'; -import { apiUrl } from '../../states/ApiState'; +import { handleVerifyEmail } from '../../functions/auth'; +import { Wrapper } from './Layout'; export default function VerifyEmail() { const { key } = useParams(); const navigate = useNavigate(); - function invalidKey() { - notifications.show({ - title: t`Key invalid`, - message: t`You need to provide a valid key.`, - color: 'red' - }); - navigate('/login'); - } - + // make sure we have a key useEffect(() => { - // make sure we have a key if (!key) { - invalidKey(); + notifications.show({ + title: t`Key invalid`, + message: t`You need to provide a valid key.`, + color: 'red' + }); + navigate('/login'); } }, [key]); - function handleSet() { - // Set password with call to backend - api - .post(apiUrl(ApiEndpoints.auth_email_verify), { - key: key - }) - .then((val) => { - if (val.status === 200) { - navigate('/login'); - } - }); - } - return ( - -
- - - - <Trans>Verify Email</Trans> - - - - -
-
+ + + ); } diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 1b5b32d4f1..ab48b75304 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -107,7 +107,6 @@ function EmailSection() { queryFn: () => authApi(apiUrl(ApiEndpoints.auth_email)).then((res) => res.data.data) }); - const emailAvailable = useMemo(() => { return data == undefined || data.length == 0; }, [data]); diff --git a/src/frontend/src/router.tsx b/src/frontend/src/router.tsx index 4fc9a2a6df..4ffb6ec6b8 100644 --- a/src/frontend/src/router.tsx +++ b/src/frontend/src/router.tsx @@ -7,6 +7,10 @@ import { Loadable } from './functions/loading'; export const LayoutComponent = Loadable( lazy(() => import('./components/nav/Layout')) ); +export const LoginLayoutComponent = Loadable( + lazy(() => import('./pages/Auth/Layout')) +); + export const Home = Loadable(lazy(() => import('./pages/Index/Home'))); export const CompanyDetail = Loadable( @@ -103,17 +107,19 @@ export const AdminCenter = Loadable( export const NotFound = Loadable( lazy(() => import('./components/errors/NotFound')) ); + +// Auth export const Login = Loadable(lazy(() => import('./pages/Auth/Login'))); -export const MFALogin = Loadable(lazy(() => import('./pages/Auth/MFALogin'))); -export const MFASetup = Loadable(lazy(() => import('./pages/Auth/MFASetup'))); +export const LoggedIn = Loadable(lazy(() => import('./pages/Auth/LoggedIn'))); export const Logout = Loadable(lazy(() => import('./pages/Auth/Logout'))); -export const Logged_In = Loadable(lazy(() => import('./pages/Auth/Logged-In'))); -export const Reset = Loadable(lazy(() => import('./pages/Auth/Reset'))); +export const Register = Loadable(lazy(() => import('./pages/Auth/Register'))); +export const Mfa = Loadable(lazy(() => import('./pages/Auth/MFA'))); +export const MfaSetup = Loadable(lazy(() => import('./pages/Auth/MFASetup'))); export const ChangePassword = Loadable( lazy(() => import('./pages/Auth/ChangePassword')) ); - +export const Reset = Loadable(lazy(() => import('./pages/Auth/Reset'))); export const ResetPassword = Loadable( lazy(() => import('./pages/Auth/ResetPassword')) ); @@ -173,16 +179,20 @@ export const routes = ( } /> - }> + } + errorElement={} + > } />, - } />, - } />, - } />, + } /> } />, - } /> + } />, + } />, + } />, + } /> } /> } /> - } /> } /> diff --git a/src/frontend/tests/pui_settings.spec.ts b/src/frontend/tests/pui_settings.spec.ts index 9afe28f219..f47e5577ea 100644 --- a/src/frontend/tests/pui_settings.spec.ts +++ b/src/frontend/tests/pui_settings.spec.ts @@ -13,7 +13,7 @@ test('Settings - Language / Color', async ({ page }) => { await page.getByRole('button', { name: 'Ally Access' }).click(); await page.getByRole('menuitem', { name: 'Logout' }).click(); await page.getByRole('button', { name: 'Send me an email' }).click(); - await page.getByRole('button').nth(3).click(); + await page.getByLabel('Language toggle').click(); await page.getByLabel('Select language').first().click(); await page.getByRole('option', { name: 'German' }).click(); await page.waitForTimeout(200);