* fix: honour gallery.yaml order for homepage albums
getStandaloneAlbums() filtered the Immich API album list but kept the
API's own (creation/update) order, so reordering the albums list in
gallery.yaml or the admin panel had no effect on the homepage. Sort by
the config list's index instead.
Fixes#352
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: serve fresh settings after admin save without restart
getConfig() short-circuited on a per-worker in-memory cache in
production. Admin saves invalidate that cache only in the worker that
handles the API request; when Next.js renders pages in a different
worker/process, its copy stayed stale until a container restart.
Drop the in-memory config cache entirely and rebuild the config per
call. Freshness comes from the existing mtime-checked YAML cache in
loadYaml() — one statSync per file, negligible next to Immich API
calls, and the same behaviour dev mode has always had.
Fixes#351
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Seven security issues from an audit, plus album pages and the map coming
up empty against Immich 3.x. Verified against a real Immich 3.0.3
instance in a production build.
Security:
- Admin session forgery via a hardcoded 'dev-fallback-secret' signing key
that bypassed the production AUTH_SECRET guard
- /api/map ignored album passwords and returned working asset tokens;
rewriting ?size=thumbnail to ?size=original downloaded full-resolution
originals from protected albums
- Admin login had no rate limit, and the client IP came from a spoofable
header, so rotating it bypassed every limit in the app
- Raw Immich asset UUIDs were serialized into the RSC payload
- Album tokens never expired server-side; a captured token replayed forever
- Path traversal into the Immich API via an unvalidated album id
- /admin ran with no enforced CSP (only Report-Only reached it)
TRUSTED_PROXIES is replaced by TRUSTED_PROXY_HOPS. The old variable
matched proxy IPs against the socket peer address, which self-hosted
Next.js never exposes, so the check could never pass: unset, the limiter
read a spoofable header; set, every request shared one bucket and a single
client could rate-limit the whole site. Migrates automatically.
Immich 3.x: GET /albums/:id no longer embeds assets. Assets now come from
POST /search/metadata with withExif: true — without that flag EXIF is
omitted and grid aspect ratios, the lightbox panel and map markers stay
empty. Drops support for Immich below 3.0, which now logs a warning.
Upgrade: requires Immich 3.0+; set TRUSTED_PROXY_HOPS (nginx = 1);
visitors of protected areas and admin sessions must log in once more.
91 unit tests (25 new); the new admin-auth and rate-limit cases were
confirmed to fail against the pre-fix code.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add /api/video/[id] route with Range header support for seeking
- Add /api/webhook route for Immich event-driven cache invalidation
- Add streamVideo() to ImmichClient with 206 Partial Content support
- Support album description overrides in gallery.yaml
- Add videoUrl() helper to lib/urls.ts
- Export cache.delete() and cache.clear() methods
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
- Implement an in-memory `Map` to cache parsed YAML objects.
- Use `structuredClone` to prevent caller mutations from corrupting the cached object state.
- Implement negative caching for missing files.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
- Remove dead theme preset (no CSS, not in DevToolbar)
- Extract from closure → module-level export in lib/config/index.ts
- Extract helper in app/[...path]/page.tsx to deduplicate password gate check
- Extract inner component in app/page.tsx (was 4× inline in minimal/fullbleed/mosaic/split)
- Add 8 unit tests for buildSubpageGrid covering undefined, subset fields, layout fallback, and all fields
- Add SubpageSectionConfig type to schema.ts
- Update config parser to handle sections with album overrides
- Refactor SubpageGridView to render TOC + section headers
- Add base CSS for subpage-toc and subpage-section in globals.css
- Add theme-specific overrides for all 5 themes (minimal, editorial,
classic, noir, monograph)
- Update gallery.yaml.example and docs/gallery-config.md
- Extract EXIF and Swipe logic into custom hooks for Lightbox
- Extract Map layout data formatting into mapService
- Extract AlbumDetailView and SubpageGridView components from page router
- Restructure monolithic config.ts into clean config/ directory