Commit Graph

40 Commits

Author SHA1 Message Date
Ralksta
746e9ae4c9 ⚡ Bolt: Memoize auth checks in Map API (#236)
Introduced a request-scoped `Map` (`authCache`) in `app/api/map/route.ts` to memoize `isAuthenticated` calls.
The `getMapData` loop iterates over all albums across all locations. Albums within the same subpage slug trigger repeated `isAuthenticated` checks, which execute expensive HMAC cryptographic operations, causing performance degradation for large datasets.
Eliminates redundant cryptographic operations during map data fetching, reducing CPU utilization and response times for the Map API.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-05-31 10:03:03 +02:00
Ralksta
c2fa8e7596 🛡️ Sentinel: Fix Stored XSS via Content-Type spoofing in asset proxies (#237)
Sanitized the Content-Type header on image and video proxy routes (`/api/image/*`, `/api/video/*`). The content type is forced to lowercase, validated against a safe prefix (`image/`, `video/`, `audio/`), and restricted to block `svg` and `xml`. Any invalid type falls back to `application/octet-stream`. This prevents an attacker from uploading an SVG file containing a malicious script and triggering a Stored XSS attack via these proxy endpoints, which are excluded from the global CSP.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-05-31 10:02:59 +02:00
ralksta
19c3727afc feat: per-album hero image – picker from album assets, fullbleed banner on detail page 2026-05-30 15:33:43 +02:00
ralksta
6f4cb467fd fix: revalidate all pages on admin save so hero/config changes appear immediately
Add revalidatePath('/', 'layout') calls to gallery save, settings save,
and reload routes. This tells Next.js to purge any cached page responses
so the next browser refresh picks up the new hero images and config.
2026-05-29 18:10:48 +02:00
ralksta
e8e65e7172 feat(admin): add asset picker with favorites, drag-and-drop reordering, preview links, and keyboard shortcuts
- Asset Picker: Visual browser for hero images with Favorites tab (shown first)
  and All Photos tab. Includes infinite scroll and UUID fallback input for
  assets not easily found in the grid.
- Drag & Drop: All lists (hero images, standalone albums, subpage albums,
  subpage tiles) are now reorderable via drag handles using @dnd-kit.
- Preview Links: 'Preview Site' button in save bar, '↗ Site' in header, and
  per-subpage preview links with URL slug preview shown inline.
- Keyboard Shortcuts: ⌘+S / Ctrl+S to save in both Pages and Settings tabs.
- Unsaved Changes Guard: beforeunload warning prevents accidental data loss.
- New API route /api/admin/assets for browsing Immich assets with pagination
  and favorites filter via search/metadata endpoint.
2026-05-29 17:59:25 +02:00
ralksta
2e4cde4b6e fix: clear album cache on admin save so changes appear immediately
When saving gallery/settings via admin, the config cache was invalidated
but the Immich album cache (which stores resolved title overrides and
descriptions) was not cleared. This meant changes only appeared after
the TTL expired or a manual 'Reload' click.

Now both gallery and settings saves also call immich.invalidateAll()
to flush the album cache, making changes visible on next page load.
2026-05-29 17:47:08 +02:00
ralksta
b4fa37d680 fix: admin panel light/dark theme support, improved contrast, and backup confirmation
a) Admin panel now respects the site's light/dark theme toggle via CSS
   custom properties that respond to [data-theme='light'] on <html>.
   All hardcoded dark colors replaced with semantic CSS variables.

b) Improved contrast in dark mode: secondary text changed from #666/#555
   to #a0a0a0, muted text from #555 to #707070. All text is now clearly
   readable against dark backgrounds.

c) Backup system was already implemented in yaml-service.ts (creates
   timestamped backups in content/.backups/ before each save, keeps last
   10 versions). Now the UI shows a confirmation message that a backup
   was created after each successful save.
2026-05-29 17:20:56 +02:00
ralksta
4a0b57b932 feat: add thumbnail previews to admin panel for albums and hero images
- Add /api/admin/thumbnail/[id] proxy endpoint for serving Immich thumbnails
- Show album cover thumbnails in AlbumCard components (subpages + standalone)
- Show actual image thumbnails in AlbumPicker modal instead of emoji placeholders
- Show small thumbnail previews for hero image entries
- Add corresponding CSS styles for all thumbnail elements
2026-05-29 17:14:58 +02:00
ralksta
e9063d3a71 feat: add video streaming proxy and webhook cache invalidation
- 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
2026-05-29 17:04:57 +02:00
ralksta
9e52d7a0b8 feat: add visual admin panel with page builder and settings editor
- 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
2026-05-29 17:04:37 +02:00
google-labs-jules[bot]
71381342d6 🛡️ Sentinel: [security improvement]\n\nAdded rate limiting to the /api/health endpoint using the shared checkRateLimit utility. This prevents potential DoS attacks by restricting the number of requests per IP, utilizing the configured rateLimitRpm.
Co-authored-by: ralksta <16941050+ralksta@users.noreply.github.com>
2026-05-03 10:20:32 +00:00
Ralksta
f18d9ae3d9 🛡️ Sentinel: [CRITICAL] Fix rate limit DoS vulnerability (#136)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-24 22:12:42 +02:00
Ralksta
bb7c1aa1bd Improve rate-limit headers in auth API (#102)
Destructures `remaining` and `resetAt` from `checkRateLimit` and adds standard rate-limiting headers (`Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`) to the 429 Too Many Requests response.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-21 22:51:42 +02:00
Ralksta
16e1c7647b 🎨 Palette: [UX improvement] Auto-focus Close button in Lightbox (#104)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-21 22:51:31 +02:00
ralksta
17398dea39 feat: implement album-level password protection 2026-04-07 23:04:03 +02:00
ralksta
bf72a88d6c perf: implement proper LRU Cache and ETag optimizations 2026-03-27 11:11:23 +01:00
Ralksta
43d5b4d15d 🛡️ Sentinel: Add rate limiting to /api/og endpoint (#47)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-03-27 10:36:25 +01:00
google-labs-jules[bot]
cdc26c00ac 🛡️ Sentinel: [HIGH] Cache health check endpoint to prevent upstream DoS
Co-authored-by: ralksta <16941050+ralksta@users.noreply.github.com>
2026-03-20 11:33:38 +00:00
ralksta
e9154cedb8 fix(security): resolve rate-limiting IP spoofing and eviction bypass 2026-03-15 07:41:49 +01:00
ralksta
922302ce5c chore: implement audit action items and translate hardcoded German text 2026-03-15 07:32:42 +01:00
ralksta
a97b261c14 fix: raise map rate limit 30→120/min, allow 5min private browser cache
Prevents spurious 429s during normal browsing/refreshing.
Browser now caches personalised map data for 5 minutes (private,
so CDNs/proxies won't share it between users).
2026-03-13 12:55:07 +01:00
ralksta
d64a7239d1 refactor: audit improvements - rate limit ceiling, map route protection, lazy DevToolbar, lang=de
- lib/rate-limit.ts: add MAX_STORE_ENTRIES=10k ceiling + evictIfFull() to prevent DDoS memory exhaustion
- app/api/map/route.ts: add IP-based rate limiting (30 req/min) with proper Retry-After headers
- app/layout.tsx: lang='en' → 'de', DevToolbar loaded via dynamic import (reduces main bundle)
- app/layout.tsx: lazy DevToolbar keeps ~17KB out of production bundle via next/dynamic
2026-03-13 11:41:56 +01:00
ralksta
43c59f4bc9 chore: clean up repo root and improve .gitignore
- Remove debug scripts: check_albums.js, check_albums.ts
- Remove git-filter-repo binary from root
- Untrack server.log (was accidentally committed)
- Delete .DS_Store files
- Rewrite .gitignore with clear sections: deps, next.js,
  TypeScript, testing, logs, env, OS/editor, agent tools,
  one-off scripts, deployment, user content
- Add .Jules/ and *.log glob to .gitignore
2026-03-13 11:34:28 +01:00
google-labs-jules[bot]
63e6a2f784 🛡️ Sentinel: [HIGH] Fix missing rate limiting on /api/exif/[id]
Co-authored-by: ralksta <16941050+ralksta@users.noreply.github.com>
2026-03-12 11:30:45 +00:00
ralksta
a0364ac9f0 fix: remove request.ip which was removed in Next.js 15+
The request.ip property no longer exists on NextRequest in Next.js 15+.
Fall back to x-real-ip (set by trusted reverse proxies) then x-forwarded-for.
The security intent from PR #4 is preserved — x-real-ip is preferred over
the more easily spoofed x-forwarded-for header.

Fixes build error: Property 'ip' does not exist on type 'NextRequest'
2026-03-11 23:36:40 +01:00
Ralksta
a94b55704e security: prioritize request.ip over x-forwarded-for to prevent IP spoofing [#4]
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-03-11 23:32:32 +01:00
Ralksta
96fe4db099 security: validate auth input types and limit length to prevent DoS [#1]
Added type checking and length validation to the `slug` and `password` variables in the `app/api/auth/route.ts` file to prevent potential DoS attacks via excessively long strings passed to expensive crypto functions.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-03-11 23:32:22 +01:00
ralksta
23d8bf0425 refactor: apply Clean Architecture principles
- 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
2026-03-08 18:55:45 +01:00
ralksta
ea72d720df chore: production-readiness audit fixes and linting cleanup 2026-03-08 16:26:05 +01:00
ralksta
21f9fda53a fix: improve image proxy compatibility with CORS headers and Content-Type fallbacks to resolve broken lightbox images 2026-03-07 09:46:09 +01:00
ralksta
faab17f9cd fix: increase rate limit and add detailed logging to diagnose broken thumbnails 2026-03-07 09:42:33 +01:00
ralksta
cf5860563e fix: resolve edge runtime and server component style build errors 2026-03-07 09:03:32 +01:00
ralksta
e0bf02b31d security: respect global exifOnHover config in API 2026-03-07 08:19:20 +01:00
ralksta
e394fe84a0 security: implement AUTH_SECRET and secure map/exif API routes 2026-03-07 08:18:23 +01:00
ralksta
62a7b178d6 feat: add smart map view with location-level clustering
- /map page with Leaflet (CartoDB dark tiles)
- Photos clustered by city/country (not per-photo)
- /api/map endpoint with encrypted cover asset IDs
- Configurable via gallery.yaml (map: true/false)
- Conditional nav link in header
- CSP updated for Leaflet CDN and tile servers
- Theme-aware popups with cover thumbnails and album links
2026-02-15 23:07:04 +01:00
ralksta
a9737ad8e7 feat: theming system, documentation, and photo grid regression tests
- Add configurable theme system with 4 presets (studio, minimal, editorial, classic)
- Theme controls fonts, colors, accent, photo frames, grain, and header dot
- Resolve themes from gallery.yaml: string shorthand or full object override
- Inject theme as CSS custom properties on <html> element
- Dynamic Google Fonts loading based on theme config
- OG image route reads accent/background from theme
- Refactor Studio overrides into conditional CSS via data-attributes

Documentation:
- Add docs/gallery-config.md with comprehensive configuration reference
- Add docs/theming.md with preset reference and custom theme guide
- Slim README to link to detailed docs

Fix CSS regression:
- Fix passepartout frame overflow:visible breaking next/image fill rendering
- Remove duplicate .photo-grid__item block that stripped overflow:hidden

Testing:
- Add e2e/photo-grid.spec.ts with 3 regression tests:
  - Grid items render with real dimensions
  - overflow:hidden CSS guard (catches the exact bug fixed)
  - Images load with real pixel data (not blurhash)

Tooling:
- Add scripts/screenshots.ts for automated theme screenshot generation
- Handles dev server lifecycle, theme cycling, FadeIn + lazy image handling
2026-02-15 12:21:02 +01:00
ralksta
a0ee0ff433 chore: production readiness audit
- Move plans/ to dev-docs/, delete unused scaffold SVGs
- Create .dockerignore, rename container to immich-folio
- Extract BackLink component and toPhotoItems() helper in page.tsx
- Add keyboard accessibility (role=button, tabIndex, onKeyDown) to PhotoGrid
- Add aria-label to Lightbox EXIF toggle
- Fix React lint: replace setState-in-effect with useState initializer
- Add rate limiting to /api/auth (brute-force protection, 10 RPM)
- Add HSTS and X-DNS-Prefetch-Control security headers
- Add Secure flag to auth cookie in production
- New unit tests: rate-limit.test.ts (5), auth.test.ts (12)
- All 47 tests pass, 0 lint errors, build clean
2026-02-15 11:23:51 +01:00
ralksta
52fa16c1c8 feat: visual polish & UX — fade-in, theme toggle, scroll-to-top, footer, cursor, badge
- Add FadeIn component (IntersectionObserver scroll-reveal + load animation)
- Add dark/light theme toggle (useSyncExternalStore + localStorage)
- Add scroll-to-top button (appears after 300px scroll)
- Add configurable footer with social links (gallery.yaml)
- Add crosshair cursor on photo grid items
- Add 'X photos' count badge on subpage cover images
- Add light theme color palette in tokens.css
2026-02-14 11:47:26 +01:00
ralksta
b7217c4a76 feat: photography features & image optimizations
- Hero carousel: gallery.yaml hero accepts single UUID or array, crossfade component
- Full-bleed single albums: subpages with 1 album skip grid, render photos directly
- EXIF on hover: camera/lens/focal length overlay on grid items (toggle via exifOnHover)
- Customizable grid: columns/gap/aspectRatio/layout (masonry|uniform) via gallery.yaml
- Per-subpage grid overrides: each subpage can override global grid config
- True masonry: per-image aspect ratios from EXIF dimensions
- next/image migration: homepage hero, album covers, grid thumbnails, about portrait
- ThumbHash blur placeholders and dominant color backgrounds
- Custom next/image loader via image proxy with width/quality params
- Lightbox fade-in transition with dominant color background
- Password-protected subpages, health check, rate limiting, OG images
2026-02-14 10:30:27 +01:00
ralksta
415aef98f2 feat: portfolio with subpages, hero homepage, and obfuscated asset URLs 2026-02-14 00:59:57 +01:00