Files
rate-my-shots/README.md
2026-05-03 18:39:27 +01:00

49 lines
2.3 KiB
Markdown

# rate-my-shots
A photo-rating webapp that pairs assets from an Immich album for ELO-style head-to-head voting.
## Local development
```bash
bun install
cp .env.example .env # fill in IMMICH_URL, IMMICH_API_KEY, IMMICH_ALBUM_ID, ADMIN_PASSWORD_HASH
bun run scripts/migrate.ts
bun dev
```
Open [http://localhost:3000](http://localhost:3000).
## Required env
| Var | Description |
| --------------------- | ----------------------------------------------------- |
| `IMMICH_URL` | Base URL of the Immich instance |
| `IMMICH_API_KEY` | Immich API key with read + rating-write scope |
| `IMMICH_ALBUM_ID` | UUID of the album to pull assets from |
| `ADMIN_PASSWORD_HASH` | scrypt hash, format `<salt-hex>:<hash-hex>` (see below) |
| `DATABASE_PATH` | SQLite file path (default `data/rate-my-shots.db`) |
Generate the admin password hash:
```bash
node -e "const c=require('crypto');const s=c.randomBytes(16);process.stdout.write(s.toString('hex')+':'+c.scryptSync(process.argv[1],s,64).toString('hex')+'\n')" 'your-password'
```
## Deployment
The Dockerfile produces a standalone Next.js bundle that runs as a non-root `bun` user. Mount `./data` on the host for SQLite persistence; the host directory must be writable by UID 1000.
**The app trusts `X-Forwarded-For` for client IP** (rate limiting, vote IP-binding). Run it behind a reverse proxy that strips/sets that header from untrusted clients — Caddy with `reverse_proxy` does this by default. Do **not** expose the container directly to untrusted networks; clients can otherwise spoof XFF and bypass rate limits or hijack pairings.
```bash
docker compose up -d --build
```
## Security model
- Admin auth is a hashed password (scrypt) verified at login; sessions are random opaque tokens stored in-memory with a 7-day TTL (server restart logs admins out).
- Login attempts are rate-limited per IP (5 / 5 min, then exponential backoff).
- The `/img/[id]` proxy only serves assets present in the local DB (album members or configured lens portraits). Other Immich assets are 404.
- All `/api/**` POST/PUT/DELETE requests require `Origin` to match `Host`.
- CSP, `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, and `Referrer-Policy: same-origin` are set on all responses.