Files
headquarter/apps/web/src/pages/CallbackPage.tsx
T
2026-05-16 14:52:05 +00:00

112 lines
3.3 KiB
TypeScript

import { useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { getPKCE, clearPKCE, storeToken, getState, clearState } from '../auth/oidc'
import { api } from '../api/client'
import { useAuthStore } from '../stores/auth'
const CLIENT_ID = import.meta.env.VITE_OIDC_CLIENT_ID
const REDIRECT_URI = import.meta.env.VITE_OIDC_REDIRECT_URI
export default function CallbackPage() {
const navigate = useNavigate()
const { setUser, setError } = useAuthStore()
const [status, setStatus] = useState('Processing authentication...')
const isMounted = useRef(true)
useEffect(() => {
return () => {
isMounted.current = false
}
}, [])
useEffect(() => {
const handleCallback = async () => {
try {
const urlParams = new URLSearchParams(window.location.search)
const code = urlParams.get('code')
const error = urlParams.get('error')
const state = urlParams.get('state')
const storedState = getState()
if (error) {
throw new Error(`Authentication error: ${error}`)
}
if (!code) {
throw new Error('No authorization code received')
}
if (!state || state !== storedState) {
throw new Error('Invalid or missing state parameter')
}
const verifier = getPKCE()
if (!verifier) {
throw new Error('PKCE verifier not found')
}
if (isMounted.current) {
setStatus('Exchanging code for token...')
}
const tokenEndpoint = 'https://auth.commumedia.org/application/o/token/'
const tokenResponse = await fetch(tokenEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: CLIENT_ID,
code,
redirect_uri: REDIRECT_URI,
code_verifier: verifier,
}),
})
if (!tokenResponse.ok) {
const errorData = await tokenResponse.json().catch(() => ({}))
throw new Error(errorData.error_description || errorData.error || 'Token exchange failed')
}
const tokenData = await tokenResponse.json()
storeToken(tokenData.access_token)
clearPKCE()
clearState()
if (isMounted.current) {
setStatus('Fetching user information...')
}
const user = await api.getCurrentUser()
if (isMounted.current) {
setUser(user)
navigate('/')
}
} catch (error) {
console.error('Callback error:', error)
clearPKCE()
clearState()
if (isMounted.current) {
setError(error instanceof Error ? error : new Error('Authentication failed'))
setStatus('Authentication failed')
setTimeout(() => navigate('/login'), 3000)
}
}
}
handleCallback()
}, [navigate, setUser, setError])
return (
<div className="flex min-h-screen items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold mb-4">{status}</h1>
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-accent mx-auto"></div>
</div>
</div>
)
}