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

View File

@@ -0,0 +1,49 @@
import { createContext, useContext, useState, useEffect, type ReactNode } from 'react';
import { authApi } from '../api/endpoints';
import type { User } from '../api/types';
interface AuthCtx {
user: User | null;
loading: boolean;
logout: () => Promise<void>;
refresh: () => Promise<void>;
}
const Ctx = createContext<AuthCtx | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const refresh = async () => {
try {
const u = await authApi.me();
setUser(u);
} catch {
setUser(null);
}
};
useEffect(() => {
refresh().finally(() => setLoading(false));
}, []);
useEffect(() => {
const onExpired = () => setUser(null);
window.addEventListener('auth:expired', onExpired);
return () => window.removeEventListener('auth:expired', onExpired);
}, []);
const logout = async () => {
await authApi.logout();
setUser(null);
};
return <Ctx.Provider value={{ user, loading, logout, refresh }}>{children}</Ctx.Provider>;
}
export function useAuth() {
const ctx = useContext(Ctx);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
}