chore: harden security, clean deps, add vitest baseline
- .env.example: vider les secrets (à rotater côté Plesk)
- src/lib/auth.ts: fail-fast lazy si ADMIN_JWT_SECRET < 32 chars
- src/pages/api/upload.ts: retirer .svg (anti-XSS) + try/catch FS
- src/pages/api/content/[section].ts: masquer err.message (sauf ZodError)
- retirer gsap/three/@types/three/@react-three/{fiber,drei}/lucide-react
- bump astro 6.1.10 (GHSA-xr5h-phrj-8vxv)
- zod 4: z.email()/z.url() au lieu de .email()/.url()
- nettoyer vars inutilisées + Header script is:inline
Vitest (38 tests, 615ms):
- tests/lib/auth.test.ts: verifyPassword + verifySession (round-trip, secrets distincts)
- tests/lib/content.test.ts: load/save + comportement seed
- tests/api/files.test.ts: path traversal (../, /, imbriqué) + 200
- tests/api/upload.test.ts: extension/taille/sanitize folder + bucket
- tests/middleware.test.ts: gating admin pages vs APIs vs routes publiques
- astro check: 0 errors / 0 warnings / 0 hints
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,9 +9,22 @@ const env = {
|
||||
};
|
||||
|
||||
const ADMIN_PASSWORD_HASH = (env.ADMIN_PASSWORD_HASH as string | undefined) ?? '';
|
||||
const JWT_SECRET =
|
||||
(env.ADMIN_JWT_SECRET as string | undefined) ?? 'dev-secret-change-me-in-production';
|
||||
const SECRET_KEY = new TextEncoder().encode(JWT_SECRET);
|
||||
const JWT_SECRET = (env.ADMIN_JWT_SECRET as string | undefined) ?? '';
|
||||
|
||||
// Lazy : on ne throw pas au chargement du module (Astro build importe ce fichier
|
||||
// en SSR sans forcément avoir le .env). On throw au premier usage runtime.
|
||||
let cachedKey: Uint8Array | null = null;
|
||||
function getSecretKey(): Uint8Array {
|
||||
if (cachedKey) return cachedKey;
|
||||
if (!JWT_SECRET || JWT_SECRET.length < 32) {
|
||||
throw new Error(
|
||||
'[auth] ADMIN_JWT_SECRET manquant ou trop court (>= 32 chars). ' +
|
||||
'Générer via `npm run hash:password -- <motdepasse>` et reporter dans .env.',
|
||||
);
|
||||
}
|
||||
cachedKey = new TextEncoder().encode(JWT_SECRET);
|
||||
return cachedKey;
|
||||
}
|
||||
|
||||
export const ADMIN_COOKIE = 'portfolio_admin';
|
||||
|
||||
@@ -28,13 +41,13 @@ export async function createSession(): Promise<string> {
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('7d')
|
||||
.sign(SECRET_KEY);
|
||||
.sign(getSecretKey());
|
||||
}
|
||||
|
||||
export async function verifySession(token: string | undefined): Promise<boolean> {
|
||||
if (!token) return false;
|
||||
try {
|
||||
await jwtVerify(token, SECRET_KEY);
|
||||
await jwtVerify(token, getSecretKey());
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user