From 0eed23f7d013f6963d2322f9fccfbb3ea9575bac Mon Sep 17 00:00:00 2001 From: OxSaitama Date: Wed, 11 Oct 2023 12:33:04 +0200 Subject: [PATCH 01/16] refacto refresh token --- .../{LeCoffreApi => Auth}/Id360/BaseId360.ts | 0 .../Id360/Customers/Customers.ts | 9 ++- src/front/Api/Auth/IdNot/User.ts | 10 --- src/front/Api/Auth/franceConnect/Customer.ts | 38 ----------- .../Layouts/ClientDashboard/index.tsx | 1 - .../Folder/AddClientToFolder/index.tsx | 1 - .../Layouts/LoginCallback/index.tsx | 23 ++++--- .../Layouts/LoginCallbackCustomer/index.tsx | 35 ++++++---- .../Layouts/LoginCustomer/index.tsx | 34 +++++++++- .../Components/Layouts/SelectFolder/index.tsx | 1 - src/front/Services/JwtService/JwtService.ts | 65 ++++++++++++++----- src/front/Stores/CustomerStore.ts | 18 ----- src/front/Stores/UserStore.ts | 18 ----- src/middleware.ts | 15 ++--- 14 files changed, 132 insertions(+), 136 deletions(-) rename src/front/Api/{LeCoffreApi => Auth}/Id360/BaseId360.ts (100%) rename src/front/Api/{LeCoffreApi => Auth}/Id360/Customers/Customers.ts (85%) delete mode 100644 src/front/Api/Auth/franceConnect/Customer.ts diff --git a/src/front/Api/LeCoffreApi/Id360/BaseId360.ts b/src/front/Api/Auth/Id360/BaseId360.ts similarity index 100% rename from src/front/Api/LeCoffreApi/Id360/BaseId360.ts rename to src/front/Api/Auth/Id360/BaseId360.ts diff --git a/src/front/Api/LeCoffreApi/Id360/Customers/Customers.ts b/src/front/Api/Auth/Id360/Customers/Customers.ts similarity index 85% rename from src/front/Api/LeCoffreApi/Id360/Customers/Customers.ts rename to src/front/Api/Auth/Id360/Customers/Customers.ts index edf23384..a97c6ca0 100644 --- a/src/front/Api/LeCoffreApi/Id360/Customers/Customers.ts +++ b/src/front/Api/Auth/Id360/Customers/Customers.ts @@ -7,6 +7,11 @@ export interface IConnectionUrlResponse { } } +export interface ICustomerTokens { + accessToken: string; + refreshToken: string; +} + export default class Customers extends BaseId360 { private static instance: Customers; private readonly baseURl = this.namespaceUrl.concat("/customers"); @@ -33,10 +38,10 @@ export default class Customers extends BaseId360 { } } - public async loginCallback(callbackToken: string | string[]): Promise { + public async loginCallback(callbackToken: string | string[]): Promise { const url = new URL(this.baseURl.concat(`/login-callback/${callbackToken}`)); try { - return await this.postRequest(url); + return await this.postRequest(url); } catch (err) { this.onError(err); return Promise.reject(err); diff --git a/src/front/Api/Auth/IdNot/User.ts b/src/front/Api/Auth/IdNot/User.ts index 1854979a..706ef3a3 100644 --- a/src/front/Api/Auth/IdNot/User.ts +++ b/src/front/Api/Auth/IdNot/User.ts @@ -35,14 +35,4 @@ export default class User extends BaseApiService { return Promise.reject(err); } } - - public async refreshToken(refreshToken: string): Promise<{ accessToken: string }> { - const url = new URL(`${this.baseURl}/refresh-token`); - try { - return await this.postRequest(url, {}, refreshToken); - } catch (err) { - this.onError(err); - return Promise.reject(err); - } - } } diff --git a/src/front/Api/Auth/franceConnect/Customer.ts b/src/front/Api/Auth/franceConnect/Customer.ts deleted file mode 100644 index b7c60ba5..00000000 --- a/src/front/Api/Auth/franceConnect/Customer.ts +++ /dev/null @@ -1,38 +0,0 @@ -import BaseApiService from "@Front/Api/BaseApiService"; - -export default class Customer extends BaseApiService { - private static instance: Customer; - private readonly baseURl = this.getBaseUrl().concat("/france-connect/customer"); - - private constructor() { - super(); - } - - public static getInstance() { - if (!this.instance) { - return new Customer(); - } else { - return this.instance; - } - } - - public async login(email: string) { - const url = new URL(this.baseURl.concat("/login/").concat(email)); - try { - return await this.postRequest(url); - } catch (err) { - this.onError(err); - return Promise.reject(err); - } - } - - public async refreshToken(refreshToken: string): Promise<{ accessToken: string }> { - const url = new URL(this.baseURl.concat("/refresh-token")); - try { - return await this.postRequest(url, {}, refreshToken); - } catch (err) { - this.onError(err); - return Promise.reject(err); - } - } -} diff --git a/src/front/Components/Layouts/ClientDashboard/index.tsx b/src/front/Components/Layouts/ClientDashboard/index.tsx index 1f7bce1e..6c048e86 100644 --- a/src/front/Components/Layouts/ClientDashboard/index.tsx +++ b/src/front/Components/Layouts/ClientDashboard/index.tsx @@ -31,7 +31,6 @@ export default function ClientDashboard(props: IProps) { } const folder = await Folders.getInstance().getByUid(folderUid as string, { q: { office: true, customers: true } }); - console.log(folder); const actualCustomer = folder?.customers?.find((customer) => customer.uid === jwt?.customerId); if (!actualCustomer) throw new Error("Customer not found"); diff --git a/src/front/Components/Layouts/Folder/AddClientToFolder/index.tsx b/src/front/Components/Layouts/Folder/AddClientToFolder/index.tsx index ddf1418d..656b7436 100644 --- a/src/front/Components/Layouts/Folder/AddClientToFolder/index.tsx +++ b/src/front/Components/Layouts/Folder/AddClientToFolder/index.tsx @@ -259,7 +259,6 @@ class AddClientToFolderClass extends BasePage { return Customer.hydrate(customer); }), }); - console.log(body); await Folders.getInstance().put(this.props.selectedFolderUid, body); this.props.router.push(`/folders/${this.props.selectedFolderUid}`); } diff --git a/src/front/Components/Layouts/LoginCallback/index.tsx b/src/front/Components/Layouts/LoginCallback/index.tsx index 3ca30a34..5079e38b 100644 --- a/src/front/Components/Layouts/LoginCallback/index.tsx +++ b/src/front/Components/Layouts/LoginCallback/index.tsx @@ -12,6 +12,7 @@ import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Loader from "@Front/Components/DesignSystem/Loader"; import UserStore from "@Front/Stores/UserStore"; import Link from "next/link"; +import JwtService from "@Front/Services/JwtService/JwtService"; export default function LoginCallBack() { const router = useRouter(); @@ -19,16 +20,22 @@ export default function LoginCallBack() { useEffect(() => { async function getUser() { const code = router.query["code"]; - if (!code) return; - try { - const token = await Auth.getInstance().getIdnotJwt(code as string); - if (!token) return router.push(Module.getInstance().get().modules.pages.Login.props.path); - await UserStore.instance.connect(token.accessToken, token.refreshToken); + const refreshedTokens = await JwtService.getInstance().refreshToken(); + if (refreshedTokens) { return router.push(Module.getInstance().get().modules.pages.Folder.props.path); - } catch (e) { - router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); - return; } + if (code) { + try { + const token = await Auth.getInstance().getIdnotJwt(code as string); + if (!token) return router.push(Module.getInstance().get().modules.pages.Login.props.path); + await UserStore.instance.connect(token.accessToken, token.refreshToken); + return router.push(Module.getInstance().get().modules.pages.Folder.props.path); + } catch (e) { + router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); + return; + } + } + return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); } getUser(); }), diff --git a/src/front/Components/Layouts/LoginCallbackCustomer/index.tsx b/src/front/Components/Layouts/LoginCallbackCustomer/index.tsx index 7ec5bf15..104fdb1b 100644 --- a/src/front/Components/Layouts/LoginCallbackCustomer/index.tsx +++ b/src/front/Components/Layouts/LoginCallbackCustomer/index.tsx @@ -4,33 +4,40 @@ import CoffreIcon from "@Assets/Icons/coffre.svg"; import { useRouter } from "next/router"; import React, { useEffect } from "react"; import classes from "./classes.module.scss"; -//import Module from "@Front/Config/Module"; -//import Auth from "@Front/Api/Auth/IdNot"; import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage"; import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Loader from "@Front/Components/DesignSystem/Loader"; -// import { FrontendVariables } from "@Front/Config/VariablesFront"; -// import CustomerStore from "@Front/Stores/CustomerStore"; -import Customers from "@Front/Api/LeCoffreApi/Id360/Customers/Customers"; +import Customers, { ICustomerTokens } from "@Front/Api/Auth/Id360/Customers/Customers"; import CustomerStore from "@Front/Stores/CustomerStore"; import Module from "@Front/Config/Module"; import Link from "next/link"; +import JwtService from "@Front/Services/JwtService/JwtService"; -export default function LoginCallBack() { +export default function LoginCallBackCustomer() { const router = useRouter(); useEffect(() => { const getReport = async () => { const tokenid360 = router.query["token"]; - if (!tokenid360) return; - // const variables = FrontendVariables.getInstance(); - // console.log(`${variables.DOCAPOST_API_URL}/enrollment/status/${tokenid360}/`) - // const reportRes = await fetch(`${variables.DOCAPOST_API_URL}/enrollment/status/${tokenid360}`, { method: "GET"}); - // const report = await reportRes.json() as id360ProcessResponse; - const token = await Customers.getInstance().loginCallback(tokenid360); - CustomerStore.instance.connect(token.accessToken, token.refreshToken); - router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path); + const refreshedTokens = await JwtService.getInstance().refreshToken(); + if (refreshedTokens) { + return router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path); + } + if (tokenid360) { + let token: ICustomerTokens | undefined; + try { + token = await Customers.getInstance().loginCallback(tokenid360); + } catch (e) { + console.log(e); + //router.push(Module.getInstance().get().modules.pages.CustomersLogin.props.path + "?error=1"); + return; + } + if (!token) return router.reload(); + CustomerStore.instance.connect(token.accessToken, token.refreshToken); + router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path); + } + return router.push(Module.getInstance().get().modules.pages.CustomersLogin.props.path + "?error=1"); }; getReport(); }), diff --git a/src/front/Components/Layouts/LoginCustomer/index.tsx b/src/front/Components/Layouts/LoginCustomer/index.tsx index 7968e55c..40cb3edf 100644 --- a/src/front/Components/Layouts/LoginCustomer/index.tsx +++ b/src/front/Components/Layouts/LoginCustomer/index.tsx @@ -5,14 +5,18 @@ import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography"; import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage"; import Image from "next/image"; import { useRouter } from "next/router"; -import { useCallback } from "react"; -import Customers from "@Front/Api/LeCoffreApi/Id360/Customers/Customers"; +import { useCallback, useEffect, useState } from "react"; +import Customers from "@Front/Api/Auth/Id360/Customers/Customers"; import classes from "./classes.module.scss"; import LandingImage from "./landing-connect.jpeg"; import Link from "next/link"; +import Confirm from "@Front/Components/DesignSystem/Modal/Confirm"; export default function Login() { const router = useRouter(); + const error = router.query["error"]; + + const [isErrorModalOpen, setIsErrorModalOpen] = useState(false); const redirectCustomerOnConnection = useCallback(() => { async function getCustomer() { @@ -26,6 +30,18 @@ export default function Login() { getCustomer(); }, [router]); + const openErrorModal = useCallback(() => { + setIsErrorModalOpen(true); + }, []); + + const closeErrorModal = useCallback(() => { + setIsErrorModalOpen(false); + }, []); + + useEffect(() => { + if (error === "1") openErrorModal(); + }, [error, openErrorModal]); + return (
@@ -41,6 +57,20 @@ export default function Login() {
+ +
+ + Une erreur est survenue lors de la connexion. Veuillez réessayer. + +
+
); } diff --git a/src/front/Components/Layouts/SelectFolder/index.tsx b/src/front/Components/Layouts/SelectFolder/index.tsx index 7d59f914..d23a8146 100644 --- a/src/front/Components/Layouts/SelectFolder/index.tsx +++ b/src/front/Components/Layouts/SelectFolder/index.tsx @@ -18,7 +18,6 @@ export default function SelectFolder() { async function getFolders() { const jwt = JwtService.getInstance().decodeCustomerJwt(); if (!jwt) return; - console.log(jwt); const folders = await Folders.getInstance().get({ q: { diff --git a/src/front/Services/JwtService/JwtService.ts b/src/front/Services/JwtService/JwtService.ts index dd1dbf2c..dbe8ca4d 100644 --- a/src/front/Services/JwtService/JwtService.ts +++ b/src/front/Services/JwtService/JwtService.ts @@ -1,6 +1,8 @@ import jwt_decode from "jwt-decode"; import CookieService from "../CookieService/CookieService"; -import User from "@Front/Api/Auth/IdNot/User"; +import UserStore from "@Front/Stores/CustomerStore"; +import CustomerStore from "@Front/Stores/CustomerStore"; +import { FrontendVariables } from "@Front/Config/VariablesFront"; enum PROVIDER_OPENID { idNot = "idNot", @@ -22,6 +24,7 @@ export interface IUserJwtPayload { export interface ICustomerJwtPayload { customerId: string; email: string; + exp: number; } export default class JwtService { @@ -48,23 +51,55 @@ export default class JwtService { * @description : set a cookie with a name and a value that expire in 7 days * @throws {Error} If the name or the value is empty */ - public async checkJwt() { - const decodedToken = this.decodeJwt(); + public async refreshToken() { + const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); + const variables = await FrontendVariables.getInstance(); + if (!refreshToken) return false; + const userToken = jwt_decode(refreshToken) as IUserJwtPayload; + const customerToken = jwt_decode(refreshToken) as ICustomerJwtPayload; - if (!decodedToken) return; - - const now = Math.floor(Date.now() / 1000); - - if (decodedToken.exp < now) { - const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); - - if (!refreshToken) return; - const newAccessToken: { accessToken: string } = await User.getInstance().refreshToken(refreshToken); - - if (newAccessToken) { - CookieService.getInstance().setCookie("leCoffreAccessToken", newAccessToken.accessToken); + if (userToken?.exp && userToken.exp > Math.floor(Date.now() / 1000)) { + if (userToken?.userId) { + try { + const headers = new Headers(); + headers.append("Authorization", `Bearer ${refreshToken}`); + const response = await fetch( + `${ + variables.BACK_API_PROTOCOL + variables.BACK_API_HOST + variables.BACK_API_ROOT_URL + variables.BACK_API_VERSION + }/idnot/user/auth/refresh-token`, + { method: 'POST', headers: headers }, + ); + const newAccessToken: { accessToken: string } = await response.json(); + if (newAccessToken) { + await UserStore.instance.connect(newAccessToken.accessToken, refreshToken); + return true; + } + } catch (err) { + console.log(err); + return false; + } + } else if (customerToken?.customerId) { + try { + const headers = new Headers(); + headers.append("Authorization", `Bearer ${refreshToken}`); + const response = await fetch( + `${ + variables.BACK_API_PROTOCOL + variables.BACK_API_HOST + variables.BACK_API_ROOT_URL + variables.BACK_API_VERSION + }/id360/customers/refresh-token`, + { method: 'POST', headers: headers }, + ); + const newAccessToken: { accessToken: string } = await response.json(); + if (newAccessToken) { + await CustomerStore.instance.connect(newAccessToken.accessToken, refreshToken); + return true; + } + } catch (err) { + console.log(err); + return false; + } } } + return false; } public hasRule(name: string, action: string) { diff --git a/src/front/Stores/CustomerStore.ts b/src/front/Stores/CustomerStore.ts index b360998c..2a3ff88e 100644 --- a/src/front/Stores/CustomerStore.ts +++ b/src/front/Stores/CustomerStore.ts @@ -1,6 +1,5 @@ "use client"; -import Customer from "@Front/Api/Auth/franceConnect/Customer"; import CookieService from "@Front/Services/CookieService/CookieService"; import EventEmitter from "@Front/Services/EventEmitter"; import JwtService from "@Front/Services/JwtService/JwtService"; @@ -36,23 +35,6 @@ export default class UserStore { return true; } - public async connectCustomer(email: string) { - try { - //call connection function - const customer: any = await Customer.getInstance().login(email); - - //Save tokens in cookies - CookieService.getInstance().setCookie("leCoffreAccessToken", customer.accessToken); - CookieService.getInstance().setCookie("leCoffreRefreshToken", customer.refreshToken); - - this.event.emit("connection", this.accessToken); - } catch (error) { - console.error(error); - return false; - } - return true; - } - public async disconnect() { try { //Remove tokens from cookies diff --git a/src/front/Stores/UserStore.ts b/src/front/Stores/UserStore.ts index b360998c..2a3ff88e 100644 --- a/src/front/Stores/UserStore.ts +++ b/src/front/Stores/UserStore.ts @@ -1,6 +1,5 @@ "use client"; -import Customer from "@Front/Api/Auth/franceConnect/Customer"; import CookieService from "@Front/Services/CookieService/CookieService"; import EventEmitter from "@Front/Services/EventEmitter"; import JwtService from "@Front/Services/JwtService/JwtService"; @@ -36,23 +35,6 @@ export default class UserStore { return true; } - public async connectCustomer(email: string) { - try { - //call connection function - const customer: any = await Customer.getInstance().login(email); - - //Save tokens in cookies - CookieService.getInstance().setCookie("leCoffreAccessToken", customer.accessToken); - CookieService.getInstance().setCookie("leCoffreRefreshToken", customer.refreshToken); - - this.event.emit("connection", this.accessToken); - } catch (error) { - console.error(error); - return false; - } - return true; - } - public async disconnect() { try { //Remove tokens from cookies diff --git a/src/middleware.ts b/src/middleware.ts index 13034279..39b4491e 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -15,14 +15,13 @@ export async function middleware(request: NextRequest) { // If no JWT provided, redirect to login page if (!userDecodedToken && !customerDecodedToken) return NextResponse.redirect(new URL("/login", request.url)); - // If JWT expired, redirect to login page - const token = userDecodedToken ?? customerDecodedToken; - const currentDate = new Date(); - const time = currentDate.getTime(); - const now = Math.floor(time / 1000); - if (token.exp < now) { - console.log("token expired"); - return NextResponse.redirect(new URL("/login", request.url)); + // If JWT expired, redirect to login callback page to refresh tokens + const now = Math.floor(Date.now() / 1000); + if (userDecodedToken.userId && userDecodedToken.exp < now) { + return NextResponse.redirect(new URL("/authorized-client", request.url)); + } + if (customerDecodedToken.customerId && customerDecodedToken.exp < now) { + return NextResponse.redirect(new URL("/id360/customer-callback", request.url)); } return NextResponse.next(); From 6023cf8b1d39c24eead7fb37dd490634a151dacb Mon Sep 17 00:00:00 2001 From: OxSaitama Date: Wed, 11 Oct 2023 13:01:24 +0200 Subject: [PATCH 02/16] fix redirection on login error --- src/front/Components/Layouts/LoginCallbackCustomer/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/front/Components/Layouts/LoginCallbackCustomer/index.tsx b/src/front/Components/Layouts/LoginCallbackCustomer/index.tsx index 104fdb1b..214bfca6 100644 --- a/src/front/Components/Layouts/LoginCallbackCustomer/index.tsx +++ b/src/front/Components/Layouts/LoginCallbackCustomer/index.tsx @@ -30,10 +30,9 @@ export default function LoginCallBackCustomer() { token = await Customers.getInstance().loginCallback(tokenid360); } catch (e) { console.log(e); - //router.push(Module.getInstance().get().modules.pages.CustomersLogin.props.path + "?error=1"); + router.push(Module.getInstance().get().modules.pages.CustomersLogin.props.path + "?error=1"); return; } - if (!token) return router.reload(); CustomerStore.instance.connect(token.accessToken, token.refreshToken); router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path); } From e4b1379c1269204d8f50cb5f2e9db4d15ea3d8df Mon Sep 17 00:00:00 2001 From: OxSaitama Date: Wed, 11 Oct 2023 14:52:19 +0200 Subject: [PATCH 03/16] refacto error messages --- src/front/Components/Layouts/Login/index.tsx | 35 +++++++++++++++---- .../Layouts/LoginCallback/index.tsx | 13 ++++--- .../Layouts/LoginCallbackCustomer/index.tsx | 13 ++++--- src/front/Services/JwtService/JwtService.ts | 19 +++++++--- 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/front/Components/Layouts/Login/index.tsx b/src/front/Components/Layouts/Login/index.tsx index 36609094..b5da5a40 100644 --- a/src/front/Components/Layouts/Login/index.tsx +++ b/src/front/Components/Layouts/Login/index.tsx @@ -17,7 +17,7 @@ export default function Login() { const router = useRouter(); const error = router.query["error"]; - const [isErrorModalOpen, setIsErrorModalOpen] = useState(false); + const [isErrorModalOpen, setIsErrorModalOpen] = useState(0); const redirectUserOnConnection = useCallback(() => { const variables = FrontendVariables.getInstance(); @@ -28,16 +28,25 @@ export default function Login() { ); }, [router]); - const openErrorModal = useCallback(() => { - setIsErrorModalOpen(true); + const openErrorModal = useCallback((index: number) => { + setIsErrorModalOpen(index); }, []); const closeErrorModal = useCallback(() => { - setIsErrorModalOpen(false); + setIsErrorModalOpen(0); }, []); useEffect(() => { - if (error === "1") openErrorModal(); + switch (error) { + case "1": + openErrorModal(1); + break; + case "2": + openErrorModal(2); + break; + default: + break; + } }, [error, openErrorModal]); return ( @@ -58,7 +67,7 @@ export default function Login() { + +
+ + Veuillez vous reconnecter. + +
+
); } diff --git a/src/front/Components/Layouts/LoginCallback/index.tsx b/src/front/Components/Layouts/LoginCallback/index.tsx index 5079e38b..44e1ba60 100644 --- a/src/front/Components/Layouts/LoginCallback/index.tsx +++ b/src/front/Components/Layouts/LoginCallback/index.tsx @@ -13,6 +13,7 @@ import Loader from "@Front/Components/DesignSystem/Loader"; import UserStore from "@Front/Stores/UserStore"; import Link from "next/link"; import JwtService from "@Front/Services/JwtService/JwtService"; +import CookieService from "@Front/Services/CookieService/CookieService"; export default function LoginCallBack() { const router = useRouter(); @@ -20,10 +21,6 @@ export default function LoginCallBack() { useEffect(() => { async function getUser() { const code = router.query["code"]; - const refreshedTokens = await JwtService.getInstance().refreshToken(); - if (refreshedTokens) { - return router.push(Module.getInstance().get().modules.pages.Folder.props.path); - } if (code) { try { const token = await Auth.getInstance().getIdnotJwt(code as string); @@ -35,7 +32,13 @@ export default function LoginCallBack() { return; } } - return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); + const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); + if(!refreshToken) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); + const isTokenRefreshed = await JwtService.getInstance().refreshToken(refreshToken); + if (isTokenRefreshed) { + return router.push(Module.getInstance().get().modules.pages.Folder.props.path); + } + return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=2"); } getUser(); }), diff --git a/src/front/Components/Layouts/LoginCallbackCustomer/index.tsx b/src/front/Components/Layouts/LoginCallbackCustomer/index.tsx index 214bfca6..1db7b577 100644 --- a/src/front/Components/Layouts/LoginCallbackCustomer/index.tsx +++ b/src/front/Components/Layouts/LoginCallbackCustomer/index.tsx @@ -13,6 +13,7 @@ import CustomerStore from "@Front/Stores/CustomerStore"; import Module from "@Front/Config/Module"; import Link from "next/link"; import JwtService from "@Front/Services/JwtService/JwtService"; +import CookieService from "@Front/Services/CookieService/CookieService"; export default function LoginCallBackCustomer() { const router = useRouter(); @@ -20,10 +21,6 @@ export default function LoginCallBackCustomer() { useEffect(() => { const getReport = async () => { const tokenid360 = router.query["token"]; - const refreshedTokens = await JwtService.getInstance().refreshToken(); - if (refreshedTokens) { - return router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path); - } if (tokenid360) { let token: ICustomerTokens | undefined; try { @@ -36,7 +33,13 @@ export default function LoginCallBackCustomer() { CustomerStore.instance.connect(token.accessToken, token.refreshToken); router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path); } - return router.push(Module.getInstance().get().modules.pages.CustomersLogin.props.path + "?error=1"); + const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); + if(!refreshToken) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1"); + const isTokenRefreshed = await JwtService.getInstance().refreshToken(refreshToken); + if (isTokenRefreshed) { + return router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path); + } + return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=2"); }; getReport(); }), diff --git a/src/front/Services/JwtService/JwtService.ts b/src/front/Services/JwtService/JwtService.ts index dbe8ca4d..dc2e30fe 100644 --- a/src/front/Services/JwtService/JwtService.ts +++ b/src/front/Services/JwtService/JwtService.ts @@ -35,6 +35,19 @@ export default class JwtService { return (this.instance ??= new this()); } + public getUserJwtPayload(): IUserJwtPayload | undefined { + const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken"); + if (!accessToken) return; + return jwt_decode(accessToken); + } + + public getCustomerJwtPayload(): ICustomerJwtPayload | undefined { + const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken"); + if (!accessToken) return; + return jwt_decode(accessToken); + } + + public decodeJwt(): IUserJwtPayload | undefined { const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken"); if (!accessToken) return; @@ -51,14 +64,12 @@ export default class JwtService { * @description : set a cookie with a name and a value that expire in 7 days * @throws {Error} If the name or the value is empty */ - public async refreshToken() { - const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken"); - const variables = await FrontendVariables.getInstance(); - if (!refreshToken) return false; + public async refreshToken(refreshToken: string): Promise { const userToken = jwt_decode(refreshToken) as IUserJwtPayload; const customerToken = jwt_decode(refreshToken) as ICustomerJwtPayload; if (userToken?.exp && userToken.exp > Math.floor(Date.now() / 1000)) { + const variables = FrontendVariables.getInstance(); if (userToken?.userId) { try { const headers = new Headers(); From 930b51c14015fe57c1fe08af4e793b2081164628 Mon Sep 17 00:00:00 2001 From: OxSaitama Date: Thu, 12 Oct 2023 19:44:53 +0200 Subject: [PATCH 04/16] add form errors --- package.json | 2 +- .../DesignSystem/Form/SelectField/index.tsx | 2 +- .../DeedTypes/DeedTypesCreate/index.tsx | 79 ++++++++++++------- .../Layouts/DeedTypes/DeedTypesEdit/index.tsx | 29 +++++-- .../DocumentTypes/DocumentTypesEdit/index.tsx | 25 ++++-- .../Layouts/Folder/CreateFolder/index.tsx | 5 +- .../Folder/UpdateFolderMetadata/index.tsx | 38 ++++++--- .../Layouts/Roles/RolesCreate/index.tsx | 29 +++++-- 8 files changed, 151 insertions(+), 58 deletions(-) diff --git a/package.json b/package.json index 9d3fba5b..b63744b0 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "eslint-config-next": "13.2.4", "form-data": "^4.0.0", "jwt-decode": "^3.1.2", - "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.90", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.92", "next": "13.2.4", "prettier": "^2.8.7", "react": "18.2.0", diff --git a/src/front/Components/DesignSystem/Form/SelectField/index.tsx b/src/front/Components/DesignSystem/Form/SelectField/index.tsx index 20377a29..dc0e4479 100644 --- a/src/front/Components/DesignSystem/Form/SelectField/index.tsx +++ b/src/front/Components/DesignSystem/Form/SelectField/index.tsx @@ -200,7 +200,7 @@ class SelectFieldClass extends React.Component { if (!this.state.errors) return null; return ( - {this.props.placeholder} est requis + {this.props.placeholder} ne peut pas être vide ); } diff --git a/src/front/Components/Layouts/DeedTypes/DeedTypesCreate/index.tsx b/src/front/Components/Layouts/DeedTypes/DeedTypesCreate/index.tsx index a7220d15..c12e2529 100644 --- a/src/front/Components/Layouts/DeedTypes/DeedTypesCreate/index.tsx +++ b/src/front/Components/Layouts/DeedTypes/DeedTypesCreate/index.tsx @@ -13,62 +13,77 @@ import { useRouter } from "next/router"; import { useCallback, useState } from "react"; import classes from "./classes.module.scss"; +import { validateOrReject, ValidationError } from "class-validator"; type IProps = {}; export default function DeedTypesCreate(props: IProps) { const [hasChanged, setHasChanged] = useState(false); const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); + const [validationError, setValidationError] = useState([]); const router = useRouter(); const onSubmitHandler = useCallback( async (e: React.FormEvent | null, values: { [key: string]: string }) => { try { const jwt = JwtService.getInstance().decodeJwt(); - const deedType = await DeedTypes.getInstance().post( + const deedType = DeedType.hydrate({ + name: values["name"], + description: values["description"], + office: Office.hydrate({ + uid: jwt?.office_Id, + }), + }); + try { + await validateOrReject(deedType, { groups: ["createDeedType"], forbidUnknownValues: true }); + } catch (validationErrors: Array | any) { + console.log(validationErrors); + setValidationError(validationErrors as ValidationError[]); + return; + } + + const deedTypeCreated = await DeedTypes.getInstance().post( DeedType.hydrate({ name: values["name"], description: values["description"], office: Office.hydrate({ - uid: jwt?.office_Id - }) + uid: jwt?.office_Id, + }), }), ); router.push( Module.getInstance() .get() - .modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", deedType.uid!), + .modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", deedTypeCreated.uid!), ); - } catch (e) { - console.error(e); + } catch (validationErrors: Array | any) { + console.log(validationErrors); + setValidationError(validationErrors as ValidationError[]); + return; } }, - [router], + [router, validationError], ); const closeConfirmModal = useCallback(() => { setIsConfirmModalVisible(false); }, []); - const onFieldChange = useCallback((name: string, field: any) => { - setHasChanged(true); - }, []); + const onFieldChange = useCallback((name: string, field: any) => { + setHasChanged(true); + }, []); - const redirect = useCallback(() => { - router.push( - Module.getInstance() - .get() - .modules.pages.DeedTypes.props.path - ); - }, [router]); + const redirect = useCallback(() => { + router.push(Module.getInstance().get().modules.pages.DeedTypes.props.path); + }, [router]); - const onCancel = useCallback(() => { - if (hasChanged) { - setIsConfirmModalVisible(true); - } else { - redirect(); - } - }, [hasChanged, redirect]); + const onCancel = useCallback(() => { + if (hasChanged) { + setIsConfirmModalVisible(true); + } else { + redirect(); + } + }, [hasChanged, redirect]); return ( @@ -77,10 +92,20 @@ export default function DeedTypesCreate(props: IProps) { Créer un type d'acte
- - + error.property === "name")} + /> + error.property === "description")} + />
- +
diff --git a/src/front/Components/Layouts/DeedTypes/DeedTypesEdit/index.tsx b/src/front/Components/Layouts/DeedTypes/DeedTypesEdit/index.tsx index 3b5c8f24..ab21dabd 100644 --- a/src/front/Components/Layouts/DeedTypes/DeedTypesEdit/index.tsx +++ b/src/front/Components/Layouts/DeedTypes/DeedTypesEdit/index.tsx @@ -10,9 +10,11 @@ import Module from "@Front/Config/Module"; import { DeedType } from "le-coffre-resources/dist/Admin"; import { useRouter } from "next/router"; import { useCallback, useEffect, useState } from "react"; +import { ValidationError } from "class-validator"; import classes from "./classes.module.scss"; + export default function DeedTypesEdit() { const router = useRouter(); let { deedTypeUid } = router.query; @@ -20,6 +22,7 @@ export default function DeedTypesEdit() { const [deedTypeSelected, setDeedTypeSelected] = useState(null); const [hasChanged, setHasChanged] = useState(false); const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); + const [validationError, setValidationError] = useState([]); useEffect(() => { setHasChanged(false); @@ -41,7 +44,19 @@ export default function DeedTypesEdit() { }, []); const onSubmitHandler = useCallback( - async (e: React.FormEvent | null, values: { [key: string]: string }) => { + async (e: React.FormEvent | null, values: { [key: string]: string | undefined }) => { + const deedType = DeedType.hydrate({ + uid: deedTypeUid as string, + name: values["name"], + description: values["description"], + }); + try { + await deedType.validateOrReject?.({ groups: ["updateDeedType"], forbidUnknownValues: true }); + } catch (validationErrors: Array | any) { + if(!Array.isArray(validationErrors)) return; + setValidationError(validationErrors as ValidationError[]); + return; + } try { await DeedTypes.getInstance().put( deedTypeUid as string, @@ -56,11 +71,13 @@ export default function DeedTypesEdit() { .get() .modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", deedTypeUid as string), ); - } catch (e) { - console.error(e); + } catch (validationErrors) { + if(!Array.isArray(validationErrors)) return; + setValidationError(validationErrors as ValidationError[]); + return; } }, - [deedTypeUid, router], + [deedTypeUid, router, validationError], ); const onFieldChange = useCallback((name: string, field: any) => { @@ -90,8 +107,8 @@ export default function DeedTypesEdit() { Modifier les informations de l'acte
- - + error.property === 'name')}/> + error.property === 'description')} />
- + error.property === "name")} /> error.property === "private_description")} /> error.property === "public_description")} />
diff --git a/src/front/Components/Layouts/Folder/CreateFolder/index.tsx b/src/front/Components/Layouts/Folder/CreateFolder/index.tsx index be8558a2..3637f6c4 100644 --- a/src/front/Components/Layouts/Folder/CreateFolder/index.tsx +++ b/src/front/Components/Layouts/Folder/CreateFolder/index.tsx @@ -12,7 +12,7 @@ import RadioBox from "@Front/Components/DesignSystem/RadioBox"; import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography"; import BackArrow from "@Front/Components/Elements/BackArrow"; import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage"; -import { ValidationError } from "class-validator"; +import { ValidationError } from "class-validator/types/validation/ValidationError"; import { Deed, DeedType, Office, OfficeFolder } from "le-coffre-resources/dist/Notary"; import User from "le-coffre-resources/dist/Notary"; import { NextRouter, useRouter } from "next/router"; @@ -261,6 +261,7 @@ class CreateFolderClass extends BasePage { try { await officeFolderForm.validateOrReject?.({ groups: ["createFolder"], forbidUnknownValues: false }); } catch (validationErrors) { + console.log(validationErrors); this.setState({ validationError: validationErrors as ValidationError[], }); @@ -271,7 +272,7 @@ class CreateFolderClass extends BasePage { const newOfficeFolder = await Folders.getInstance().post(officeFolderForm); if (!newOfficeFolder) return; this.props.router.push(`/folders/${newOfficeFolder.uid}`); - } catch (backError: any) { + } catch (backError) { if (!Array.isArray(backError)) return; this.setState({ validationError: backError as ValidationError[], diff --git a/src/front/Components/Layouts/Folder/UpdateFolderMetadata/index.tsx b/src/front/Components/Layouts/Folder/UpdateFolderMetadata/index.tsx index 6e3f30d5..336d55e2 100644 --- a/src/front/Components/Layouts/Folder/UpdateFolderMetadata/index.tsx +++ b/src/front/Components/Layouts/Folder/UpdateFolderMetadata/index.tsx @@ -14,6 +14,7 @@ import { NextRouter, useRouter } from "next/router"; import BasePage from "../../Base"; import classes from "./classes.module.scss"; import DateField from "@Front/Components/DesignSystem/Form/DateField"; +import { ValidationError } from "class-validator/types/validation/ValidationError"; type IProps = {}; @@ -24,12 +25,14 @@ type IPropsClass = IProps & { type IState = { selectedFolder: OfficeFolder | null; + validationError: ValidationError[]; }; class UpdateFolderMetadataClass extends BasePage { constructor(props: IPropsClass) { super(props); this.state = { selectedFolder: null, + validationError: [], }; this.onSelectedFolder = this.onSelectedFolder.bind(this); this.getFolder = this.getFolder.bind(this); @@ -56,11 +59,17 @@ class UpdateFolderMetadataClass extends BasePage {
- + error.property === "name")} + /> error.property === "folder_number")} /> -
-
- Deposit document -
-
-
- -
- {this.props.document.document_type?.name} -
-
- - Sélectionnez des documents .jpg, .pdf ou .png - -
-
- {this.state.currentFiles && this.state.currentFiles.length > 0 && ( -
- {this.state.currentFiles.map((file) => { - - const fileObj = file.file; - - return ( -
-
- Document check - - {this.shortName(fileObj.name)} - -
- Cross icon +
+ +
+
+ + Vous souhaitez envoyer un autre document à votre notaire ? + + + Glissez / Déposez votre document dans la zone prévue à cet effet ou cliquez sur la zone puis sélectionnez le + document correspondant. + +
+ +
+
+ Deposit document +
+
+
+ +
+ {this.props.document.document_type?.name}
- ); - })} +
+ + Sélectionnez des documents .jpg, .pdf ou .png + +
+
+ {this.state.currentFiles && this.state.currentFiles.length > 0 && ( +
+ {this.state.currentFiles.map((file) => { + const fileObj = file.file; + + return ( +
+
+ Document check + + {this.shortName(fileObj.name)} + +
+ Cross icon +
+ ); + })} +
+ )} +
+
- )} -
-
-
- + + {this.state.showFailedUploaded && ( + +
+ + {this.state.showFailedUploaded} + +
+
+ )} +
); } public override componentDidMount(): void {} + private onCloseAlertUpload() { + this.setState({ showFailedUploaded: null }); + } + private async onAccept() { this.setState({ isLoading: true, @@ -182,7 +203,12 @@ export default class DepositOtherDocument extends React.Component