S288 Desktop Auth Fix Technical

Documentation for Sessions

S288: Vitto Desktop Auth/Login Fix — Technical Documentation

Session: 288.2 Branch: session-280-290 Date: 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

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:

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():

  1. Builds the deep link URL
  2. Sets redirecting and preAuthenticated signals
  3. Shows a prominent "Open Vitto" button (primary mechanism — requires user click = user gesture = works)
  4. 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

Next Steps

  1. Build & test Spec A changes on a real bundled Tauri app
  2. Start S290 — implement server-side token exchange (can start immediately)
  3. After S290: deep link + polling racing in parallel = robust auth for all scenarios