auth fixes

This commit is contained in:
2026-05-16 14:38:10 +00:00
parent 84038c25ec
commit 082e8d03ff
12 changed files with 224 additions and 56 deletions
+37 -11
View File
@@ -1,10 +1,9 @@
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { getPKCE, clearPKCE, storeToken } from '../auth/oidc'
import { getPKCE, clearPKCE, storeToken, getState, clearState } from '../auth/oidc'
import { api } from '../api/client'
import { useAuthStore } from '../stores/auth'
const OIDC_ISSUER = import.meta.env.VITE_OIDC_ISSUER
const CLIENT_ID = import.meta.env.VITE_OIDC_CLIENT_ID
const REDIRECT_URI = import.meta.env.VITE_OIDC_REDIRECT_URI
@@ -12,6 +11,13 @@ 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 () => {
@@ -19,6 +25,8 @@ export default function CallbackPage() {
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}`)
@@ -28,12 +36,18 @@ export default function CallbackPage() {
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')
}
setStatus('Exchanging code for token...')
if (isMounted.current) {
setStatus('Exchanging code for token...')
}
const tokenEndpoint = 'https://auth.commumedia.org/application/o/token/'
const tokenResponse = await fetch(tokenEndpoint, {
@@ -51,23 +65,35 @@ export default function CallbackPage() {
})
if (!tokenResponse.ok) {
throw new Error('Token exchange failed')
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...')
}
setStatus('Fetching user information...')
const user = await api.getCurrentUser()
setUser(user)
navigate('/')
if (isMounted.current) {
setUser(user)
window.location.href = '/'
}
} catch (error) {
console.error('Callback error:', error)
setError(error instanceof Error ? error : new Error('Authentication failed'))
setStatus('Authentication failed')
setTimeout(() => navigate('/login'), 3000)
clearPKCE()
clearState()
if (isMounted.current) {
setError(error instanceof Error ? error : new Error('Authentication failed'))
setStatus('Authentication failed')
setTimeout(() => navigate('/login'), 3000)
}
}
}
+11 -2
View File
@@ -1,16 +1,24 @@
import { useEffect } from 'react'
import { createPKCE, storePKCE } from '../auth/oidc'
import { createPKCE, storePKCE, storeState } from '../auth/oidc'
const OIDC_ISSUER = import.meta.env.VITE_OIDC_ISSUER
const CLIENT_ID = import.meta.env.VITE_OIDC_CLIENT_ID
const REDIRECT_URI = import.meta.env.VITE_OIDC_REDIRECT_URI
function generateState(): string {
const array = new Uint8Array(32)
crypto.getRandomValues(array)
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('')
}
export default function LoginPage() {
useEffect(() => {
const initiateLogin = async () => {
const { verifier, challenge } = await createPKCE()
storePKCE(verifier)
const state = generateState()
storeState(state)
const params = new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
@@ -18,6 +26,7 @@ export default function LoginPage() {
scope: 'openid profile email',
code_challenge: challenge,
code_challenge_method: 'S256',
state,
})
const authorizationEndpoint = 'https://auth.commumedia.org/application/o/authorize/'