From a2ddef568b498c41d76f6f9d30db24201c8b0ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20B=C3=A9gaudeau?= Date: Sat, 9 Sep 2023 00:40:12 +0200 Subject: [PATCH] [245] Improve the reusability of the code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: https://github.com/svalyn/svalyn-studio/issues/245 Signed-off-by: Stéphane Bégaudeau --- .../src/login/LoginWithCredentials.tsx | 24 ++----- .../src/login/useAuthentication.tsx | 62 +++++++++++++++++++ .../src/login/useAuthentication.types.ts | 23 +++++++ .../src/navbars/UserMenu.tsx | 15 +---- 4 files changed, 92 insertions(+), 32 deletions(-) create mode 100644 frontend/svalyn-studio-app/src/login/useAuthentication.tsx create mode 100644 frontend/svalyn-studio-app/src/login/useAuthentication.types.ts diff --git a/frontend/svalyn-studio-app/src/login/LoginWithCredentials.tsx b/frontend/svalyn-studio-app/src/login/LoginWithCredentials.tsx index 649f2ba5..32bdbe0d 100644 --- a/frontend/svalyn-studio-app/src/login/LoginWithCredentials.tsx +++ b/frontend/svalyn-studio-app/src/login/LoginWithCredentials.tsx @@ -24,11 +24,9 @@ import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import { useSnackbar } from 'notistack'; import { useNavigate } from 'react-router-dom'; -import { getCookie } from '../cookies/getCookie'; import { hasMinLength, useForm } from '../forms/useForm'; import { LoginWithCredentialsFormData } from './LoginWithCredentials.types'; - -const { VITE_BACKEND_URL } = import.meta.env; +import { useAuthentication } from './useAuthentication'; export const LoginWithCredentials = () => { const { data, isFormValid, getTextFieldProps } = useForm({ @@ -43,27 +41,13 @@ export const LoginWithCredentials = () => { }); const { enqueueSnackbar } = useSnackbar(); - const navigate = useNavigate(); + const { login } = useAuthentication(); + const handleLogin: React.FormEventHandler = (event) => { event.preventDefault(); - const body = new FormData(); - body.append('username', data.username); - body.append('password', data.password); - body.append('remember-me', 'true'); - - const csrfToken = getCookie('XSRF-TOKEN'); - - fetch(`${VITE_BACKEND_URL}/api/login`, { - body, - mode: 'cors', - credentials: 'include', - method: 'POST', - headers: { - 'X-XSRF-TOKEN': csrfToken, - }, - }).then((response) => { + login(data.username, data.password).then((response) => { if (response.ok) { navigate('/'); } else { diff --git a/frontend/svalyn-studio-app/src/login/useAuthentication.tsx b/frontend/svalyn-studio-app/src/login/useAuthentication.tsx new file mode 100644 index 00000000..00fa005a --- /dev/null +++ b/frontend/svalyn-studio-app/src/login/useAuthentication.tsx @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 Stéphane Bégaudeau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { getCookie } from '../cookies/getCookie'; +import { UseAuthenticationValue } from './useAuthentication.types'; + +const { VITE_BACKEND_URL } = import.meta.env; + +export const useAuthentication = (): UseAuthenticationValue => { + const login = (username: string, password: string) => { + const body = new FormData(); + body.append('username', username); + body.append('password', password); + body.append('remember-me', 'true'); + + const csrfToken = getCookie('XSRF-TOKEN'); + + return fetch(`${VITE_BACKEND_URL}/api/login`, { + body, + mode: 'cors', + credentials: 'include', + method: 'POST', + headers: { + 'X-XSRF-TOKEN': csrfToken, + }, + }); + }; + + const logout = () => { + const csrfToken = getCookie('XSRF-TOKEN'); + + return fetch(`${VITE_BACKEND_URL}/api/logout`, { + method: 'POST', + credentials: 'include', + mode: 'cors', + headers: { + 'X-XSRF-TOKEN': csrfToken, + }, + }); + }; + + return { + login, + logout, + }; +}; diff --git a/frontend/svalyn-studio-app/src/login/useAuthentication.types.ts b/frontend/svalyn-studio-app/src/login/useAuthentication.types.ts new file mode 100644 index 00000000..cf0e2bc4 --- /dev/null +++ b/frontend/svalyn-studio-app/src/login/useAuthentication.types.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 Stéphane Bégaudeau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +export interface UseAuthenticationValue { + login(username: string, password: string): Promise; + logout(): Promise; +} diff --git a/frontend/svalyn-studio-app/src/navbars/UserMenu.tsx b/frontend/svalyn-studio-app/src/navbars/UserMenu.tsx index db2588e4..53caa7ee 100644 --- a/frontend/svalyn-studio-app/src/navbars/UserMenu.tsx +++ b/frontend/svalyn-studio-app/src/navbars/UserMenu.tsx @@ -32,9 +32,8 @@ import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import { useState } from 'react'; import { Navigate, Link as RouterLink } from 'react-router-dom'; -import { getCookie } from '../cookies/getCookie'; +import { useAuthentication } from '../login/useAuthentication'; import { UserMenuProps, UserMenuState } from './UserMenu.types'; -const { VITE_BACKEND_URL } = import.meta.env; export const UserMenu = ({ viewer, onClose, ...props }: UserMenuProps) => { const [state, setState] = useState({ @@ -47,17 +46,9 @@ export const UserMenu = ({ viewer, onClose, ...props }: UserMenuProps) => { } }; + const { logout } = useAuthentication(); const handleLogout: React.MouseEventHandler = () => { - const csrfToken = getCookie('XSRF-TOKEN'); - - fetch(`${VITE_BACKEND_URL}/api/logout`, { - method: 'POST', - credentials: 'include', - mode: 'cors', - headers: { - 'X-XSRF-TOKEN': csrfToken, - }, - }).then(() => { + logout().then(() => { setState((prevState) => ({ ...prevState, redirectToLogin: true })); }); };