retrotoon-studio/server/_core/localAuth.ts
Ubuntu 20a643c4ce fix: audit complet et pipeline fonctionnel RetroToon Studio
Corrections critiques:
- Fix titre HTML {{project_title}} -> %VITE_APP_TITLE%
- Suppression vitePluginManusRuntime (360KB -> 4KB index.html)
- Upload vidéo: multer au lieu du parsing binary maison (anti-corruption)
- Extraction audio ffmpeg + sauvegarde sourceAudioUrl en DB
- Page /login dédiée + correction redirect auth
- Test moteurs IA: vrai HEAD request avec latence
- Suppression spam logs [Auth] Missing session cookie
- Fix fuite passwordHash dans auth.me
- Cookie sameSite: none -> lax (CSRF)

Sécurité:
- Endpoints admin protégés par adminProcedure (role=admin requis)
- Sidebar admin masquée pour non-admins
- AdminPanel: page accès refusé pour non-admins
- Bootstrap admin optimisé (skip rehash si identique)

Fonctionnalités:
- Export vidéo MP4 réel via ffmpeg local (H.264 + AAC audio)
- Download parallèle par batch de 20 (export 10x plus rapide)
- Détection de scènes réelle via ffmpeg scene detect
- Analyse arrière-plans via Gemini Vision (remplace Math.random)
- Gemini: conservation du role system + support image_url
- Suppression thinking.budget_tokens:128 (LLM config)
- Thumbnails de frames dans la timeline
- Toast export avec bouton télécharger
- Endpoint extraction audio à la demande

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 01:37:08 +00:00

51 lines
No EOL
1.8 KiB
TypeScript

import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
import * as db from "../db";
const LOCAL_OPEN_ID_PREFIX = "local:";
export function normalizeEmail(email: string) {
return email.trim().toLowerCase();
}
export function hashPassword(password: string, salt = randomBytes(16).toString("hex")) {
const derivedKey = scryptSync(password, salt, 64).toString("hex");
return `${salt}:${derivedKey}`;
}
export function verifyPassword(password: string, storedHash: string) {
const [salt, expectedHash] = storedHash.split(":");
if (!salt || !expectedHash) return false;
const actualHash = scryptSync(password, salt, 64).toString("hex");
return timingSafeEqual(Buffer.from(actualHash, "hex"), Buffer.from(expectedHash, "hex"));
}
export function buildLocalOpenId(email: string) {
return `${LOCAL_OPEN_ID_PREFIX}${normalizeEmail(email)}`;
}
export async function ensureBootstrapLocalAdmin(email: string, password: string) {
const configuredEmail = normalizeEmail(process.env.LOCAL_ADMIN_EMAIL ?? "");
const configuredPassword = process.env.LOCAL_ADMIN_PASSWORD ?? "";
const normalizedEmail = normalizeEmail(email);
if (!configuredEmail || !configuredPassword) return;
if (normalizedEmail !== configuredEmail) return;
if (password !== configuredPassword) return;
const existingUser = await db.getUserByEmail(configuredEmail);
if (existingUser?.passwordHash && verifyPassword(configuredPassword, existingUser.passwordHash)) {
return;
}
const passwordHash = hashPassword(configuredPassword);
await db.upsertUser({
openId: existingUser?.openId ?? buildLocalOpenId(configuredEmail),
email: configuredEmail,
name: existingUser?.name ?? "RetroToon Admin",
passwordHash,
loginMethod: "local",
role: "admin",
lastSignedIn: new Date(),
});
}