Skip to content

Commit bc721e3

Browse files
gonericlaude
andcommitted
fix: address OAuth callback code review findings
- Remove shared _activeRedirectUri field; pass redirectUri through the call chain to avoid cross-request mutation - Use neutral "Authorization received" message before token exchange - Avoid reflecting query parameters into HTML to prevent XSS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a9dd623 commit bc721e3

File tree

1 file changed

+16
-18
lines changed

1 file changed

+16
-18
lines changed

src/features/lightspeed/lightSpeedOAuthProvider.ts

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ export class LightSpeedAuthenticationProvider
9292
private _authId: string;
9393
private _authName: string;
9494
private _externalRedirectUri: string;
95-
private _activeRedirectUri: string | undefined;
9695

9796
constructor(
9897
private readonly context: ExtensionContext,
@@ -314,12 +313,12 @@ export class LightSpeedAuthenticationProvider
314313
);
315314
resolveCode(code);
316315
} else {
317-
const errorMsg =
318-
error || "No authorization code received from the server";
319316
res.end(
320-
`<html><body><h1>Authentication failed</h1><p>${errorMsg}</p></body></html>`,
317+
"<html><body><h1>Authentication failed</h1><p>Something went wrong. Return to your editor for details.</p></body></html>",
318+
);
319+
rejectCode(
320+
new Error(error || "No authorization code received from the server"),
321321
);
322-
rejectCode(new Error(errorMsg));
323322
}
324323
});
325324

@@ -393,14 +392,13 @@ export class LightSpeedAuthenticationProvider
393392
const query = searchParams.toString();
394393
const uri = Uri.parse(base_uri).with({ path: "/o/authorize/", query });
395394

396-
// Also listen on the vscode:// URI handler as a fallback
397395
const {
398396
promise: receivedRedirectUrl,
399397
cancel: cancelWaitingForRedirectUrl,
400-
} = promiseFromEvent(this._uriHandler.event, this.handleUriForCode(scopes));
401-
402-
// Store the redirect URI used for the token exchange
403-
this._activeRedirectUri = redirectUri;
398+
} = promiseFromEvent(
399+
this._uriHandler.event,
400+
this.handleUriForCode(redirectUri),
401+
);
404402

405403
await env.openExternal(uri);
406404

@@ -430,17 +428,16 @@ export class LightSpeedAuthenticationProvider
430428

431429
if (localServer) {
432430
candidates.push(
433-
localServer.codePromise.then(async (code) => {
434-
return await this.requestOAuthAccountFromCode(code);
435-
}) as Promise<OAuthAccount>,
431+
localServer.codePromise.then((code) =>
432+
this.requestOAuthAccountFromCode(code, redirectUri),
433+
) as Promise<OAuthAccount>,
436434
);
437435
}
438436

439437
return await Promise.race<OAuthAccount>(candidates);
440438
} finally {
441439
localServer?.close();
442440
cancelWaitingForRedirectUrl.fire();
443-
this._activeRedirectUri = undefined;
444441
}
445442
},
446443
);
@@ -450,9 +447,9 @@ export class LightSpeedAuthenticationProvider
450447

451448
/* Handle the redirect to VS Code (after sign in from the Ansible Lightspeed auth service) */
452449
private handleUriForCode: (
453-
scopes: readonly string[],
450+
redirectUri: string,
454451
) => PromiseAdapter<Uri, OAuthAccount> =
455-
() => async (uri, resolve, reject) => {
452+
(redirectUri) => async (uri, resolve, reject) => {
456453
const query = new URLSearchParams(uri.query);
457454
const code = query.get("code");
458455

@@ -465,7 +462,7 @@ export class LightSpeedAuthenticationProvider
465462
return;
466463
}
467464

468-
const account = await this.requestOAuthAccountFromCode(code);
465+
const account = await this.requestOAuthAccountFromCode(code, redirectUri);
469466

470467
if (!account) {
471468
reject(new Error("Unable to form account"));
@@ -478,6 +475,7 @@ export class LightSpeedAuthenticationProvider
478475
/* Request access token from server using code */
479476
private async requestOAuthAccountFromCode(
480477
code: string,
478+
redirectUri: string,
481479
): Promise<OAuthAccount | undefined> {
482480
const headers = {
483481
"Cache-Control": "no-cache",
@@ -496,7 +494,7 @@ export class LightSpeedAuthenticationProvider
496494
{
497495
method: "POST",
498496
signal: AbortSignal.timeout(ANSIBLE_LIGHTSPEED_API_TIMEOUT),
499-
body: `client_id=${encodeURIComponent(LIGHTSPEED_CLIENT_ID)}&code_verifier=${encodeURIComponent(getCodeVerifier())}&grant_type=authorization_code&code=${code}&redirect_uri=${encodeURIComponent(this._activeRedirectUri || this._externalRedirectUri)}`,
497+
body: `client_id=${encodeURIComponent(LIGHTSPEED_CLIENT_ID)}&code_verifier=${encodeURIComponent(getCodeVerifier())}&grant_type=authorization_code&code=${code}&redirect_uri=${encodeURIComponent(redirectUri)}`,
500498
headers,
501499
},
502500
);

0 commit comments

Comments
 (0)