Seguridad y docs: fix SQLi en updatePost, middleware por segmentos exactos, cover-cache directo al upstream, JWT sin fallback hardcodeado, password sudo fuera de deploy/setup.sh; reescritura AGENTS.md y CLAUDE.md

This commit is contained in:
Renato
2026-08-04 11:13:57 +08:00
parent 5d36c635cb
commit 451518004d
12 changed files with 419 additions and 928 deletions
+15 -3
View File
@@ -1,5 +1,6 @@
import fs from "fs"
import path from "path"
import { get } from "./settings"
const COVERS_DIR = process.env.COVERS_DIR || path.join(process.cwd(), "data", "covers")
@@ -20,10 +21,21 @@ function extFromContentType(ct: string | null): string {
return exts[m[1]] || ".jpg"
}
export async function cacheCover(gid: string, proxyUrl: string): Promise<string | null> {
// Descarga la portada DIRECTAMENTE del upstream (API_BASE_URL), con la misma
// API key que usa el resto de la app. NO pasa por el proxy /api/proxy/* (que el
// middleware protege), evitando el redirect a /login que guardaba HTML como portada.
export async function cacheCover(gid: string): Promise<string | null> {
ensureDir()
try {
const res = await fetch(proxyUrl, { signal: AbortSignal.timeout(15000) })
const apiBase = get("API_BASE_URL", "http://127.0.0.1:8080/api/v1")
const apiKey = get("API_KEY")
const headers: Record<string, string> = {}
if (apiKey) headers["X-API-Key"] = apiKey
const res = await fetch(`${apiBase}/galleries/${gid}/cover`, {
headers,
signal: AbortSignal.timeout(15000),
})
if (!res.ok) return null
const buffer = Buffer.from(await res.arrayBuffer())
@@ -61,4 +73,4 @@ export function getCoverContentType(gid: string): string {
export function deleteCover(gid: string): void {
const fp = getCoverPath(gid)
if (fp) fs.unlinkSync(fp)
}
}