--- // Öffentliche Ansicht eines Teilen-Links — ohne Anmeldung erreichbar. // // Bewusst eine eigene, schlanke Seite statt der App: Wer den Link bekommt, soll // die Bilder sehen und herunterladen können, sonst nichts. Keine Navigation, // kein Zugang zur Bibliothek, kein Hinweis darauf, was es sonst noch gibt. import Base from '../../layouts/Base.astro'; import { resolveShare, shareItems, trackShare, nachweisBauen, nachweisGilt, nachweisAusCookie, nachweisName } from '../../lib/shares'; import { zuOft, echteIp } from '../../lib/limit'; const slug = Astro.params.slug!; // Zugangswort kommt per POST (nicht als `?pw=` in der Adresse — das landete // sonst im Verlauf, in Proxy-Protokollen und in jedem weitergeleiteten Link). // Nach erfolgreicher Prüfung merkt sich ein kurzlebiges, auf diesen Link // beschränktes Cookie das Ergebnis: `argon2.verify` kostet 130 ms und 64 MiB // und lief sonst bei **jedem** Bildaufruf erneut. let eingabe: string | null = null; let falsch = false; let gesperrt = false; if (Astro.request.method === 'POST') { // Durchprobieren begrenzen: Ein Zugangswort, das man am Telefon durchgibt, // ist kurz — ohne Bremse wäre Raten aussichtsreich, und jede Prüfung kostet // 130 ms und 64 MiB. if (zuOft(`wort:${slug}:${echteIp(Astro.request, Astro.clientAddress)}`, 10, 15 * 60_000)) { gesperrt = true; } else { const f = await Astro.request.formData().catch(() => null); eingabe = String(f?.get('pw') || '') || null; } } const cookieKopf = Astro.request.headers.get('cookie'); let { status, share } = await resolveShare(slug, eingabe); // Bereits nachgewiesen? Dann ohne erneutes Hashen durchlassen. if (status === 'passwort' && share && nachweisGilt(share.id, nachweisAusCookie(cookieKopf, slug))) { status = 'ok'; } if (status === 'ok' && share?.password_hash && eingabe) { Astro.response.headers.append('Set-Cookie', `${nachweisName(slug)}=${nachweisBauen(share.id)}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=21600`); } if (status === 'passwort' && eingabe) falsch = true; let bilder: Awaited> = []; if (status === 'ok' && share) { bilder = await shareItems(share.id); await trackShare(share.id, 'view', Astro.clientAddress, Astro.request.headers.get('user-agent')); } // Diese Seite gehört nie in einen Suchindex. Astro.response.headers.set('X-Robots-Tag', 'noindex, nofollow, noarchive, noimageindex'); if (status !== 'ok') Astro.response.status = status === 'nicht-gefunden' ? 404 : status === 'passwort' ? 401 : 410; const titel = share?.title || (bilder.length > 1 ? 'Bilder' : 'Bild'); const dl = (id: string) => `/api/s/${slug}/file?item=${id}&download=1`; const gross = (id: string) => `/api/s/${slug}/file?item=${id}`; const klein = (id: string) => `/api/s/${slug}/file?item=${id}&thumb=1`; const meldung: Record = { 'nicht-gefunden': { h: 'Diesen Link gibt es nicht', t: 'Vielleicht hat sich beim Kopieren ein Zeichen verloren. Frag am besten noch einmal nach.' }, 'abgelaufen': { h: 'Dieser Link ist abgelaufen', t: 'Teilen-Links gelten nur eine begrenzte Zeit. Wer ihn dir geschickt hat, kann einen neuen erzeugen.' }, 'widerrufen': { h: 'Dieser Link wurde deaktiviert', t: 'Er wurde bewusst abgeschaltet. Frag noch einmal nach, wenn du die Bilder brauchst.' }, }; ---
{status === 'ok' && share ? ( <>

geteilt über Klarbild

{titel}

{share.note &&

{share.note}

}

{bilder.length === 1 ? '1 Bild' : `${bilder.length} Bilder`} {share.expires_at && <> · gültig bis {new Date(share.expires_at).toLocaleDateString('de-DE')}}

{share.allow_download && bilder.length > 1 && (
Alle {bilder.length} Bilder herunterladen als ZIP-Datei, in voller Auflösung
)} {share.allow_download ? (

Herunterladen speichert die Datei in voller Auflösung. Am Handy geht es auch so: lange auf ein Bild drücken und speichern. {bilder.length > 1 && <> Oder alle auf einmal, oben.}

) : (

Diese Bilder sind zum Ansehen gedacht. Wenn du eine Datei brauchst, frag bei der Person nach, die dir den Link geschickt hat.

)} {bilder.length === 0 ? (

Hier ist gerade nichts zu sehen. Vielleicht wurden die Bilder inzwischen entfernt — frag am besten noch einmal bei der Person nach, die dir den Link geschickt hat.

) : ( )}

Wer dir diesen Link geschickt hat, sieht, wann und mit welcher Art Gerät er geöffnet wurde. Nicht, wer das war: Von deiner Internet-Adresse wird nur ein unkenntlicher Zahlenwert gespeichert, und der wird nach 90 Tagen gelöscht.

) : status === 'passwort' ? (

Dieser Link ist mit einem Wort geschützt

Gib das Zugangswort ein, das du zusammen mit dem Link bekommen hast.

{gesperrt ?

Zu viele Versuche. Bitte in einer Viertelstunde noch einmal.

: falsch &&

Das war leider nicht richtig.

}
) : (

{meldung[status]?.h ?? 'Nicht verfügbar'}

{meldung[status]?.t ?? ''}

)}