Address spec review findings: SQLite concurrency, TOTP flow, InfluxDB bootstrap
- Add WAL mode + busy timeout for SQLite concurrent access - Define two-step TOTP login protocol with temporary JWT - Specify JWT claims structure and error response format - Add InfluxDB bootstrap via Docker init env vars - Clarify CORS strategy (same-origin in prod, permissive in dev) - Add daemon-config.toml to deploy directory - Clarify event routing (single POST / handler) - Note sensor delete uses SQLite transaction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e706b22f31
commit
e8e2a7dd6d
@ -73,6 +73,7 @@ LORAWEB_NEU/
|
||||
├── deploy/
|
||||
│ ├── docker-compose.yml # Project name: loraweb
|
||||
│ ├── .env.example
|
||||
│ ├── daemon-config.toml # Daemon config example (not in git at runtime)
|
||||
│ └── loraweb.service # systemd unit
|
||||
└── docs/
|
||||
```
|
||||
@ -83,6 +84,13 @@ LORAWEB_NEU/
|
||||
|
||||
Three tables, created idempotently (`CREATE TABLE IF NOT EXISTS`) at startup by both daemon and API.
|
||||
|
||||
### SQLite Concurrency
|
||||
|
||||
Both daemon and API access the same SQLite file. To handle concurrent access:
|
||||
- **WAL mode** (`PRAGMA journal_mode=WAL`) enabled at connection open — allows concurrent readers during writes
|
||||
- **Busy timeout** (`PRAGMA busy_timeout=5000`) — retries on lock contention instead of failing immediately
|
||||
- Both services set these pragmas on every connection open
|
||||
|
||||
### topics
|
||||
Represents a physical LoRaWAN device identified by DevEUI.
|
||||
|
||||
@ -134,8 +142,8 @@ Single Rust binary running two async tasks via Tokio:
|
||||
- `POST /?event=up`: Parse ChirpStack JSON → register unknown DevEUI in `topics` (approved=0) → write all `object` fields + RF metadata (rssi, snr, dr, f_cnt, f_port) to InfluxDB measurement `sensor_data`
|
||||
- Tags: `dev_eui`, `device_name`, `application_name`
|
||||
- Timestamp from ChirpStack `time` field
|
||||
- Other events (join, ack, txack, log, status): return 200 OK, no processing
|
||||
- InfluxDB errors: log and continue, no crash
|
||||
- Routing: single `POST /` handler checks `?event=` query parameter. Only `event=up` is processed; all other values return 200 OK with no further action
|
||||
- InfluxDB errors: log at `warn` level and continue, no crash
|
||||
|
||||
### 4.2 Alarm Monitor (Tokio interval loop)
|
||||
|
||||
@ -178,13 +186,24 @@ Axum on port 3001, shared SQLite DB with daemon.
|
||||
|
||||
### 5.1 Authentication
|
||||
|
||||
- `POST /api/auth/login`: Verify username/password (Argon2id)
|
||||
- If TOTP enabled: return `{ totp_required: true }`
|
||||
- Second request with TOTP code → issue JWT
|
||||
- If no TOTP: issue JWT directly
|
||||
- JWT: HS256, 24h validity, contains user id + role
|
||||
- Auth middleware extracts Bearer token, injects user info
|
||||
- Default admin created at first startup (password from env var)
|
||||
- `POST /api/auth/login`: Single endpoint, two-step flow:
|
||||
- **Step 1**: `{ username, password }` → verify Argon2id hash
|
||||
- If TOTP not enabled: return `{ token: "jwt..." }`
|
||||
- If TOTP enabled: return `{ totp_required: true, totp_token: "temporary-token" }`
|
||||
- **Step 2** (only if TOTP): `{ totp_token, totp_code }` → verify TOTP code against stored secret
|
||||
- `totp_token` is a short-lived JWT (60s, contains user_id, purpose="totp")
|
||||
- If valid: return `{ token: "jwt..." }`
|
||||
- JWT claims: `{ sub: user_id, role: "user"|"admin", exp: timestamp }` (HS256, 24h)
|
||||
- Auth middleware extracts `Authorization: Bearer <token>`, injects user info into request extensions
|
||||
- Default admin created at first startup (password from `ADMIN_PASSWORD` env var)
|
||||
|
||||
### Error Response Format
|
||||
|
||||
All API errors use a consistent envelope:
|
||||
```json
|
||||
{ "error": "Human-readable error message" }
|
||||
```
|
||||
With appropriate HTTP status codes: 400 (validation), 401 (unauthorized), 403 (forbidden), 404 (not found), 500 (internal).
|
||||
|
||||
### 5.2 Endpoints
|
||||
|
||||
@ -195,7 +214,7 @@ Axum on port 3001, shared SQLite DB with daemon.
|
||||
| /api/sensors | POST | Yes | Create sensor |
|
||||
| /api/sensors/:id | GET | Yes | Sensor detail |
|
||||
| /api/sensors/:id | PUT | Yes | Update sensor |
|
||||
| /api/sensors/:id | DELETE | Yes | Delete sensor (resets topic to unapproved) |
|
||||
| /api/sensors/:id | DELETE | Yes | Delete sensor (resets topic to unapproved, in SQLite transaction) |
|
||||
| /api/sensors/:id/data | GET | Yes | InfluxDB proxy, ?hours=N |
|
||||
| /api/sensors/:id/display-config | GET | Yes | Get field config |
|
||||
| /api/sensors/:id/display-config | PUT | Yes | Save field config |
|
||||
@ -231,6 +250,7 @@ React 18 + TypeScript, Vite 6, Tailwind 4, Recharts.
|
||||
- "Diagramme": Charts per configured panels (D1-D8), time selector, RF metadata toggle
|
||||
- "Einrichten": Per-field config (label, unit, chart type, panel assignment)
|
||||
4. **UnknownTopicsPage**: List of unapproved topics, dropdown to assign to free sensors
|
||||
- Sensor edit form shows alarm threshold in **minutes** (only when topic assigned); conversion to hours happens in the API
|
||||
5. **AlarmsPage**: Active alarms and warnings list
|
||||
6. **UsersPage** (admin only): User CRUD, 2FA enable/disable
|
||||
|
||||
@ -305,7 +325,21 @@ All containers run as user `loraweb` (UID 1001).
|
||||
All via `.env` file (not in git). `.env.example` as template with:
|
||||
- `JWT_SECRET`, `ADMIN_PASSWORD`
|
||||
- `INFLUX_TOKEN`, `INFLUX_ORG`, `INFLUX_BUCKET`
|
||||
- `DOCKER_INFLUXDB_INIT_*` for InfluxDB bootstrap
|
||||
|
||||
### 7.6 InfluxDB Bootstrap
|
||||
|
||||
InfluxDB 2.7 supports automatic setup via environment variables in Docker:
|
||||
- `DOCKER_INFLUXDB_INIT_MODE=setup`
|
||||
- `DOCKER_INFLUXDB_INIT_USERNAME`, `DOCKER_INFLUXDB_INIT_PASSWORD`
|
||||
- `DOCKER_INFLUXDB_INIT_ORG=loraweb`
|
||||
- `DOCKER_INFLUXDB_INIT_BUCKET=sensors`
|
||||
- `DOCKER_INFLUXDB_INIT_ADMIN_TOKEN` (same value as `INFLUX_TOKEN`)
|
||||
|
||||
This creates org, bucket, and admin token on first start. Subsequent starts skip setup if data directory exists.
|
||||
|
||||
### 7.7 CORS
|
||||
|
||||
In production, nginx proxies all `/api/` requests (same origin) — no CORS needed. For local development (Vite dev server on different port), the API includes permissive CORS headers (`tower-http` CorsLayer with `Any` origin). This matches the Pflichtenheft's known limitation E1.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user