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 [error, setError] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
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) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|||||||
@@ -3,6 +3,25 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { signOut } from 'next-auth/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() {
|
export default function LogoutPage() {
|
||||||
const hasStarted = useRef(false);
|
const hasStarted = useRef(false);
|
||||||
|
|
||||||
@@ -10,24 +29,31 @@ export default function LogoutPage() {
|
|||||||
if (hasStarted.current) return;
|
if (hasStarted.current) return;
|
||||||
hasStarted.current = true;
|
hasStarted.current = true;
|
||||||
|
|
||||||
|
// Ensure logout flag is set (prevents TokenSync and PresenceProvider from restoring tokens)
|
||||||
|
localStorage.setItem('isLoggingOut', 'true');
|
||||||
|
|
||||||
// Clear localStorage tokens
|
// Clear localStorage tokens
|
||||||
localStorage.removeItem('accessToken');
|
localStorage.removeItem('accessToken');
|
||||||
localStorage.removeItem('refreshToken');
|
localStorage.removeItem('refreshToken');
|
||||||
localStorage.removeItem('user');
|
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
|
// Sign out via NextAuth (clears session cookie), then manually redirect
|
||||||
signOut({ redirect: false })
|
signOut({ redirect: false })
|
||||||
.then(() => {
|
.then(redirectToLogin)
|
||||||
window.location.href = '/login';
|
.catch(redirectToLogin);
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
window.location.href = '/login';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Safety fallback - if signOut hangs, redirect after 3 seconds
|
// Safety fallback - if signOut hangs, redirect after 3 seconds
|
||||||
setTimeout(() => {
|
setTimeout(redirectToLogin, 3000);
|
||||||
window.location.href = '/login';
|
|
||||||
}, 3000);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -280,6 +280,9 @@ export function CommonHeader() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowProfileMenu(false);
|
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('accessToken');
|
||||||
localStorage.removeItem('refreshToken');
|
localStorage.removeItem('refreshToken');
|
||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
|
|||||||
@@ -29,15 +29,15 @@ export function ChatHeader({
|
|||||||
<div className="border-b border-[#00293d]/10 pb-4">
|
<div className="border-b border-[#00293d]/10 pb-4">
|
||||||
{/* Top row - Name, status, actions */}
|
{/* Top row - Name, status, actions */}
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||||
<h2 className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D]">
|
<h2 className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D] truncate">
|
||||||
{name}
|
{name}
|
||||||
</h2>
|
</h2>
|
||||||
<span className={`w-2.5 h-2.5 rounded-full ${isOnline ? 'bg-green-500' : 'bg-gray-400'}`} />
|
<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]">
|
<span className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] hidden sm:inline">
|
||||||
{role}
|
{role}
|
||||||
</span>
|
</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 ? (
|
{isTyping ? (
|
||||||
<span className="text-[#e58625] italic">{typingText}</span>
|
<span className="text-[#e58625] italic">{typingText}</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -45,7 +45,7 @@ export function ChatHeader({
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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">
|
<button className="p-2 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer">
|
||||||
<Image
|
<Image
|
||||||
src="/assets/icons/three-dots-horizontal-icon.svg"
|
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 || '');
|
const gf = new GiphyFetch(process.env.NEXT_PUBLIC_GIPHY_API_KEY || '');
|
||||||
|
|
||||||
|
|
||||||
interface AttachedFile {
|
interface AttachedFile {
|
||||||
file: File;
|
file: File;
|
||||||
preview?: string; // Object URL for image preview
|
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
|
// Handle auth errors (token expiry) — single global handler
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = socketService.onAuthError(async () => {
|
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...');
|
console.log('[Presence] Auth error, refreshing token...');
|
||||||
try {
|
try {
|
||||||
const refreshToken = localStorage.getItem('refreshToken');
|
const refreshToken = localStorage.getItem('refreshToken');
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ function TokenSync() {
|
|||||||
const hasInitialized = useRef(false);
|
const hasInitialized = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
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 };
|
const user = session.user as { accessToken?: string; refreshToken?: string };
|
||||||
|
|
||||||
// Only sync tokens from NextAuth to localStorage if:
|
// 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"];
|
const authRoutes = ["/login", "/signup", "/forgot-password", "/reset-password", "/verify-email"];
|
||||||
|
|
||||||
// Public routes - accessible to everyone (logged in or not)
|
// 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) => {
|
export default auth((req) => {
|
||||||
const { nextUrl } = req;
|
const { nextUrl } = req;
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ const forceLogout = () => {
|
|||||||
if (isLoggingOut) return; // Prevent multiple redirects
|
if (isLoggingOut) return; // Prevent multiple redirects
|
||||||
isLoggingOut = true;
|
isLoggingOut = true;
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
|
// Set flag first so TokenSync/PresenceProvider don't restore tokens
|
||||||
|
localStorage.setItem('isLoggingOut', 'true');
|
||||||
localStorage.removeItem('accessToken');
|
localStorage.removeItem('accessToken');
|
||||||
localStorage.removeItem('refreshToken');
|
localStorage.removeItem('refreshToken');
|
||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface AgentSubscription {
|
|||||||
currentPeriodEnd: string | null;
|
currentPeriodEnd: string | null;
|
||||||
cancelAtPeriodEnd: boolean;
|
cancelAtPeriodEnd: boolean;
|
||||||
canceledAt: string | null;
|
canceledAt: string | null;
|
||||||
|
createdAt: string;
|
||||||
plan: SubscriptionPlan;
|
plan: SubscriptionPlan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user