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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | 10x 34x 10x 10x 7x 7x 7x 7x 7x 2x 2x 5x 5x 5x 4x 4x 4x 4x 4x 4x 1x 10x 3x 7x 3x 3x 3x 3x 4x 4x 4x | import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useAuthStore } from './authStore'
interface TokenResponse {
access_token: string
token_type: string
expires_in?: number
id_token?: string
}
export function CallbackPage() {
const navigate = useNavigate()
const setAuth = useAuthStore((s) => s.setAuth)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const code = sessionStorage.getItem('oidc-code')
const codeVerifier = sessionStorage.getItem('oidc-code-verifier')
sessionStorage.removeItem('oidc-code')
sessionStorage.removeItem('oidc-code-verifier')
if (!code || !codeVerifier) {
setError('No authorization code found in callback')
return
}
const redirectUri = `${window.location.protocol}//${window.location.hostname}/callback.html`
fetch('/api/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, code_verifier: codeVerifier, redirect_uri: redirectUri }),
})
.then((res) => {
if (!res.ok) throw new Error('Token exchange failed')
return res.json() as Promise<TokenResponse>
})
.then((data) => {
const decoded = decodeJwtPayload(data.access_token)
setAuth(data.access_token, {
sub: decoded.sub ?? '',
preferred_username: decoded.preferred_username ?? decoded.sub ?? '',
email: decoded.email ?? '',
groups: decoded.groups ?? [],
})
const returnUrl = sessionStorage.getItem('returnUrl')
sessionStorage.removeItem('returnUrl')
navigate(returnUrl ?? '/', { replace: true })
})
.catch(() => {
setError('Failed to complete authentication. Please try again.')
})
}, [navigate, setAuth])
if (error) {
return <CallbackError error={error} navigate={navigate} />
}
return (
<div className="flex min-h-screen items-center justify-center">
<p className="text-muted-foreground">Signing in...</p>
</div>
)
}
function CallbackError({ error, navigate }: { error: string; navigate: (path: string, opts?: { replace?: boolean }) => void }) {
useEffect(() => {
const timer = setTimeout(() => navigate('/login', { replace: true }), 3000)
return () => clearTimeout(timer)
}, [navigate])
return (
<div className="flex min-h-screen flex-col items-center justify-center gap-4">
<p className="text-destructive">{error}</p>
<p className="text-sm text-muted-foreground">Redirecting to login...</p>
</div>
)
}
interface JwtPayload {
sub?: string
preferred_username?: string
email?: string
groups?: string[]
}
function decodeJwtPayload(token: string): JwtPayload {
try {
const base64 = token.split('.')[1]
return JSON.parse(atob(base64)) as JwtPayload
} catch {
return {}
}
}
|