Files
vscode/src/vs/server/node/serverConnectionToken.ts
2023-05-29 15:24:14 -07:00

133 lines
4.8 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as cookie from 'cookie';
import * as fs from 'fs';
import * as http from 'http';
import * as url from 'url';
import * as path from 'vs/base/common/path';
import { generateUuid } from 'vs/base/common/uuid';
import { connectionTokenCookieName, connectionTokenQueryName } from 'vs/base/common/network';
import { ServerParsedArgs } from 'vs/server/node/serverEnvironmentService';
import { Promises } from 'vs/base/node/pfs';
const connectionTokenRegex = /^[0-9A-Za-z_-]+$/;
export const enum ServerConnectionTokenType {
None,
Optional,// TODO: Remove this soon
Mandatory
}
export class NoneServerConnectionToken {
public readonly type = ServerConnectionTokenType.None;
public validate(connectionToken: any): boolean {
return true;
}
}
export class MandatoryServerConnectionToken {
public readonly type = ServerConnectionTokenType.Mandatory;
constructor(public readonly value: string) {
}
public validate(connectionToken: any): boolean {
return (connectionToken === this.value);
}
}
export type ServerConnectionToken = NoneServerConnectionToken | MandatoryServerConnectionToken;
export class ServerConnectionTokenParseError {
constructor(
public readonly message: string
) { }
}
export async function parseServerConnectionToken(args: ServerParsedArgs, defaultValue: () => Promise<string>): Promise<ServerConnectionToken | ServerConnectionTokenParseError> {
const withoutConnectionToken = args['without-connection-token'];
const connectionToken = args['connection-token'];
const connectionTokenFile = args['connection-token-file'];
if (withoutConnectionToken) {
if (typeof connectionToken !== 'undefined' || typeof connectionTokenFile !== 'undefined') {
return new ServerConnectionTokenParseError(`Please do not use the argument '--connection-token' or '--connection-token-file' at the same time as '--without-connection-token'.`);
}
return new NoneServerConnectionToken();
}
if (typeof connectionTokenFile !== 'undefined') {
if (typeof connectionToken !== 'undefined') {
return new ServerConnectionTokenParseError(`Please do not use the argument '--connection-token' at the same time as '--connection-token-file'.`);
}
let rawConnectionToken: string;
try {
rawConnectionToken = fs.readFileSync(connectionTokenFile).toString().replace(/\r?\n$/, '');
} catch (e) {
return new ServerConnectionTokenParseError(`Unable to read the connection token file at '${connectionTokenFile}'.`);
}
if (!connectionTokenRegex.test(rawConnectionToken)) {
return new ServerConnectionTokenParseError(`The connection token defined in '${connectionTokenFile} does not adhere to the characters 0-9, a-z, A-Z, _, or -.`);
}
return new MandatoryServerConnectionToken(rawConnectionToken);
}
if (typeof connectionToken !== 'undefined') {
if (!connectionTokenRegex.test(connectionToken)) {
return new ServerConnectionTokenParseError(`The connection token '${connectionToken} does not adhere to the characters 0-9, a-z, A-Z or -.`);
}
return new MandatoryServerConnectionToken(connectionToken);
}
return new MandatoryServerConnectionToken(await defaultValue());
}
export async function determineServerConnectionToken(args: ServerParsedArgs): Promise<ServerConnectionToken | ServerConnectionTokenParseError> {
const readOrGenerateConnectionToken = async () => {
if (!args['user-data-dir']) {
// No place to store it!
return generateUuid();
}
const storageLocation = path.join(args['user-data-dir'], 'token');
// First try to find a connection token
try {
const fileContents = await Promises.readFile(storageLocation);
const connectionToken = fileContents.toString().replace(/\r?\n$/, '');
if (connectionTokenRegex.test(connectionToken)) {
return connectionToken;
}
} catch (err) { }
// No connection token found, generate one
const connectionToken = generateUuid();
try {
// Try to store it
await Promises.writeFile(storageLocation, connectionToken, { mode: 0o600 });
} catch (err) { }
return connectionToken;
};
return parseServerConnectionToken(args, readOrGenerateConnectionToken);
}
export function requestHasValidConnectionToken(connectionToken: ServerConnectionToken, req: http.IncomingMessage, parsedUrl: url.UrlWithParsedQuery) {
// First check if there is a valid query parameter
if (connectionToken.validate(parsedUrl.query[connectionTokenQueryName])) {
return true;
}
// Otherwise, check if there is a valid cookie
const cookies = cookie.parse(req.headers.cookie || '');
return connectionToken.validate(cookies[connectionTokenCookieName]);
}