Privater Bereich /api/privat fuer Behoerdenfotos (d4rk-rp-planung#227 H1)
Polizeifotos aus dem Spiel duerfen nicht unter /f/ liegen, wo jeder sie mit der Adresse bekommt. Neu: - eigener Ordner DATA_DIR/privat neben files/ (fuer /f/ und /t/ durch Bauart unerreichbar), eigene Tabelle privat_media, eigene Vorschauen - PUT/GET /api/privat/datei/<pfad>, ?vorschau=1; kein Ueberschreiben (409), kein Loeschweg; Cache-Control private, no-store - Token-Fessel "privat": ein privater Token kommt an keinen oeffentlichen Weg (upload, Fivemanage, Liste), ein oeffentlicher an keinen privaten - Dashboard: Kaestchen "nur privater Bereich" beim Anlegen und Bearbeiten, Marke in der Liste - checkPathUnter: dieselbe Pfadregel fuer beide Ablagen - tools/sichern.ps1 packt privat/ mit und zaehlt Dateien gegen Eintraege Geprueft mit tools/privat-pruefen.mjs gegen einen Wegwerf-Container des Abbilds: 21/21, mit Kontrollen (oeffentliches Bild MUSS unter /f/ kommen). Dashboard gemessen (kopfloser Edge): 7/7. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
c8080fb2eb
commit
6630def7b2
14 files changed
+641
-15
No files matched your search
+36
-4
@@ -28,6 +28,14 @@ export class PathError extends Error {}
|
||||
|
||||
/** Prueft den Pfad und gibt ihn normalisiert zurueck. Wirft PathError. */
|
||||
export function checkPath(raw: string): string {
|
||||
return checkPathUnter(raw, config.filesDir)
|
||||
}
|
||||
|
||||
/** Dieselbe Pruefung gegen einen anderen Wurzelordner -- fuer den privaten
|
||||
* Bereich (config.privatDir). EINE Regel fuer beide Ablagen, damit sie nicht
|
||||
* auseinanderlaufen; nur die Wurzel, unter der das Ergebnis liegen muss,
|
||||
* ist verschieden. */
|
||||
export function checkPathUnter(raw: string, basis: string): string {
|
||||
const path = raw.trim().replace(/^\/+/, '')
|
||||
|
||||
if (!path) throw new PathError('Pfad ist leer')
|
||||
@@ -58,8 +66,8 @@ export function checkPath(raw: string): string {
|
||||
|
||||
// Guertel und Hosentraeger: selbst wenn oben je eine Luecke waere, muss
|
||||
// das Ergebnis unterhalb des Datenordners liegen.
|
||||
const absolute = resolve(config.filesDir, path)
|
||||
if (absolute !== config.filesDir && !absolute.startsWith(config.filesDir + sep)) {
|
||||
const absolute = resolve(basis, path)
|
||||
if (absolute !== basis && !absolute.startsWith(basis + sep)) {
|
||||
throw new PathError('Pfad zeigt aus dem Datenordner heraus')
|
||||
}
|
||||
|
||||
@@ -254,7 +262,19 @@ export async function vielleichtKleiner(
|
||||
* Bild durch ein halbes ersetzen — und halbe Bilder sehen im Handy aus wie
|
||||
* ein Fehler im Handy. */
|
||||
export async function writeFileAtomic(path: string, data: Buffer): Promise<void> {
|
||||
const target = absolutePath(path)
|
||||
await schreibenNach(absolutePath(path), data)
|
||||
}
|
||||
|
||||
/** Wo eine PRIVATE Datei liegt. Den Pfad vorher mit
|
||||
* checkPathUnter(raw, config.privatDir) pruefen. */
|
||||
export const privatPfad = (path: string) => join(config.privatDir, path)
|
||||
|
||||
/** Eine private Datei schreiben -- dieselbe Atomaritaet wie writeFileAtomic. */
|
||||
export async function writePrivatAtomic(path: string, data: Buffer): Promise<void> {
|
||||
await schreibenNach(privatPfad(path), data)
|
||||
}
|
||||
|
||||
async function schreibenNach(target: string, data: Buffer): Promise<void> {
|
||||
await mkdir(dirname(target), { recursive: true })
|
||||
|
||||
const temp = `${target}.${process.pid}.${Date.now()}.tmp`
|
||||
@@ -295,7 +315,19 @@ export const kannVorschau = (mime: string) =>
|
||||
* scheitern zu lassen — das Bild selbst liegt dann schon richtig. Der
|
||||
* Aufrufer bekommt false und schreibt eine Zeile ins Protokoll. */
|
||||
export async function writeThumb(path: string, data: Buffer): Promise<boolean> {
|
||||
const ziel = thumbPath(path)
|
||||
return vorschauNach(thumbPath(path), data, path)
|
||||
}
|
||||
|
||||
/** Die Vorschau eines PRIVATEN Bildes -- unter config.privatVorschauDir, das
|
||||
* /t/ nicht ausliefert. Dieselbe Rechnung wie writeThumb. */
|
||||
export const privatVorschauPfad = (path: string) => join(config.privatVorschauDir, `${path}.webp`)
|
||||
|
||||
export async function writePrivatVorschau(path: string, data: Buffer): Promise<boolean> {
|
||||
return vorschauNach(privatVorschauPfad(path), data, `privat:${path}`)
|
||||
}
|
||||
|
||||
/** Die eine Rechnung fuer beide Ablagen: auf THUMB_BREITE, als WebP, an `ziel`. */
|
||||
async function vorschauNach(ziel: string, data: Buffer, path: string): Promise<boolean> {
|
||||
try {
|
||||
// Der Import steht hier drin und nicht oben: sharp zieht eine native
|
||||
// Bibliothek nach, und die soll nur geladen werden, wenn wirklich ein
|
||||
|
||||
Reference in new issue
Block a user