feat: implement the open fallback access point and captive portal
This commit is contained in:
@@ -49,6 +49,10 @@ Failure (maximum 160 encoded bytes):
|
||||
| -------------------------- | --------------: | --------------------------------------------- |
|
||||
| `GET /api/info` | 128 B target | public device/slot state and names, 384 B |
|
||||
| `GET /api/health` | 128 B target | diagnostics without secrets, 320 B; includes reset reason |
|
||||
| `GET /api/network/status` | 128 B target | unauthenticated state/message; never includes SSID/password |
|
||||
| `GET /api/network/scan` | 128 B target | unauthenticated bounded scan state and up to 12 SSIDs |
|
||||
| `POST /api/network/validate` | 160 B body | unauthenticated `{ssid,password}` validation; password is never returned |
|
||||
| `POST /api/network/delete` | 0 B body | unauthenticated deletion of the saved profile |
|
||||
| `POST /api/session/join` | 192 B body | `{name,requestedRole}`; token and role, 192 B |
|
||||
| `POST /api/session/resume` | 96 B body | `{token}`; role and state metadata, 192 B |
|
||||
| `POST /api/session/leave` | 80 B body | `{token,gameId}`; releases only that session |
|
||||
@@ -82,6 +86,11 @@ polling returns `SESSION_INVALIDATED` with the same bounded recovery metadata.
|
||||
The recovery generation and `gameId` make delayed commands stale; retrying a
|
||||
completed recovery is harmless and cannot mutate a newly registered session.
|
||||
|
||||
Network configuration is deliberately unauthenticated so it works from the
|
||||
open fallback AP and the regular local address. Any reachable client can change
|
||||
or delete the saved profile; the open AP and local HTTP do not provide password
|
||||
confidentiality. Network responses and diagnostics never include the password.
|
||||
|
||||
## Role-safe state event
|
||||
|
||||
The HTTP state response and WebSocket `state` event use this single 768-byte
|
||||
|
||||
+13
-2
@@ -13,8 +13,9 @@ does not replace the locked rules in [MVP.md](MVP.md), the HTTP contract in
|
||||
`esp_littlefs` 1.20.4.
|
||||
- USB serial device: normally `/dev/ttyACM0`; confirm with `pio device list`
|
||||
before an upload.
|
||||
- A local `include/wifi_config.h`, copied from `include/wifi_config.h.example`.
|
||||
It is ignored by Git. Never commit the SSID or password.
|
||||
- No firmware-embedded Wi-Fi credentials are required. On first boot, join the
|
||||
open `Battleship-open` network and open `http://192.168.4.1/setup` if the
|
||||
captive portal does not appear.
|
||||
|
||||
## Build and upload
|
||||
|
||||
@@ -49,6 +50,16 @@ The response includes free/minimum heap, largest free block, connection count,
|
||||
rejected input count, Wi-Fi state, and reset reason. It must not contain Wi-Fi
|
||||
credentials or session tokens.
|
||||
|
||||
## Network configuration and local-security limitation
|
||||
|
||||
The `/setup` screen and `/api/network/*` routes are intentionally unauthenticated
|
||||
so a new device can be configured through either the fallback AP or its regular
|
||||
local address. Any client that can reach the device can change or delete the
|
||||
saved Wi-Fi configuration. The open AP and plain local HTTP do not protect a
|
||||
submitted password from a nearby network observer. Configure the board only on
|
||||
a network you trust, and do not use this feature for credentials that require
|
||||
strong confidentiality.
|
||||
|
||||
## MVP acceptance checklist
|
||||
|
||||
Record the observed result, device address, browser/device model, and any
|
||||
|
||||
@@ -35,6 +35,40 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Health'
|
||||
/api/network/status:
|
||||
get:
|
||||
summary: Read unauthenticated network configuration status without credentials
|
||||
responses:
|
||||
'200': { description: Network state and Russian status message. }
|
||||
/api/network/scan:
|
||||
get:
|
||||
summary: Start or retrieve a bounded unauthenticated Wi-Fi scan
|
||||
responses:
|
||||
'200': { description: `scanning` or a bounded list of SSIDs; no passwords. }
|
||||
'409': { $ref: '#/components/responses/Conflict' }
|
||||
/api/network/validate:
|
||||
post:
|
||||
summary: Validate credentials and persist them only after association succeeds
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [ssid, password]
|
||||
properties:
|
||||
ssid: { type: string, minLength: 1, maxLength: 32 }
|
||||
password: { type: string, maxLength: 63, writeOnly: true }
|
||||
responses:
|
||||
'200': { description: Validation started; poll network status for result. }
|
||||
'400': { $ref: '#/components/responses/BadRequest' }
|
||||
'409': { $ref: '#/components/responses/Conflict' }
|
||||
/api/network/delete:
|
||||
post:
|
||||
summary: Delete the saved network profile and enter fallback mode
|
||||
responses:
|
||||
'200': { description: Profile deleted. }
|
||||
'409': { $ref: '#/components/responses/Conflict' }
|
||||
/api/session/join:
|
||||
post:
|
||||
summary: Create a session and request a role
|
||||
|
||||
Reference in New Issue
Block a user