Skip to content
Merged
Changes from all 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
76 changes: 59 additions & 17 deletions apps/code/src/main/services/oauth/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getOauthClientIdFromRegion,
OAUTH_SCOPES,
} from "@shared/constants/oauth";
import { type BackoffOptions, sleepWithBackoff } from "@shared/utils/backoff";
import { getCloudUrlFromRegion } from "@shared/utils/urls";
import { inject, injectable } from "inversify";
import { MAIN_TOKENS } from "../../di/tokens";
Expand All @@ -26,6 +27,16 @@ const log = logger.scope("oauth-service");
const OAUTH_TIMEOUT_MS = 180_000; // 3 minutes
const DEV_CALLBACK_PORT = 8237;

const NETWORK_ERROR_MESSAGE =
"Could not connect to PostHog. Please check your internet connection and try again.";

const TOKEN_FETCH_MAX_ATTEMPTS = 3;
const TOKEN_FETCH_BACKOFF: BackoffOptions = {
initialDelayMs: 1_000,
maxDelayMs: 5_000,
multiplier: 2,
};

interface OAuthConfig {
scopes: string[];
cloudRegion: CloudRegion;
Expand Down Expand Up @@ -212,7 +223,7 @@ export class OAuthService {
} catch {
return {
success: false,
error: "Network error",
error: NETWORK_ERROR_MESSAGE,
errorCode: "network_error",
};
}
Expand Down Expand Up @@ -428,26 +439,57 @@ export class OAuthService {
): Promise<OAuthTokenResponse> {
const cloudUrl = getCloudUrlFromRegion(config.cloudRegion);
const redirectUri = this.getRedirectUri();

const response = await fetch(`${cloudUrl}/oauth/token`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
grant_type: "authorization_code",
code,
redirect_uri: redirectUri,
client_id: getOauthClientIdFromRegion(config.cloudRegion),
code_verifier: codeVerifier,
}),
const body = JSON.stringify({
grant_type: "authorization_code",
code,
redirect_uri: redirectUri,
client_id: getOauthClientIdFromRegion(config.cloudRegion),
code_verifier: codeVerifier,
});

if (!response.ok) {
throw new Error(`Token exchange failed: ${response.statusText}`);
let lastError = "Token exchange failed";

for (let attempt = 0; attempt < TOKEN_FETCH_MAX_ATTEMPTS; attempt++) {
let response: Response;
try {
response = await fetch(`${cloudUrl}/oauth/token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body,
});
} catch (error) {
// fetch threw — DNS/TLS/socket failure. The raw message ("Failed to fetch",
// "fetch failed", "terminated", etc.) leaks to the UI as-is, so we replace
// it with something users can act on.
lastError = NETWORK_ERROR_MESSAGE;
log.warn("Token exchange network error", {
attempt,
error: error instanceof Error ? error.message : String(error),
});
if (attempt === TOKEN_FETCH_MAX_ATTEMPTS - 1) break;
await sleepWithBackoff(attempt, TOKEN_FETCH_BACKOFF);
continue;
}

if (response.ok) {
return response.json();
}

lastError = `Token exchange failed: ${response.status} ${response.statusText}`;
const isServerError = response.status >= 500;
if (!isServerError) {
throw new Error(lastError);
}

log.warn("Token exchange server error", {
attempt,
status: response.status,
});
if (attempt === TOKEN_FETCH_MAX_ATTEMPTS - 1) break;
await sleepWithBackoff(attempt, TOKEN_FETCH_BACKOFF);
}

return response.json();
throw new Error(lastError);
Comment on lines +452 to +492
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Missing tests for retry logic

The custom rule for this repo requires comprehensive tests that verify the complete retry flow with multiple retry attempts when retry logic is introduced. This PR adds a 3-attempt retry loop with exponential backoff to exchangeCodeForToken, but no test file covering the new behavior is included. Specifically, scenarios worth testing include: network failure on all attempts (exhausts retries, throws with NETWORK_ERROR_MESSAGE), network failure on first attempt then success, 5xx on first attempt then success, and 4xx immediately throwing without retrying.

Rule Used: When implementing retry logic, include comprehensi... (source)

Learned From
PostHog/posthog#32651

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/main/services/oauth/service.ts
Line: 452-492

Comment:
**Missing tests for retry logic**

The custom rule for this repo requires comprehensive tests that verify the complete retry flow with multiple retry attempts when retry logic is introduced. This PR adds a 3-attempt retry loop with exponential backoff to `exchangeCodeForToken`, but no test file covering the new behavior is included. Specifically, scenarios worth testing include: network failure on all attempts (exhausts retries, throws with `NETWORK_ERROR_MESSAGE`), network failure on first attempt then success, 5xx on first attempt then success, and 4xx immediately throwing without retrying.

**Rule Used:** When implementing retry logic, include comprehensi... ([source](https://app.greptile.com/review/custom-context?memory=24d1be3f-07fb-465d-b1a7-60dff301aed8))

**Learned From**
[PostHog/posthog#32651](https://github.com/PostHog/posthog/pull/32651)

How can I resolve this? If you propose a fix, please make it concise.

}

private buildAuthorizeUrl(region: CloudRegion, codeVerifier: string): URL {
Expand Down
Loading