mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-11-14 23:08:26 +00:00
This is the read-only API key from #1410, where you said to go ahead. I run Shelfmark behind Homepage and wanted more on the dashboard tile than up or down, without putting an admin API key in the dashboard's config. This adds a second key, `SHELFMARK_API_KEY_READONLY`, that can read one new endpoint. `GET /api/stats` returns counts only: books added over the last 7 and 30 days by format, the queue, requests by outcome, and download failures over the last 7 days. No titles and no user names. The read-only key, the admin key or an admin session can read it. The read-only key gets a 403 on every other path and on any write, including a POST to `/api/stats` itself, and it never gets a session or a cookie. If a request carries both keys, the admin one wins, so a proxy's own `Authorization` header can't downgrade a correct `X-Api-Key`. Unset means off, like the existing key. The endpoint exists either way, but without a key only an admin session can read it. It also adds a `db_path` property on `UserDB`, so the stats module can open the database read-only. Tests cover the key scope, the 403s for the read-only key, both keys at once, who can reach the endpoint, and the counters. The full suite passes, and I broke each rule on purpose to check a test catches it. A version of this has been running on my own install behind Homepage. If you'd rather have the stats in a different shape or under a different path, I'm happy to change it.
86 lines
3.8 KiB
Markdown
86 lines
3.8 KiB
Markdown
# API access with an API key
|
|
|
|
Shelfmark's web interface is driven entirely by a JSON API under `/api/`. Set
|
|
the `SHELFMARK_API_KEY` environment variable and scripts, dashboards and assistants can
|
|
call the same API without a browser session. Browser logins keep working
|
|
exactly as before: it is cookie **or** key.
|
|
|
|
## Set the key
|
|
|
|
```yaml
|
|
environment:
|
|
SHELFMARK_API_KEY: "a-long-random-secret"
|
|
```
|
|
|
|
Generate something long and random (for example `openssl rand -base64 32`).
|
|
A request carrying the key acts as an **admin**: the first admin user in
|
|
Shelfmark's user database. Create an admin before relying on the key in any
|
|
install that has none yet (for example an OIDC-only install). Without an
|
|
admin user, the key still authenticates as an admin identity with no user
|
|
row, and routes that need one (requests, activity) answer 403. To rotate,
|
|
change the variable and restart. Unset it and the feature is off. When the
|
|
instance runs with no authentication configured (`AUTH_METHOD=none`), the
|
|
key is simply unnecessary.
|
|
|
|
## Send the key
|
|
|
|
Either header works, and both are checked, so the key can be sent in
|
|
`X-Api-Key` behind a reverse proxy that sets its own `Authorization` header.
|
|
|
|
```bash
|
|
curl -s -H "Authorization: Bearer $SHELFMARK_API_KEY" https://shelfmark.example.com/api/downloads/active
|
|
curl -s -H "X-Api-Key: $SHELFMARK_API_KEY" https://shelfmark.example.com/api/downloads/active
|
|
```
|
|
|
|
A request that carries the key is authenticated by the key alone. Session
|
|
cookies are ignored and none are set. A bearer value that is not the configured
|
|
key is ignored and the request continues with normal session authentication,
|
|
so reverse proxies that forward their own tokens are unaffected; without a valid
|
|
session such a request gets the usual `401 {"error": "Unauthorized"}`. A
|
|
database error while resolving the admin returns
|
|
`500 {"error": "Authentication error"}` — never anonymous access.
|
|
`/api/auth/check` reflects the browser session only and ignores the key, so
|
|
use `/api/status` to verify a key.
|
|
|
|
## Examples
|
|
|
|
Search, then look up releases, then queue one (the same calls the web UI makes):
|
|
|
|
```bash
|
|
curl -s -H "Authorization: Bearer $SHELFMARK_API_KEY" \
|
|
"https://shelfmark.example.com/api/metadata/search?query=dune%20frank%20herbert"
|
|
# -> {"books":[{"provider":"hardcover","provider_id":"427363", ...}]}
|
|
|
|
curl -s -H "Authorization: Bearer $SHELFMARK_API_KEY" \
|
|
"https://shelfmark.example.com/api/releases?provider=hardcover&book_id=427363&content_type=ebook"
|
|
# -> {"releases":[{"source":"direct_download","source_id":"...", ...}], ...}
|
|
|
|
curl -s -X POST -H "Authorization: Bearer $SHELFMARK_API_KEY" -H "Content-Type: application/json" \
|
|
-d @release.json https://shelfmark.example.com/api/releases/download
|
|
# release.json = one object from "releases" (source and source_id are required)
|
|
|
|
curl -s -H "Authorization: Bearer $SHELFMARK_API_KEY" https://shelfmark.example.com/api/status
|
|
```
|
|
|
|
## Security notes
|
|
|
|
- The key is compared in constant time and is never logged.
|
|
- Keyed requests never set cookies and ignore any cookie sent with them.
|
|
- WebSocket (live activity) connections do not accept the key; poll `/api/status` instead.
|
|
- The key is a root-equivalent credential: an admin can configure a custom
|
|
post-download script that the server executes, so treat it like a root
|
|
password and send it only over HTTPS.
|
|
|
|
## Read-only key and dashboard stats
|
|
|
|
Set `SHELFMARK_API_KEY_READONLY` to a second secret for dashboards and monitors. It can read one endpoint,
|
|
`GET /api/stats`, and nothing else. Writes and every other path return 403, and the key never gets a session.
|
|
The admin key reaches `/api/stats` too.
|
|
|
|
```bash
|
|
curl -s -H "X-Api-Key: $SHELFMARK_API_KEY_READONLY" https://shelfmark.example.com/api/stats
|
|
```
|
|
|
|
The response has counts only: books added in the last 7 and 30 days by format, the queue, requests by outcome,
|
|
and download failures in the last 7 days. No titles and no user names.
|