diff --git a/geneit_app/src/api/AuthApi.ts b/geneit_app/src/api/AuthApi.ts index 47838a0..39debf2 100644 --- a/geneit_app/src/api/AuthApi.ts +++ b/geneit_app/src/api/AuthApi.ts @@ -9,6 +9,13 @@ export enum CreateAccountResult { Error, } +export enum PasswordLoginResult { + TooManyRequests, + InvalidCredentials, + Success, + Error, +} + export interface CheckResetTokenResponse { name: string; } @@ -65,6 +72,40 @@ export class AuthApi { } } + /** + * Authenticate using an email and a password + * + * @param mail The email address to use + * @param password The password to use + */ + static async LoginWithPassword( + mail: string, + password: string + ): Promise { + const res = await APIClient.exec({ + uri: "/auth/password_login", + method: "POST", + allowFail: true, + jsonData: { + mail: mail, + password: password, + }, + }); + + switch (res.status) { + case 429: + return PasswordLoginResult.TooManyRequests; + case 401: + return PasswordLoginResult.InvalidCredentials; + case 200: + case 201: + sessionStorage.setItem(TokenStateKey, res.data.token); + return PasswordLoginResult.Success; + default: + return PasswordLoginResult.Error; + } + } + /** * Start OpenID login * diff --git a/geneit_app/src/routes/auth/LoginRoute.tsx b/geneit_app/src/routes/auth/LoginRoute.tsx index d0730d9..93e22a0 100644 --- a/geneit_app/src/routes/auth/LoginRoute.tsx +++ b/geneit_app/src/routes/auth/LoginRoute.tsx @@ -1,13 +1,24 @@ -import { Alert, CircularProgress } from "@mui/material"; +import { + Alert, + CircularProgress, + FormControl, + IconButton, + InputAdornment, + InputLabel, + OutlinedInput, + Tooltip, +} from "@mui/material"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Grid from "@mui/material/Grid"; import TextField from "@mui/material/TextField"; import Typography from "@mui/material/Typography"; import * as React from "react"; -import { AuthApi } from "../../api/AuthApi"; +import { AuthApi, PasswordLoginResult } from "../../api/AuthApi"; import { ServerApi } from "../../api/ServerApi"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; +import { VisibilityOff, Visibility } from "@mui/icons-material"; +import { useSetAtom } from "jotai"; /** * Login form @@ -16,13 +27,55 @@ export function LoginRoute(): React.ReactElement { const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(null); - const handleSubmit = (event: React.FormEvent) => { + const setAuth = useSetAtom(AuthApi.authStatus); + const navigate = useNavigate(); + + const [mail, setMail] = React.useState(""); + const [password, setPassword] = React.useState(""); + + const canSubmit = mail.length > 0 && password.length > 0; + + const [showPassword, setShowPassword] = React.useState(false); + const handleClickShowPassword = () => setShowPassword((show) => !show); + + const handleMouseDownPassword = ( + event: React.MouseEvent + ) => { event.preventDefault(); - const data = new FormData(event.currentTarget); - console.log({ - email: data.get("email"), - password: data.get("password"), - }); + }; + + const handleLoginSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + if (!canSubmit) return; + + try { + const res = await AuthApi.LoginWithPassword(mail, password); + + switch (res) { + case PasswordLoginResult.TooManyRequests: + setError( + "Trop de tentatives de connection. Veuillez réessayer ultérieurement." + ); + break; + + case PasswordLoginResult.InvalidCredentials: + setError("Identifiants saisis invalides !"); + break; + + case PasswordLoginResult.Success: + navigate("/"); + setAuth(true); + break; + + case PasswordLoginResult.Error: + setError("Echec de la connexion !"); + break; + } + } catch (e) { + console.error(e); + setError("Echec de l'authentification !"); + } + setLoading(false); }; const authWithProvider = async (id: string) => { @@ -46,9 +99,7 @@ export function LoginRoute(): React.ReactElement { return ( <> - {error === null ? ( - <> - ) : ( + {error && ( {error} @@ -57,7 +108,12 @@ export function LoginRoute(): React.ReactElement { Connexion - + setMail(e.target.value)} autoComplete="email" autoFocus /> - + + + Mot de passe + setPassword(e.target.value)} + autoComplete="current-password" + endAdornment={ + + + + {showPassword ? : } + + + + } + /> + + @@ -117,7 +196,7 @@ export function LoginRoute(): React.ReactElement { style={{ textAlign: "center", width: "100%", marginTop: "20px" }} onClick={() => authWithProvider(p.id)} > - Connection avec {p.name} + Connexion avec {p.name} ))}