feat: PKCE auth + CI/CD deploy
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 1m2s

- Frontend: PKCE flow (oauth.ts, AuthCallback code exchange, 401 interceptor)
- Backend: token introspection via SuperOAuth (no more JWT secret)
- User model: superOauthId (unified) replaces oauthId+provider
- Cookies httpOnly session + refresh token
- POST /auth/refresh endpoint
- Gitea CI workflow (vps-runner pattern)
- DB_SYNC env var for initial schema creation
This commit is contained in:
2026-03-24 13:01:14 +01:00
parent c1bf793234
commit 8c6777c980
61 changed files with 5850 additions and 66 deletions

51
frontend/src/App.tsx Normal file
View File

@@ -0,0 +1,51 @@
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AuthProvider, useAuth } from './context/AuthContext';
import { Layout } from './components/Layout';
import { LoginPage } from './pages/LoginPage';
import { AuthCallback } from './pages/AuthCallback';
import { DashboardPage } from './pages/DashboardPage';
import { CombatPage } from './pages/CombatPage';
import { InventoryPage } from './pages/InventoryPage';
import { CraftPage } from './pages/CraftPage';
import { ForgePage } from './pages/ForgePage';
const qc = new QueryClient({ defaultOptions: { queries: { retry: 1, staleTime: 30_000 } } });
function ProtectedLayout({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth();
if (loading) return (
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#6b7a99', fontSize: 14 }}>
Chargement
</div>
);
if (!user) return <Navigate to="/login" replace />;
return <Layout>{children}</Layout>;
}
function AppRoutes() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/auth/callback" element={<AuthCallback />} />
<Route path="/dashboard" element={<ProtectedLayout><DashboardPage /></ProtectedLayout>} />
<Route path="/combat" element={<ProtectedLayout><CombatPage /></ProtectedLayout>} />
<Route path="/inventory" element={<ProtectedLayout><InventoryPage /></ProtectedLayout>} />
<Route path="/craft" element={<ProtectedLayout><CraftPage /></ProtectedLayout>} />
<Route path="/forge" element={<ProtectedLayout><ForgePage /></ProtectedLayout>} />
<Route path="*" element={<Navigate to="/dashboard" replace />} />
</Routes>
);
}
export default function App() {
return (
<QueryClientProvider client={qc}>
<AuthProvider>
<BrowserRouter>
<AppRoutes />
</BrowserRouter>
</AuthProvider>
</QueryClientProvider>
);
}