Skip to content

feat(auth): Sign In via BossConsole account in the Share menu#295

Open
kshivang wants to merge 1 commit into
masterfrom
feat/sign-in-share-menu
Open

feat(auth): Sign In via BossConsole account in the Share menu#295
kshivang wants to merge 1 commit into
masterfrom
feat/sign-in-share-menu

Conversation

@kshivang

Copy link
Copy Markdown
Owner

Adds Sign In as the 4th item in the Share menus, against BossConsole's shared Supabase backend (one user pool) over raw GoTrue REST — no Supabase SDK, reusing the existing ktor client. Magic-link flow: enter email → GoTrue emails a link → BossConsole's redirect function emits bossterm://auth/verify?token_hash=…&type=… → BossTerm verifies and persists the session. Hidden in embedded builds (serverName != "bossterm").

Backend half: risa-labs-inc/BossConsole#789 (closes #787). Until that ships, the email is BOSS-Console-branded and its link opens BossConsole — the client here is complete and testable via a manually-opened deep link.

New compose-ui/.../auth/ package

  • BossAccountManagerStateFlow<AccountState>; sendMagicLink (POST /auth/v1/otp, redirect_to=bossterm://auth/verify), handleAuthDeepLink (POST /auth/v1/verify with token_hash + verbatim type), signOut, refresh-on-startup.
  • AuthModels / AuthStorage — DTOs + parseAuthDeepLink + error mapping; session in ~/.bossterm/auth.json (atomic write, chmod 600 on POSIX, never logged).
  • DeepLinkHandler / DeepLinkSocket — new bossterm:// support: macOS setOpenURIHandler (installed before AWT), Windows/Linux argv + single-instance loopback forwarder (bounded read, constant-time secret compare), -Dbossterm.debug.deeplink dev hook.
  • WindowsProtocolRegistrar — HKCU scheme registration on packaged Windows launches.
  • SignInWindow — email → sent (60s resend cooldown) → verifying → signed-in.

Wiring

Main.kt (fun main(args) + DeepLinkHandler.install first thing); macOS CFBundleURLTypes; .deb .desktop patch (MimeType + %U); snap x-scheme-handler/bossterm; "Sign In…" at all three Share menu sites; a deferred "Signed in as …" toast.

Security (reviewed)

Anon key is byte-identical to BossConsole's already-public fallback (RLS is the gate); tokens chmod-600 + never logged; deeplink params go into a JSON body (no injection); socket secret is 128-bit, owner-only, constant-time-compared.

Tests / verification

:compose-ui:desktopTest auth suite (deeplink parsing incl. tokentoken_hash + verbatim type, error mapping, storage round-trip/corruption, 600-perms assertion) green; full build green.

Manual before release (OS-delivery has no automated coverage): packaged .app cold-start open "bossterm://auth/verify?token_hash=…&type=magiclink"; a Linux .deb .desktop carries the MimeType + %U; Windows HKCU registration. RPM scheme registration is a known gap (jpackage .rpm isn't repacked).

Generated with Claude Code

Adds account sign-in as the 4th item in the Share menus, against BossConsole's
shared Supabase backend (one user pool) over raw GoTrue REST — no Supabase SDK,
reusing the existing ktor client. Magic-link flow: enter email → GoTrue emails a
link → BossConsole's redirect function emits a bossterm://auth/verify deep link →
BossTerm verifies the token and persists the session. Hidden in embedded builds
(serverName != "bossterm"); the BossConsole backend half is risa-labs-inc/BossConsole#787.

New compose-ui/.../auth/ package:
- BossAccountManager: StateFlow<AccountState>; sendMagicLink (POST /auth/v1/otp,
  redirect_to=bossterm://auth/verify), handleAuthDeepLink (POST /auth/v1/verify,
  token_hash + verbatim type), signOut, refresh-on-startup.
- AuthModels / AuthStorage: DTOs + parseAuthDeepLink + error mapping; session in
  ~/.bossterm/auth.json (atomic write, chmod 600 on POSIX, never logged).
- DeepLinkHandler / DeepLinkSocket: new bossterm:// support — macOS
  setOpenURIHandler (installed before AWT), Windows/Linux argv + single-instance
  loopback forwarder (bounded read, constant-time secret check),
  -Dbossterm.debug.deeplink dev hook.
- WindowsProtocolRegistrar: HKCU scheme registration on packaged Windows launches.
- SignInWindow: email → sent (60s resend cooldown) → verifying → signed-in.

Wiring: Main.kt (fun main(args) + DeepLinkHandler.install first thing); macOS
CFBundleURLTypes (build.gradle.kts) + .deb .desktop patch (MimeType + %U) + snap
x-scheme-handler/bossterm; "Sign In…" at all three Share menu sites; a deferred
"Signed in as …" toast.

Generated with [Claude Code](https://claude.com/claude-code)
@claude

claude Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review: feat(auth): Sign In via BossConsole account

Thorough, well-documented PR — the comments explaining why (verbatim type, tokentoken_hash, atomic+chmod-600 storage, constant-time secret compare, the URI host/path parsing gotcha) are excellent and made review easy. The pure-logic test suite covers the right surfaces. A few things worth addressing before release.

Potential bugs

1. Cold-start sign-in toast is silently lost (functional gap).
signInEvents is a MutableSharedFlow(extraBufferCapacity = 1) with replay = 0. When the app is launched by a deep link (Windows/Linux argv path → handle(uri) in install(), or a macOS cold start), handleAuthDeepLink fires immediately in main() — long before TabbedTerminal composes and subscribes to signInEvents. If the network verify completes before the subscriber attaches, tryEmit has no collector and replay=0 means the value is dropped, so the "Signed in as …" toast never appears. The state flow is still correct (StateFlow replays), so it's UX-only — but it's exactly the first-run case. Consider giving signInEvents replay = 1, or driving the toast off a SignedIn state transition instead of a one-shot event.

2. URLDecoder.decode turns + into a space.
In parseAuthDeepLink, values go through URLDecoder.decode(..., UTF_8), which follows application/x-www-form-urlencoded rules — a literal + in the query becomes a space. GoTrue token_hash values are normally hex / base64url (-_), so this is unlikely to bite, but a token containing a raw + would be corrupted into a space and fail verify confusingly. The test only covers the percent-encoded form (%2B). Low risk, but worth a comment or a decoder that preserves + for the token field.

Security (looks solid)

  • Anon key public-by-design with env/property override, chmod-600 atomic token storage (nicely tested), deep-link params marshalled into a JSON body (no injection), 128-bit owner-only socket secret with constant-time compare, bounded socket read — all good and clearly reasoned.
  • OtpRequest(createUser = true) means any address can self-register via magic link. That is the expected magic-link signup model and also avoids account enumeration, so probably intended — just flagging it is effectively open signup against the shared user pool.
  • errorFrom surfaces raw GoTrue body text to the user for non-429/401/403 cases. GoTrue messages are generally benign, but it does pass server-controlled strings straight to the UI; fine as-is, just be aware.

Other notes

  • refresh-on-401 gap is already called out in restoreSession's comment — good that it is documented. Just make sure the first feature to actually use the access token honors that contract, since nothing enforces it today.
  • DeepLinkSocket.tryForward sets soTimeout (read timeout) but the Socket(...) constructor uses the default connect timeout. Loopback connect to a live ServerSocket is effectively instant, so this is fine; only relevant if the listener is wedged rather than dead.
  • Multiple windows: showSignInWindow and the toast collector live per-TabbedTerminal, so with several app windows you can get multiple sign-in windows and a toast per window. Minor, likely acceptable.
  • RPM scheme registration is acknowledged as an unhandled gap in the PR body — fine to ship as a known limitation, just make sure it lands in release notes / follow-up.

Tests

Good coverage of the pure layer (parse, error mapping, storage round-trip + corruption + 600-perms). The honest caveat stands: the network flow and all the OS-delivery paths (macOS Apple Event, Windows HKCU, .deb/snap MimeType, single-instance forwarding) have no automated coverage and rest entirely on the manual checklist. Given how platform-specific and hard-to-retest these are, that checklist is the real gate here — worth running it on all three before merge.

Overall: clean, conventions-respecting (ShellCustomizationUtils, no backwards-compat hacks, tokens never logged), and the embedded-build hiding via serverName is the right call. The cold-start toast and the +-decoding note are the only things I would change in code; the rest are confirm-before-release items.

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.

1 participant