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>
51 lines
No EOL
1.8 KiB
TypeScript
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(),
|
|
});
|
|
} |