feat(auth): Complete OIDC security refactor and modernize test suite

- Refactored OIDC flow to implement PKCE, state/nonce validation, and BFF pattern.
- Centralized configuration in Settings class (DEV_MODE, FRONTEND_URL, OIDC_REDIRECT_URI).
- Updated auth routers to use conditional secure cookie flags based on DEV_MODE.
- Modernized and cleaned up test suite by removing legacy Streamlit tests.
- Fixed linting errors and unused imports across the backend.
This commit is contained in:
Yunxiao Xu
2026-02-15 02:50:26 -08:00
parent 48ad0ebdd7
commit 68c0985482
50 changed files with 222 additions and 515 deletions

View File

@@ -3,7 +3,6 @@ import { Routes, Route } from "react-router-dom"
import { MainLayout } from "./components/layout/MainLayout"
import { LoginForm } from "./components/auth/LoginForm"
import { RegisterForm } from "./components/auth/RegisterForm"
import { AuthCallback } from "./components/auth/AuthCallback"
import { ChatInterface } from "./components/chat/ChatInterface"
import { AuthService, type UserResponse } from "./services/auth"
import { ChatService, type MessageResponse } from "./services/chat"
@@ -136,6 +135,9 @@ function App() {
setThreadMessages(prev => ({ ...prev, [id]: messages }))
}
const queryParams = new URLSearchParams(window.location.search)
const externalError = queryParams.get("error")
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-background">
@@ -146,7 +148,6 @@ function App() {
return (
<Routes>
<Route path="/auth/callback" element={<AuthCallback />} />
<Route
path="*"
element={
@@ -156,6 +157,7 @@ function App() {
<LoginForm
onSuccess={handleAuthSuccess}
onToggleMode={() => setAuthMode("register")}
externalError={externalError === "oidc_failed" ? "SSO authentication failed. Please try again." : null}
/>
) : (
<RegisterForm

View File

@@ -1,39 +0,0 @@
import { useEffect } from "react"
import { useNavigate } from "react-router-dom"
import { AuthService } from "@/services/auth"
export function AuthCallback() {
const navigate = useNavigate()
useEffect(() => {
const verifyAuth = async () => {
const urlParams = new URLSearchParams(window.location.search)
const code = urlParams.get("code")
try {
if (code) {
// If we have a code, exchange it for a cookie
await AuthService.exchangeOIDCCode(code)
} else {
// If no code, just verify existing cookie (backend-driven redirect)
await AuthService.getMe()
}
// Success - go to home. We use window.location.href to ensure a clean reload of App state
window.location.href = "/"
} catch (err) {
console.error("Auth callback verification failed:", err)
navigate("/?error=auth_failed", { replace: true })
}
}
verifyAuth()
}, [navigate])
return (
<div className="min-h-screen flex flex-col items-center justify-center bg-background">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mb-4"></div>
<p className="text-muted-foreground">Completing login...</p>
</div>
)
}

View File

@@ -22,10 +22,11 @@ import axios from "axios"
interface LoginFormProps {
onSuccess: () => void
onToggleMode: () => void
externalError?: string | null
}
export function LoginForm({ onSuccess, onToggleMode }: LoginFormProps) {
const [error, setError] = useState<string | null>(null)
export function LoginForm({ onSuccess, onToggleMode, externalError }: LoginFormProps) {
const [error, setError] = useState<string | null>(externalError || null)
const [isLoading, setIsLoading] = useState(false)
const form = useForm<LoginInput>({

View File

@@ -28,11 +28,6 @@ export const AuthService = {
}
},
async exchangeOIDCCode(code: string): Promise<AuthResponse> {
const response = await api.get<AuthResponse>(`/auth/oidc/callback?code=${code}`)
return response.data
},
async register(email: string, password: string): Promise<UserResponse> {
const response = await api.post<UserResponse>("/auth/register", {
email,