Skip to content

feat: pairing screen - #10

Merged
mikahatdichlieb merged 6 commits into
mainfrom
feat/pairing-screen
Oct 26, 2026
Merged

mikahatdichlieb merged 6 commits into
mainfrom
feat/pairing-screen

Conversation

@mikahatdichlieb

Copy link
Copy Markdown
Contributor

Closes #5

What

First-time setup: pair this device with a Daycompass server.

  • On start the app loads the stored state and shows one of three screens: the pairing screen, the same screen in a "Pair this device again" variant (unreadable token or unknown server), or "Connected" with the server's hostname.
  • The form asks for server URL, pairing code and device name. The device name is prefilled with Build.MODEL, cut to 64 characters, with control characters removed and "Android device" as fallback; it is checked against the server's name policy before sending.
  • Server URL: a bare host gets https://; http:// is refused, not upgraded. A path prefix such as /daycompass is kept.
  • Pairing code: sent exactly as typed, with an uppercase keyboard and no autocorrect.
  • Errors appear at the field they belong to; general errors sit in a notice above the button that screen readers announce right away. They cover:
    • invalid URL, not https, unreachable
    • untrusted certificate, redirect, not a Daycompass server
    • missing code, invalid code, invalid device name
    • device limit reached (the same code still works after removing a device)
    • rate limited, with the Retry-After delay in minutes
    • server error, unexpected answer, storage failure
  • The pairing request and the token write run in the ViewModel without cancellation. They survive rotation, and the token is stored even if the screen goes away, because the server has already used up the code.

Security / privacy relevant

  • DeviceSetup stores the server URL as plain text in noBackupFilesDir/server_url.txt (not a secret, excluded from backups) and the token through the encrypted store from Encrypted device token storage (Keystore AES-GCM) #4. A token without a usable server URL means the device has to be paired again.
  • The old token is deleted before the new URL is written. Without that, a failed token save during re-pairing could leave an old token next to a new server's URL, and the app would send it there. A regression test covers this.
  • A rejected certificate gets its own error message. There is deliberately no way to accept the certificate anyway.
  • PairingForm.toString() leaves the pairing code out. Nothing is logged.
  • The device name comes from Build.MODEL, never from the personal device name in the settings.
  • Not done yet: no local "disconnect" action, and no /device/me clock check (that belongs to the sync milestone).

Dependencies

androidx.lifecycle:lifecycle-viewmodel-compose 2.9.4, the lifecycle version that was already resolved transitively. Verification metadata gains its entries, and all CI tasks pass one at a time with a cold GRADLE_USER_HOME.

Tests

The new tests cover URL parsing, the name policy, the error mapping, an untrusted certificate told apart from an unreachable server, a failing Keystore, the old-token/new-server case, and that a rejected code stores nothing. The screens have not been run on a device or emulator yet.

Server URLs must be https (a bare host gets https://, http:// is refused),
device names follow the server's policy and default to Build.MODEL.
DeviceSetup pairs, then stores the server URL and the encrypted token in
noBackupFilesDir; a token without a usable server or key means the
device has to be paired again.
The pairing request and the token write run to completion even if the
screen goes away, because the server has already consumed the code. The
form's toString leaves the pairing code out.
If saving the new token failed after the new URL was written, an old
token could be paired with the new server and sent there.
@mikahatdichlieb
mikahatdichlieb merged commit 2ea41a8 into main Oct 26, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pairing screen

1 participant