Commit Graph
4 Commits
Author SHA1 Message Date
puttaalu a7f2afc3ad Add a /manage page to edit, delete and restore posts
A new auth-gated web UI at /manage lists every post for inline editing of
caption, date and tags, with per-row and bulk delete and bulk tag add/remove.
It is backed by a small JSON API (all behind the same htpasswd Basic auth as
/upload, and unlinked from the public site):

  GET  /api/photos        list posts (newest first)
  POST /api/edit          rewrite one sidecar (caption, date, tags)
  POST /api/tags          bulk add/remove tags across posts
  POST /api/delete        move posts to content/.trash/ (reversible)
  GET  /api/trash         list trashed items
  POST /api/restore       move a trashed item back (409 on live-slug clash)
  POST /api/trash/empty   hard delete trashed items (id… or all=1)

Design notes:
- Editing only rewrites the .toml; the slug (and URL) never changes.
- Delete is reversible: image + sidecar move to content/.trash/ with a
  timestamp suffix, excluded from the site (loadPhotos skips dirs). The only
  hard delete is emptying the trash.
- Build now prunes: it drops the derived HTML trees (photo/ day/ album/ page/
  grid/page/) and regenerates them, and pruneRenditions removes orphaned
  images/thumbs/medium/og. Without this a deleted post would linger at its old
  URL and on stale day/album pages; this also cleans up after a hand-removed
  sidecar.
- Sidecars are written atomically (temp + rename) so a concurrent build never
  observes a half-written file.
- Slugs and trash ids are validated as bare filenames, closing path traversal
  and dotfile access.
- Every state-changing POST passes a sameOrigin (CSRF) check, since Basic-auth
  credentials are replayed by the browser on every request to the host. It
  honours Sec-Fetch-Site, falls back to Origin vs base-url/Host, and allows
  header-less non-browser clients. Ported from the independent /manage on
  plex/trunk-ng.

module.nix proxies /manage and /api/ to the app (nginx otherwise serves them
as missing static files). The apps are unchanged; the API is there for them to
adopt edit/delete later.
2026-10-18 21:52:11 +02:00
puttaalu 434d2437bf Move upload auth from nginx into the app
Add HTTP Basic auth on /upload, reading an Apache htpasswd file (bcrypt
only, `htpasswd -B`) via a new -htpasswd flag. This lets nginx drop to a
plain TLS-terminating reverse proxy with no auth of its own; the existing
bcrypt htpasswd file is reused unchanged.

auth.go:
- LoadHtpasswd parses user:hash lines, keeps bcrypt entries ($2a/$2b/$2y,
  so Apache's $2y$ works), skips MD5/SHA/crypt with a warning.
- Unknown users still pay for one bcrypt comparison against a dummy hash,
  so response timing can't enumerate valid usernames.
- Failed attempts draw from a token-bucket limiter (x/time/rate, ~1/s,
  burst 10) returning 429 once drained; successful and bulk uploads never
  touch it.

server.go guards both /upload routes (nil authenticator = passthrough for
local --serve). /health stays open. Empty -htpasswd disables auth and logs
a warning.

module.nix: new top-level services.photogallery.htpasswdFile, delivered to
the service via systemd LoadCredential (so a root-owned 0400 secret works).
Removed nginx.htpasswdFile and basic_auth from the vhost.

Both uploader apps already send Basic credentials, so no client change.

Deps: golang.org/x/crypto/bcrypt, golang.org/x/time/rate (vendorHash bump).
2026-10-18 14:45:33 +02:00
puttaalu a42abfab48 Add albums: group photos by free-text tags
An album is a tag. Photos carry lowercase free-text tags in their .toml
sidecar (`tags = [...]`), parallel to the existing day grouping — no
database, all derived at build time.

- albums.go: parseTags (lowercase/trim/dedupe, slug via sanitiseSlug),
  groupByTag mirroring groupByDay, and rendering for album pages, the
  index, and the JSON API. Slug collisions between distinct names are
  suffixed and logged.
- Routes: /album/<slug>/ (paginated square grid per tag), /albums/ (index
  with counts), /tags.json (machine-readable tag list, served statically
  like feed.xml).
- POST /upload gains a `tags` field written into the sidecar; the web
  upload form gains a tags input.
- Tags shown on each photo page linking to their album; an Albums link in
  the timeline/grid header.
2026-10-08 16:14:09 +02:00
puttaalu fd2828f1e7 Initial commit 2026-07-02 00:53:17 +02:00