1- import * as http from 'http' ;
21import * as crypto from 'crypto' ;
3- import * as argon2 from 'argon2 ' ;
2+ import * as express from 'express ' ;
43import { ServerParsedArgs } from 'vs/server/node/args' ;
5- import { serveError } from 'vs/server/node/http' ;
64
75/** Ensures that the input is sanitized by checking
86 * - it's a string
@@ -15,92 +13,39 @@ export function sanitizeString(str: string): string {
1513 return typeof str === 'string' && str . trim ( ) . length > 0 ? str . trim ( ) : '' ;
1614}
1715
18- export const ensureAuthenticated = async ( args : ServerParsedArgs , req : http . IncomingMessage , res : http . ServerResponse ) : Promise < boolean > => {
19- const isAuthenticated = await authenticated ( args , req ) ;
20- if ( ! isAuthenticated ) {
21- serveError ( req , res , 401 , 'Unauthorized' ) ;
22- }
23- return isAuthenticated ;
24- } ;
25-
2616/**
2717 * Return true if authenticated via cookies.
2818 */
29- export const authenticated = async ( args : ServerParsedArgs , req : http . IncomingMessage ) : Promise < boolean > => {
19+ export const authenticated = async ( args : ServerParsedArgs , req : express . Request ) : Promise < boolean > => {
3020 if ( ! args . password && ! args . hashedPassword ) {
3121 return true ;
3222 }
33- const passwordMethod = getPasswordMethod ( args . hashedPassword ) ;
34- const cookies = parseCookies ( req ) ;
3523 const isCookieValidArgs : IsCookieValidArgs = {
36- passwordMethod,
37- cookieKey : sanitizeString ( cookies . key ) ,
38- passwordFromArgs : args . password || '' ,
39- hashedPasswordFromArgs : args . hashedPassword ,
24+ cookieKey : sanitizeString ( req . cookies . key ) ,
25+ passwordFromArgs : args . password || ''
4026 } ;
4127
4228 return await isCookieValid ( isCookieValidArgs ) ;
4329} ;
4430
45- function parseCookies ( request : http . IncomingMessage ) : Record < string , string > {
46- const cookies : Record < string , string > = { } ,
47- rc = request . headers . cookie ;
48-
49- // eslint-disable-next-line code-no-unused-expressions
50- rc && rc . split ( ';' ) . forEach ( cookie => {
51- let parts = cookie . split ( '=' ) ;
52- if ( parts . length > 0 ) {
53- const name = parts . shift ( ) ! . trim ( ) ;
54- let value = decodeURI ( parts . join ( '=' ) ) ;
55- value = value . substring ( 1 , value . length - 1 ) ;
56- cookies [ name ] = value ;
57- }
58- } ) ;
59-
60- return cookies ;
61- }
62-
63- export type PasswordMethod = 'ARGON2' | 'PLAIN_TEXT' ;
64-
65- /**
66- * Used to determine the password method.
67- *
68- * There are three options for the return value:
69- * 1. "SHA256" -> the legacy hashing algorithm
70- * 2. "ARGON2" -> the newest hashing algorithm
71- * 3. "PLAIN_TEXT" -> regular ol' password with no hashing
72- *
73- * @returns "ARGON2" | "PLAIN_TEXT"
74- */
75- export function getPasswordMethod ( hashedPassword : string | undefined ) : PasswordMethod {
76- if ( ! hashedPassword ) {
77- return 'PLAIN_TEXT' ;
78- }
79- return 'ARGON2' ;
80- }
81-
8231type PasswordValidation = {
8332 isPasswordValid : boolean
8433 hashedPassword : string
8534} ;
8635
8736type HandlePasswordValidationArgs = {
88- /** The PasswordMethod */
89- passwordMethod : PasswordMethod
9037 /** The password provided by the user */
91- passwordFromRequestBody : string
38+ passwordFromRequestBody : string | undefined
9239 /** The password set in PASSWORD or config */
9340 passwordFromArgs : string | undefined
94- /** The hashed-password set in HASHED_PASSWORD or config */
95- hashedPasswordFromArgs : string | undefined
9641} ;
9742
9843function safeCompare ( a : string , b : string ) : boolean {
9944 if ( b . length > a . length ) {
100- a = a . padEnd ( b . length ) ;
45+ a = a . padEnd ( b . length , '0' ) ;
10146 }
10247 if ( a . length > b . length ) {
103- b = b . padEnd ( a . length ) ;
48+ b = b . padEnd ( a . length , '0' ) ;
10449 }
10550 return crypto . timingSafeEqual ( Buffer . from ( a ) , Buffer . from ( b ) ) ;
10651}
@@ -113,114 +58,44 @@ export const generatePassword = async (length = 24): Promise<string> => {
11358 return buffer . toString ( 'hex' ) . substring ( 0 , length ) ;
11459} ;
11560
116- /**
117- * Used to hash the password.
118- */
119- export const hash = async ( password : string ) : Promise < string > => {
120- try {
121- return await argon2 . hash ( password ) ;
122- } catch ( error ) {
123- console . error ( error ) ;
124- return '' ;
125- }
126- } ;
127-
128- /**
129- * Used to verify if the password matches the hash
130- */
131- export const isHashMatch = async ( password : string , hash : string ) => {
132- if ( password === '' || hash === '' || ! hash . startsWith ( '$' ) ) {
133- return false ;
134- }
135- try {
136- return await argon2 . verify ( hash , password ) ;
137- } catch ( error ) {
138- throw new Error ( error ) ;
139- }
140- } ;
141-
142- /**
143- * Used to hash the password using the sha256
144- * algorithm. We only use this to for checking
145- * the hashed-password set in the config.
146- *
147- * Kept for legacy reasons.
148- */
149- export const hashLegacy = ( str : string ) : string => {
61+ export const hash = ( str : string ) : string => {
15062 return crypto . createHash ( 'sha256' ) . update ( str ) . digest ( 'hex' ) ;
15163} ;
15264
153- /**
154- * Used to check if the password matches the hash using
155- * the hashLegacy function
156- */
157- export const isHashLegacyMatch = ( password : string , hashPassword : string ) => {
158- const hashedWithLegacy = hashLegacy ( password ) ;
65+ export const isHashMatch = ( password : string , hashPassword : string ) => {
66+ const hashedWithLegacy = hash ( password ) ;
15967 return safeCompare ( hashedWithLegacy , hashPassword ) ;
16068} ;
16169
162- /**
163- * Checks if a password is valid and also returns the hash
164- * using the PasswordMethod
165- */
16670export async function handlePasswordValidation ( {
167- passwordMethod,
16871 passwordFromArgs,
169- passwordFromRequestBody,
170- hashedPasswordFromArgs,
72+ passwordFromRequestBody
17173} : HandlePasswordValidationArgs ) : Promise < PasswordValidation > {
17274 const passwordValidation : PasswordValidation = {
17375 isPasswordValid : false ,
17476 hashedPassword : '' ,
17577 } ;
17678
177- switch ( passwordMethod ) {
178- case 'PLAIN_TEXT' : {
179- const isValid = passwordFromArgs ? safeCompare ( passwordFromRequestBody , passwordFromArgs ) : false ;
180- passwordValidation . isPasswordValid = isValid ;
79+ if ( passwordFromRequestBody ) {
80+ const isValid = passwordFromArgs ? safeCompare ( passwordFromRequestBody , passwordFromArgs ) : false ;
81+ passwordValidation . isPasswordValid = isValid ;
18182
182- const hashedPassword = await hash ( passwordFromRequestBody ) ;
183- passwordValidation . hashedPassword = hashedPassword ;
184- break ;
185- }
186- case 'ARGON2' : {
187- const isValid = await isHashMatch ( passwordFromRequestBody , hashedPasswordFromArgs || '' ) ;
188- passwordValidation . isPasswordValid = isValid ;
189-
190- passwordValidation . hashedPassword = hashedPasswordFromArgs || '' ;
191- break ;
192- }
193- default :
194- break ;
83+ const hashedPassword = hash ( passwordFromRequestBody ) ;
84+ passwordValidation . hashedPassword = hashedPassword ;
19585 }
19686
19787 return passwordValidation ;
19888}
19989
20090export type IsCookieValidArgs = {
201- passwordMethod : PasswordMethod
20291 cookieKey : string
203- hashedPasswordFromArgs : string | undefined
20492 passwordFromArgs : string | undefined
20593} ;
20694
20795/** Checks if a req.cookies.key is valid using the PasswordMethod */
20896export async function isCookieValid ( {
20997 passwordFromArgs = '' ,
210- cookieKey,
211- hashedPasswordFromArgs = '' ,
212- passwordMethod,
98+ cookieKey
21399} : IsCookieValidArgs ) : Promise < boolean > {
214- let isValid = false ;
215- switch ( passwordMethod ) {
216- case 'PLAIN_TEXT' :
217- isValid = await isHashMatch ( passwordFromArgs , cookieKey ) ;
218- break ;
219- case 'ARGON2' :
220- isValid = safeCompare ( cookieKey , hashedPasswordFromArgs ) ;
221- break ;
222- default :
223- break ;
224- }
225- return isValid ;
100+ return isHashMatch ( passwordFromArgs , cookieKey ) ;
226101}
0 commit comments