Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,29 @@ per-file limit, and 32 MiB total virtual filesystem limit.
./gradlew testDebugUnitTest # run JVM unit tests
./gradlew installDebug # install on a connected device/emulator
```
2. Run the app, fill in the gateway WebSocket URL and your SealGate API key
(from the dashboard), and tap **Start tunnel**. Settings persist across
restarts; the ongoing notification shows the live connection state.
2. Run the app, confirm (or edit) the gateway WebSocket URL, and tap
**Sign in with SealGate**. The app runs the OAuth 2.0 device-authorization
flow (RFC 8628, with PKCE): it shows a short code and opens the dashboard's
device page, where you approve the phone with one click. On approval the app
receives a scoped `ewc_` tunnel credential (never a human API key) bound to a
backend-issued device id, stores it, and starts the tunnel. Settings persist
across restarts; the ongoing notification shows the live connection state.

Pasting a SealGate API key under **Or connect with an API key** and tapping
**Connect** still works as an alternative to signing in.

Sign-in reuses the shared device-auth flow the desktop daemon uses, under a
dedicated `mobile` client id; the backend side lives in `edison-watch`
(`src/api/v1/routes/device_auth.py`, `dev-docs/architecture/mobile-hardware-gateway-design.md`).

The credential (and the in-flight PKCE verifier of an interrupted sign-in) is
stored encrypted at rest with an AES-256-GCM key held in the AndroidKeyStore
(`SecretCipher`), so a prefs dump or a backup restored to another phone cannot
Comment on lines +112 to +114

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: For users upgrading with an existing API key, this statement is false until the settings are saved again: legacy plaintext is read unchanged and is not re-encrypted during load. Document the migration caveat so users do not assume the credential is already protected immediately after upgrading.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 112:

<comment>For users upgrading with an existing API key, this statement is false until the settings are saved again: legacy plaintext is read unchanged and is not re-encrypted during load. Document the migration caveat so users do not assume the credential is already protected immediately after upgrading.</comment>

<file context>
@@ -94,9 +94,29 @@ per-file limit, and 32 MiB total virtual filesystem limit.
+   dedicated `mobile` client id; the backend side lives in `edison-watch`
+   (`src/api/v1/routes/device_auth.py`, `dev-docs/architecture/mobile-hardware-gateway-design.md`).
+
+   The credential (and the in-flight PKCE verifier of an interrupted sign-in) is
+   stored encrypted at rest with an AES-256-GCM key held in the AndroidKeyStore
+   (`SecretCipher`), so a prefs dump or a backup restored to another phone cannot
</file context>
Suggested change
The credential (and the in-flight PKCE verifier of an interrupted sign-in) is
stored encrypted at rest with an AES-256-GCM key held in the AndroidKeyStore
(`SecretCipher`), so a prefs dump or a backup restored to another phone cannot
Newly saved credentials (and the in-flight PKCE verifier of an interrupted sign-in) are
stored encrypted at rest with an AES-256-GCM key held in the AndroidKeyStore
(`SecretCipher`). Existing plaintext credentials are re-encrypted the next time
settings are saved, so a prefs dump or a backup restored to another phone cannot
lift a newly saved credential.

lift it. To disconnect, open settings and tap **Sign out**: the app stops the
tunnel, forgets the local credential, and revokes the installation in the
dashboard (`POST /api/v1/auth/device/revoke`). If the gateway later revokes the

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Sign out does not always revoke an installation: API-key sign-out skips the revoke endpoint, and OAuth revocation is best-effort. Qualify this as a best-effort revoke for OAuth credentials so the fallback behavior and failure case are documented accurately.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 117:

<comment>Sign out does not always revoke an installation: API-key sign-out skips the revoke endpoint, and OAuth revocation is best-effort. Qualify this as a best-effort revoke for OAuth credentials so the fallback behavior and failure case are documented accurately.</comment>

<file context>
@@ -94,9 +94,29 @@ per-file limit, and 32 MiB total virtual filesystem limit.
+   (`SecretCipher`), so a prefs dump or a backup restored to another phone cannot
+   lift it. To disconnect, open settings and tap **Sign out**: the app stops the
+   tunnel, forgets the local credential, and revokes the installation in the
+   dashboard (`POST /api/v1/auth/device/revoke`). If the gateway later revokes the
+   credential itself, the tunnel stops reconnecting and the app asks you to sign
+   in again instead of looping.
</file context>
Suggested change
dashboard (`POST /api/v1/auth/device/revoke`). If the gateway later revokes the
+ dashboard for OAuth credentials on a best-effort basis (`POST /api/v1/auth/device/revoke`). If the gateway later revokes the

credential itself, the tunnel stops reconnecting and the app asks you to sign
in again instead of looping.

While the tunnel is running, pull down from the top of the app screen to
close the current socket and reconnect immediately with the saved settings.
Expand Down
13 changes: 12 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@
android:name="android.hardware.usb.host"
android:required="false" />

<!-- Sign-in opens the dashboard's device-approval page in a browser. On
Android 11+ package visibility requires declaring the web intent to
resolve and launch an external browser. -->
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
</queries>

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
Expand All @@ -63,7 +73,8 @@

<activity
android:name=".MainActivity"
android:exported="true">
android:exported="true"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
Expand Down
Loading
Loading