Authentification OIDC : AUTH_MODE=local|oidc
- AUTH_MODE=local (défaut) : comportement inchangé, Basic si AUTH_USER et AUTH_PASSWORD sont définis, sinon aucune authentification intégrée. - AUTH_MODE=oidc : flux Authorization Code + PKCE (S256) avec state et nonce, découverte .well-known, vérification de l'ID Token (RS*/PS*/ES* via JWKS avec rotation des clés, HS* avec le secret client), émetteur, audience, expiration ; restriction optionnelle par groupes (OIDC_ALLOWED_GROUPS, OIDC_GROUPS_CLAIM, chemin pointé accepté). - Session : cookie HttpOnly SameSite=Lax signé HMAC (SESSION_SECRET, sinon DATA_DIR/.session-secret), prolongée à l'usage (SESSION_TTL d'inactivité). - Routes /auth/login, /auth/callback, /auth/logout (déconnexion propagée au fournisseur si OIDC_LOGOUT), /auth/logged-out ; GET /api/me. - /s/, /api/public/, /healthz et fichiers statiques restent publics. - Sans session : l'API répond 401 + X-Auth-Login, la navigation renvoie vers la connexion en conservant le schéma ouvert (#/chemin). - Frontend : reconnexion automatique sur 401, bouton de déconnexion. - Bibliothèque standard uniquement (internal/auth), tests avec un fournisseur simulé. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018GBsBpHJW7GxFDBV7z1zmo
This commit is contained in:
1 parent
2b6b6fc72c
commit
80fd60d006
4 files changed
+119
-4
No files matched your search
@@ -74,6 +74,13 @@ export interface PublicScene {
|
||||
scene: { elements?: unknown[]; appState?: Record<string, unknown>; files?: Record<string, unknown> };
|
||||
}
|
||||
|
||||
/** Session courante (GET /api/me) ; logout présent en mode OIDC. */
|
||||
export interface Me {
|
||||
mode: "none" | "local" | "oidc";
|
||||
user?: { sub: string; name: string; email?: string };
|
||||
logout?: string;
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(public status: number, message: string) {
|
||||
super(message);
|
||||
@@ -83,6 +90,9 @@ export class ApiError extends Error {
|
||||
async function request(url: string, init?: RequestInit): Promise<Response> {
|
||||
const res = await fetch(url, init);
|
||||
if (!res.ok) {
|
||||
// Session OIDC expirée : reconnexion, puis retour sur la page courante.
|
||||
const login = res.status === 401 ? res.headers.get("X-Auth-Login") : null;
|
||||
if (login) location.assign(`${login}?next=${encodeURIComponent(location.pathname + location.search + location.hash)}`);
|
||||
let msg = res.statusText;
|
||||
try {
|
||||
msg = (await res.json()).error ?? msg;
|
||||
@@ -104,6 +114,8 @@ const json = (body: unknown): RequestInit => ({
|
||||
export const api = {
|
||||
update: async (): Promise<UpdateStatus> => (await request("/api/update")).json(),
|
||||
|
||||
me: async (): Promise<Me> => (await request("/api/me")).json(),
|
||||
|
||||
list: async (dir: string): Promise<Entry[]> =>
|
||||
(await request(`/api/list?path=${q(dir)}`)).json(),
|
||||
|
||||
|
||||
Reference in new issue
Block a user