Admin Console — Sessions & CSRF¶
The admin console at https://admin.proxy.worv.ai uses Flask cookie
sessions, not API keys.
POST /api/login¶
Authenticate with username/password. Issues a session cookie + CSRF token.
| Auth | None |
| Rate limit | 10 / minute / IP |
| CSRF | Not required |
Request¶
POST /api/login HTTP/1.1
Host: admin.proxy.worv.ai
Content-Type: application/json
{
"username": "admin",
"password": "..."
}
Responses¶
| Status | Body |
|---|---|
| 200 | {"ok": true, "username": "<u>", "csrf_token": "<hex>"} plus Set-Cookie: session=... |
| 401 | {"error": "Invalid credentials"} |
| 429 | rate-limit exhausted |
The admin password is auto-generated on first boot and printed to the
proxy's stdout / journal. Override with the ADMIN_PASSWORD env var
when starting glm-proxy.service.
POST /api/logout¶
Tear down the session.
| Auth | Session |
| CSRF | Required (X-CSRF-Token header or _csrf JSON field) |
Response¶
200 {"ok": true} and Set-Cookie: session= (cleared).
GET /api/me¶
Probe whether the caller has a live session.
Responses¶
| Condition | Status | Body |
|---|---|---|
| Authenticated | 200 | {"authenticated": true, "username": "<u>", "csrf_token": "<hex>"} |
| Anonymous | 401 | {"authenticated": false} |
The csrf_token here is the same one issued at login — safe to
re-fetch when reloading a page; it does not rotate on read.
CSRF mechanics¶
State-changing methods (POST, PATCH, DELETE) on /api/*
authenticated endpoints require a CSRF token via either:
X-CSRF-Token: <hex>header, or"_csrf": "<hex>"field in the JSON body.
Both must equal the csrf_token stored in the session. secrets.compare_digest is used so timing-attack resistance is built in.
| Method | Authenticated | CSRF check |
|---|---|---|
GET / HEAD / OPTIONS |
yes | exempt |
POST / PATCH / DELETE |
yes | required |
| any | no | exempt (only login itself) |
A missing or mismatched token returns
403 {"error":"CSRF token missing or invalid"}.
Session cookie¶
| Attribute | Value |
|---|---|
HttpOnly |
yes |
Secure |
yes |
SameSite |
Lax |
| Lifetime | 365 days (sliding; refreshed on each request) |
| Signature key | SECRET_KEY env var (rotated by operator) |
| Cookie name | session (Flask default) |
Front-end code (web/) calls GET /api/me on load to bootstrap state;
401 should redirect to the login page.