S288 Desktop Auth Fix Technical
Documentation for Sessions
S288: Vitto Desktop Auth/Login Fix — Technical Documentation
Session: 288.2 Branch:
session-280-290Date: 2026-03-30 Status: Spec A complete (4 fixes implemented), Spec B written (pending S290)
Problem
Vitto Desktop's "Sign in with GoGrab" deep link flow fails silently. The system browser shows "Opening Vitto..." but the Tauri app never receives the authentication token. After 90 seconds, the desktop app displays "Browser sign-in timed out."
Email and Phone sign-in tabs are unaffected (CORS verified — isTauriOrigin() in cors.factory.ts handles tauri://localhost).
Root Cause Analysis
The Auth Flow (as designed)
Desktop /welcome (GoGrab tab)
→ tauriBridge.openBrowser("https://ai.gograb.space/auth/desktop")
→ Browser: DesktopAuthPageComponent loads
→ If already auth'd: ngOnInit() → redirectToVitto()
→ window.location.href = "vitto://auth?token=JWT&user=B64"
→ macOS routes vitto:// → Tauri on_open_url handler
→ Rust emits vitto:auth-token event → Angular listener
→ handleAuthCallback() → store token → reload
Where It Breaks
Primary cause (Confidence: 0.90): DesktopAuthPageComponent.ngOnInit() calls window.location.href = 'vitto://auth?token=...' without any user gesture. Modern browsers (Chrome 2023+, Safari) silently block window.location.href navigation to custom URL schemes when not triggered by a user interaction (click, submit, keypress). The page renders "Opening Vitto..." because Angular's template evaluates the redirecting() signal, but the actual browser-level navigation to vitto:// is suppressed.
Secondary risk (Confidence: 0.85): No Info.plist with CFBundleURLTypes exists in src-tauri/. While Tauri's deep-link plugin config should auto-generate this during build, the plugin docs state macOS requires explicit build-time registration. Without it, macOS may not associate the vitto:// scheme with the app.
Compounding factor: The Rust on_open_url handler had zero logging — making it impossible to distinguish "URL never arrived" from "URL arrived but parsing failed" from "event emitted but Angular missed it."
What Was Eliminated
- CORS: Verified
cors.factory.ts:112-114—isTauriOrigin()allowstauri://localhost. Not a factor. - URL parsing: The
urlcrate parsesvitto://auth?token=...correctly (scheme="vitto", host="auth", query pairs intact). - Tauri event system:
on_open_url+window.emit("vitto:auth-token", ...)pattern is correct per Tauri v2 docs. - Token size: JWT + base64 user payload fits well within URL length limits.
Changes Made (Spec A)
Fix 1: Info.plist with CFBundleURLTypes
Files: src-tauri/Info.plist (NEW), src-tauri/tauri.conf.json (MODIFIED)
Created explicit Info.plist registering the vitto URL scheme via CFBundleURLTypes. Referenced from tauri.conf.json via bundle.macOS.infoPlist. Tauri merges this with its auto-generated plist during build.
This is belt-and-suspenders — the deep-link plugin config should handle this, but explicit registration eliminates the risk of auto-generation failure.
Fix 2: Rust Deep Link Logging
File: src-tauri/src/lib.rs (lines 207-237)
Added eprintln! logging at every branch of the on_open_url handler:
- Number of URLs received
- Scheme, host, query of each URL
- Skip reasons (scheme/host mismatch)
- Parse success (token length, user presence)
- Window found/not-found
- Event emission confirmation
This makes future debugging possible from the Tauri console output.
Fix 3: Browser-Side Redirect Requires User Gesture
Files: desktop-auth.component.ts, .html, .scss
Before: ngOnInit() → if authenticated → window.location.href = 'vitto://...' (blocked by browser)
After: ngOnInit() → if authenticated → prepareDeepLink():
- Builds the deep link URL
- Sets
redirectingandpreAuthenticatedsignals - Shows a prominent "Open Vitto" button (primary mechanism — requires user click = user gesture = works)
- Attempts auto-redirect after 500ms delay (best-effort — may still be blocked)
Post-login flow (user just clicked Submit = user gesture active) still does direct window.location.href redirect — this works because the gesture context is still active.
Fix 4: retryAuth() Actually Retries
File: desktop-welcome.component.ts (line 272-276)
Before: retryAuth() only cleared error state — user had to manually click "Sign in with GoGrab" again.
After: retryAuth() clears state AND calls signIn(), which re-opens the browser. One-click retry.
Files Changed Summary
| File | Action | Lines Changed |
|---|---|---|
apps/vitto-desktop/src-tauri/Info.plist |
CREATED | 15 lines |
apps/vitto-desktop/src-tauri/tauri.conf.json |
MODIFIED | +1 line (infoPlist ref) |
apps/vitto-desktop/src-tauri/src/lib.rs |
MODIFIED | ~20 lines (logging) |
apps/gograb-ai/src/app/pages/desktop-auth/desktop-auth.component.ts |
MODIFIED | Rewrote redirect logic |
apps/gograb-ai/src/app/pages/desktop-auth/desktop-auth.component.html |
MODIFIED | Prominent button UI |
apps/gograb-ai/src/app/pages/desktop-auth/desktop-auth.component.scss |
MODIFIED | +btn--large, hint--secondary |
apps/gograb-ai/src/app/pages/desktop-welcome/desktop-welcome.component.ts |
MODIFIED | retryAuth() calls signIn() |
docs/english/Plans/S290-desktop-auth-server-side-token-exchange.md |
CREATED | Full spec for next session |
Architecture Decision: Two-Spec Approach
Spec A (this session, S288): Fixes the immediate deep link failure. Quick, targeted, low-risk.
Spec B (next session, S290): Adds a server-side token exchange as a parallel fallback. Desktop generates a sessionId, browser POSTs token to backend, desktop polls. RxJS race() resolves whichever arrives first. This eliminates all deep link failure modes.
The two specs are independent — S290 can start before S288 is built and tested. The only shared file is desktop-welcome.component.ts, but S290 adds a new service injection + polling, while S288 modifies retryAuth(). Easily mergeable.
MCP Intelligence Captured
| Type | ID | Description |
|---|---|---|
| Decision (bugfix) | dev_1774888144528_kuhurnt |
Root cause + 4-fix approach, confidence 0.90 |
| Decision (architecture) | dev_1774888164518_jocsbng |
Two-spec strategy, confidence 0.92 |
| Decision (investigation) | dev_1774888179262_kt7qa8y |
CORS non-issue verification, confidence 0.98 |
| Convention | conv_...78hvs |
User gesture required for custom URL scheme navigation |
| Convention | conv_...ec1bv |
Tauri deep links require bundled app + explicit Info.plist |
| Convention | conv_...1duw5 |
Rust event handlers must have eprintln! logging |
| Reflection | reflection-1774888214671 |
6 lessons learned |
| Module outcome | vitto-desktop-auth | Partial (awaiting S290 for full robustness) |
Testing Checklist
- Build Tauri app:
tauri build --debug - Install in
/Applications - Test GoGrab tab: browser opens → already authenticated → click "Open Vitto" button → app receives token
- Test GoGrab tab: browser opens → login form → submit → auto-redirect works (user gesture active)
- Test Email tab: email + password → login → navigates to dashboard
- Test Phone tab: phone → code → verify → navigates to dashboard
- Test timeout: open browser, don't interact, wait 90s → error + "Try again" button
- Test retry: click "Try again" → browser re-opens (not just state reset)
- Check Tauri console: verify
[Vitto:DeepLink]logs appear when deep link fires - Verify
Info.plistin built .app bundle containsCFBundleURLTypes
Next Steps
- Build & test Spec A changes on a real bundled Tauri app
- Start S290 — implement server-side token exchange (can start immediately)
- After S290: deep link + polling racing in parallel = robust auth for all scenarios