Skip to content

fix(desktop): offline sign out behavior fixed#512

Merged
AviPeltz merged 6 commits into
mainfrom
oauth-secret-check
Dec 27, 2025
Merged

fix(desktop): offline sign out behavior fixed#512
AviPeltz merged 6 commits into
mainfrom
oauth-secret-check

Conversation

@AviPeltz
Copy link
Copy Markdown
Collaborator

@AviPeltz AviPeltz commented Dec 26, 2025

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes

Summary by CodeRabbit

  • Refactor

    • Authentication refresh redesigned to return explicit outcomes: success, invalid, or network_error for clearer startup and token handling.
  • Improvements

    • Startup and token retrieval now distinguish network failures from invalid credentials; offline sessions are preserved for continued use.
    • Better validation and persistence of refreshed tokens to ensure only well-formed credentials are stored.
  • Bug Fixes

    • Treat malformed responses and 401/403 results as invalid to avoid corrupt sessions.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 26, 2025

📝 Walkthrough

Walkthrough

The auth service now returns explicit result codes ("success", "invalid", "network_error") from token refresh operations; callers branch on these: "success" updates tokens and restores session, "invalid" clears the session, and "network_error" preserves the existing session for offline use.

Changes

Cohort / File(s) Summary
Auth service core change
apps/desktop/src/main/lib/auth/auth-service.ts
refreshTokens() signature changed from Promise<boolean> to `Promise<"success"
Auth call sites / startup & token access
apps/desktop/src/main/lib/auth/...
Startup flow and getAccessToken() updated to handle the three return codes: restore on success, preserve session on network_error, clear on invalid. Inline comments and logging updated to reflect offline behavior.
Imports & storage
apps/desktop/src/main/lib/auth/auth-service.ts
Added import of tokenStorage and integrated runtime validation and persistence around token refresh operations.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant AuthSvc as Auth Service
    participant Remote as Auth Server
    participant Session
    Note right of AuthSvc `#DDEBF7`: refreshTokens() → "success" | "invalid" | "network_error"

    Caller->>AuthSvc: refreshTokens()
    AuthSvc->>Remote: POST /auth/refresh (with refresh token)
    alt 200 + valid JSON & shape
        Remote-->>AuthSvc: 200 + tokens
        AuthSvc->>Session: persist updated tokens/session
        AuthSvc-->>Caller: "success"
        Caller->>Session: resume restored session
    else 401 or 403 OR malformed JSON
        Remote-->>AuthSvc: 401/403 or invalid body
        AuthSvc->>Session: clear session
        AuthSvc-->>Caller: "invalid"
        Caller->>Caller: trigger login flow
    else other HTTP error or network failure
        Remote--x AuthSvc: network/server error
        AuthSvc-->>Caller: "network_error"
        Caller->>Session: preserve session for offline use
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through refresh, three paths to choose,

"success" brings tokens, "invalid" I lose,
If the net is shaky, I keep your den,
Else I tidy the keys and start again,
A rabbit's nod to safer renews.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description contains only the repository template structure with no substantive content filled in. The Description, Related Issues, and Additional Notes sections are empty, severely limiting reviewer understanding. Fill in the Description section with details about the offline sign-out behavior fix, link any related issues, and add context in Additional Notes explaining the testing approach.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(desktop): offline sign out behavior fixed' directly relates to the main change—improving offline session handling in the auth service during sign-out scenarios.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch oauth-secret-check

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@AviPeltz AviPeltz changed the title fix(desktop): offline sign out bug fixed fix(desktop): offline sign out bahavior fixed Dec 26, 2025
@AviPeltz AviPeltz marked this pull request as ready for review December 26, 2025 23:43
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/main/lib/auth/auth-service.ts (1)

183-188: Add runtime validation for token refresh response.

The type assertion assumes the API returns the expected structure without validation. If the refresh endpoint returns a 200 status but with missing or incorrectly typed fields, this will cause runtime errors when accessing token properties (e.g., line 191-195).

🔎 Proposed fix with runtime validation
 		const tokens = (await response.json()) as {
 			accessToken: string;
 			accessTokenExpiresAt: number;
 			refreshToken: string;
 			refreshTokenExpiresAt: number;
 		};
+
+		// Validate response structure
+		if (
+			typeof tokens.accessToken !== "string" ||
+			typeof tokens.accessTokenExpiresAt !== "number" ||
+			typeof tokens.refreshToken !== "string" ||
+			typeof tokens.refreshTokenExpiresAt !== "number"
+		) {
+			console.error("[auth] Invalid token refresh response structure");
+			return "invalid";
+		}
🧹 Nitpick comments (1)
apps/desktop/src/main/lib/auth/auth-service.ts (1)

176-180: Consider the implications of treating 5xx errors as network errors.

Server errors (500, 502, 503, etc.) are currently treated as "network_error" to keep the session for offline use. While this provides better UX, a 500 could also indicate that the tokens are malformed or the refresh endpoint is broken, not just a temporary connectivity issue. This is a design trade-off between offline UX and strict token validation.

Consider whether certain 5xx errors (like 500 Internal Server Error) should be treated as "invalid" if they consistently fail, while keeping 502/503/504 as "network_error" since those are typically temporary issues.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82d5c63 and f0139f5.

📒 Files selected for processing (1)
  • apps/desktop/src/main/lib/auth/auth-service.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid using any type in TypeScript - maintain type safety unless absolutely necessary

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Run Biome for formatting, linting, import organization, and safe fixes at the root level using bun run lint:fix

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
apps/desktop/src/{main,renderer,preload}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use type-safe IPC communication - define channel types in apps/desktop/src/shared/ipc-channels.ts before implementing handlers

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
apps/desktop/src/main/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Accept object parameters in IPC handlers - do not use positional parameters in ipcMain.handle()

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (2)
apps/desktop/src/main/lib/auth/auth-service.ts (2)

66-82: Offline session handling looks good.

The logic correctly preserves the session when a network error occurs during token refresh on startup, enabling offline usage. The fall-through behavior for invalid tokens (line 81) is clear and leads to proper session cleanup at lines 84-86.


123-134: Consistent offline handling in token access.

The null return on network errors (line 131) allows callers to handle offline scenarios gracefully while maintaining the user's session. This is consistent with the startup behavior.

Comment thread apps/desktop/src/main/lib/auth/auth-service.ts
@AviPeltz AviPeltz changed the title fix(desktop): offline sign out bahavior fixed fix(desktop): offline sign out behavior fixed Dec 27, 2025
@charliecreates
Copy link
Copy Markdown
Contributor

charliecreates Bot commented Dec 27, 2025

Expand this to see my work.
  • Fetched PR #512 metadata and diff to see the full scope of changes, confirming it only touches apps/desktop/src/main/lib/auth/auth-service.ts and introduces richer outcomes for refreshTokens.
  • Read through the modified initialize() and getAccessToken() flows to verify that 'network_error' now preserves this.session for offline use while 'invalid' falls through to clearSession() and returns null as expected.
  • Reviewed the refactored private async refreshTokens() signature and implementation, confirming the new return union 'success' | 'invalid' | 'network_error' is consistently handled at all call sites and that 401/403 vs other HTTP status codes are differentiated correctly.
  • Checked the JSON parsing path for the refresh response to ensure the tokens type is well-scoped, the parse error is logged via console.error, and that returning 'invalid' in this case won’t leave a partially updated this.session.
  • Validated that the new error logging format in refreshTokens (including errType and errMsg) won’t leak sensitive token values and keeps logs focused on error classification rather than payload contents.
  • Skimmed surrounding auth flows (signIn, clearSession, getState) to confirm there are no other assumptions about refreshTokens returning a boolean and that no unreachable or dead code remains after the refactor.
  • Searched the desktop codebase (apps/desktop/src/main) for other refreshTokens call sites and existing zod usage (for example in env.main.ts) to see whether a shared schema or stronger typing for the token refresh response would align with current patterns.
  • Checked CI for PR #512 via gh pr checks, confirming Lint, Test, and Typecheck all pass and Build/CodeRabbit are still pending, indicating the TypeScript changes in auth-service.ts are type-safe under the current config.
  • Inspected line-numbered context in apps/desktop/src/main/index.ts to confirm the startup sequence (initAppState(), then authService.initialize(), then makeAppSetup) ensures renderer code only ever sees auth state after the new offline-aware initialization completes.
  • Reviewed apps/desktop/src/lib/trpc/routers/auth/index.ts to verify onStateChange subscribes to authService's "state-changed" event and that getState/signOut semantics remain compatible with the updated authService.getState() and signOut() behavior under offline and token-invalid scenarios.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f0139f5 and c839d64.

📒 Files selected for processing (1)
  • apps/desktop/src/main/lib/auth/auth-service.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid using any type in TypeScript - maintain type safety unless absolutely necessary

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Run Biome for formatting, linting, import organization, and safe fixes at the root level using bun run lint:fix

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
apps/desktop/src/{main,renderer,preload}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use type-safe IPC communication - define channel types in apps/desktop/src/shared/ipc-channels.ts before implementing handlers

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
apps/desktop/src/main/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Accept object parameters in IPC handlers - do not use positional parameters in ipcMain.handle()

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (5)
apps/desktop/src/main/lib/auth/auth-service.ts (5)

66-82: LGTM! Startup refresh flow handles all three outcomes correctly.

The three-way result handling appropriately restores the session on success, preserves it for offline use on network errors, and clears it when tokens are invalid.


123-134: LGTM! Offline behavior is correctly preserved.

The token refresh handling returns null for network errors without clearing the session, allowing users to remain signed in while offline. Invalid tokens correctly fall through to clear the session.


152-181: LGTM! Three-way result model and HTTP status handling are well-designed.

The explicit return type provides type safety, and the distinction between authentication failures (401/403 → "invalid") versus temporary issues (other errors → "network_error") is appropriate for offline support.


183-199: JSON parse error handling correctly addresses previous review feedback.

The separate try-catch block now returns "invalid" for malformed server responses rather than treating them as network errors, which prevents corrupted state. However, see the next comment about response validation.


212-218: Past review comment fully resolved.

The improved error handling correctly distinguishes network errors from JSON parsing failures. Parse errors are now caught separately (lines 190-199) and return "invalid", while this catch block handles only genuine network connectivity issues. The enhanced error logging provides better debugging information.

Comment thread apps/desktop/src/main/lib/auth/auth-service.ts Outdated
@charliecreates
Copy link
Copy Markdown
Contributor

This change does fix the “offline sign-out” class of bugs nicely: initialize() and getAccessToken() now preserve the stored session when refresh fails due to connectivity / transient server issues, instead of clearing tokens immediately.

A few things I’d tighten up before merge (mostly around correctness + debuggability):

  • Add runtime validation for the refresh response shape in refreshTokens() before persisting it. Right now we type-assert response.json() and then write the values straight into this.session + tokenStorage (see auth-service.ts around L183-L210). If the API ever returns { error: ... } with a 200, or missing fields, we’ll save a malformed session and potentially break auth in a harder-to-recover way.

    Suggested pattern (Zod)
    import { z } from "zod";
    
    const TokensSchema = z.object({
      accessToken: z.string(),
      accessTokenExpiresAt: z.number(),
      refreshToken: z.string(),
      refreshTokenExpiresAt: z.number(),
    });
    
    const parsed = TokensSchema.safeParse(await response.json());
    if (!parsed.success) {
      console.error("[auth] Invalid token refresh response", parsed.error);
      return "invalid";
    }
    
    const tokens = parsed.data;
  • Be more explicit about which HTTP statuses count as invalid vs network_error. Today it’s “401/403 => invalid, everything else => network_error” (see L173-L180).

    • The API route returns 400 for missing refresh token and 401 for invalid/expired refresh token (apps/api/.../refresh/route.ts). While the desktop client shouldn’t hit 400 (since it checks refreshToken), a corrupted on-disk session or future API changes could.
    • I’d recommend: treat most 4xx as "invalid" (or at least 400/404/422) and keep "network_error" mainly for 5xx + actual fetch failures. At minimum, a comment explaining the intended mapping (and why) would make future maintenance easier.
  • The catch currently labels all errors as “network error,” including non-network failures like tokenStorage.save() throwing (disk/permissions). Since the try covers fetch + parsing + save, a storage failure will currently return "network_error" and may mask a real local persistence problem. Consider splitting into:

    1. fetch/HTTP handling (can be "network_error")
    2. parse/validate (likely "invalid")
    3. persistence errors (log separately; arguably still return "success" since the in-memory session is now valid, but warn that persistence failed)
  • (Optional, but worthwhile) De-dupe refresh attempts while offline / expired. When access tokens are expired and the app is offline, repeated API calls will repeatedly attempt refresh and spam logs. A simple refreshInFlight?: Promise<RefreshResult> (or a small backoff window timestamp) would reduce noisy behavior and load.

Non-code / PR hygiene:

  • The PR template is still blank (especially “Testing”). Given this is auth/offline behavior, it’d help to include explicit repro + verify steps (e.g., sign in → disconnect network → restart app → confirm still signed in; then reconnect → confirm refresh resumes).
  • There’s an “invalid check” commit in the PR history; if the repo prefers a clean history, this looks like a good candidate to squash away before merge.

Build is still pending on the PR checks as of now; everything else is green.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/desktop/src/main/lib/auth/auth-service.ts (1)

178-246: LGTM: Comprehensive token refresh with proper error categorization.

The refactored refreshTokens() method correctly implements the offline fix by:

  • Returning explicit result codes for different failure scenarios
  • Distinguishing authentication failures (401/403 → "invalid") from network/server issues (other errors → "network_error")
  • Safely parsing and validating responses before updating session state
  • Providing detailed error logging for debugging

The error handling properly addresses both previous review comments (JSON parse errors and response validation).

Consider adding unit tests for the error scenarios.

Given the complexity of the error handling logic (network errors, auth errors, parse errors, validation errors), unit tests would help ensure all branches behave correctly and prevent regressions.

Would you like me to generate a test suite covering these scenarios?

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c839d64 and bf899f5.

📒 Files selected for processing (1)
  • apps/desktop/src/main/lib/auth/auth-service.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid using any type in TypeScript - maintain type safety unless absolutely necessary

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Run Biome for formatting, linting, import organization, and safe fixes at the root level using bun run lint:fix

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
apps/desktop/src/{main,renderer,preload}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use type-safe IPC communication - define channel types in apps/desktop/src/shared/ipc-channels.ts before implementing handlers

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
apps/desktop/src/main/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Accept object parameters in IPC handlers - do not use positional parameters in ipcMain.handle()

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/main/lib/auth/auth-service.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (5)
apps/desktop/src/main/lib/auth/auth-service.ts (5)

6-6: LGTM: Import properly organized.

The tokenStorage import is correctly placed and necessary for the functionality used throughout the file.


40-45: LGTM: Well-defined interface.

The TokenResponse interface clearly defines the expected shape of the token refresh response, supporting the runtime validation logic.


47-65: LGTM: Robust runtime validation.

The type guard comprehensively validates the token response shape, checking field presence, types, and basic constraints. This addresses the previous concern about runtime validation.


92-108: LGTM: Offline behavior correctly implemented.

The initialization flow now properly handles offline scenarios by preserving the session when refreshTokens() returns "network_error". This allows users to remain signed in and work offline, which is the core fix for the offline sign-out behavior described in the PR.


133-133: LGTM: Graceful offline handling in token retrieval.

The access token retrieval correctly handles offline scenarios by returning null without clearing the session when refresh fails due to network issues. The updated comment accurately describes this behavior.

Also applies to: 149-159

@AviPeltz AviPeltz merged commit a3070a2 into main Dec 27, 2025
5 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

Service Status
Neon Database (Neon) ⚠️

Thank you for your contribution! 🎉


Preview resources have been processed for cleanup

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