|
1 | | -# Geo Auth Design |
| 1 | +# Geo-Auth Design Document |
2 | 2 |
|
3 | | -## Goals |
| 3 | +This document outlines the design for geographic authentication in the MeshCore GOME WarDriver system. |
4 | 4 |
|
5 | | -- Provide a secure authentication mechanism for wardrive post submissions. |
6 | | -- Prevent accidental token leakage via URLs, logs, or referrers. |
7 | | -- Keep the client implementation simple and standards-based. |
| 5 | +## Overview |
8 | 6 |
|
9 | | -## Confirmed Decisions |
| 7 | +The Geo-Auth system provides location-based authentication for wardriving sessions. Devices must be within designated geographic zones to connect and submit data. |
10 | 8 |
|
11 | | -- **Token transport for wardrive posts MUST use the HTTP `Authorization: Bearer <token>` header.** |
12 | | -- **Tokens MUST NEVER be accepted via query string parameters.** |
13 | | -- Tokens are treated as sensitive secrets and must be stored securely on device. |
14 | | -- If a token is compromised, it can be rotated/revoked without requiring a client app update. |
| 9 | +## Architecture |
15 | 10 |
|
16 | | -## Open Items |
| 11 | +### Components |
17 | 12 |
|
18 | | -- Ensure server and reverse-proxy logging does **not** record `Authorization` headers (or otherwise redact them), especially for wardrive endpoints. |
| 13 | +1. **Zone Manager** — Manages geographic zone definitions and capacity |
| 14 | +2. **Auth Service** — Handles authentication requests and token management |
| 15 | +3. **GPS Validator** — Validates GPS coordinates for freshness and accuracy |
19 | 16 |
|
20 | | -## Notes |
| 17 | +### Flow |
21 | 18 |
|
22 | | -### Rationale |
| 19 | +1. Device checks zone status (preflight) |
| 20 | +2. Device requests auth with coordinates |
| 21 | +3. Server validates location and issues token |
| 22 | +4. Device submits wardrive data with keepalive |
| 23 | +5. Device disconnects when session ends |
23 | 24 |
|
24 | | -Using the `Authorization: Bearer` header: |
| 25 | +## Zone Configuration |
25 | 26 |
|
26 | | -- Aligns with standard HTTP auth practices. |
27 | | -- Avoids token exposure in shared links, browser history, referrer headers, and many default access logs. |
| 27 | +Zones are defined as circular regions with: |
| 28 | +- Center coordinates (lat/lng) |
| 29 | +- Radius in kilometers |
| 30 | +- Maximum concurrent slots |
| 31 | +- Enable/disable flag |
28 | 32 |
|
29 | | -Query string tokens are explicitly forbidden because they are commonly logged and leaked. |
| 33 | +## Token Management |
| 34 | + |
| 35 | +Tokens are opaque bearer tokens with: |
| 36 | +- 30-minute expiration |
| 37 | +- Session binding |
| 38 | +- Zone assignment |
| 39 | + |
| 40 | +## GPS Validation Rules |
| 41 | + |
| 42 | +- **Staleness threshold:** 60 seconds max age |
| 43 | +- **Accuracy threshold:** 50 meters max horizontal accuracy |
| 44 | +- **Coordinate bounds:** Valid lat (-90 to 90), lng (-180 to 180) |
| 45 | + |
| 46 | +--- |
| 47 | + |
| 48 | +## API Endpoint Examples |
| 49 | + |
| 50 | +This section captures sample requests and responses for the major endpoints described in this document. See implementation notes for exact contract details. |
| 51 | + |
| 52 | +> **HTTP Status Code Conventions:** |
| 53 | +> - `200 OK` — Success |
| 54 | +> - `400 Bad Request` — Missing/invalid parameters (`invalid_request`) |
| 55 | +> - `401 Unauthorized` — Missing or invalid token (`missing_token`, `bad_token`) |
| 56 | +> - `403 Forbidden` — Valid request but denied (`zone_full`, `outside_zone`, `zone_disabled`, `gps_stale`, `gps_inaccurate`) |
| 57 | +> - `429 Too Many Requests` — Rate limit exceeded (applies to `/zones/status`) |
| 58 | +
|
| 59 | +--- |
| 60 | + |
| 61 | +### Preflight — Zone Status Check |
| 62 | + |
| 63 | +**Endpoint:** |
| 64 | +`POST /zones/status` |
| 65 | +Content-Type: `application/json` |
| 66 | + |
| 67 | +> **Note:** POST is used (instead of GET) to allow structured JSON coordinates in the request body. |
| 68 | +
|
| 69 | +**Request:** |
| 70 | +```json |
| 71 | +{ |
| 72 | + "lat": 45.4215, |
| 73 | + "lng": -75.6972, |
| 74 | + "accuracy_m": 15.3, |
| 75 | + "timestamp": 1703980800 |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +**Response — In Zone (200 OK):** |
| 80 | +```json |
| 81 | +{ |
| 82 | + "in_zone": true, |
| 83 | + "zone": { |
| 84 | + "name": "Ottawa", |
| 85 | + "code": "YOW", |
| 86 | + "enabled": true, |
| 87 | + "at_capacity": false, |
| 88 | + "slots_available": 5, |
| 89 | + "slots_max": 10 |
| 90 | + } |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +**Response — Outside All Zones (200 OK):** |
| 95 | +```json |
| 96 | +{ |
| 97 | + "in_zone": false, |
| 98 | + "nearest_zone": { |
| 99 | + "name": "Ottawa", |
| 100 | + "code": "YOW", |
| 101 | + "distance_km": 2.3 |
| 102 | + } |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +**Response — GPS Too Stale (403 Forbidden):** |
| 107 | +```json |
| 108 | +{ |
| 109 | + "in_zone": false, |
| 110 | + "error": true, |
| 111 | + "reason": "gps_stale" |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +--- |
| 116 | + |
| 117 | +### Auth — Connect Request |
| 118 | + |
| 119 | +**Endpoint:** |
| 120 | +`POST /auth` |
| 121 | +Content-Type: `application/json` |
| 122 | + |
| 123 | +**Request:** |
| 124 | +```json |
| 125 | +{ |
| 126 | + "public_key": "<device_public_key>", |
| 127 | + "who": "Alice's Pixel 8", |
| 128 | + "version": "2.1.0", |
| 129 | + "reason": "connect", |
| 130 | + "coords": { |
| 131 | + "lat": 45.4216, |
| 132 | + "lng": -75.6970, |
| 133 | + "accuracy_m": 12.0, |
| 134 | + "timestamp": 1703980842 |
| 135 | + } |
| 136 | +} |
| 137 | +``` |
| 138 | + |
| 139 | +**Response — Allowed (200 OK):** |
| 140 | +```json |
| 141 | +{ |
| 142 | + "allowed": true, |
| 143 | + "token": "<opaque_bearer_token>", |
| 144 | + "session_id": "<session_id>", |
| 145 | + "zone": { |
| 146 | + "name": "Ottawa", |
| 147 | + "code": "YOW" |
| 148 | + }, |
| 149 | + "expires_at": 1703982642 |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +**Response — Denied, At Capacity (403 Forbidden):** |
| 154 | +```json |
| 155 | +{ |
| 156 | + "allowed": false, |
| 157 | + "reason": "zone_full" |
| 158 | +} |
| 159 | +``` |
| 160 | + |
| 161 | +**Response — Denied, Outside Zone (403 Forbidden):** |
| 162 | +```json |
| 163 | +{ |
| 164 | + "allowed": false, |
| 165 | + "reason": "outside_zone", |
| 166 | + "nearest_zone": { |
| 167 | + "name": "Ottawa", |
| 168 | + "code": "YOW", |
| 169 | + "distance_km": 1.2 |
| 170 | + } |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +--- |
| 175 | + |
| 176 | +### Wardrive Post (Keepalive + Data Submission) |
| 177 | + |
| 178 | +**Endpoint:** |
| 179 | +`POST /wardrive` |
| 180 | +Headers: |
| 181 | +- `Authorization: Bearer <token>` |
| 182 | +- `Content-Type: application/json` |
| 183 | + |
| 184 | +**Request:** |
| 185 | +```json |
| 186 | +{ |
| 187 | + "session_id": "<session_id>", |
| 188 | + "public_key": "<device_public_key>", |
| 189 | + "data": { ... }, |
| 190 | + "coords": { |
| 191 | + "lat": 45.4217, |
| 192 | + "lng": -75.6975, |
| 193 | + "accuracy_m": 10.1, |
| 194 | + "timestamp": 1703980860 |
| 195 | + } |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +**Response — Allowed, Session Extended (200 OK):** |
| 200 | +```json |
| 201 | +{ |
| 202 | + "allowed": true, |
| 203 | + "expires_at": 1703982660 |
| 204 | +} |
| 205 | +``` |
| 206 | + |
| 207 | +**Response — Denied, Outside Assigned Zone (403 Forbidden):** |
| 208 | +```json |
| 209 | +{ |
| 210 | + "allowed": false, |
| 211 | + "reason": "outside_zone" |
| 212 | +} |
| 213 | +``` |
| 214 | + |
| 215 | +**Response — Denied, Invalid Token (401 Unauthorized):** |
| 216 | +```json |
| 217 | +{ |
| 218 | + "allowed": false, |
| 219 | + "reason": "bad_token" |
| 220 | +} |
| 221 | +``` |
| 222 | + |
| 223 | +--- |
| 224 | + |
| 225 | +### Disconnect |
| 226 | + |
| 227 | +**Endpoint:** |
| 228 | +`POST /auth` |
| 229 | +Headers: |
| 230 | +- `Authorization: Bearer <token>` |
| 231 | +- `Content-Type: application/json` |
| 232 | + |
| 233 | +**Request:** |
| 234 | +```json |
| 235 | +{ |
| 236 | + "reason": "disconnect", |
| 237 | + "session_id": "<session_id>" |
| 238 | +} |
| 239 | +``` |
| 240 | + |
| 241 | +**Response — Success (200 OK):** |
| 242 | +```json |
| 243 | +{ |
| 244 | + "disconnected": true |
| 245 | +} |
| 246 | +``` |
| 247 | + |
| 248 | +--- |
| 249 | + |
| 250 | +_Add further endpoint examples as APIs expand._ |
| 251 | + |
| 252 | +## Open Items / Next Brainstorm |
| 253 | + |
| 254 | +- [ ] Define rate limiting strategy for `/zones/status` |
| 255 | +- [ ] Decide on token refresh vs re-auth approach |
| 256 | +- [ ] Specify error response format consistency |
| 257 | +- [ ] Consider WebSocket alternative for keepalive |
| 258 | +- [ ] Document admin endpoints for zone management |
| 259 | +- [ ] Add session type? TX vs RX? RX does not require to be limited. |
0 commit comments