49 lines
2.3 KiB
Markdown
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.
|