mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
chore: WIP uilisant d'un CSRF global à l'appli
This commit is contained in:
@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
||||
|
||||
import Logo from '@/components/Logo';
|
||||
import { useSearchParams, useRouter } from 'next/navigation'
|
||||
import InputTextIcon from '@/components/InputTextIcon';
|
||||
@ -9,13 +8,11 @@ import Loader from '@/components/Loader'; // Importez le composant Loader
|
||||
import Button from '@/components/Button'; // Importez le composant Button
|
||||
import { User, KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires
|
||||
import {
|
||||
FE_ADMIN_SUBSCRIPTIONS_EDIT_URL,
|
||||
FE_ADMIN_SUBSCRIPTIONS_URL,
|
||||
FE_PARENTS_HOME_URL,
|
||||
FE_USERS_NEW_PASSWORD_URL } from '@/utils/Url';
|
||||
FE_USERS_NEW_PASSWORD_URL,
|
||||
BE_AUTH_INFO_SESSION } from '@/utils/Url';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import useCsrfToken from '@/hooks/useCsrfToken';
|
||||
import { login } from '@/app/lib/authAction';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsrfToken
|
||||
|
||||
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
||||
|
||||
@ -30,77 +27,38 @@ export default function Page() {
|
||||
const [userId, setUserId] = useLocalStorage("userId", '') ;
|
||||
|
||||
const router = useRouter();
|
||||
const csrfToken = useCsrfToken();
|
||||
|
||||
const csrfToken = useCsrfToken(); // Utilisez le hook useCsrfToken
|
||||
|
||||
function isOK(data) {
|
||||
return data.errorMessage === ""
|
||||
}
|
||||
|
||||
function handleFormLogin(formData) {
|
||||
if (useFakeData) {
|
||||
// Simuler une réponse réussie
|
||||
const data = {
|
||||
errorFields: {},
|
||||
errorMessage: "",
|
||||
profil: "fakeProfileId"
|
||||
};
|
||||
setUserFieldError("")
|
||||
setPasswordFieldError("")
|
||||
setErrorMessage("")
|
||||
if(isOK(data)){
|
||||
localStorage.setItem('userId', data.profil); // Stocker l'identifiant de l'utilisateur
|
||||
router.push(`${FE_ADMIN_SUBSCRIPTIONS_EDIT_URL}?id=${data.profil}`);
|
||||
} else {
|
||||
if(data.errorFields){
|
||||
setUserFieldError(data.errorFields.email)
|
||||
setPasswordFieldError(data.errorFields.password);
|
||||
}
|
||||
if(data.errorMessage){
|
||||
setErrorMessage(data.errorMessage)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const data = {
|
||||
email: formData.get('login'),
|
||||
password: formData.get('password'),
|
||||
}
|
||||
login(data,csrfToken)
|
||||
.then(data => {
|
||||
console.log('Success:', data);
|
||||
setUserFieldError("")
|
||||
setPasswordFieldError("")
|
||||
setErrorMessage("")
|
||||
if(isOK(data)){
|
||||
localStorage.setItem('userId', data.profil); // Stocker l'identifiant de l'utilisateur
|
||||
if (data.droit == 0) {
|
||||
// Vue ECOLE
|
||||
} else if (data.droit == 1) {
|
||||
// Vue ADMIN
|
||||
router.push(`${FE_ADMIN_SUBSCRIPTIONS_URL}`);
|
||||
} else if (data.droit == 2) {
|
||||
// Vue PARENT
|
||||
router.push(`${FE_PARENTS_HOME_URL}`);
|
||||
} else {
|
||||
// Cas anormal
|
||||
}
|
||||
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
|
||||
|
||||
} else {
|
||||
if(data.errorFields){
|
||||
setUserFieldError(data.errorFields.email)
|
||||
setPasswordFieldError(data.errorFields.password);
|
||||
}
|
||||
if(data.errorMessage){
|
||||
setErrorMessage(data.errorMessage)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching data:', error);
|
||||
error = error.message;
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
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 => {
|
||||
console.log('Sign In Result', result);
|
||||
setIsLoading(false);
|
||||
|
||||
if (result.error) {
|
||||
setErrorMessage(result.error);
|
||||
} else {
|
||||
router.push(result.url);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during sign in:', error);
|
||||
setIsLoading(false);
|
||||
setErrorMessage('An error occurred during sign in.');
|
||||
});
|
||||
}
|
||||
|
||||
if (isLoading === true) {
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
import { CsrfProvider } from '@/context/CsrfContext'; // Importez le CsrfProvider
|
||||
|
||||
import {getMessages} from 'next-intl/server';
|
||||
import { getMessages } from 'next-intl/server';
|
||||
import "@/css/tailwind.css";
|
||||
|
||||
export const metadata = {
|
||||
@ -27,9 +28,11 @@ export default async function RootLayout({ children, params: { locale } }) {
|
||||
return (
|
||||
<html lang={locale}>
|
||||
<body>
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
<CsrfProvider> {/* Enveloppez votre application avec le CsrfProvider */}
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
</CsrfProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@ -25,8 +25,8 @@ const requestResponseHandler = async (response) => {
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
export const login = (data, csrfToken) => {
|
||||
console.log('data', data);
|
||||
const request = new Request(
|
||||
`${BE_AUTH_LOGIN_URL}`,
|
||||
{
|
||||
|
||||
39
Front-End/src/context/CsrfContext.js
Normal file
39
Front-End/src/context/CsrfContext.js
Normal file
@ -0,0 +1,39 @@
|
||||
'use client'; // Ajoutez cette ligne pour marquer ce fichier comme un composant client
|
||||
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import { BE_AUTH_CSRF_URL } from '@/utils/Url';
|
||||
import { setCsrfToken } from '@/utils/getCsrfToken';
|
||||
|
||||
const CsrfContext = createContext();
|
||||
|
||||
export const CsrfProvider = ({ children }) => {
|
||||
const [csrfToken, setCsrfTokenState] = 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 && data.csrfToken) {
|
||||
setCsrfTokenState(data.csrfToken);
|
||||
setCsrfToken(data.csrfToken); // Définir le token CSRF global
|
||||
console.log('CSRF Token reçu:', data.csrfToken);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching CSRF token:', error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<CsrfContext.Provider value={csrfToken}>
|
||||
{children}
|
||||
</CsrfContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useCsrfToken = () => {
|
||||
return useContext(CsrfContext);
|
||||
};
|
||||
12
Front-End/src/csrfMiddleware.js
Normal file
12
Front-End/src/csrfMiddleware.js
Normal file
@ -0,0 +1,12 @@
|
||||
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);
|
||||
};
|
||||
};
|
||||
@ -1,29 +1,29 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { BE_AUTH_CSRF_URL } from '@/utils/Url';
|
||||
// import { useEffect, useState } from 'react';
|
||||
// import { BE_AUTH_CSRF_URL } from '@/utils/Url';
|
||||
|
||||
const useCsrfToken = () => {
|
||||
const [token, setToken] = useState('');
|
||||
// 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);
|
||||
});
|
||||
}, []);
|
||||
// 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;
|
||||
};
|
||||
// return token;
|
||||
// };
|
||||
|
||||
export default useCsrfToken;
|
||||
// export default useCsrfToken;
|
||||
|
||||
79
Front-End/src/pages/api/auth/[...nextauth].js
Normal file
79
Front-End/src/pages/api/auth/[...nextauth].js
Normal file
@ -0,0 +1,79 @@
|
||||
import NextAuth from 'next-auth';
|
||||
import CredentialsProvider from 'next-auth/providers/credentials';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { csrfMiddleware } from '@/csrfMiddleware'; // Importez le middleware csrfMiddleware
|
||||
|
||||
const options = {
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
name: 'Credentials',
|
||||
credentials: {
|
||||
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`, {
|
||||
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');
|
||||
});
|
||||
}
|
||||
})
|
||||
],
|
||||
session: {
|
||||
jwt: true
|
||||
},
|
||||
callbacks: {
|
||||
async jwt(token, user) {
|
||||
if (user) {
|
||||
token.id = user.id;
|
||||
token.email = user.email;
|
||||
token.role = user.role;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
async session(session, token) {
|
||||
session.user.id = token.id;
|
||||
session.user.email = token.email;
|
||||
session.user.role = token.role;
|
||||
return session;
|
||||
}
|
||||
},
|
||||
pages: {
|
||||
signIn: '/[locale]/users/login'
|
||||
},
|
||||
csrf: false // Désactiver la gestion CSRF de NextAuth.js
|
||||
};
|
||||
|
||||
export default csrfMiddleware((req, res) => NextAuth(req, res, options));
|
||||
22
Front-End/src/pages/api/auth/signin.js
Normal file
22
Front-End/src/pages/api/auth/signin.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { getCsrfToken } from 'next-auth/react';
|
||||
import useCsrfToken from '@/hooks/useCsrfToken';
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
|
||||
|
||||
export default function SignIn({ csrfToken }) {
|
||||
|
||||
const csrfToken = useCsrfToken();
|
||||
return (
|
||||
<form method="post" action="/api/auth/callback/credentials">
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
<label>
|
||||
Email
|
||||
<input name="email" type="email" />
|
||||
</label>
|
||||
<label>
|
||||
Password
|
||||
<input name="password" type="password" />
|
||||
</label>
|
||||
<button type="submit">Sign in</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
9
Front-End/src/pages/api/auth/signout.js
Normal file
9
Front-End/src/pages/api/auth/signout.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { signOut } from 'next-auth/client';
|
||||
|
||||
export default function SignOut() {
|
||||
return (
|
||||
<button onClick={() => signOut({ callbackUrl: '/' })}>
|
||||
Sign out
|
||||
</button>
|
||||
);
|
||||
}
|
||||
42
Front-End/src/pages/protected-page.js
Normal file
42
Front-End/src/pages/protected-page.js
Normal file
@ -0,0 +1,42 @@
|
||||
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 <p>Loading...</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Protected Page</h1>
|
||||
<p>Welcome, {session.user.email}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await getSession(context);
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/auth/signin',
|
||||
permanent: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: { session }
|
||||
};
|
||||
}
|
||||
@ -13,6 +13,7 @@ export const BE_AUTH_LOGIN_URL = `${BASE_URL}/Auth/login`
|
||||
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`
|
||||
export const BE_AUTH_INFO_SESSION = `${BASE_URL}/Auth/infoSession`
|
||||
|
||||
// GESTION INSCRIPTION
|
||||
export const BE_SUBSCRIPTION_STUDENTS_URL = `${BASE_URL}/Subscriptions/students` // Récupère la liste des élèves inscrits ou en cours d'inscriptions
|
||||
|
||||
9
Front-End/src/utils/getCsrfToken.js
Normal file
9
Front-End/src/utils/getCsrfToken.js
Normal file
@ -0,0 +1,9 @@
|
||||
let csrfToken = '';
|
||||
|
||||
export const setCsrfToken = (token) => {
|
||||
csrfToken = token;
|
||||
};
|
||||
|
||||
export const getCsrfToken = () => {
|
||||
return csrfToken;
|
||||
};
|
||||
Reference in New Issue
Block a user