fix bug
This commit is contained in:
parent
af9dae72d7
commit
08e614dd1e
@ -325,7 +325,16 @@ export class AuthController {
|
||||
throw new UnauthorizedException('No refresh token provided');
|
||||
}
|
||||
|
||||
const result = await this.authService.refreshAccessToken(refreshToken);
|
||||
let result: Awaited<ReturnType<typeof this.authService.refreshAccessToken>>;
|
||||
try {
|
||||
result = await this.authService.refreshAccessToken(refreshToken);
|
||||
} catch (error) {
|
||||
// The refresh token is expired/revoked: wipe every auth cookie (including
|
||||
// the readable session flag) so the client stops believing a session
|
||||
// exists. Without this the frontend keeps retrying and reloading.
|
||||
this.clearAuthCookies(res);
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.setAuthCookies(res, result, result.rememberMe);
|
||||
|
||||
|
||||
@ -27,11 +27,46 @@ export function hasSession(): boolean {
|
||||
return document.cookie.split('; ').some(cookie => cookie.startsWith(`${SESSION_FLAG_COOKIE}=`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Every domain scope a cookie could have been set on for the current host.
|
||||
*
|
||||
* The backend sets its cookies with `COOKIE_DOMAIN` (e.g. `.preprod.xpeditis.com`),
|
||||
* so deleting them from JS without the matching `domain` attribute silently
|
||||
* fails — the browser scopes the deletion to the exact host and the original
|
||||
* domain-scoped cookie survives. Trying every parent domain guarantees removal
|
||||
* whatever COOKIE_DOMAIN is set to.
|
||||
*/
|
||||
function cookieDomainScopes(): Array<string | null> {
|
||||
const scopes: Array<string | null> = [null]; // host-only cookie
|
||||
const host = window.location.hostname;
|
||||
|
||||
// No domain cookies on IPs or on `localhost`
|
||||
if (host === 'localhost' || /^[\d.]+$/.test(host) || host.includes(':')) return scopes;
|
||||
|
||||
const parts = host.split('.');
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
const domain = parts.slice(i).join('.');
|
||||
if (domain.includes('.')) {
|
||||
scopes.push(domain);
|
||||
scopes.push(`.${domain}`);
|
||||
}
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
function deleteCookie(name: string): void {
|
||||
const expiry = 'expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax';
|
||||
for (const domain of cookieDomainScopes()) {
|
||||
const domainPart = domain ? `; domain=${domain}` : '';
|
||||
document.cookie = `${name}=; path=/${domainPart}; ${expiry}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear client-side auth state.
|
||||
* Token cookies are httpOnly and are cleared by the backend on logout;
|
||||
* this removes the user cache and any tokens left over from the legacy
|
||||
* localStorage-based auth.
|
||||
* this removes the user cache, any tokens left over from the legacy
|
||||
* localStorage-based auth, and the readable session flag.
|
||||
*/
|
||||
export function clearAuthTokens(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
@ -43,8 +78,50 @@ export function clearAuthTokens(): void {
|
||||
sessionStorage.removeItem('user');
|
||||
// Expire the legacy middleware cookie and the session flag (best effort —
|
||||
// the backend clears the authoritative httpOnly cookies)
|
||||
document.cookie = 'accessToken=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax';
|
||||
document.cookie = `${SESSION_FLAG_COOKIE}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
|
||||
deleteCookie('accessToken');
|
||||
deleteCookie(SESSION_FLAG_COOKIE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pages that must never be redirected away from on an auth failure: they ARE
|
||||
* the destination. Redirecting from here caused an infinite reload loop when a
|
||||
* stale session cookie survived (`/login` → 401 → refresh fails → reload
|
||||
* `/login` → …). Matched after stripping the locale prefix.
|
||||
*/
|
||||
const AUTH_ENTRY_PATHS = [
|
||||
'/login',
|
||||
'/admin/login',
|
||||
'/register',
|
||||
'/forgot-password',
|
||||
'/reset-password',
|
||||
'/verify-email',
|
||||
'/carrier',
|
||||
];
|
||||
|
||||
// Kept in sync with i18n/routing.ts (imported statically here to avoid pulling
|
||||
// next-intl into the API client bundle).
|
||||
const LOCALES = ['fr', 'en'];
|
||||
|
||||
function currentLocalePrefix(): string {
|
||||
const segment = window.location.pathname.split('/')[1];
|
||||
return LOCALES.includes(segment) ? `/${segment}` : '';
|
||||
}
|
||||
|
||||
function isOnAuthEntryPage(): boolean {
|
||||
const path = window.location.pathname.slice(currentLocalePrefix().length) || '/';
|
||||
return AUTH_ENTRY_PATHS.some(p => path === p || path.startsWith(`${p}/`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the user back to login after an unrecoverable auth failure.
|
||||
* No-op when already on a login/registration page, so a stale session can never
|
||||
* turn into a reload loop. The locale prefix is preserved to avoid a needless
|
||||
* round-trip through the i18n middleware.
|
||||
*/
|
||||
export function redirectToLogin(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
if (isOnAuthEntryPage()) return;
|
||||
window.location.href = `${currentLocalePrefix()}/login`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,10 +156,7 @@ async function refreshSession(): Promise<void> {
|
||||
if (!response.ok) {
|
||||
// Refresh token invalid or expired, clear everything
|
||||
clearAuthTokens();
|
||||
// Redirect to login
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
redirectToLogin();
|
||||
throw new Error('Failed to refresh session');
|
||||
}
|
||||
}
|
||||
@ -146,9 +220,7 @@ export async function apiRequest<T>(
|
||||
if (!hasSession()) {
|
||||
// No session, redirect to login
|
||||
clearAuthTokens();
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
redirectToLogin();
|
||||
throw new ApiError('Session expired', 401);
|
||||
}
|
||||
|
||||
@ -281,9 +353,7 @@ export async function upload<T>(
|
||||
return retryResponse.json();
|
||||
} catch (refreshError) {
|
||||
clearAuthTokens();
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
redirectToLogin();
|
||||
throw refreshError;
|
||||
}
|
||||
}
|
||||
@ -343,9 +413,7 @@ export async function download(
|
||||
return;
|
||||
} catch (refreshError) {
|
||||
clearAuthTokens();
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
redirectToLogin();
|
||||
throw refreshError;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user