All files / src/pages/auth LogoutPage.tsx

100% Statements 66/66
100% Branches 4/4
100% Functions 1/1
100% Lines 66/66

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 671x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 1x 1x  
/**
 * @file LogoutPage.tsx
 * @description
 * Public route that performs logout without XHR:
 *  - Clears React Query cache and AuthContext immediately (client-side).
 *  - Submits a top-level POST form to `${API_BASE}/logout?return=<origin>/logout-success`.
 *  - Avoids CORS/XHR redirect issues and guarantees server-side session invalidation.
 *
 * @enterprise
 * - Uses form POST navigation to bypass CORS on /logout.
 * - Works when CSRF is disabled. If CSRF is enabled in future, allow GET logout
 *   or include the CSRF token as a hidden field.
 *
 * @i18n
 * - Uses 'auth' namespace: logoutSigningOut.
 */
 
import * as React from 'react';
import { useEffect } from 'react';
import { Box, CircularProgress, Stack, Typography } from '@mui/material';
import { useQueryClient } from '@tanstack/react-query';
import { useAuth } from '../../hooks/useAuth';
import { useTranslation } from 'react-i18next';
import { API_BASE } from '../../api/httpClient';
 
const LogoutPage: React.FC = () => {
  const { t } = useTranslation<'auth'>('auth');
  const queryClient = useQueryClient();
  const { logout } = useAuth();
 
  useEffect(() => {
    console.debug('[LogoutPage] effect start, posting to backend logout');
    // 1) Clear client-side state immediately
    queryClient.clear();
    logout();
 
    // 2) Submit a real form POST to /logout with a return URL
    const form = document.createElement('form');
    form.method = 'POST';
    const returnUrl = `${window.location.origin}/logout-success`;
    form.action = `${API_BASE}/logout?return=${encodeURIComponent(returnUrl)}`;
    form.style.display = 'none';
    document.body.appendChild(form);
      console.debug('[LogoutPage] submitting logout form to', form.action);
    form.submit();
 
    return () => {
      try { document.body.removeChild(form); } catch { /* noop */ }
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
 
  // Progress UI while navigation occurs
  return (
    <Box sx={{ display: 'grid', placeItems: 'center', minHeight: '60vh', px: 3 }}>
      <Stack spacing={2} sx={{ textAlign: 'center' }}>
        <CircularProgress />
        <Typography variant="body2" color="text.secondary">
          {t('logoutSigningOut')}
        </Typography>
      </Stack>
    </Box>
  );
};
 
export default LogoutPage;