Password forgotten working

This commit is contained in:
Maxime Lalo 2023-11-29 16:05:12 +01:00
parent 7dcc4b2cb1
commit 07313fba82
6 changed files with 157 additions and 15 deletions

18
package-lock.json generated
View File

@ -43,9 +43,9 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz",
"integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==",
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
"dependencies": {
"@babel/highlight": "^7.23.4",
"chalk": "^2.4.2"
@ -223,9 +223,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.4.tgz",
"integrity": "sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==",
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz",
"integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@ -234,9 +234,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.4.tgz",
"integrity": "sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==",
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz",
"integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==",
"dependencies": {
"@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",

View File

@ -32,6 +32,10 @@ export type ILoginParams = {
totpCode: string;
};
export type IAskNewPasswordParams = {
email: string;
};
export default class Auth extends BaseApiService {
private static instance: Auth;
protected readonly namespaceUrl = this.getBaseUrl().concat("/customer");
@ -80,4 +84,14 @@ export default class Auth extends BaseApiService {
return Promise.reject(err);
}
}
public async askNewPassword(body: IAskNewPasswordParams): Promise<IMailVerifyReturn> {
const url = new URL(this.baseURl.concat("/ask-new-password"));
try {
return this.postRequest<IMailVerifyReturn>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -0,0 +1,29 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
max-width: 530px;
margin: auto;
margin-top: 220px;
.title {
text-align: left;
@media (max-width: $screen-s) {
font-family: 48px;
}
}
.form {
margin-top: 32px;
.password_indication {
margin-top: 8px;
margin-bottom: 24px;
}
.submit_button {
margin-top: 32px;
}
}
}

View File

@ -0,0 +1,43 @@
import React from "react";
import classes from "./classes.module.scss";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import Form from "@Front/Components/DesignSystem/Form";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { ValidationError } from "class-validator";
type IProps = {
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
validationErrors: ValidationError[];
};
export default function PasswordForgotten(props: IProps) {
const { onSubmit, validationErrors } = props;
return (
<div className={classes["root"]}>
<Typography typo={ITypo.H1}>
<div className={classes["title"]}>Réinitialisez votre mot de passe</div>
</Typography>
<Form className={classes["form"]} onSubmit={onSubmit}>
<TextField
placeholder="Mot de passe"
name="password"
validationError={validationErrors.find((error) => error.property === "password")}
password
/>
<Typography typo={ITypo.CAPTION_14} color={ITypoColor.GREY} className={classes["password_indication"]}>
Au moins 8 caractères dont 1 majuscule, 1 minuscule et 1 chiffre.
</Typography>
<TextField
placeholder="Confirmation du mot de passe"
name="confirm_password"
validationError={validationErrors.find((error) => error.property === "confirm_password")}
password
/>
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
Valider
</Button>
</Form>
</div>
);
}

View File

@ -5,14 +5,29 @@ import Form from "@Front/Components/DesignSystem/Form";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { ValidationError } from "class-validator";
import Link from "next/link";
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
type IProps = {
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
validationErrors: ValidationError[];
onPasswordForgotClicked: () => void;
};
export default function StepPassword(props: IProps) {
const { onSubmit, validationErrors } = props;
const { onSubmit, validationErrors, onPasswordForgotClicked } = props;
const [isModalOpened, setIsModalOpened] = React.useState(false);
const closeModal = () => {
setIsModalOpened(false);
};
const openModal = () => {
setIsModalOpened(true);
};
const onModalAccept = () => {
onPasswordForgotClicked();
setIsModalOpened(false);
};
return (
<div className={classes["root"]}>
@ -26,15 +41,29 @@ export default function StepPassword(props: IProps) {
validationError={validationErrors.find((error) => error.property === "password")}
password
/>
<Link href="/forgot-password">
<div onClick={openModal}>
<Typography typo={ITypo.P_16} className={classes["forgot-password"]}>
Mot de passe oublié ?
</Typography>
</Link>
</div>
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
Valider
</Button>
</Form>
<Confirm
isOpen={isModalOpened}
onClose={closeModal}
showCancelButton={true}
onAccept={onModalAccept}
closeBtn
header={"Mot de passe oublié ?"}
confirmText={"Valider"}>
<div className={classes["modal-content"]}>
<Typography typo={ITypo.P_16} className={classes["text"]}>
Un code à usage unique va vous être envoyé par sms pour réinitialiser votre mot de passe.
</Typography>
</div>
</Confirm>
</div>
);
}

View File

@ -15,12 +15,14 @@ import StepNewPassword from "./StepNewPassword";
import CustomerStore from "@Front/Stores/CustomerStore";
import Module from "@Front/Config/Module";
import { TotpCodesReasons } from "le-coffre-resources/dist/Customer/TotpCodes";
import PasswordForgotten from "./PasswordForgotten";
export enum LoginStep {
EMAIL,
TOTP,
PASSWORD,
NEW_PASSWORD,
PASSWORD_FORGOTTEN,
}
export default function Login() {
const router = useRouter();
@ -85,6 +87,8 @@ export default function Login() {
// If it's first connection, show the form for first connection
if (res.reason === TotpCodesReasons.FIRST_LOGIN) setStep(LoginStep.NEW_PASSWORD);
// If it's password forgotten, show the form for password forgotten
else if (res.reason === TotpCodesReasons.RESET_PASSWORD) setStep(LoginStep.PASSWORD_FORGOTTEN);
// Else just login normally
else setStep(LoginStep.PASSWORD);
} catch (error: any) {
@ -105,7 +109,6 @@ export default function Login() {
const onNewPasswordSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try {
console.log(values);
if (!values["password"] || !values["confirm_password"]) return;
if (values["password"] !== values["confirm_password"]) {
setValidationErrors([
@ -159,6 +162,21 @@ export default function Login() {
[email, router, totpCode],
);
const onPasswordForgotClicked = useCallback(async () => {
try {
const res = await Auth.getInstance().askNewPassword({ email });
setPartialPhoneNumber(res.partialPhoneNumber);
setStep(LoginStep.TOTP);
} catch (error: any) {
// If token already exists and is still valid redirect to the connect/register page
if (error.http_status === 425) {
setStep(LoginStep.TOTP);
return;
}
return;
}
}, [email]);
return (
<DefaultDoubleSidePage title={"Login"} image={LandingImage}>
<div className={classes["root"]}>
@ -166,8 +184,17 @@ export default function Login() {
{step === LoginStep.TOTP && (
<StepTotp onSubmit={onSmsCodeSubmit} validationErrors={validationErrors} partialPhoneNumber={partialPhoneNumber} />
)}
{step === LoginStep.PASSWORD && <StepPassword onSubmit={onPasswordSubmit} validationErrors={validationErrors} />}
{step === LoginStep.PASSWORD && (
<StepPassword
onSubmit={onPasswordSubmit}
validationErrors={validationErrors}
onPasswordForgotClicked={onPasswordForgotClicked}
/>
)}
{step === LoginStep.NEW_PASSWORD && <StepNewPassword onSubmit={onNewPasswordSubmit} validationErrors={validationErrors} />}
{step === LoginStep.PASSWORD_FORGOTTEN && (
<PasswordForgotten onSubmit={onNewPasswordSubmit} validationErrors={validationErrors} />
)}
</div>
<Confirm
isOpen={isErrorModalOpen}