Files
songs2vid/lib/upload-paths.ts
T
Atakan Doğan Özban 9404efd86c Align preview typography with FFmpeg output, add video/song title split, and ship OSS updates.
Separate YouTube video titles from on-video song/artist fields with Pro gating, serve curated fonts and watermark assets for 1:1 preview parity, and include billing/API/docs/deploy stack for self-hosted release.
2026-07-27 16:26:19 +02:00

48 lines
1.6 KiB
TypeScript

import path from "path";
import { randomBytes } from "crypto";
import { getUploadDir } from "./storage";
/** Allow only safe staging folder names from clients. */
export function sanitizeUploadSessionKey(raw?: string | null): string {
const cleaned = (raw ?? "").replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64);
if (cleaned.length >= 8) return cleaned;
return `${Date.now().toString(36)}-${randomBytes(6).toString("hex")}`;
}
function userUploadRoot(userId: string) {
return path.resolve(getUploadDir(), userId);
}
function isInsideDir(filePath: string, dir: string) {
const resolvedFile = path.resolve(filePath);
const resolvedDir = path.resolve(dir);
return resolvedFile === resolvedDir || resolvedFile.startsWith(resolvedDir + path.sep);
}
/** Ensure a path stays under uploads/{userId}/ (blocks traversal & cross-user access). */
export function assertPathInUserUploads(userId: string, filePath: string): string {
if (!filePath?.trim()) {
throw new Error("Invalid upload path");
}
const resolved = path.resolve(filePath);
const root = userUploadRoot(userId);
if (!isInsideDir(resolved, root)) {
throw new Error("Invalid upload path");
}
return resolved;
}
export function getUserStagingDir(userId: string, sessionKey: string) {
const safeSession = sanitizeUploadSessionKey(sessionKey);
const dir = path.join(userUploadRoot(userId), "staging", safeSession);
// Defense in depth: resolve and re-check
const resolved = path.resolve(dir);
if (!isInsideDir(resolved, userUploadRoot(userId))) {
throw new Error("Invalid upload session");
}
return resolved;
}