nice connexion + ooublications counter

This commit is contained in:
Louis Mazin 2026-09-25 11:02:14 +02:00
parent 4bf1370543
commit 044751c6d9
4 changed files with 1042 additions and 145 deletions

View File

@ -149,7 +149,7 @@ from datetime import timedelta
SIMPLE_JWT = { SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1), 'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7), 'REFRESH_TOKEN_LIFETIME': timedelta(days=36500),
'ROTATE_REFRESH_TOKENS': True, 'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True, 'BLACKLIST_AFTER_ROTATION': True,
'AUTH_HEADER_TYPES': ('Bearer',), 'AUTH_HEADER_TYPES': ('Bearer',),

View File

@ -22,11 +22,12 @@ function expireSession() {
} }
} }
function refreshAccessToken() { export function refreshAccessToken() {
if (!refreshPromise) { if (!refreshPromise) {
const refresh = localStorage.getItem('refresh'); const refresh = localStorage.getItem('refresh');
if (!refresh) return Promise.reject(new Error('Aucun jeton de renouvellement.'));
refreshPromise = refreshClient.post('/auth/refresh/', { refresh }).then(({ data }) => { refreshPromise = refreshClient.post('/auth/refresh/', { refresh }).then(({ data }) => {
// Une déconnexion ou une nouvelle connexion peut avoir eu lieu pendant la requête.
if (localStorage.getItem('refresh') !== refresh) { if (localStorage.getItem('refresh') !== refresh) {
throw new Error('La session a changé.'); throw new Error('La session a changé.');
} }
@ -43,11 +44,19 @@ function refreshAccessToken() {
return refreshPromise; return refreshPromise;
} }
api.interceptors.request.use((config) => { api.interceptors.request.use(async (config) => {
const publicRequest = ['/auth/login/', '/users/register/'].includes(config.url); const publicRequest = ['/auth/login/', '/users/register/'].includes(config.url);
const token = localStorage.getItem('token');
config._publicRequest = publicRequest; config._publicRequest = publicRequest;
if (token && !publicRequest) config.headers.Authorization = `Bearer ${token}`; if (publicRequest) {
delete config.headers.Authorization;
return config;
}
let token = localStorage.getItem('token');
if (!token && localStorage.getItem('refresh')) {
token = await refreshAccessToken();
}
if (token) config.headers.Authorization = `Bearer ${token}`;
else delete config.headers.Authorization; else delete config.headers.Authorization;
return config; return config;
}); });
@ -65,7 +74,6 @@ api.interceptors.response.use(
request._retry = true; request._retry = true;
const currentToken = localStorage.getItem('token'); const currentToken = localStorage.getItem('token');
const sentToken = request.headers.Authorization; const sentToken = request.headers.Authorization;
// Une autre requête a peut-être déjà renouvelé la session.
const access = currentToken && sentToken !== `Bearer ${currentToken}` const access = currentToken && sentToken !== `Bearer ${currentToken}`
? currentToken : await refreshAccessToken(); ? currentToken : await refreshAccessToken();
request.headers.Authorization = `Bearer ${access}`; request.headers.Authorization = `Bearer ${access}`;

View File

@ -14,26 +14,32 @@ export const AuthProvider = ({ children }) => {
setIsAdmin(false); setIsAdmin(false);
}; };
window.addEventListener('auth:logout', onLogout); window.addEventListener('auth:logout', onLogout);
const checkAuth = async () => { const checkAuth = async () => {
const token = localStorage.getItem('token'); if (localStorage.getItem('token') || localStorage.getItem('refresh')) {
if (token) {
try { try {
// Verify token by fetching user profile const { data: userData } = await api.get('/users/me/');
const response = await api.get('/users/me/'); if (!cancelled && (localStorage.getItem('token') || localStorage.getItem('refresh'))) {
const userData = response.data;
if (!cancelled && localStorage.getItem('token')) {
localStorage.setItem('user', JSON.stringify(userData)); localStorage.setItem('user', JSON.stringify(userData));
setUser(userData); setUser(userData);
setIsAdmin(userData.is_admin || false); setIsAdmin(Boolean(userData.is_admin));
} }
} catch (error) { } catch (error) {
console.error('Failed to verify authentication', error); console.error('Failed to verify authentication', error);
// Une panne réseau ne doit pas effacer la session conservée localement.
if (!cancelled && !error.response && localStorage.getItem('refresh')) {
try {
const cachedUser = JSON.parse(localStorage.getItem('user'));
if (cachedUser) {
setUser(cachedUser);
setIsAdmin(Boolean(cachedUser.is_admin));
}
} catch {
// Le profil en cache est illisible : une prochaine requête pourra le récupérer.
}
}
} }
} }
if (!cancelled) setLoading(false); if (!cancelled) setLoading(false);
}; };
@ -45,52 +51,31 @@ export const AuthProvider = ({ children }) => {
}, []); }, []);
const login = async (username, password) => { const login = async (username, password) => {
const response = await api.post('/auth/login/', { const response = await api.post('/auth/login/', { username, password });
username,
password,
});
const { access, refresh } = response.data; const { access, refresh } = response.data;
localStorage.setItem('token', access); localStorage.setItem('token', access);
localStorage.setItem('refresh', refresh); localStorage.setItem('refresh', refresh);
// Fetch user info after login const { data: userData } = await api.get('/users/me/');
const userResponse = await api.get('/users/me/');
const userData = userResponse.data;
localStorage.setItem('user', JSON.stringify(userData)); localStorage.setItem('user', JSON.stringify(userData));
setUser(userData); setUser(userData);
setIsAdmin(userData.is_admin || false); setIsAdmin(Boolean(userData.is_admin));
return userData; return userData;
}; };
const register = async (data) => { const register = async (data) => {
const response = await api.post('/users/register/', data); const response = await api.post('/users/register/', data);
return response.data; return response.data;
}; };
const logout = () => { const logout = () => {
clearSession(); clearSession();
setUser(null); setUser(null);
setIsAdmin(false); setIsAdmin(false);
}; };
return ( return (
<AuthContext.Provider <AuthContext.Provider value={{ user, login, register, logout, loading, isAdmin }}>
value={{
user,
login,
register,
logout,
loading,
isAdmin,
}}
>
{children} {children}
</AuthContext.Provider> </AuthContext.Provider>
); );

File diff suppressed because it is too large Load Diff