Per-device tokens with revocation (issue #11)
Auth previously used the shared IRIS_TOKEN as the security principal: a leaked token meant access to all devices, and a compromised device could not be isolated. Gateway: - pairing.py: devices.token column (in-place migration) + revoked denylist table; issue_token (idempotent, 64 hex), token_for, reissue_token, revoke/unrevoke/is_revoked/list_revoked. The token never leaks into device dicts (push fan-out / listings). - http_server.py: auth accepts the shared token (bootstrap/legacy) OR the device's own token (both constant-time); a revoked device_id is rejected with 401 before either comparison. On SSE open (pairing) the per-device token is minted and returned in hello.ack. - protocol.py: hello_ack(..., device_token). - adapter.py: setup flow (hermes gateway setup -> Iris) now offers 'Remove a paired device?' on an existing setup: numbered select menu (last option = exit the removal loop), confirmation, back to the menu for further removals. - tools/iris_devices.py: operator CLI (list / revoke / unrevoke / reissue), stdlib only. App: - SecureStore.deviceToken (Android: EncryptedSharedPreferences; Desktop: second keyring slot iris-device-token / device_token.enc). - HelloAckPayload.deviceToken; GatewayClient stores it on hello and presents it instead of the shared token from then on (live provider in HttpGateway); savePairing/clear wipe it for re-pairing. Docs: 09 §9.3 stretch -> implemented (revocation semantics, both control surfaces), 04 hello.ack example, frames.schema.json, M7 row 13. Tests: 8 new Python tests (issuance, acceptance, revocation, isolation, unrevoke, registry unit x2, setup-flow menu) - 94/94 pass; 2 new Kotlin wire tests - green. Live-verified against a running gateway (hello.ack token matches devices.db; revoke -> 401 even with shared token; unrevoke -> 200; setup TUI both paths).
This commit is contained in:
1 parent
746d809d48
commit
7faaf2aa1c
23 files changed
+837
-66
No files matched your search
+13
-1
@@ -9,6 +9,7 @@ First-time setup on a machine (verified baseline: CachyOS/Arch, `pacman`,
|
||||
pacman -S jdk17-openjdk
|
||||
java -version # expect 17.x
|
||||
```
|
||||
|
||||
(Compose Multiplatform + current AGP are happy on JDK 17. Use 17 to match the
|
||||
Android toolchain; 21 also works but 17 is the safe floor.)
|
||||
|
||||
@@ -28,10 +29,12 @@ export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-
|
||||
sdkmanager --licenses
|
||||
sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"
|
||||
```
|
||||
|
||||
Persist `ANDROID_HOME`/`PATH` in `~/.bashrc`. ADB is already installed system-wide;
|
||||
`platform-tools` from the SDK is fine too (whichever is first on `PATH`).
|
||||
|
||||
Create `app/local.properties`:
|
||||
|
||||
```
|
||||
sdk.dir=/home/<you>/android-sdk
|
||||
```
|
||||
@@ -39,10 +42,12 @@ sdk.dir=/home/<you>/android-sdk
|
||||
## 12.3 Gradle
|
||||
|
||||
No system install — use the project wrapper:
|
||||
|
||||
```bash
|
||||
cd app
|
||||
./gradlew tasks # first run downloads the wrapper distribution
|
||||
```
|
||||
|
||||
(The wrapper version is pinned in `app/gradle/wrapper/gradle-wrapper.properties`.)
|
||||
|
||||
## 12.4 hermes environment (for the plugin + running the gateway)
|
||||
@@ -53,7 +58,9 @@ uv sync # creates .venv with all core deps (websockets, httpx,
|
||||
source .venv/bin/activate
|
||||
hermes --version # sanity
|
||||
```
|
||||
|
||||
- Run the gateway with the plugin:
|
||||
|
||||
```bash
|
||||
# install the plugin (dev: symlink)
|
||||
mkdir -p ~/.hermes/plugins
|
||||
@@ -61,7 +68,9 @@ hermes --version # sanity
|
||||
hermes gateway status # should list "iris"
|
||||
hermes gateway # run
|
||||
```
|
||||
|
||||
- Tests use hermes's hermetic runner (never bare `pytest`):
|
||||
|
||||
```bash
|
||||
scripts/run_tests.sh tests/gateway/test_android.py
|
||||
```
|
||||
@@ -84,6 +93,7 @@ hermes --version # sanity
|
||||
## 12.6 Environment variables (summary)
|
||||
|
||||
**Secrets (`~/.hermes/.env`):**
|
||||
|
||||
```
|
||||
IRIS_TOKEN=<64-hex>
|
||||
IRIS_PUSH_BACKEND=ntfy # default; fcm = opt-in (metadata via Google)
|
||||
@@ -96,6 +106,7 @@ IRIS_FCM_SERVICE_ACCOUNT=/path/to/service-account.json
|
||||
```
|
||||
|
||||
**Behavioral (`~/.hermes/config.yaml`):**
|
||||
|
||||
```yaml
|
||||
gateway:
|
||||
platforms:
|
||||
@@ -135,5 +146,6 @@ async def main():
|
||||
asyncio.run(main())
|
||||
PY
|
||||
```
|
||||
|
||||
Expect a `hello.ack`. If you get `error {code:"auth"}`, the token/host/port is
|
||||
wrong.
|
||||
wrong.
|
||||
Reference in new issue
Block a user