Skip to content
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 56 additions & 12 deletions apps/desktop/src/main/lib/auth/auth-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,25 @@ class AuthService extends EventEmitter {
if (session.refreshToken && session.refreshTokenExpiresAt > Date.now()) {
console.log("[auth] Attempting to refresh tokens on startup");
this.session = session; // Temporarily set to allow refresh
const refreshed = await this.refreshTokens();
if (refreshed) {
const result = await this.refreshTokens();

if (result === "success") {
console.log("[auth] Session restored via token refresh");
return;
}

if (result === "network_error") {
// Offline - keep session with expired access token
// User can work offline, and we'll refresh when online
console.log("[auth] Offline - keeping session for offline use");
this.session = session;
return;
}

// result === "invalid" - tokens are revoked, must clear
}

// Refresh failed or no valid refresh token
// Refresh token invalid/expired or no refresh token
console.log("[auth] Session fully expired, clearing");
await this.clearSession();
return;
Expand All @@ -93,6 +104,7 @@ class AuthService extends EventEmitter {
/**
* Get access token for API calls
* Automatically refreshes if access token is expired but refresh token is valid
* Returns null if offline or tokens invalid (caller should handle gracefully)
*/
async getAccessToken(): Promise<string | null> {
if (!this.session) {
Expand All @@ -108,10 +120,17 @@ class AuthService extends EventEmitter {
this.session.refreshToken &&
this.session.refreshTokenExpiresAt > Date.now()
) {
const refreshed = await this.refreshTokens();
if (refreshed) {
const result = await this.refreshTokens();
if (result === "success") {
return this.session.accessToken;
}
if (result === "network_error") {
// Offline - return null but don't clear session
// User stays signed in, but can't make API calls until online
console.log("[auth] Offline - cannot refresh token");
return null;
}
// result === "invalid" - fall through to clear session
}

// Refresh failed or no valid refresh token
Expand All @@ -125,10 +144,16 @@ class AuthService extends EventEmitter {

/**
* Refresh tokens using the refresh token
* Returns: 'success' | 'invalid' | 'network_error'
* - 'success': Tokens refreshed successfully
* - 'invalid': Tokens are invalid/revoked (should clear session)
* - 'network_error': Network unavailable (should keep session for offline use)
*/
private async refreshTokens(): Promise<boolean> {
private async refreshTokens(): Promise<
"success" | "invalid" | "network_error"
> {
if (!this.session?.refreshToken) {
return false;
return "invalid";
}

try {
Expand All @@ -147,16 +172,32 @@ class AuthService extends EventEmitter {

if (!response.ok) {
console.error("[auth] Token refresh failed:", response.status);
return false;
// 401/403 means tokens are actually invalid, not a network issue
if (response.status === 401 || response.status === 403) {
return "invalid";
}
// Other errors (500, etc) - treat as temporary, keep session
return "network_error";
}

const tokens = (await response.json()) as {
let tokens: {
accessToken: string;
accessTokenExpiresAt: number;
refreshToken: string;
refreshTokenExpiresAt: number;
};

try {
tokens = (await response.json()) as typeof tokens;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
} catch (parseErr) {
// JSON parse error indicates malformed server response - treat as invalid
console.error(
"[auth] Token refresh JSON parse error:",
parseErr instanceof Error ? parseErr.message : parseErr,
);
return "invalid";
}

// Update session with new tokens
this.session = {
accessToken: tokens.accessToken,
Expand All @@ -167,10 +208,13 @@ class AuthService extends EventEmitter {

await tokenStorage.save(this.session);
console.log("[auth] Tokens refreshed successfully");
return true;
return "success";
} catch (err) {
console.error("[auth] Token refresh error:", err);
return false;
// Network errors (offline, DNS failure, etc) - keep session for offline use
const errType = err instanceof Error ? err.constructor.name : typeof err;
const errMsg = err instanceof Error ? err.message : String(err);
console.error(`[auth] Token refresh network error (${errType}):`, errMsg);
return "network_error";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

Expand Down