From ae84ae88224f68d04ba4e0fa56fc6ace33511674 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Tue, 13 Jun 2023 10:06:04 +0200 Subject: [PATCH] Can request account creation from web app --- geneit_app/src/App.tsx | 2 + geneit_app/src/api/AuthApi.ts | 40 +++++ geneit_app/src/routes/auth/LoginRoute.tsx | 3 +- .../src/routes/auth/NewAccountRoute.tsx | 156 ++++++++++++++++++ .../src/controllers/auth_controller.rs | 21 +-- 5 files changed, 208 insertions(+), 14 deletions(-) create mode 100644 geneit_app/src/routes/auth/NewAccountRoute.tsx diff --git a/geneit_app/src/App.tsx b/geneit_app/src/App.tsx index dfff5ac..4f3161f 100644 --- a/geneit_app/src/App.tsx +++ b/geneit_app/src/App.tsx @@ -9,6 +9,7 @@ import { useAtom } from "jotai"; import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage"; import { PasswordForgottenRoute } from "./routes/auth/PasswordForgottenRoute"; import { ResetPasswordRoute } from "./routes/auth/ResetPasswordRoute"; +import { NewAccountRoute } from "./routes/auth/NewAccountRoute"; /** * Core app @@ -24,6 +25,7 @@ function App() { }> } /> } /> + } /> } diff --git a/geneit_app/src/api/AuthApi.ts b/geneit_app/src/api/AuthApi.ts index 9a62fc0..47838a0 100644 --- a/geneit_app/src/api/AuthApi.ts +++ b/geneit_app/src/api/AuthApi.ts @@ -1,6 +1,14 @@ import { atom } from "jotai"; import { APIClient } from "./ApiClient"; +export enum CreateAccountResult { + TooManyRequests, + BadInputData, + MailAlreadyExists, + Success, + Error, +} + export interface CheckResetTokenResponse { name: string; } @@ -25,6 +33,38 @@ export class AuthApi { return sessionStorage.getItem(TokenStateKey)!; } + /** + * Create a new account + */ + static async CreateAccount( + name: string, + mail: string + ): Promise { + const res = await APIClient.exec({ + uri: "/auth/create_account", + method: "POST", + allowFail: true, + jsonData: { + name: name, + email: mail, + }, + }); + + switch (res.status) { + case 429: + return CreateAccountResult.TooManyRequests; + case 400: + return CreateAccountResult.BadInputData; + case 409: + return CreateAccountResult.MailAlreadyExists; + case 200: + case 201: + return CreateAccountResult.Success; + default: + return CreateAccountResult.Error; + } + } + /** * Start OpenID login * diff --git a/geneit_app/src/routes/auth/LoginRoute.tsx b/geneit_app/src/routes/auth/LoginRoute.tsx index 550dd03..d0730d9 100644 --- a/geneit_app/src/routes/auth/LoginRoute.tsx +++ b/geneit_app/src/routes/auth/LoginRoute.tsx @@ -100,9 +100,8 @@ export function LoginRoute(): React.ReactElement { - {" "} Créer un nouveau compte diff --git a/geneit_app/src/routes/auth/NewAccountRoute.tsx b/geneit_app/src/routes/auth/NewAccountRoute.tsx new file mode 100644 index 0000000..5a39176 --- /dev/null +++ b/geneit_app/src/routes/auth/NewAccountRoute.tsx @@ -0,0 +1,156 @@ +import { + Alert, + Box, + Button, + CircularProgress, + TextField, + Typography, +} from "@mui/material"; +import React from "react"; +import { ServerApi } from "../../api/ServerApi"; +import { Link } from "react-router-dom"; +import { AuthSingleMessage } from "../../widgets/AuthSingleMessage"; +import { AuthApi, CreateAccountResult } from "../../api/AuthApi"; + +export function NewAccountRoute(): React.ReactElement { + const [mail, setMail] = React.useState(""); + const [name, setName] = React.useState(""); + + const [showErrors, setShowErrors] = React.useState(false); + const [loading, setLoading] = React.useState(false); + const [error, setError] = React.useState(null); + const [success, setSuccess] = React.useState(false); + + const nameLen = ServerApi.Config.constraints.user_name_len; + const mailLen = ServerApi.Config.constraints.mail_len; + + const mailValid = mail.length >= mailLen.min && mail.length <= mailLen.max; + const nameValid = name.length >= nameLen.min && name.length <= nameLen.max; + const canSubmit = mailValid && nameValid; + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + setShowErrors(true); + if (!canSubmit) return; + setLoading(true); + try { + const res = await AuthApi.CreateAccount(name, mail); + switch (res) { + case CreateAccountResult.Success: + setSuccess(true); + break; + + case CreateAccountResult.TooManyRequests: + setError("Trop de tentatives. Veuillez réessayer ultérieurement."); + break; + + case CreateAccountResult.MailAlreadyExists: + setError( + "Cette adresse mail est associée à un compte existant. Veuillez essayer de vous connecter ou de réinitialiser votre mot de passe." + ); + break; + + case CreateAccountResult.BadInputData: + setError("Les données saisies sont invalides !"); + break; + + case CreateAccountResult.Error: + setError("Une erreur a survenue lors de la création du compte !"); + break; + } + } catch (e) { + console.error(e); + setError( + "Une erreur a survenue lors de la tentative de création de compte !" + ); + } + + setLoading(false); + }; + + if (loading) + return ( + <> + + + ); + + if (success) + return ( + + ); + + return ( + <> + {error && ( + + {error} + + )} + + + Nouveau compte + + + + setName(v.target.value)} + id="name" + label="Nom" + autoComplete="name" + autoFocus + inputProps={{ + maxLength: nameLen.max, + }} + helperText={`Saisissez votre nom (entre ${nameLen.min} et ${nameLen.max} caractères)`} + /> + setMail(v.target.value)} + label="Adresse mail" + autoComplete="email" + autoFocus + inputProps={{ maxLength: mailLen.max }} + helperText={`Saisissez votre adresse mail (entre ${mailLen.min} et ${mailLen.max} caractères)`} + /> + + + + + + + Retour au formulaire de connexion + + + + ); +} diff --git a/geneit_backend/src/controllers/auth_controller.rs b/geneit_backend/src/controllers/auth_controller.rs index 804d436..155a4de 100644 --- a/geneit_backend/src/controllers/auth_controller.rs +++ b/geneit_backend/src/controllers/auth_controller.rs @@ -19,7 +19,6 @@ pub async fn create_account(remote_ip: RemoteIP, req: web::Json HttpResult { if !user.active { log::error!("Auth failed: account for mail {} is disabled!", user.email); - return Ok(HttpResponse::ExpectationFailed().json("Ce compte est désactivé !")); + return Ok(HttpResponse::ExpectationFailed().json("This account is disabled!")); } Ok(HttpResponse::Ok().json(LoginResponse { @@ -271,16 +272,13 @@ pub async fn finish_openid_login( if user_info.email_verified != Some(true) { log::error!("Email is not verified!"); - return Ok( - HttpResponse::Unauthorized().json("Email non vérifié par le fournisseur d'identité !") - ); + return Ok(HttpResponse::Unauthorized().json("Email unverified by IDP!")); } let mail = match user_info.email { Some(m) => m, None => { - return Ok(HttpResponse::Unauthorized() - .json("Email non spécifié par le fournisseur d'identité !")); + return Ok(HttpResponse::Unauthorized().json("Email not provided by the IDP!")); } }; @@ -290,8 +288,7 @@ pub async fn finish_openid_login( (Some(name), _, _) => name, (None, Some(g), Some(f)) => format!("{g} {f}"), (_, _, _) => { - return Ok(HttpResponse::Unauthorized() - .json("Nom non spécifié par le fournisseur d'identité !")); + return Ok(HttpResponse::Unauthorized().json("Name unspecified by the IDP!")); } };