Files
veille-ia-gen/app/src/hooks.server.ts
T
cyber-mawonaj ff26136dfb feat(app) : socle SvelteKit phase 0 — SSO, thème OKI, adapter-node
- Scaffold SvelteKit 2 + Svelte 5 (runes), TypeScript strict, adapter-node
- Auth SSO via header Ynh-User (hooks.server) ; accueil public minimal sans header,
  401 sur toute autre route ; DEV_USER en dev local uniquement
- Design system OKI : tokens, thème sombre par défaut, clair opt-in anti-FOUC,
  flag-bar, fonts Archivo/Inter self-hébergées, sprite icons.svg (zéro emoji)
- CSP via kit.csp (mode nonce) + en-têtes de sécurité ; paths.base configurable
  au build (BASE_PATH) pour le sous-chemin YunoHost, paths.relative = false
- i18n fr (structure prête gcf/en), pages : accueil public/privé, erreurs designées
- Tests vitest : résolution SSO + routes publiques (10 cas)
- Vérifié : check/lint/test/build verts, test fumée HTTP sur build (SSO, CSP,
  sous-chemin /veille), zéro emoji et zéro domaine tiers dans le build
2026-08-01 09:18:27 -04:00

37 lines
1.4 KiB
TypeScript

import { dev } from '$app/environment';
import { base } from '$app/paths';
import { sequence } from '@sveltejs/kit/hooks';
import { error, type Handle } from '@sveltejs/kit';
import { config } from '$lib/server/config';
import { estRoutePublique, resoudreUtilisateur } from '$lib/server/sso';
/**
* Authentification SSO : l'utilisateur vient du header injecté par le reverse proxy
* YunoHost (SSOwat). Sans utilisateur, seule la page d'accueil publique est accessible ;
* toute autre route (pages et endpoints API) renvoie 401.
*/
const authHandle: Handle = async ({ event, resolve }) => {
event.locals.user = resoudreUtilisateur(event.request.headers, {
ssoHeader: config.ssoHeader,
devUser: config.devUser,
dev
});
if (!event.locals.user && !estRoutePublique(event.url.pathname, base)) {
error(401, 'Authentification requise');
}
return resolve(event);
};
/** En-têtes de sécurité complétant la CSP (gérée par la config SvelteKit, mode nonce). */
const securityHeadersHandle: Handle = async ({ event, resolve }) => {
const response = await resolve(event);
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
return response;
};
export const handle = sequence(authHandle, securityHeadersHandle);