feat(oauth): add csas connection, allow oauth from react

This commit is contained in:
2025-10-21 22:01:09 +02:00
parent be4a3b401a
commit 3ebf47e371
13 changed files with 509 additions and 20 deletions

View File

@@ -3,21 +3,54 @@ import './App.css';
import LoginRegisterPage from './pages/LoginRegisterPage';
import Dashboard from './pages/Dashboard';
import { logout } from './api';
import { BACKEND_URL } from './config';
function App() {
const [hasToken, setHasToken] = useState<boolean>(!!localStorage.getItem('token'));
const [processingCallback, setProcessingCallback] = useState<boolean>(false);
useEffect(() => {
// Handle OAuth callback: /oauth-callback?access_token=...&token_type=...
if (window.location.pathname === '/oauth-callback') {
const params = new URLSearchParams(window.location.search);
const token = params.get('access_token');
if (token) {
localStorage.setItem('token', token);
setHasToken(true);
const path = window.location.pathname;
// Minimal handling for provider callbacks: /auth|/oauth/:provider/callback?code=...&state=...
const parts = path.split('/').filter(Boolean);
const isCallback = parts.length === 3 && (parts[0] === 'auth') && parts[2] === 'callback';
if (isCallback) {
// Guard against double invocation in React 18 StrictMode/dev
const w = window as any;
if (w.__oauthCallbackHandled) {
return;
}
// Clean URL and redirect to home
window.history.replaceState({}, '', '/');
w.__oauthCallbackHandled = true;
setProcessingCallback(true);
const provider = parts[1];
const qs = window.location.search || '';
const base = BACKEND_URL.replace(/\/$/, '');
const url = `${base}/auth/${encodeURIComponent(provider)}/callback${qs}`;
(async () => {
try {
const token = localStorage.getItem('token');
const res = await fetch(url, {
method: 'GET',
credentials: 'include',
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
});
let data: any = null;
try {
data = await res.json();
} catch {}
if (provider !== 'csas' && res.ok && data?.access_token) {
localStorage.setItem('token', data?.access_token);
setHasToken(true);
}
} catch {}
// Clean URL and go home regardless of result
setProcessingCallback(false);
window.history.replaceState({}, '', '/');
})();
}
const onStorage = (e: StorageEvent) => {
@@ -27,6 +60,24 @@ function App() {
return () => window.removeEventListener('storage', onStorage);
}, []);
if (processingCallback) {
return (
<div style={{ display: 'grid', placeItems: 'center', height: '100vh' }}>
<div className="card" style={{ width: 360, textAlign: 'center', padding: 24 }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>
<svg width="48" height="48" viewBox="0 0 50 50" aria-label="Loading">
<circle cx="25" cy="25" r="20" fill="none" stroke="#3b82f6" strokeWidth="5" strokeLinecap="round" strokeDasharray="31.4 31.4">
<animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.9s" repeatCount="indefinite" />
</circle>
</svg>
<div>Finishing sign-in</div>
<div className="muted">Please wait</div>
</div>
</div>
</div>
);
}
if (!hasToken) {
return <LoginRegisterPage onLoggedIn={() => setHasToken(true)} />;
}