fix: robust logout session clearing with cookie cleanup and isLoggingOut guard
- Clear next-auth session cookies directly as backup in logout page - Add isLoggingOut localStorage flag to prevent TokenSync/PresenceProvider from restoring tokens - Clear isLoggingOut flag on login page load as safety net - Add createdAt to AgentSubscription interface (fixes build error) - Add /education to public routes in middleware
This commit is contained in:
@@ -13,6 +13,11 @@ export default function LoginPage() {
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Safety net: clear logout flag when login page loads
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem('isLoggingOut');
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
@@ -3,6 +3,25 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { signOut } from 'next-auth/react';
|
||||
|
||||
function clearAuthCookies() {
|
||||
// Clear next-auth v5 (Auth.js) and v4 session cookies directly
|
||||
const cookieNames = [
|
||||
'authjs.session-token',
|
||||
'__Secure-authjs.session-token',
|
||||
'authjs.callback-url',
|
||||
'authjs.csrf-token',
|
||||
'next-auth.session-token',
|
||||
'__Secure-next-auth.session-token',
|
||||
'next-auth.callback-url',
|
||||
'next-auth.csrf-token',
|
||||
];
|
||||
|
||||
cookieNames.forEach((name) => {
|
||||
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
||||
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=${window.location.hostname}`;
|
||||
});
|
||||
}
|
||||
|
||||
export default function LogoutPage() {
|
||||
const hasStarted = useRef(false);
|
||||
|
||||
@@ -10,24 +29,31 @@ export default function LogoutPage() {
|
||||
if (hasStarted.current) return;
|
||||
hasStarted.current = true;
|
||||
|
||||
// Ensure logout flag is set (prevents TokenSync and PresenceProvider from restoring tokens)
|
||||
localStorage.setItem('isLoggingOut', 'true');
|
||||
|
||||
// Clear localStorage tokens
|
||||
localStorage.removeItem('accessToken');
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('user');
|
||||
|
||||
// Clear auth cookies directly as a robust backup
|
||||
clearAuthCookies();
|
||||
|
||||
const redirectToLogin = () => {
|
||||
clearAuthCookies();
|
||||
// Clear the logout flag right before redirecting
|
||||
localStorage.removeItem('isLoggingOut');
|
||||
window.location.href = '/login';
|
||||
};
|
||||
|
||||
// Sign out via NextAuth (clears session cookie), then manually redirect
|
||||
signOut({ redirect: false })
|
||||
.then(() => {
|
||||
window.location.href = '/login';
|
||||
})
|
||||
.catch(() => {
|
||||
window.location.href = '/login';
|
||||
});
|
||||
.then(redirectToLogin)
|
||||
.catch(redirectToLogin);
|
||||
|
||||
// Safety fallback - if signOut hangs, redirect after 3 seconds
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 3000);
|
||||
setTimeout(redirectToLogin, 3000);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -280,6 +280,9 @@ export function CommonHeader() {
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowProfileMenu(false);
|
||||
// Set logout flag FIRST so TokenSync and PresenceProvider
|
||||
// don't restore tokens during the logout navigation
|
||||
localStorage.setItem('isLoggingOut', 'true');
|
||||
localStorage.removeItem('accessToken');
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('user');
|
||||
|
||||
@@ -29,15 +29,15 @@ export function ChatHeader({
|
||||
<div className="border-b border-[#00293d]/10 pb-4">
|
||||
{/* Top row - Name, status, actions */}
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D]">
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
<h2 className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D] truncate">
|
||||
{name}
|
||||
</h2>
|
||||
<span className={`w-2.5 h-2.5 rounded-full ${isOnline ? 'bg-green-500' : 'bg-gray-400'}`} />
|
||||
<span className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D]">
|
||||
<span className={`w-2.5 h-2.5 rounded-full flex-shrink-0 ${isOnline ? 'bg-green-500' : 'bg-gray-400'}`} />
|
||||
<span className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] hidden sm:inline">
|
||||
{role}
|
||||
</span>
|
||||
<span className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D]">
|
||||
<span className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] hidden sm:inline">
|
||||
{isTyping ? (
|
||||
<span className="text-[#e58625] italic">{typingText}</span>
|
||||
) : (
|
||||
@@ -45,7 +45,7 @@ export function ChatHeader({
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
<button className="p-2 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer">
|
||||
<Image
|
||||
src="/assets/icons/three-dots-horizontal-icon.svg"
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Grid } from '@giphy/react-components';
|
||||
|
||||
const gf = new GiphyFetch(process.env.NEXT_PUBLIC_GIPHY_API_KEY || '');
|
||||
|
||||
|
||||
interface AttachedFile {
|
||||
file: File;
|
||||
preview?: string; // Object URL for image preview
|
||||
|
||||
@@ -75,6 +75,12 @@ export function PresenceProvider({ children }: { children: React.ReactNode }) {
|
||||
// Handle auth errors (token expiry) — single global handler
|
||||
useEffect(() => {
|
||||
const unsubscribe = socketService.onAuthError(async () => {
|
||||
// Skip token refresh if logout is in progress
|
||||
if (typeof window !== 'undefined' && localStorage.getItem('isLoggingOut') === 'true') {
|
||||
console.log('[Presence] Auth error during logout, skipping refresh');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Presence] Auth error, refreshing token...');
|
||||
try {
|
||||
const refreshToken = localStorage.getItem('refreshToken');
|
||||
|
||||
@@ -9,7 +9,10 @@ function TokenSync() {
|
||||
const hasInitialized = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === "authenticated" && session?.user) {
|
||||
// Never restore tokens if a logout is in progress
|
||||
const loggingOut = localStorage.getItem("isLoggingOut") === "true";
|
||||
|
||||
if (status === "authenticated" && session?.user && !loggingOut) {
|
||||
const user = session.user as { accessToken?: string; refreshToken?: string };
|
||||
|
||||
// Only sync tokens from NextAuth to localStorage if:
|
||||
|
||||
@@ -5,7 +5,7 @@ import { NextResponse } from "next/server";
|
||||
const authRoutes = ["/login", "/signup", "/forgot-password", "/reset-password", "/verify-email"];
|
||||
|
||||
// Public routes - accessible to everyone (logged in or not)
|
||||
const publicRoutes = ["/", "/contact", "/about", "/faq", "/logout", "/user/dashboard", "/user/profiles", "/user/profile"];
|
||||
const publicRoutes = ["/", "/contact", "/about", "/faq", "/education", "/logout", "/user/dashboard", "/user/profiles", "/user/profile"];
|
||||
|
||||
export default auth((req) => {
|
||||
const { nextUrl } = req;
|
||||
|
||||
@@ -33,6 +33,8 @@ const forceLogout = () => {
|
||||
if (isLoggingOut) return; // Prevent multiple redirects
|
||||
isLoggingOut = true;
|
||||
if (typeof window !== 'undefined') {
|
||||
// Set flag first so TokenSync/PresenceProvider don't restore tokens
|
||||
localStorage.setItem('isLoggingOut', 'true');
|
||||
localStorage.removeItem('accessToken');
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('user');
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface AgentSubscription {
|
||||
currentPeriodEnd: string | null;
|
||||
cancelAtPeriodEnd: boolean;
|
||||
canceledAt: string | null;
|
||||
createdAt: string;
|
||||
plan: SubscriptionPlan;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user