Portable mode improvements and bug fixes (#287063)

Disabled protocol handlers and registry updates on Windows in portable mode.
Added API proposal to detect if VS Code is running in portable mode from extensions.
Skipped protocol redirect in GitHub authentication in portable mode.
This commit is contained in:
Dmitriy Vasyura
2026-01-24 04:22:53 -08:00
committed by GitHub
parent 141d5452e8
commit aa19df565f
25 changed files with 156 additions and 26 deletions

View File

@@ -354,7 +354,7 @@ class LocalServerFlow implements IFlow {
path: '/login/oauth/authorize',
query: searchParams.toString()
});
const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl.toString(true), callbackUri.toString(true));
const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl.toString(true), callbackUri.toString(true), env.isAppPortable);
const port = await server.start();
let codeToExchange;

View File

@@ -87,7 +87,7 @@ export class LoopbackAuthServer implements ILoopbackServer {
return this._startingRedirect.searchParams.get('state') ?? undefined;
}
constructor(serveRoot: string, startingRedirect: string, callbackUri: string) {
constructor(serveRoot: string, startingRedirect: string, callbackUri: string, isPortable: boolean) {
if (!serveRoot) {
throw new Error('serveRoot must be defined');
}
@@ -132,7 +132,11 @@ export class LoopbackAuthServer implements ILoopbackServer {
throw new Error('Nonce does not match.');
}
deferred.resolve({ code, state });
res.writeHead(302, { location: `/?redirect_uri=${encodeURIComponent(callbackUri)}${appNameQueryParam}` });
if (isPortable) {
res.writeHead(302, { location: `/?app_name=${encodeURIComponent(env.appName)}` });
} else {
res.writeHead(302, { location: `/?redirect_uri=${encodeURIComponent(callbackUri)}${appNameQueryParam}` });
}
res.end();
break;
}

View File

@@ -12,7 +12,7 @@ suite('LoopbackAuthServer', () => {
let port: number;
setup(async () => {
server = new LoopbackAuthServer(__dirname, 'http://localhost:8080', 'https://code.visualstudio.com');
server = new LoopbackAuthServer(__dirname, 'http://localhost:8080', 'https://code.visualstudio.com', false);
port = await server.start();
});
@@ -64,3 +64,35 @@ suite('LoopbackAuthServer', () => {
]);
});
});
suite('LoopbackAuthServer (portable mode)', () => {
let server: LoopbackAuthServer;
let port: number;
setup(async () => {
server = new LoopbackAuthServer(__dirname, 'http://localhost:8080', 'https://code.visualstudio.com', true);
port = await server.start();
});
teardown(async () => {
await server.stop();
});
test('should redirect to success page without redirect_uri on /callback', async () => {
server.state = 'valid-state';
const response = await fetch(
`http://localhost:${port}/callback?code=valid-code&state=${server.state}&nonce=${server.nonce}`,
{ redirect: 'manual' }
);
assert.strictEqual(response.status, 302);
// In portable mode, should redirect to success page without redirect_uri
assert.strictEqual(response.headers.get('location'), `/?app_name=${encodeURIComponent(env.appName)}`);
await Promise.race([
server.waitForOAuthResponse().then(result => {
assert.strictEqual(result.code, 'valid-code');
assert.strictEqual(result.state, server.state);
}),
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000))
]);
});
});