mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-01 05:51:32 +01:00
Depend on platform implementations for common features (#165950)
* don't depend on externals * remove unneeded externals
This commit is contained in:
committed by
GitHub
parent
1784a7ed34
commit
b9b93f2b45
198
extensions/github-authentication/src/node/authServer.ts
Normal file
198
extensions/github-authentication/src/node/authServer.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as http from 'http';
|
||||
import { URL } from 'url';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
function sendFile(res: http.ServerResponse, filepath: string) {
|
||||
fs.readFile(filepath, (err, body) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
} else {
|
||||
res.writeHead(200, {
|
||||
'content-length': body.length,
|
||||
});
|
||||
res.end(body);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface IOAuthResult {
|
||||
code: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
interface ILoopbackServer {
|
||||
/**
|
||||
* If undefined, the server is not started yet.
|
||||
*/
|
||||
port: number | undefined;
|
||||
|
||||
/**
|
||||
* The nonce used
|
||||
*/
|
||||
nonce: string;
|
||||
|
||||
/**
|
||||
* The state parameter used in the OAuth flow.
|
||||
*/
|
||||
state: string | undefined;
|
||||
|
||||
/**
|
||||
* Starts the server.
|
||||
* @returns The port to listen on.
|
||||
* @throws If the server fails to start.
|
||||
* @throws If the server is already started.
|
||||
*/
|
||||
start(): Promise<number>;
|
||||
/**
|
||||
* Stops the server.
|
||||
* @throws If the server is not started.
|
||||
* @throws If the server fails to stop.
|
||||
*/
|
||||
stop(): Promise<void>;
|
||||
/**
|
||||
* Returns a promise that resolves to the result of the OAuth flow.
|
||||
*/
|
||||
waitForOAuthResponse(): Promise<IOAuthResult>;
|
||||
}
|
||||
|
||||
export class LoopbackAuthServer implements ILoopbackServer {
|
||||
private readonly _server: http.Server;
|
||||
private readonly _resultPromise: Promise<IOAuthResult>;
|
||||
private _startingRedirect: URL;
|
||||
|
||||
public nonce = randomBytes(16).toString('base64');
|
||||
public port: number | undefined;
|
||||
|
||||
public set state(state: string | undefined) {
|
||||
if (state) {
|
||||
this._startingRedirect.searchParams.set('state', state);
|
||||
} else {
|
||||
this._startingRedirect.searchParams.delete('state');
|
||||
}
|
||||
}
|
||||
public get state(): string | undefined {
|
||||
return this._startingRedirect.searchParams.get('state') ?? undefined;
|
||||
}
|
||||
|
||||
constructor(serveRoot: string, startingRedirect: string) {
|
||||
if (!serveRoot) {
|
||||
throw new Error('serveRoot must be defined');
|
||||
}
|
||||
if (!startingRedirect) {
|
||||
throw new Error('startingRedirect must be defined');
|
||||
}
|
||||
this._startingRedirect = new URL(startingRedirect);
|
||||
let deferred: { resolve: (result: IOAuthResult) => void; reject: (reason: any) => void };
|
||||
this._resultPromise = new Promise<IOAuthResult>((resolve, reject) => deferred = { resolve, reject });
|
||||
|
||||
this._server = http.createServer((req, res) => {
|
||||
const reqUrl = new URL(req.url!, `http://${req.headers.host}`);
|
||||
switch (reqUrl.pathname) {
|
||||
case '/signin': {
|
||||
const receivedNonce = (reqUrl.searchParams.get('nonce') ?? '').replace(/ /g, '+');
|
||||
if (receivedNonce !== this.nonce) {
|
||||
res.writeHead(302, { location: `/?error=${encodeURIComponent('Nonce does not match.')}` });
|
||||
res.end();
|
||||
}
|
||||
res.writeHead(302, { location: this._startingRedirect.toString() });
|
||||
res.end();
|
||||
break;
|
||||
}
|
||||
case '/callback': {
|
||||
const code = reqUrl.searchParams.get('code') ?? undefined;
|
||||
const state = reqUrl.searchParams.get('state') ?? undefined;
|
||||
const nonce = (reqUrl.searchParams.get('nonce') ?? '').replace(/ /g, '+');
|
||||
if (!code || !state || !nonce) {
|
||||
res.writeHead(400);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
if (this.state !== state) {
|
||||
res.writeHead(302, { location: `/?error=${encodeURIComponent('State does not match.')}` });
|
||||
res.end();
|
||||
throw new Error('State does not match.');
|
||||
}
|
||||
if (this.nonce !== nonce) {
|
||||
res.writeHead(302, { location: `/?error=${encodeURIComponent('Nonce does not match.')}` });
|
||||
res.end();
|
||||
throw new Error('Nonce does not match.');
|
||||
}
|
||||
deferred.resolve({ code, state });
|
||||
res.writeHead(302, { location: '/' });
|
||||
res.end();
|
||||
break;
|
||||
}
|
||||
// Serve the static files
|
||||
case '/':
|
||||
sendFile(res, path.join(serveRoot, 'index.html'));
|
||||
break;
|
||||
default:
|
||||
// substring to get rid of leading '/'
|
||||
sendFile(res, path.join(serveRoot, reqUrl.pathname.substring(1)));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public start(): Promise<number> {
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
if (this._server.listening) {
|
||||
throw new Error('Server is already started');
|
||||
}
|
||||
const portTimeout = setTimeout(() => {
|
||||
reject(new Error('Timeout waiting for port'));
|
||||
}, 5000);
|
||||
this._server.on('listening', () => {
|
||||
const address = this._server.address();
|
||||
if (typeof address === 'string') {
|
||||
this.port = parseInt(address);
|
||||
} else if (address instanceof Object) {
|
||||
this.port = address.port;
|
||||
} else {
|
||||
throw new Error('Unable to determine port');
|
||||
}
|
||||
|
||||
clearTimeout(portTimeout);
|
||||
|
||||
// set state which will be used to redirect back to vscode
|
||||
this.state = `http://127.0.0.1:${this.port}/callback?nonce=${encodeURIComponent(this.nonce)}`;
|
||||
|
||||
resolve(this.port);
|
||||
});
|
||||
this._server.on('error', err => {
|
||||
reject(new Error(`Error listening to server: ${err}`));
|
||||
});
|
||||
this._server.on('close', () => {
|
||||
reject(new Error('Closed'));
|
||||
});
|
||||
this._server.listen(0, '127.0.0.1');
|
||||
});
|
||||
}
|
||||
|
||||
public stop(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!this._server.listening) {
|
||||
throw new Error('Server is not started');
|
||||
}
|
||||
this._server.close((err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public waitForOAuthResponse(): Promise<IOAuthResult> {
|
||||
return this._resultPromise;
|
||||
}
|
||||
}
|
||||
8
extensions/github-authentication/src/node/crypto.ts
Normal file
8
extensions/github-authentication/src/node/crypto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { webcrypto } from 'crypto';
|
||||
|
||||
export const crypto = webcrypto as any as Crypto;
|
||||
7
extensions/github-authentication/src/node/fetch.ts
Normal file
7
extensions/github-authentication/src/node/fetch.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
export const fetching = fetch;
|
||||
Reference in New Issue
Block a user