refactor: Deplacement du JWT dans le back

This commit is contained in:
Luc SORIGNET
2025-02-21 19:22:33 +01:00
parent 214fb186fa
commit eb89a324ab
27 changed files with 145 additions and 156 deletions

View File

@ -3807,7 +3807,6 @@
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"license": "MIT",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",

15
Front-End/src/app/500.js Normal file
View File

@ -0,0 +1,15 @@
import Link from 'next/link'
import Logo from '../components/Logo'
export default function Custom500() {
return (
<div className='flex items-center justify-center min-h-screen bg-emerald-500'>
<div className='text-center p-6 '>
<Logo className="w-32 h-32 mx-auto mb-4" />
<h2 className='text-2xl font-bold text-emerald-900 mb-4'>500 | Erreur interne</h2>
<p className='text-emerald-900 mb-4'>Une erreur interne est survenue.</p>
<Link className="text-gray-900 hover:underline" href="/">Retour Accueil</Link>
</div>
</div>
)
}

View File

@ -25,8 +25,8 @@ import {
FE_ADMIN_SETTINGS_URL
} from '@/utils/Url';
import { disconnect } from '@/app/lib/authAction';
import { fetchEstablishment } from '@/app/lib/schoolAction';
import { disconnect } from '@/app/actions/authAction';
import { fetchEstablishment } from '@/app/actions/schoolAction';
import ProtectedRoute from '@/components/ProtectedRoute';
import { SessionProvider } from 'next-auth/react';

View File

@ -5,7 +5,7 @@ import { useTranslations } from 'next-intl';
import { Users, Clock, CalendarCheck, School, TrendingUp, UserCheck } from 'lucide-react';
import Loader from '@/components/Loader';
import ClasseDetails from '@/components/ClasseDetails';
import { fetchClasses } from '@/app/lib/schoolAction';
import { fetchClasses } from '@/app/actions/schoolAction';
// Composant StatCard pour afficher une statistique
const StatCard = ({ title, value, icon, change, color = "blue" }) => (

View File

@ -9,22 +9,22 @@ import { ClassesProvider } from '@/context/ClassesContext';
import { createDatas,
updateDatas,
removeDatas,
fetchSpecialities,
fetchTeachers,
fetchClasses,
fetchSchedules,
fetchRegistrationDiscounts,
fetchTuitionDiscounts,
fetchRegistrationFees,
fetchSpecialities,
fetchTeachers,
fetchClasses,
fetchSchedules,
fetchRegistrationDiscounts,
fetchTuitionDiscounts,
fetchRegistrationFees,
fetchTuitionFees,
fetchRegistrationPaymentPlans,
fetchTuitionPaymentPlans,
fetchRegistrationPaymentModes,
fetchTuitionPaymentModes } from '@/app/lib/schoolAction';
fetchTuitionPaymentModes } from '@/app/actions/schoolAction';
import SidebarTabs from '@/components/SidebarTabs';
import FilesManagement from '@/components/Structure/Files/FilesManagement';
import { fetchRegisterFormFileTemplate } from '@/app/lib/subscriptionAction';
import { fetchRegisterFormFileTemplate } from '@/app/actions/subscriptionAction';
@ -156,7 +156,7 @@ export default function Page() {
const handleRegistrationPaymentPlans = () => {
fetchRegistrationPaymentPlans()
.then(data => {
.then(data => {
setRegistrationPaymentPlans(data);
})
.catch(error => console.error('Error fetching registration payment plans:', error));
@ -164,7 +164,7 @@ export default function Page() {
const handleTuitionPaymentPlans = () => {
fetchTuitionPaymentPlans()
.then(data => {
.then(data => {
setTuitionPaymentPlans(data);
})
.catch(error => console.error('Error fetching tuition payment plans:', error));
@ -172,7 +172,7 @@ export default function Page() {
const handleRegistrationPaymentModes = () => {
fetchRegistrationPaymentModes()
.then(data => {
.then(data => {
setRegistrationPaymentModes(data);
})
.catch(error => console.error('Error fetching registration payment modes:', error));
@ -180,7 +180,7 @@ export default function Page() {
const handleTuitionPaymentModes = () => {
fetchTuitionPaymentModes()
.then(data => {
.then(data => {
setTuitionPaymentModes(data);
})
.catch(error => console.error('Error fetching tuition payment modes:', error));

View File

@ -5,7 +5,7 @@ import InscriptionFormShared from '@/components/Inscription/InscriptionFormShare
import { FE_ADMIN_SUBSCRIPTIONS_URL } from '@/utils/Url';
import { useCsrfToken } from '@/context/CsrfContext';
import { mockStudent } from '@/data/mockStudent';
import { editRegisterForm, fetchRegisterForm } from '@/app/lib/subscriptionAction';
import { editRegisterForm, fetchRegisterForm } from '@/app/actions/subscriptionAction';
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';

View File

@ -26,16 +26,16 @@ import {
archiveRegisterForm,
fetchRegisterFormFileTemplate,
fetchStudents,
editRegisterForm } from "@/app/lib/subscriptionAction"
editRegisterForm } from "@/app/actions/subscriptionAction"
import {
fetchClasses,
fetchRegistrationDiscounts,
fetchTuitionDiscounts,
fetchRegistrationFees,
fetchTuitionFees } from '@/app/lib/schoolAction';
fetchTuitionFees } from '@/app/actions/schoolAction';
import { createProfile } from '@/app/lib/authAction';
import { createProfile } from '@/app/actions/authAction';
import {
BASE_URL,
@ -43,7 +43,7 @@ import {
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
import { useCsrfToken } from '@/context/CsrfContext';
import { fetchRegistrationFileGroups } from '@/app/lib/registerFileGroupAction';
import { fetchRegistrationFileGroups } from '@/app/actions/registerFileGroupAction';
import { ESTABLISHMENT_ID } from '@/utils/Url';
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';

View File

@ -4,7 +4,7 @@ import InscriptionFormShared from '@/components/Inscription/InscriptionFormShare
import { useSearchParams, useRouter } from 'next/navigation';
import { useCsrfToken } from '@/context/CsrfContext';
import { FE_PARENTS_HOME_URL} from '@/utils/Url';
import { editRegisterForm} from '@/app/lib/subscriptionAction';
import { editRegisterForm} from '@/app/actions/subscriptionAction';
export default function Page() {
const searchParams = useSearchParams();

View File

@ -7,10 +7,10 @@ import { User, MessageSquare, LogOut, Settings, Home } from 'lucide-react'; // A
import Logo from '@/components/Logo'; // Ajout de l'importation du composant Logo
import { FE_PARENTS_HOME_URL,FE_PARENTS_MESSAGERIE_URL,FE_PARENTS_SETTINGS_URL } from '@/utils/Url'; // Ajout de l'importation de l'URL de la page d'accueil parent
import useLocalStorage from '@/hooks/useLocalStorage';
import { fetchMessages } from '@/app/lib/messagerieAction';
import { fetchMessages } from '@/app/actions/messagerieAction';
import ProtectedRoute from '@/components/ProtectedRoute';
import { SessionProvider } from 'next-auth/react';
import { disconnect } from '@/app/lib/authAction';
import { disconnect } from '@/app/actions/authAction';
import Popup from '@/components/Popup';
export default function Layout({

View File

@ -6,7 +6,7 @@ import { Edit } from 'lucide-react';
import StatusLabel from '@/components/StatusLabel';
import useLocalStorage from '@/hooks/useLocalStorage';
import { FE_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url';
import { fetchChildren } from '@/app/lib/subscriptionAction';
import { fetchChildren } from '@/app/actions/subscriptionAction';
export default function ParentHomePage() {
const [actions, setActions] = useState([]);

View File

@ -0,0 +1,7 @@
"use client";
function ErrorBoundary({
error
}) {
return <>{error.message}</>;
}

View File

@ -13,6 +13,7 @@ import {
FE_ADMIN_SUBSCRIPTIONS_URL,
FE_PARENTS_HOME_URL
} from '@/utils/Url';
import { login } from '@/app/actions/authAction';
import useLocalStorage from '@/hooks/useLocalStorage';
import { signIn, getSession } from 'next-auth/react';
import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsrfToken
@ -36,59 +37,15 @@ export default function Page() {
return data.errorMessage === ""
}
/*async function handleFormLogin(formData) {
setIsLoading(true);
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 {
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) {
console.error('Error during sign in:', error);
setIsLoading(false);
setErrorMessage('An error occurred during sign in.');
}
}*/
function handleFormLogin(formData) {
setIsLoading(true);
signIn('credentials', {
redirect: false,
login({
email: formData.get('login'),
password: formData.get('password'),
}).then(result => {
console.log('Sign In Result', result);
setIsLoading(false);
if (result.error) {
setErrorMessage(result.error);
} else {

View File

@ -11,7 +11,7 @@ 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 '@/context/CsrfContext';
import { sendNewPassword } from '@/app/lib/authAction';
import { sendNewPassword } from '@/app/actions/authAction';
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';

View File

@ -12,7 +12,7 @@ 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 '@/context/CsrfContext';
import { getResetPassword, resetPassword } from '@/app/lib/authAction';
import { getResetPassword, resetPassword } from '@/app/actions/authAction';
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';

View File

@ -12,7 +12,7 @@ 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 '@/context/CsrfContext';
import { subscribe } from '@/app/lib/authAction';
import { subscribe } from '@/app/actions/authAction';
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
export default function Page() {

View File

@ -1,6 +1,7 @@
import { signOut } from 'next-auth/react';
import { signOut, signIn, getSession } from 'next-auth/react';
import {
BE_AUTH_LOGIN_URL,
BE_AUTH_REFRESH_JWT_URL,
BE_AUTH_REGISTER_URL,
BE_AUTH_PROFILES_URL,
BE_AUTH_RESET_PASSWORD_URL,
@ -18,40 +19,47 @@ const requestResponseHandler = async (response) => {
throw error;
};
/*export const login = (data, csrfToken) => {
const request = new Request(
`${BE_AUTH_LOGIN_URL}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify(data),
credentials: 'include',
}
);
return fetch(request).then(requestResponseHandler);
};*/
export const login = (data, csrfToken) => {
const request = new Promise((resolve, reject) => {
signIn('credentials', {
/**
* Login action
*/
export const login = (data) => {
return signIn('credentials', {
redirect: false,
email: data.email,
password: data.password,
}).then(result => {
if (result.error) {
reject(new Error(result.error));
} else {
resolve(result);
}
}).catch(reject);
});
return request.then(requestResponseHandler);
})
};
/**
* Login user with API
*/
export const getJWT = (data) =>{
const request = new Request(
`${BE_AUTH_LOGIN_URL}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
credentials: 'include'
}
);
return fetch(request).then(requestResponseHandler)
}
export const refreshJWT = (data) =>{
const request = new Request(
`${BE_AUTH_REFRESH_JWT_URL}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
credentials: 'include'
}
);
return fetch(request).then(requestResponseHandler)
}
/**
* Disconnects the user after confirming the action.
* If `NEXT_PUBLIC_USE_FAKE_DATA` environment variable is set to 'true', it will log a fake disconnect and redirect to the login URL.

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch
import DraggableFileUpload from './DraggableFileUpload';
import { fetchRegistrationFileGroups } from '@/app/lib/registerFileGroupAction';
import { fetchRegistrationFileGroups } from '@/app/actions/registerFileGroupAction';
export default function FileUpload({ onFileUpload, fileToEdit = null }) {
const [fileName, setFileName] = useState('');

View File

@ -7,8 +7,8 @@ import Loader from '@/components/Loader';
import Button from '@/components/Button';
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
import Table from '@/components/Table';
import { fetchRegisterFormFileTemplate, createRegistrationFormFile, fetchRegisterForm, deleteRegisterFormFile } from '@/app/lib/subscriptionAction';
import { fetchRegistrationFileFromGroup } from '@/app/lib/registerFileGroupAction';
import { fetchRegisterFormFileTemplate, createRegistrationFormFile, fetchRegisterForm, deleteRegisterFormFile } from '@/app/actions/subscriptionAction';
import { fetchRegistrationFileFromGroup } from '@/app/actions/registerFileGroupAction';
import { Download, Upload, Trash2, Eye } from 'lucide-react';
import { BASE_URL } from '@/utils/Url';
import DraggableFileUpload from '@/components/DraggableFileUpload';

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { fetchRegistrationFileGroups } from '@/app/lib/registerFileGroupAction';
import { fetchRegistrationFileGroups } from '@/app/actions/registerFileGroupAction';
export default function RegistrationFileGroupList() {
const [groups, setGroups] = useState([]);

View File

@ -3,7 +3,7 @@ import { Plus, Edit3, Trash2, GraduationCap, Check, X, Hand } from 'lucide-react
import Table from '@/components/Table';
import Popup from '@/components/Popup';
import ToggleSwitch from '@/components/ToggleSwitch';
import { createProfile, updateProfile } from '@/app/lib/authAction';
import { createProfile, updateProfile } from '@/app/actions/authAction';
import { useCsrfToken } from '@/context/CsrfContext';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';

View File

@ -11,13 +11,13 @@ import {
editRegistrationFormFileTemplate,
deleteRegisterFormFileTemplate,
getRegisterFormFileTemplate
} from '@/app/lib/subscriptionAction';
} from '@/app/actions/subscriptionAction';
import {
fetchRegistrationFileGroups,
createRegistrationFileGroup,
deleteRegistrationFileGroup,
editRegistrationFileGroup
} from '@/app/lib/registerFileGroupAction';
} from '@/app/actions/registerFileGroupAction';
import RegistrationFileGroupForm from '@/components/RegistrationFileGroupForm';
export default function FilesManagement({ csrfToken }) {

View File

@ -1,6 +1,8 @@
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { BE_AUTH_LOGIN_URL } from '@/utils/Url';
import { getJWT, refreshJWT } from '@/app/actions/authAction';
import jwt_decode from 'jsonwebtoken'; // Changed import
const options = {
providers: [
@ -11,63 +13,66 @@ const options = {
password: { label: 'Password', type: 'password' }
},
authorize: async (credentials, req) => {
const response = await fetch(`${BE_AUTH_LOGIN_URL}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
try {
const data = {
email: credentials.email,
password: credentials.password
}),
credentials: 'include'
});
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');
const user = await getJWT(data);
if (user) {
console.log("API response:", user);
return user;
}
throw new Error('Invalid credentials');
} catch (error) {
throw new Error(error.message || 'Invalid credentials');
}
}
})
],
session: {
jwt: true,
maxAge: 24 * 60 * 60, // 1 day in seconds
updateAge: 24 * 60 * 60 // Update session every day
},
jwt: {
maxAge: 24 * 60 * 60 // 1 day in seconds
strategy:"jwt",
},
callbacks: {
async jwt({ token, user }) {
console.log("JWT callback called", user);
if (user) {
token.id = user.id;
token.role = user.role;
token.droit = user.droit;
if (user) {
token.token = user.token;
token.refresh = user.refresh;
token.tokenExpires = jwt_decode.decode(user.token).exp * 1000;
}
// Vérifie si l'access token a expiré
if (Date.now() < token.tokenExpires) {
return token;
}
// Renouvelle le token expiré
try {
const data = {refresh: token.refresh}
const res = await refreshJWT(data);
console.log(res);
token.token = res.token;
token.refresh = res.refresh;
token.tokenExpires = jwt_decode.decode(res.token).exp * 1000;
console.log("Token refreshed", token);
return token;
} catch (error) {
console.error("Erreur lors du rafraîchissement du token", error);
return token;
}
return token;
},
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;
else{
const decodedToken = jwt_decode.decode(token.token);
const {user_id,email,droit} = decodedToken;
session.user = {id:user_id,email,droit};
return session;
}
}
},
pages: {
@ -76,7 +81,4 @@ const options = {
csrf: true
};
export default (req, res) => {
console.log("NextAuth handler called");
return NextAuth(req, res, options);
};
export default (req, res) => NextAuth(req, res, options);

View File

@ -10,6 +10,7 @@ export const BE_AUTH_NEW_PASSWORD_URL = `${BASE_URL}/Auth/newPassword`
export const BE_AUTH_REGISTER_URL = `${BASE_URL}/Auth/subscribe`
export const BE_AUTH_RESET_PASSWORD_URL = `${BASE_URL}/Auth/resetPassword`
export const BE_AUTH_LOGIN_URL = `${BASE_URL}/Auth/login`
export const BE_AUTH_REFRESH_JWT_URL = `${BASE_URL}/Auth/refreshJWT`
export const BE_AUTH_LOGOUT_URL = `${BASE_URL}/Auth/logout`
export const BE_AUTH_PROFILES_URL = `${BASE_URL}/Auth/profiles`
export const BE_AUTH_CSRF_URL = `${BASE_URL}/Auth/csrf`