forked from microsoft/vscode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserverConnectionToken.ts
More file actions
155 lines (124 loc) · 5.58 KB
/
serverConnectionToken.ts
File metadata and controls
155 lines (124 loc) · 5.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/*---------------------------------------------------------------------------------------------
* 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 OptionalServerConnectionToken {
public readonly type = ServerConnectionTokenType.Optional;
constructor(public readonly value: string) {
}
public validate(connectionToken: any): boolean {
return (connectionToken === this.value);
}
}
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 | OptionalServerConnectionToken | 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'];
const compatibility = (args['compatibility'] === '1.63');
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 -.`);
}
if (compatibility) {
// TODO: Remove this case soon
return new OptionalServerConnectionToken(connectionToken);
}
return new MandatoryServerConnectionToken(connectionToken);
}
if (compatibility) {
// TODO: Remove this case soon
console.log(`Breaking change in the next release: Please use one of the following arguments: '--connection-token', '--connection-token-file' or '--without-connection-token'.`);
return new OptionalServerConnectionToken(await defaultValue());
}
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]);
}