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:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>({
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user