From 8ea68bbad0646d99209d1821a2b71364630005b3 Mon Sep 17 00:00:00 2001 From: N3WT DE COMPET Date: Mon, 17 Feb 2025 16:11:15 +0100 Subject: [PATCH] feat: Suite de la gestion des sessions --- Back-End/Auth/views.py | 19 +++-- Back-End/N3wtSchool/settings.py | 13 +--- Front-End/src/app/[locale]/admin/layout.js | 8 +++ .../src/app/[locale]/admin/structure/page.js | 2 +- .../subscriptions/editInscription/page.js | 2 +- .../app/[locale]/admin/subscriptions/page.js | 2 +- .../[locale]/parents/editInscription/page.js | 2 +- .../src/app/[locale]/users/login/page.js | 49 ++++++++----- .../app/[locale]/users/password/new/page.js | 2 +- .../app/[locale]/users/password/reset/page.js | 2 +- .../src/app/[locale]/users/subscribe/page.js | 2 +- Front-End/src/app/layout.js | 14 ++-- Front-End/src/components/ProtectedRoute.js | 19 ++--- .../Configuration/TeachersSection.js | 2 +- Front-End/src/csrfMiddleware.js | 12 ---- Front-End/src/hooks/useCsrfToken.js | 29 -------- Front-End/src/pages/api/auth/[...nextauth].js | 72 +++++++++---------- Front-End/src/pages/protected-page.js | 42 ----------- 18 files changed, 113 insertions(+), 180 deletions(-) delete mode 100644 Front-End/src/csrfMiddleware.js delete mode 100644 Front-End/src/hooks/useCsrfToken.js delete mode 100644 Front-End/src/pages/protected-page.js diff --git a/Back-End/Auth/views.py b/Back-End/Auth/views.py index 16c71dc..2eb71a5 100644 --- a/Back-End/Auth/views.py +++ b/Back-End/Auth/views.py @@ -148,8 +148,7 @@ class ProfileSimpleView(APIView): def delete(self, request, id): return bdd.delete_object(Profile, id) -@method_decorator(csrf_protect, name='dispatch') -@method_decorator(ensure_csrf_cookie, name='dispatch') +@method_decorator(csrf_exempt, name='dispatch') class LoginView(APIView): @swagger_auto_schema( operation_description="Connexion utilisateur", @@ -168,13 +167,14 @@ class LoginView(APIView): 'errorFields': openapi.Schema(type=openapi.TYPE_OBJECT), 'errorMessage': openapi.Schema(type=openapi.TYPE_STRING), 'profil': openapi.Schema(type=openapi.TYPE_INTEGER), - 'droit': openapi.Schema(type=openapi.TYPE_INTEGER) + 'droit': openapi.Schema(type=openapi.TYPE_INTEGER), + 'id': openapi.Schema(type=openapi.TYPE_INTEGER), } )) } ) def post(self, request): - data=JSONParser().parse(request) + data = JSONParser().parse(request) validatorAuthentication = validator.ValidatorAuthentication(data=data) retour = error.returnMessage[error.WRONG_ID] validationOk, errorFields = validatorAuthentication.validate() @@ -196,13 +196,12 @@ class LoginView(APIView): else: retour = error.returnMessage[error.WRONG_ID] - return JsonResponse({ - 'errorFields':errorFields, - 'errorMessage':retour, - 'profil':user.id if user else -1, - 'droit':user.droit if user else -1, - #'jwtToken':jwt_token if profil != -1 else '' + 'errorFields': errorFields, + 'errorMessage': retour, + 'profil': user.id if user else -1, + 'droit': user.droit if user else -1, + 'id': user.id if user else -1, }, safe=False) @method_decorator(csrf_protect, name='dispatch') diff --git a/Back-End/N3wtSchool/settings.py b/Back-End/N3wtSchool/settings.py index 1ce52b6..8f08fac 100644 --- a/Back-End/N3wtSchool/settings.py +++ b/Back-End/N3wtSchool/settings.py @@ -252,19 +252,10 @@ CORS_ALLOW_ALL_HEADERS = True CORS_ALLOW_CREDENTIALS = True CORS_ALLOWED_ORIGINS = [ - 'http://localhost:3000' + os.getenv('CORS_ALLOWED_ORIGINS', 'http://localhost:3000') ] -CSRF_TRUSTED_ORIGINS = [ - 'http://localhost:3000', - 'http://localhost:8080' -] - -# CORS_ALLOWED_ORIGINS = [ -# os.getenv('CORS_ALLOWED_ORIGINS', 'http://localhost:3000') -# ] - -# CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000,http://localhost:8080').split(',') +CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000,http://localhost:8080').split(',') CSRF_COOKIE_HTTPONLY = False CSRF_COOKIE_SECURE = False diff --git a/Front-End/src/app/[locale]/admin/layout.js b/Front-End/src/app/[locale]/admin/layout.js index 3f86c1c..223dc03 100644 --- a/Front-End/src/app/[locale]/admin/layout.js +++ b/Front-End/src/app/[locale]/admin/layout.js @@ -26,6 +26,8 @@ import { import { disconnect } from '@/app/lib/authAction'; import { fetchEstablishment } from '@/app/lib/schoolAction'; +import ProtectedRoute from '@/components/ProtectedRoute'; +import { SessionProvider } from 'next-auth/react'; export default function Layout({ children, @@ -73,6 +75,9 @@ export default function Layout({ return ( <> + + + {!isLoading && (
@@ -105,6 +110,9 @@ export default function Layout({
)} + +
+
); } diff --git a/Front-End/src/app/[locale]/admin/structure/page.js b/Front-End/src/app/[locale]/admin/structure/page.js index dd1360c..b8de3f8 100644 --- a/Front-End/src/app/[locale]/admin/structure/page.js +++ b/Front-End/src/app/[locale]/admin/structure/page.js @@ -4,7 +4,7 @@ import StructureManagement from '@/components/Structure/Configuration/StructureM import ScheduleManagement from '@/components/Structure/Planning/ScheduleManagement'; import FeesManagement from '@/components/Structure/Tarification/FeesManagement'; import DjangoCSRFToken from '@/components/DjangoCSRFToken'; -import useCsrfToken from '@/hooks/useCsrfToken'; +import { useCsrfToken } from '@/context/CsrfContext'; import { ClassesProvider } from '@/context/ClassesContext'; import { createDatas, updateDatas, diff --git a/Front-End/src/app/[locale]/admin/subscriptions/editInscription/page.js b/Front-End/src/app/[locale]/admin/subscriptions/editInscription/page.js index 510e121..2b95eb9 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/editInscription/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/editInscription/page.js @@ -3,7 +3,7 @@ import React, { useState, useEffect } from 'react'; import { useSearchParams, useRouter } from 'next/navigation'; import InscriptionFormShared from '@/components/Inscription/InscriptionFormShared'; import { FE_ADMIN_SUBSCRIPTIONS_URL } from '@/utils/Url'; -import useCsrfToken from '@/hooks/useCsrfToken'; +import { useCsrfToken } from '@/context/CsrfContext'; import { mockStudent } from '@/data/mockStudent'; import { editRegisterForm, fetchRegisterForm } from '@/app/lib/subscriptionAction'; diff --git a/Front-End/src/app/[locale]/admin/subscriptions/page.js b/Front-End/src/app/[locale]/admin/subscriptions/page.js index 9c09fca..9e0b486 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/page.js @@ -42,7 +42,7 @@ import { FE_ADMIN_SUBSCRIPTIONS_EDIT_URL } from '@/utils/Url'; import DjangoCSRFToken from '@/components/DjangoCSRFToken' -import useCsrfToken from '@/hooks/useCsrfToken'; +import { useCsrfToken } from '@/context/CsrfContext'; import { fetchRegistrationFileGroups } from '@/app/lib/registerFileGroupAction'; const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true'; diff --git a/Front-End/src/app/[locale]/parents/editInscription/page.js b/Front-End/src/app/[locale]/parents/editInscription/page.js index 9d44d3f..1de9938 100644 --- a/Front-End/src/app/[locale]/parents/editInscription/page.js +++ b/Front-End/src/app/[locale]/parents/editInscription/page.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import InscriptionFormShared from '@/components/Inscription/InscriptionFormShared'; import { useSearchParams, useRouter } from 'next/navigation'; -import useCsrfToken from '@/hooks/useCsrfToken'; +import { useCsrfToken } from '@/context/CsrfContext'; import { FE_PARENTS_HOME_URL} from '@/utils/Url'; import { editRegisterForm} from '@/app/lib/subscriptionAction'; diff --git a/Front-End/src/app/[locale]/users/login/page.js b/Front-End/src/app/[locale]/users/login/page.js index f980710..64d31a5 100644 --- a/Front-End/src/app/[locale]/users/login/page.js +++ b/Front-End/src/app/[locale]/users/login/page.js @@ -9,9 +9,12 @@ import Button from '@/components/Button'; // Importez le composant Button import { User, KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires import { FE_USERS_NEW_PASSWORD_URL, - BE_AUTH_INFO_SESSION } from '@/utils/Url'; + BE_AUTH_INFO_SESSION, + FE_ADMIN_SUBSCRIPTIONS_URL, + FE_PARENTS_HOME_URL +} from '@/utils/Url'; import useLocalStorage from '@/hooks/useLocalStorage'; -import { signIn } from 'next-auth/react'; +import { signIn, getSession } from 'next-auth/react'; import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsrfToken const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true'; @@ -33,32 +36,46 @@ export default function Page() { return data.errorMessage === "" } - function handleFormLogin(formData) { + async function handleFormLogin(formData) { setIsLoading(true); - console.log('Form Data', Object.fromEntries(formData.entries())); // Affichez les entrées du FormData - console.log('csrf passé ', csrfToken); // Affichez le token CSRF - signIn('credentials', { - redirect: false, - email: formData.get('login'), - password: formData.get('password'), - csrfToken: csrfToken // Utilisez le token CSRF récupéré par le hook - }) - .then(result => { + try { + const result = await signIn('credentials', { + redirect: false, + email: formData.get('login'), + password: formData.get('password'), + }); + console.log('Sign In Result', result); setIsLoading(false); if (result.error) { setErrorMessage(result.error); } else { - router.push(result.url); + const session = await getSession(); + if (!session || !session.user) { + throw new Error('Session not found'); + } + const user = session.user; + console.log('User Session:', user); + localStorage.setItem('userId', user.id); // Stocker l'identifiant de l'utilisateur + if (user.droit === 0) { + // Vue ECOLE + } else if (user.droit === 1) { + // Vue ADMIN + router.push(FE_ADMIN_SUBSCRIPTIONS_URL); + } else if (user.droit === 2) { + // Vue PARENT + router.push(FE_PARENTS_HOME_URL); + } else { + // Cas anormal + } } - }) - .catch(error => { + } catch (error) { console.error('Error during sign in:', error); setIsLoading(false); setErrorMessage('An error occurred during sign in.'); - }); + } } if (isLoading === true) { diff --git a/Front-End/src/app/[locale]/users/password/new/page.js b/Front-End/src/app/[locale]/users/password/new/page.js index dcc108e..900e76d 100644 --- a/Front-End/src/app/[locale]/users/password/new/page.js +++ b/Front-End/src/app/[locale]/users/password/new/page.js @@ -10,7 +10,7 @@ import Button from '@/components/Button'; // Importez le composant Button import Popup from '@/components/Popup'; // Importez le composant Popup import { User } from 'lucide-react'; // Importez directement les icônes nécessaires import { FE_USERS_LOGIN_URL } from '@/utils/Url'; -import useCsrfToken from '@/hooks/useCsrfToken'; +import { useCsrfToken } from '@/context/CsrfContext'; import { sendNewPassword } from '@/app/lib/authAction'; const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true'; diff --git a/Front-End/src/app/[locale]/users/password/reset/page.js b/Front-End/src/app/[locale]/users/password/reset/page.js index cae7a16..427ad57 100644 --- a/Front-End/src/app/[locale]/users/password/reset/page.js +++ b/Front-End/src/app/[locale]/users/password/reset/page.js @@ -11,7 +11,7 @@ import Button from '@/components/Button'; // Importez le composant Button import Popup from '@/components/Popup'; import { FE_USERS_LOGIN_URL } from '@/utils/Url'; import { KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires -import useCsrfToken from '@/hooks/useCsrfToken'; +import { useCsrfToken } from '@/context/CsrfContext'; import { getResetPassword, resetPassword } from '@/app/lib/authAction'; const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true'; diff --git a/Front-End/src/app/[locale]/users/subscribe/page.js b/Front-End/src/app/[locale]/users/subscribe/page.js index 5ea51ca..768bfbc 100644 --- a/Front-End/src/app/[locale]/users/subscribe/page.js +++ b/Front-End/src/app/[locale]/users/subscribe/page.js @@ -11,7 +11,7 @@ import Button from '@/components/Button'; // Importez le composant Button import Popup from '@/components/Popup'; // Importez le composant Popup import { User, KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires import { FE_USERS_LOGIN_URL } from '@/utils/Url'; -import useCsrfToken from '@/hooks/useCsrfToken'; +import { useCsrfToken } from '@/context/CsrfContext'; import { subscribe } from '@/app/lib/authAction'; const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true'; diff --git a/Front-End/src/app/layout.js b/Front-End/src/app/layout.js index d953956..21e0d14 100644 --- a/Front-End/src/app/layout.js +++ b/Front-End/src/app/layout.js @@ -1,8 +1,7 @@ import React from 'react'; -import { NextIntlClientProvider } from 'next-intl'; -import { CsrfProvider } from '@/context/CsrfContext'; // Importez le CsrfProvider - import { getMessages } from 'next-intl/server'; +import { NextIntlClientProvider } from 'next-intl'; +import { CsrfProvider } from '@/context/CsrfContext'; import "@/css/tailwind.css"; export const metadata = { @@ -22,14 +21,15 @@ export const metadata = { }, }; -export default async function RootLayout({ children, params: { locale } }) { - const messages = await getMessages(); +export default async function RootLayout({ children, params }) { + const { locale } = params; + const messages = await getMessages(locale); // Passez le locale ici return ( - {/* Enveloppez votre application avec le CsrfProvider */} - + + {/* Passez le locale ici */} {children} diff --git a/Front-End/src/components/ProtectedRoute.js b/Front-End/src/components/ProtectedRoute.js index 43f02c5..34cdd4c 100644 --- a/Front-End/src/components/ProtectedRoute.js +++ b/Front-End/src/components/ProtectedRoute.js @@ -1,24 +1,27 @@ import React, { useEffect } from 'react'; import { useRouter } from 'next/navigation'; -import useLocalStorage from '@/hooks/useLocalStorage'; +import { useSession } from 'next-auth/react'; +import Loader from '@/components/Loader'; // Importez le composant Loader import { FE_USERS_LOGIN_URL } from '@/utils/Url'; const ProtectedRoute = ({ children }) => { + const { data: session, status } = useSession(); const router = useRouter(); - const [userId] = useLocalStorage("userId", ''); useEffect(() => { - if (!userId) { + if (status === 'loading') return; // Ne rien faire tant que le statut est "loading" + if (!session) { // Rediriger vers la page de login si l'utilisateur n'est pas connecté - router.push(FE_USERS_LOGIN_URL); + router.push(`${FE_USERS_LOGIN_URL}`); } - }, [userId, router]); + }, [session, status, router]); - if (!userId) { - return
Loading...
; + if (status === 'loading' || !session) { + return ; // Affichez un loader pendant le chargement ou si l'utilisateur n'est pas connecté } + // Afficher les enfants seulement si l'utilisateur est connecté - return userId ? children : null; + return children; }; export default ProtectedRoute; \ No newline at end of file diff --git a/Front-End/src/components/Structure/Configuration/TeachersSection.js b/Front-End/src/components/Structure/Configuration/TeachersSection.js index 01d979c..f421b3d 100644 --- a/Front-End/src/components/Structure/Configuration/TeachersSection.js +++ b/Front-End/src/components/Structure/Configuration/TeachersSection.js @@ -4,7 +4,7 @@ import Table from '@/components/Table'; import Popup from '@/components/Popup'; import ToggleSwitch from '@/components/ToggleSwitch'; import { createProfile, updateProfile } from '@/app/lib/authAction'; -import useCsrfToken from '@/hooks/useCsrfToken'; +import { useCsrfToken } from '@/context/CsrfContext'; import { DndProvider, useDrag, useDrop } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import InputText from '@/components/InputText'; diff --git a/Front-End/src/csrfMiddleware.js b/Front-End/src/csrfMiddleware.js deleted file mode 100644 index 9638410..0000000 --- a/Front-End/src/csrfMiddleware.js +++ /dev/null @@ -1,12 +0,0 @@ -import { getCsrfToken } from '@/utils/getCsrfToken'; - -export const csrfMiddleware = (handler) => { - return async (req, res) => { - const csrfToken = getCsrfToken(); - if (!csrfToken) { - console.error('CSRF Token is undefined'); - } - req.csrfToken = csrfToken; - return handler(req, res); - }; -}; \ No newline at end of file diff --git a/Front-End/src/hooks/useCsrfToken.js b/Front-End/src/hooks/useCsrfToken.js deleted file mode 100644 index a5b9ca8..0000000 --- a/Front-End/src/hooks/useCsrfToken.js +++ /dev/null @@ -1,29 +0,0 @@ -// import { useEffect, useState } from 'react'; -// import { BE_AUTH_CSRF_URL } from '@/utils/Url'; - -// const useCsrfToken = () => { -// const [token, setToken] = useState(''); - -// useEffect(() => { -// fetch(`${BE_AUTH_CSRF_URL}`, { -// method: 'GET', -// credentials: 'include' // Inclut les cookies dans la requête -// }) -// .then(response => response.json()) -// .then(data => { -// if (data) { -// if(data.csrfToken != token) { -// setToken(data.csrfToken); -// console.log('------------> CSRF Token reçu:', data.csrfToken); -// } -// } -// }) -// .catch(error => { -// console.error('Error fetching CSRF token:', error); -// }); -// }, []); - -// return token; -// }; - -// export default useCsrfToken; diff --git a/Front-End/src/pages/api/auth/[...nextauth].js b/Front-End/src/pages/api/auth/[...nextauth].js index e08d741..51b604f 100644 --- a/Front-End/src/pages/api/auth/[...nextauth].js +++ b/Front-End/src/pages/api/auth/[...nextauth].js @@ -1,7 +1,6 @@ import NextAuth from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; -import jwt from 'jsonwebtoken'; -import { csrfMiddleware } from '@/csrfMiddleware'; // Importez le middleware csrfMiddleware +import { BE_AUTH_LOGIN_URL } from '@/utils/Url'; const options = { providers: [ @@ -11,43 +10,32 @@ const options = { email: { label: 'Email', type: 'email' }, password: { label: 'Password', type: 'password' } }, - authorize: (credentials, req) => { - console.log('Credentials:', credentials); // Vérifiez si ce log s'affiche - - // Utilisez le token CSRF injecté par le middleware - const csrfToken = req.csrfToken; - console.log("data to send : ", JSON.stringify({ - email: credentials.email, - password: credentials.password - }), "csrfToken : ", csrfToken); - - return fetch(`${process.env.NEXT_PUBLIC_API_URL}/Auth/login`, { + authorize: async (credentials, req) => { + const response = await fetch(`${BE_AUTH_LOGIN_URL}`, { method: 'POST', headers: { 'Content-Type': 'application/json', - 'X-CSRFToken': csrfToken // Utiliser le token CSRF ici }, body: JSON.stringify({ email: credentials.email, password: credentials.password }), credentials: 'include' - }) - .then(response => response.text()) - .then(text => { - console.log('Response Text:', text); // Loggez la réponse - const user = JSON.parse(text); // Parsez la réponse en JSON - - if (response.ok && user) { - return user; - } else { - throw new Error(user.errorMessage || 'Invalid credentials'); - } - }) - .catch(error => { - console.error('Error during authentication:', error); - throw new Error('Authentication failed'); }); + + const user = await response.json(); + + console.log("API response:", user); + if (response.ok && user) { + const userData = { + id: user.id, + role: user.profil, + droit: user.droit + }; + return userData; + } else { + throw new Error(user.errorMessage || 'Invalid credentials'); + } } }) ], @@ -55,25 +43,35 @@ const options = { jwt: true }, callbacks: { - async jwt(token, user) { + async jwt({ token, user }) { + console.log("JWT callback called", user); if (user) { token.id = user.id; - token.email = user.email; token.role = user.role; + token.droit = user.droit; } return token; }, - async session(session, token) { - session.user.id = token.id; - session.user.email = token.email; - session.user.role = token.role; + async session({ session, token }) { + console.log("Session callback called", token); + if (!token) { + throw new Error('Token not found'); + } + session.user = { + id: token.id, + role: token.role, + droit: token.droit + }; return session; } }, pages: { signIn: '/[locale]/users/login' }, - csrf: false // Désactiver la gestion CSRF de NextAuth.js + csrf: true }; -export default csrfMiddleware((req, res) => NextAuth(req, res, options)); \ No newline at end of file +export default (req, res) => { + console.log("NextAuth handler called"); + return NextAuth(req, res, options); +}; \ No newline at end of file diff --git a/Front-End/src/pages/protected-page.js b/Front-End/src/pages/protected-page.js deleted file mode 100644 index 341959b..0000000 --- a/Front-End/src/pages/protected-page.js +++ /dev/null @@ -1,42 +0,0 @@ -import { useSession, getSession } from 'next-auth/react'; -import { useRouter } from 'next/router'; -import { useEffect } from 'react'; - -export default function ProtectedPage() { - const [session, loading] = useSession(); - const router = useRouter(); - - useEffect(() => { - if (!loading && !session) { - router.push('/auth/signin'); - } - }, [loading, session, router]); - - if (loading || !session) { - return

Loading...

; - } - - return ( -
-

Protected Page

-

Welcome, {session.user.email}

-
- ); -} - -export async function getServerSideProps(context) { - const session = await getSession(context); - - if (!session) { - return { - redirect: { - destination: '/auth/signin', - permanent: false - } - }; - } - - return { - props: { session } - }; -} \ No newline at end of file