Add MFA handling to AuthenticationLoading component

This commit is contained in:
Mo Tarbin
2026-05-25 17:36:44 -04:00
parent fdac632b1c
commit 6b5f5c54dd

View File

@@ -10,6 +10,7 @@ import { useUserProfile } from '../../queries/UserQueries'
import { apiClient } from '../../utils/ApiClient' import { apiClient } from '../../utils/ApiClient'
import { GetUserProfile } from '../../utils/Fetcher' import { GetUserProfile } from '../../utils/Fetcher'
import { saveTokens } from '../../utils/TokenStorage' import { saveTokens } from '../../utils/TokenStorage'
import MFAVerificationModal from './MFAVerificationModal'
const AuthenticationLoading = () => { const AuthenticationLoading = () => {
const { data: userProfile, refetch: refetchUserProfile } = useUserProfile() const { data: userProfile, refetch: refetchUserProfile } = useUserProfile()
@@ -18,6 +19,8 @@ const AuthenticationLoading = () => {
const [message, setMessage] = useState('Authenticating') const [message, setMessage] = useState('Authenticating')
const [subMessage, setSubMessage] = useState('Please wait') const [subMessage, setSubMessage] = useState('Please wait')
const [status, setStatus] = useState('pending') const [status, setStatus] = useState('pending')
const [mfaModalOpen, setMfaModalOpen] = useState(false)
const [mfaSessionToken, setMfaSessionToken] = useState('')
const { provider } = useParams() const { provider } = useParams()
useEffect(() => { useEffect(() => {
if (provider === 'oauth2' && !hasCalledHandleOAuth2.current) { if (provider === 'oauth2' && !hasCalledHandleOAuth2.current) {
@@ -44,6 +47,29 @@ const AuthenticationLoading = () => {
}) })
}) })
} }
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('Authentication failed')
setSubMessage('Two-factor authentication was cancelled')
setStatus('error')
}
const handleOAuth2 = async () => { const handleOAuth2 = async () => {
// get provider from params: // get provider from params:
const urlParams = new URLSearchParams(window.location.search) const urlParams = new URLSearchParams(window.location.search)
@@ -65,41 +91,71 @@ const AuthenticationLoading = () => {
const redirectURI = Capacitor.isNativePlatform() const redirectURI = Capacitor.isNativePlatform()
? 'donetick://auth/oauth2' ? 'donetick://auth/oauth2'
: `${window.location.origin}/auth/oauth2` : `${window.location.origin}/auth/oauth2`
fetch(`${baseURL}/auth/oauth2/callback`, { try {
method: 'POST', const response = await fetch(`${baseURL}/auth/oauth2/callback`, {
headers: { method: 'POST',
'Content-Type': 'application/json', headers: {
}, 'Content-Type': 'application/json',
body: JSON.stringify({ },
code, body: JSON.stringify({
state: returnedState, code,
redirect_uri: redirectURI, state: returnedState,
}), redirect_uri: redirectURI,
}).then(response => { }),
if (response.status === 200) { })
return response.json().then(async data => {
await saveTokens({
accessToken: data.token,
accessTokenExpiry: data.expire,
refreshToken: data.refresh_token,
refreshTokenExpiry: data.refresh_token_expiry
})
const redirectUrl = Cookies.get('ca_redirect') if (!response.ok) {
if (redirectUrl) {
Cookies.remove('ca_redirect')
Navigate(redirectUrl)
} else {
getUserProfileAndNavigateToHome()
}
})
} else {
console.error('Authentication failed') console.error('Authentication failed')
setMessage('Authentication failed') setMessage('Authentication failed')
setSubMessage('Please try again') setSubMessage('Please try again')
setStatus('error') setStatus('error')
return
} }
})
const data = await response.json()
if (data.mfaRequired) {
if (!data.sessionToken) {
setMessage('Authentication failed')
setSubMessage('MFA session is missing. Please try again')
setStatus('error')
return
}
setMfaSessionToken(data.sessionToken)
setMfaModalOpen(true)
setMessage('Two-Factor Authentication Required')
setSubMessage('Please verify your login to continue')
return
}
if (!data.token && !data.access_token) {
setMessage('Authentication failed')
setSubMessage('No valid authentication token 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('Authentication failed')
setSubMessage('Please try again')
setStatus('error')
}
} }
} }
@@ -143,6 +199,17 @@ const AuthenticationLoading = () => {
<Link to='/login'>Go back Login</Link> <Link to='/login'>Go back Login</Link>
</Button> </Button>
)} )}
<MFAVerificationModal
open={mfaModalOpen}
onClose={handleMFAClose}
sessionToken={mfaSessionToken}
onSuccess={handleMFASuccess}
onError={() => {
setMessage('Authentication failed')
setSubMessage('Two-factor authentication failed. Please try again')
}}
/>
</Box> </Box>
</Container> </Container>
) )