- 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
37 lines
1.4 KiB
TypeScript
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);
|