Skip to content

Commit 5aad982

Browse files
committed
fix(cli): keep OAuth query parameters when opening the browser on Windows
1 parent f97b801 commit 5aad982

3 files changed

Lines changed: 59 additions & 7 deletions

File tree

.changeset/windows-browser-url.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@pymodel/pythinker-code': patch
3+
---
4+
5+
Open the browser on Windows through `rundll32` instead of `cmd /c start`. `cmd` cut every URL at the first `&`, so OAuth logins reached the provider with only the first query parameter and failed with an invalid authorize request.
Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
11
import { execFile } from 'node:child_process';
22

3+
export interface OpenUrlCommand {
4+
readonly command: string;
5+
readonly args: readonly string[];
6+
}
7+
8+
/**
9+
* Windows uses `rundll32` rather than `cmd /c start` because `cmd` re-parses
10+
* its arguments and cuts a URL at the first `&`, which strips every OAuth
11+
* query parameter after `client_id`.
12+
*/
13+
export function openUrlCommandFor(
14+
url: string,
15+
platform: NodeJS.Platform = process.platform,
16+
): OpenUrlCommand {
17+
switch (platform) {
18+
case 'darwin':
19+
return { command: 'open', args: [url] };
20+
case 'win32':
21+
return { command: 'rundll32', args: ['url.dll,FileProtocolHandler', url] };
22+
default:
23+
return { command: 'xdg-open', args: [url] };
24+
}
25+
}
26+
327
export function openUrl(url: string): void {
4-
const command: [string, string[]] =
5-
process.platform === 'darwin'
6-
? ['open', [url]]
7-
: process.platform === 'win32'
8-
? ['cmd', ['/c', 'start', '', url]]
9-
: ['xdg-open', [url]];
10-
execFile(command[0], command[1], () => {});
28+
const { command, args } = openUrlCommandFor(url);
29+
execFile(command, [...args], () => {});
1130
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { openUrlCommandFor } from '#/utils/open-url';
4+
5+
const AUTHORIZE_URL =
6+
'https://auth.openai.com/oauth/authorize?client_id=app_test&response_type=code&state=abc';
7+
8+
describe('openUrlCommandFor', () => {
9+
it('keeps every query parameter on Windows', () => {
10+
const { command, args } = openUrlCommandFor(AUTHORIZE_URL, 'win32');
11+
expect(command).toBe('rundll32');
12+
// `cmd /c start` cuts the URL at the first `&`, so the launcher must not
13+
// hand the URL to a command interpreter.
14+
expect(command).not.toBe('cmd');
15+
expect(args.at(-1)).toBe(AUTHORIZE_URL);
16+
});
17+
18+
it('uses the platform launcher elsewhere', () => {
19+
expect(openUrlCommandFor(AUTHORIZE_URL, 'darwin')).toEqual({
20+
command: 'open',
21+
args: [AUTHORIZE_URL],
22+
});
23+
expect(openUrlCommandFor(AUTHORIZE_URL, 'linux')).toEqual({
24+
command: 'xdg-open',
25+
args: [AUTHORIZE_URL],
26+
});
27+
});
28+
});

0 commit comments

Comments
 (0)