Files
Valentin Le Moign 59dfbe4155 Générateur de portfolios PDF Figures Libres (app Nuxt autonome)
App Nuxt 3 + Nuxt UI qui lit les projets du CMS Grav et compose des portfolios
A3 paysage exportés en PDF (gabarit book_v3) : composeur WYSIWYG, templates
cover/toc/projet/grille/page libre, pipeline Playwright + Ghostscript, gel du
contenu à la génération, partage public opt-in.

Dépôt autonome : Dockerfile (base Playwright + Ghostscript + ffmpeg + rsync) inclus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:37:09 +02:00

49 lines
1.7 KiB
TypeScript

// Session « mot de passe partagé » (spec §12) : cookie HMAC signé, sans stockage serveur.
import { createHmac, timingSafeEqual } from 'node:crypto'
import type { H3Event } from 'h3'
const COOKIE_NAME = 'fl_session'
const SESSION_TTL_MS = 1000 * 60 * 60 * 24 * 30 // 30 jours
function sign(secret: string, payload: string): string {
return createHmac('sha256', secret).update(payload).digest('base64url')
}
export function createSessionToken(secret: string): string {
const exp = String(Date.now() + SESSION_TTL_MS)
return `${exp}.${sign(secret, exp)}`
}
export function isValidSessionToken(secret: string, token: string | undefined): boolean {
if (!token) return false
const [exp, sig] = token.split('.')
if (!exp || !sig) return false
if (Number(exp) < Date.now()) return false
const expected = sign(secret, exp)
const a = Buffer.from(sig)
const b = Buffer.from(expected)
return a.length === b.length && timingSafeEqual(a, b)
}
export function setSessionCookie(event: H3Event): void {
const { sessionSecret } = useRuntimeConfig(event)
setCookie(event, COOKIE_NAME, createSessionToken(sessionSecret), {
httpOnly: true,
sameSite: 'lax',
path: '/',
maxAge: SESSION_TTL_MS / 1000,
})
}
export function clearSessionCookie(event: H3Event): void {
deleteCookie(event, COOKIE_NAME, { path: '/' })
}
export function isAuthenticated(event: H3Event): boolean {
const config = useRuntimeConfig(event)
// Jeton interne : utilisé par le rendu print (Playwright) en phase 2.
const headerToken = getHeader(event, 'x-internal-token') ?? getQuery(event)['internal-token']
if (headerToken && headerToken === config.internalToken) return true
return isValidSessionToken(config.sessionSecret, getCookie(event, COOKIE_NAME))
}