diff --git a/client/src/App.tsx b/client/src/App.tsx index c76070e..3e16504 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -3,11 +3,15 @@ import { queryClient } from "./lib/queryClient"; import { Toaster } from "@/components/ui/toaster"; import { TooltipProvider } from "@/components/ui/tooltip"; import { useAuth } from "@/hooks/useAuth"; +import { useSessionTimeout } from "@/hooks/useSessionTimeout"; import Login from "@/pages/Login"; import Dashboard from "@/pages/Dashboard"; function AppContent() { const { isAuthenticated, isLoading } = useAuth(); + + // Initialize session timeout management + useSessionTimeout(); if (isLoading) { return ( diff --git a/client/src/hooks/useSessionTimeout.ts b/client/src/hooks/useSessionTimeout.ts new file mode 100644 index 0000000..913d372 --- /dev/null +++ b/client/src/hooks/useSessionTimeout.ts @@ -0,0 +1,102 @@ +import { useEffect, useRef, useCallback } from 'react'; +import { useAuth } from './useAuth'; + +const SESSION_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes +const WARNING_TIME_MS = 2 * 60 * 1000; // 2 minutes before timeout + +export function useSessionTimeout() { + const { isAuthenticated } = useAuth(); + const timeoutRef = useRef(null); + const warningTimeoutRef = useRef(null); + const lastActivityRef = useRef(Date.now()); + + const logout = useCallback(() => { + // Redirect to logout endpoint which will handle the logout process + window.location.href = '/api/auth/logout'; + }, []); + + const showWarning = useCallback(() => { + // Show a warning that session will expire soon + const remainingTime = Math.ceil((SESSION_TIMEOUT_MS - (Date.now() - lastActivityRef.current)) / 1000 / 60); + if (confirm(`Votre session va expirer dans ${remainingTime} minute(s). Voulez-vous rester connecté ?`)) { + // User wants to stay logged in, reset the timer + resetTimer(); + } else { + // User chose to logout or didn't respond + logout(); + } + }, [logout]); + + const resetTimer = useCallback(() => { + if (!isAuthenticated) return; + + lastActivityRef.current = Date.now(); + + // Clear existing timeouts + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + if (warningTimeoutRef.current) { + clearTimeout(warningTimeoutRef.current); + } + + // Set warning timeout (2 minutes before expiration) + warningTimeoutRef.current = setTimeout(showWarning, SESSION_TIMEOUT_MS - WARNING_TIME_MS); + + // Set logout timeout + timeoutRef.current = setTimeout(logout, SESSION_TIMEOUT_MS); + }, [isAuthenticated, logout, showWarning]); + + const handleActivity = useCallback(() => { + resetTimer(); + }, [resetTimer]); + + useEffect(() => { + if (!isAuthenticated) { + // Clear timeouts if user is not authenticated + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + if (warningTimeoutRef.current) { + clearTimeout(warningTimeoutRef.current); + } + return; + } + + // Start the timer when component mounts or user becomes authenticated + resetTimer(); + + // Activity events to monitor + const events = [ + 'mousedown', + 'mousemove', + 'keypress', + 'scroll', + 'touchstart', + 'click' + ]; + + // Add event listeners for user activity + events.forEach(event => { + document.addEventListener(event, handleActivity, true); + }); + + // Cleanup function + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + if (warningTimeoutRef.current) { + clearTimeout(warningTimeoutRef.current); + } + events.forEach(event => { + document.removeEventListener(event, handleActivity, true); + }); + }; + }, [isAuthenticated, handleActivity, resetTimer]); + + return { + resetTimer, + timeRemaining: () => Math.max(0, SESSION_TIMEOUT_MS - (Date.now() - lastActivityRef.current)) + }; +} \ No newline at end of file diff --git a/server/routes.ts b/server/routes.ts index d83d456..be66300 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -61,7 +61,7 @@ export async function registerRoutes(app: Express): Promise { saveUninitialized: false, cookie: { secure: false, // Set to true in production with HTTPS - maxAge: 24 * 60 * 60 * 1000 // 24 hours + maxAge: 15 * 60 * 1000 // 15 minutes } })); @@ -96,6 +96,15 @@ export async function registerRoutes(app: Express): Promise { } }); + app.get('/api/auth/logout', (req, res) => { + req.session.destroy((err) => { + if (err) { + console.error('Logout error:', err); + } + res.redirect('/'); + }); + }); + app.post('/api/auth/logout', (req, res) => { req.session.destroy((err) => { if (err) {