🚧 WIP
This commit is contained in:
@@ -106,7 +106,19 @@ npm run dev # serves the SPA on :5173, proxying /api to localhost:18080
|
||||
|
||||
## API reference
|
||||
|
||||
Authentication: pass one of the configured app tokens either as the `X-App-Token` header or as the `app_token` query parameter. Missing/invalid tokens get `401`.
|
||||
Authentication is layered:
|
||||
|
||||
- **App tokens** (`X-App-Token` header or `app_token` query param) — for machine clients (scripts, integrations). Unrestricted access; SHA-256 digests live in `app_tokens`.
|
||||
- **User sessions** — for humans via the management UI. `POST /auth/login` (username/password) returns an opaque Bearer token; only its SHA-256 digest is stored in `sessions` (30-day server-side expiry). Roles gate writes:
|
||||
|
||||
| Capability | `user` | `superuser` | `admin` | app token |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Read messages/devices/attributes/users | ✓ | ✓ | ✓ | ✓ |
|
||||
| Write devices, settings, attribute timelines, configs, tokens | — 403 | ✓ | ✓ | ✓ |
|
||||
| Add users | — 403 | ✓ — only role `user` | ✓ any role | ✓ |
|
||||
| Modify/delete users | — 403 | ✓ — only `user`-role accounts | ✓ (never the last admin) | ✓ |
|
||||
|
||||
The first admin is seeded from `BOOTSTRAP_ADMIN_USER`/`BOOTSTRAP_ADMIN_PASSWORD` on the first login attempt against an empty `users` table (same bootstrap pattern as `BOOTSTRAP_APP_TOKEN`). Sessions resolve the user's CURRENT role on every request, so role changes apply immediately. Login is throttled: 5 failed attempts per username → 15-minute lockout (429). Missing/invalid credentials get `401`; valid credentials but insufficient role get `403`.
|
||||
|
||||
| Endpoint | Auth | Description |
|
||||
| -------- | ---- | ----------- |
|
||||
@@ -123,10 +135,17 @@ Authentication: pass one of the configured app tokens either as the `X-App-Token
|
||||
| `GET /devices/{imei}/attributes/history` | token | The full timeline per key, oldest version first: `{key: [{value, effective_from, note}, ...]}` |
|
||||
| `PUT/POST /devices/{imei}/attributes` | token | **Append one version** to a key's timeline. Body: `{"key": ..., "value": ..., "effective_from": ISO8601?, "note"?}`; `effective_from` defaults to now, backdating allowed (a sensor swap discovered after the fact becomes a backdated row). Re-using an exact existing `(key, effective_from)` edits that version in place |
|
||||
| `DELETE /devices/{imei}/attributes/{key}` | token | Delete **only the latest** version of a key (typo/undo); earlier history stays immutable and the previous value is back in effect. Unknown key → 404 |
|
||||
| `POST /devices/{imei}/rename` | superuser+ | **Backfill-rename** the device name on stored messages (both the `device` column and the payload key): `{"device": "New", "from"?: ISO8601, "to"?: ISO8601, "dry_run"?}`. Bounds are inclusive on `received_at`; an absent/null bound means **no framing on that end**. `dry_run: true` returns the affected count without writing. No undo — the IMEI is the identity, names are labels |
|
||||
| `GET /tokens` | token | App names with a token (secrets are unrecoverable, shown as `null`) |
|
||||
| `PUT /tokens/{app}` | token | **Add or rotate ONE app token** (single-row upsert — never touches the other apps' rows). Body: `{"token": "secret"}`. Takes effect immediately |
|
||||
| `DELETE /tokens/{app}` | token | Remove one app token; unknown name → 404. Deleting the last token locks the API until a new one is added |
|
||||
| `GET /docs`, `GET /redoc` | none | OpenAPI docs (public) |
|
||||
| `POST /auth/login` | none | `{"username", "password"}` → `{"token", "username", "role"}`. Opaque Bearer token for the session; 5 failed attempts lock the username for 15 min (429) |
|
||||
| `GET /auth/me` | session or app token | `{"username", "role"}` (both `null` for app tokens) |
|
||||
| `POST /auth/logout` | session or app token | Revokes the Bearer session |
|
||||
| `GET /users` | any | All users as `[{username, role}]` (passwords are unrecoverable) |
|
||||
| `PUT /users/{username}` | superuser+ | Create/reset a user: `{"password", "role"?}` (role default `user`). Superusers may only assign role `user` (403 otherwise); admins any role |
|
||||
| `DELETE /users/{username}` | superuser+ | Delete a user (their sessions are revoked via cascade). Superusers only `user`-role accounts; admins any except the last admin |
|
||||
|
||||
When the database is unreachable, authenticated endpoints return **503** (the API has no purpose without the db).
|
||||
|
||||
@@ -214,4 +233,13 @@ Example response:
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## TODO
|
||||
- [x] Add possability to rename ingested device names in message payloads, e g based on the imei. Must be possible to perform between datetimes. If either field(from or to) is unset, no framing is performed in that end. (→ `POST /devices/{imei}/rename`, see the endpoint table)
|
||||
- [x] Separate payload relative settings from auth settings. Sub-sections in menu? (→ sidebar grouped into **Data** / **Access** sections)
|
||||
- [x] Hide features from users not able to use them based on authorization. (→ auth-management links hidden for read-only `user` role; route guard blocks direct URL access; the API enforces 403 server-side regardless)
|
||||
- [ ] Fix dashboard view, message expanding outside table. Make it look like the table in messages view.
|
||||
- [ ] Make all tables able to sort on header.
|
||||
- [ ] Remove clickable device links in tables for users that is not allowed to see the device view, or does not have enought authorization to be able to change anything. CLicking as a user ends up on a page with only some text.
|
||||
- [ ] Change datetime localization. How is it handled now, from browser or static? I'm in sweden and not following swedish rules on layout.
|
||||
|
||||
Reference in New Issue
Block a user