mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-22 11:19:32 +00:00
Support PKCE for GitHub Auth (#265381)
Fixes https://github.com/microsoft/vscode/issues/264795
This commit is contained in:
committed by
GitHub
parent
4407c1e0b3
commit
71b461ab86
@@ -9,6 +9,7 @@ import { Log } from './common/logger';
|
||||
import { Config } from './config';
|
||||
import { UriEventHandler } from './github';
|
||||
import { fetching } from './node/fetch';
|
||||
import { crypto } from './node/crypto';
|
||||
import { LoopbackAuthServer } from './node/authServer';
|
||||
import { promiseFromEvent } from './common/utils';
|
||||
import { isHostedGitHubEnterprise } from './common/env';
|
||||
@@ -112,11 +113,44 @@ interface IFlow {
|
||||
trigger(options: IFlowTriggerOptions): Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a cryptographically secure random string for PKCE code verifier.
|
||||
* @param length The length of the string to generate
|
||||
* @returns A random hex string
|
||||
*/
|
||||
function generateRandomString(length: number): string {
|
||||
const array = new Uint8Array(length);
|
||||
crypto.getRandomValues(array);
|
||||
return Array.from(array)
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
.substring(0, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a PKCE code challenge from a code verifier using SHA-256.
|
||||
* @param codeVerifier The code verifier string
|
||||
* @returns A base64url-encoded SHA-256 hash of the code verifier
|
||||
*/
|
||||
async function generateCodeChallenge(codeVerifier: string): Promise<string> {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(codeVerifier);
|
||||
const digest = await crypto.subtle.digest('SHA-256', data);
|
||||
|
||||
// Base64url encode the digest
|
||||
const base64String = btoa(String.fromCharCode(...new Uint8Array(digest)));
|
||||
return base64String
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=+$/, '');
|
||||
}
|
||||
|
||||
async function exchangeCodeForToken(
|
||||
logger: Log,
|
||||
endpointUri: Uri,
|
||||
redirectUri: Uri,
|
||||
code: string,
|
||||
codeVerifier: string,
|
||||
enterpriseUri?: Uri
|
||||
): Promise<string> {
|
||||
logger.info('Exchanging code for token...');
|
||||
@@ -130,7 +164,8 @@ async function exchangeCodeForToken(
|
||||
['code', code],
|
||||
['client_id', Config.gitHubClientId],
|
||||
['redirect_uri', redirectUri.toString(true)],
|
||||
['client_secret', clientSecret]
|
||||
['client_secret', clientSecret],
|
||||
['code_verifier', codeVerifier]
|
||||
]);
|
||||
if (enterpriseUri) {
|
||||
body.append('github_enterprise', enterpriseUri.toString(true));
|
||||
@@ -199,13 +234,19 @@ class UrlHandlerFlow implements IFlow {
|
||||
}),
|
||||
cancellable: true
|
||||
}, async (_, token) => {
|
||||
// Generate PKCE parameters
|
||||
const codeVerifier = generateRandomString(64);
|
||||
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
||||
|
||||
const promise = uriHandler.waitForCode(logger, scopes, nonce, token);
|
||||
|
||||
const searchParams = new URLSearchParams([
|
||||
['client_id', Config.gitHubClientId],
|
||||
['redirect_uri', redirectUri.toString(true)],
|
||||
['scope', scopes],
|
||||
['state', encodeURIComponent(callbackUri.toString(true))]
|
||||
['state', encodeURIComponent(callbackUri.toString(true))],
|
||||
['code_challenge', codeChallenge],
|
||||
['code_challenge_method', 'S256']
|
||||
]);
|
||||
if (existingLogin) {
|
||||
searchParams.append('login', existingLogin);
|
||||
@@ -236,7 +277,7 @@ class UrlHandlerFlow implements IFlow {
|
||||
? Uri.parse(`${proxyEndpoints.github}login/oauth/access_token`)
|
||||
: baseUri.with({ path: '/login/oauth/access_token' });
|
||||
|
||||
const accessToken = await exchangeCodeForToken(logger, endpointUrl, redirectUri, code, enterpriseUri);
|
||||
const accessToken = await exchangeCodeForToken(logger, endpointUrl, redirectUri, code, codeVerifier, enterpriseUri);
|
||||
return accessToken;
|
||||
});
|
||||
}
|
||||
@@ -283,10 +324,16 @@ class LocalServerFlow implements IFlow {
|
||||
}),
|
||||
cancellable: true
|
||||
}, async (_, token) => {
|
||||
// Generate PKCE parameters
|
||||
const codeVerifier = generateRandomString(64);
|
||||
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
||||
|
||||
const searchParams = new URLSearchParams([
|
||||
['client_id', Config.gitHubClientId],
|
||||
['redirect_uri', redirectUri.toString(true)],
|
||||
['scope', scopes],
|
||||
['code_challenge', codeChallenge],
|
||||
['code_challenge_method', 'S256']
|
||||
]);
|
||||
if (existingLogin) {
|
||||
searchParams.append('login', existingLogin);
|
||||
@@ -329,6 +376,7 @@ class LocalServerFlow implements IFlow {
|
||||
baseUri.with({ path: '/login/oauth/access_token' }),
|
||||
redirectUri,
|
||||
codeToExchange,
|
||||
codeVerifier,
|
||||
enterpriseUri);
|
||||
return accessToken;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user