import { Box, Button, LinearProgress } from '@mui/joy' import { useEffect, useState } from 'react' import { Capacitor } from '@capacitor/core' import Cookies from 'js-cookie' import { useRef } from 'react' import { Link, useNavigate, useParams } from 'react-router-dom' import { useUserProfile } from '../../queries/UserQueries' import { apiClient } from '../../utils/ApiClient' import { endOAuthExchange } from '../../utils/OAuthExchangeState' import { GetUserProfile } from '../../utils/Fetcher' import { saveTokens } from '../../utils/TokenStorage' import AuthShell from './AuthShell' import { authButtonSx } from './authStyles' import MFAVerificationModal from './MFAVerificationModal' const AuthenticationLoading = () => { const { refetch: refetchUserProfile } = useUserProfile() const Navigate = useNavigate() const hasCalledHandleOAuth2 = useRef(false) const [message, setMessage] = useState('Signing you in') const [subMessage, setSubMessage] = useState('This will only take a moment.') const [status, setStatus] = useState('pending') const [mfaModalOpen, setMfaModalOpen] = useState(false) const [mfaSessionToken, setMfaSessionToken] = useState('') const { provider } = useParams() useEffect(() => { if (provider === 'oauth2' && !hasCalledHandleOAuth2.current) { hasCalledHandleOAuth2.current = true // Release the guard once the exchange settles, so a stuck flag can never // suppress a genuine session expiry later on. handleOAuth2().finally(endOAuthExchange) } else if (provider !== 'oauth2') { setMessage('Unknown sign-in provider') setSubMessage('Please contact support.') setStatus('error') } return endOAuthExchange }, [provider]) const getUserProfileAndNavigateToHome = () => { GetUserProfile().then(response => { response.json().then(() => { refetchUserProfile().then(() => { // check if redirect url is set in cookie: const redirectUrl = Cookies.get('ca_redirect') if (redirectUrl) { Cookies.remove('ca_redirect') Navigate(redirectUrl) } else { Navigate('/chores') } }) }) }) } const handleMFASuccess = async data => { await saveTokens({ accessToken: data.token, accessTokenExpiry: data.expire, refreshToken: data.refresh_token, refreshTokenExpiry: data.refresh_token_expiry, }) setMfaModalOpen(false) setMfaSessionToken('') getUserProfileAndNavigateToHome() } const handleMFAClose = () => { setMfaModalOpen(false) setMfaSessionToken('') setMessage('Sign-in failed') setSubMessage('Two-factor authentication was cancelled.') setStatus('error') } const handleOAuth2 = async () => { // get provider from params: const urlParams = new URLSearchParams(window.location.search) const code = urlParams.get('code') const returnedState = urlParams.get('state') const storedState = localStorage.getItem('authState') if (returnedState !== storedState) { setMessage('Sign-in failed') setSubMessage('The sign-in request could not be verified.') setStatus('error') return } if (code) { await apiClient.init() const baseURL = apiClient.getApiURL() const redirectURI = Capacitor.isNativePlatform() ? 'donetick://auth/oauth2' : `${window.location.origin}/auth/oauth2` try { const response = await fetch(`${baseURL}/auth/oauth2/callback`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ code, state: returnedState, redirect_uri: redirectURI, }), }) if (!response.ok) { console.error('Authentication failed') setMessage('Sign-in failed') setSubMessage('Please try again.') setStatus('error') return } const data = await response.json() if (data.mfaRequired) { if (!data.sessionToken) { setMessage('Sign-in failed') setSubMessage('The MFA session is missing. Please try again.') setStatus('error') return } setMfaSessionToken(data.sessionToken) setMfaModalOpen(true) setMessage('Two-factor authentication') setSubMessage('Verify your login to continue.') return } if (!data.token && !data.access_token) { setMessage('Sign-in failed') setSubMessage('No valid authentication token was returned.') setStatus('error') return } await saveTokens({ accessToken: data.token || data.access_token, accessTokenExpiry: data.expire || data.access_token_expiry, refreshToken: data.refresh_token, refreshTokenExpiry: data.refresh_token_expiry, }) const redirectUrl = Cookies.get('ca_redirect') if (redirectUrl) { Cookies.remove('ca_redirect') Navigate(redirectUrl) } else { getUserProfileAndNavigateToHome() } } catch (error) { console.error('Authentication request failed', error) setMessage('Sign-in failed') setSubMessage('Please try again.') setStatus('error') } } } return ( {status === 'pending' && ( )} {status === 'error' && ( )} { setMessage('Sign-in failed') setSubMessage('Two-factor authentication failed. Please try again.') }} /> ) } export default AuthenticationLoading