Add curator authentication with audit logging and fix empty 3D gallery sessions.

Introduce session-based curator login, gate debug/checkup routes, log mutations to curator_audit_log, and keep guest hall preload public. Fix gallery view mounting so WebGL halls render reliably after navigation.
This commit is contained in:
Danila Khodjaef
2026-07-06 00:17:01 +03:00
parent aa31a2aa6e
commit 9da065acbe
27 changed files with 1252 additions and 112 deletions
+69 -2
View File
@@ -26,6 +26,66 @@ curl.exe -sk https://devgallery.mysuperlab.netcraze.pro/api/bounds
---
## Authentication
Anonymous visitors have implicit role **`user`** (browse only). **Curator** accounts unlock debug mode, the Checkup page, and all mutating audit routes.
Sessions use an HTTP-only cookie (`gallery.sid`). The client sends `credentials: 'include'` on API requests.
### `GET /api/auth/me`
**Response (anonymous)**
```json
{ "role": "user" }
```
**Response (curator session)**
```json
{ "role": "curator", "username": "curator" }
```
### `POST /api/auth/login`
**Body:** `{ "username": "curator", "password": "…" }`
**Response:** `{ "role": "curator", "username": "curator" }`
**Errors:** `401` invalid credentials, `400` missing fields.
### `POST /api/auth/logout`
Destroys the session cookie.
**Response:** `{ "ok": true }`
### Curator-only routes
These return **`401`** with `{ "error": "Curator login required" }` without a valid curator session:
| Route | Audit action (mutations only) |
|-------|-------------------------------|
| `GET /api/paintings/checkup` | — (read) |
| `GET /api/paintings/:id/debug-image-search` (+ `/more`) | — |
| `GET /api/artists/:id/debug-portrait-search` (+ `/more`) | — |
| `GET /api/debug/image-proxy` | — |
| `PATCH /api/paintings/:id/checkup-flags` | `painting.checkup_flags` |
| `PATCH /api/artists/:id/checkup-flags` | `artist.checkup_flags` |
| `POST /api/paintings/:id/fix-image` | `painting.fix_image` |
| `POST /api/paintings/:id/clear-image` | `painting.clear_image` |
| `POST /api/paintings/:id/upload-image` | `painting.upload_image` |
| `DELETE /api/paintings/:id` | `painting.delete` |
| `POST /api/artists/:id/fix-portrait` | `artist.fix_portrait` |
| `POST /api/artists/:id/clear-portrait` | `artist.clear_portrait` |
| `POST /api/artists/:id/upload-portrait` | `artist.upload_portrait` |
**Public** (no login): all catalog `GET` routes, `POST /api/artists/:id/preload-images` (local file linking for 3D halls), `/images`, SPA static.
Curator mutations are recorded in `curator_audit_log` (see [DB_structure.md](DB_structure.md)).
---
## `GET /api/bounds`
Returns the overall timeline year range used to initialise the zoomable timeline.
@@ -316,7 +376,9 @@ Both lists are grouped by art movement and exclude the current artist. Each arti
## `POST /api/artists/:id/preload-images`
Fast local scan: links paintings to files already on disk. Does **not** download from the internet (safe to call before opening the 3D gallery).
**Public** — no curator login required.
Fast local scan: links paintings to files already on disk. Does **not** download from the internet. The 3D client calls this automatically when entering an **artist** hall.
**Response**
@@ -398,6 +460,8 @@ Returns the image bytes with `Cache-Control: public, max-age=86400`, or `404` if
## Developer image audit
**Curator login required** for every route in this section. See [Authentication](#authentication) above.
Routes for the **Checkup** page and **Debug mode** on painting detail and artist bio. Register `GET /api/paintings/checkup` **before** `GET /api/paintings/:id` so `"checkup"` is not parsed as a painting id.
### `GET /api/paintings/checkup`
@@ -590,10 +654,13 @@ Returns image bytes with appropriate `Content-Type`.
## Frontend helpers
The React client wraps these endpoints in `client/src/api/client.ts`:
The React client wraps these endpoints in `client/src/api/client.ts`. All requests send `credentials: 'include'` for session cookies.
| Function | Maps to |
|----------|---------|
| `getAuthMe()` | `GET /api/auth/me` |
| `loginCurator(user, pass)` | `POST /api/auth/login` |
| `logoutCurator()` | `POST /api/auth/logout` |
| `api.getBounds()` | `GET /api/bounds` |
| `api.getTimeline(start, end)` | `GET /api/timeline` |
| `api.getArtists(...)` | `GET /api/artists` |