@@ -18,6 +18,7 @@ import * as os from "os";
18
18
import * as path from "path" ;
19
19
import * as pem from "pem" ;
20
20
import * as util from "util" ;
21
+ import * as url from "url" ;
21
22
import * as ws from "ws" ;
22
23
import { buildDir } from "./constants" ;
23
24
import { createPortScanner } from "./portScanner" ;
@@ -69,13 +70,18 @@ export const createApp = async (options: CreateAppOptions): Promise<{
69
70
70
71
// Try/catch placed here just in case
71
72
const cookies = parseCookies ( req ) ;
73
+ logger . trace ( `Attempting to log in with password ${ cookies . password } ` ) ;
72
74
if ( cookies . password && safeCompare ( cookies . password , options . password ) ) {
75
+ logger . trace ( "Succeeded login attempt" ) ;
76
+
73
77
return true ;
74
78
}
75
79
} catch ( ex ) {
76
80
logger . error ( "Failed to parse cookies" , field ( "error" , ex ) ) ;
77
81
}
78
82
83
+ logger . trace ( "Failed login attempt" ) ;
84
+
79
85
return false ;
80
86
} ;
81
87
@@ -140,13 +146,13 @@ export const createApp = async (options: CreateAppOptions): Promise<{
140
146
} ;
141
147
142
148
const portScanner = createPortScanner ( ) ;
143
- wss . on ( "connection" , ( ws , req ) => {
149
+ wss . on ( "connection" , async ( ws , req ) => {
144
150
if ( req . url && req . url . startsWith ( "/tunnel" ) ) {
145
151
try {
146
152
const rawPort = req . url . split ( "/" ) . pop ( ) ;
147
153
const port = Number . parseInt ( rawPort ! , 10 ) ;
148
154
149
- handleTunnel ( ws , port ) ;
155
+ await handleTunnel ( ws , port ) ;
150
156
} catch ( ex ) {
151
157
ws . close ( TunnelCloseCode . Error , ex . toString ( ) ) ;
152
158
}
@@ -189,31 +195,70 @@ export const createApp = async (options: CreateAppOptions): Promise<{
189
195
new Server ( connection , options . serverOptions ) ;
190
196
} ) ;
191
197
198
+ const redirect = (
199
+ req : express . Request , res : express . Response ,
200
+ to : string = "" , from : string = "" ,
201
+ code : number = 302 , protocol : string = req . protocol ,
202
+ ) : void => {
203
+ const currentUrl = `${ protocol } ://${ req . headers . host } ${ req . originalUrl } ` ;
204
+ const newUrl = url . parse ( currentUrl ) ;
205
+ if ( from && newUrl . pathname ) {
206
+ newUrl . pathname = newUrl . pathname . replace ( new RegExp ( `\/${ from } \/?$` ) , "/" ) ;
207
+ }
208
+ if ( to ) {
209
+ newUrl . pathname = ( newUrl . pathname || "" ) . replace ( / \/ $ / , "" ) + `/${ to } ` ;
210
+ }
211
+ newUrl . path = undefined ; // Path is not necessary and I'm not sure if it causes issues if it doesn't match pathname.
212
+ const newUrlString = url . format ( newUrl ) ;
213
+ logger . trace ( `Redirecting from ${ currentUrl } to ${ newUrlString } ` ) ;
214
+
215
+ return res . redirect ( code , newUrlString ) ;
216
+ } ;
217
+
192
218
const baseDir = buildDir || path . join ( __dirname , ".." ) ;
193
- const authStaticFunc = expressStaticGzip ( path . join ( baseDir , "build/web/auth " ) ) ;
194
- const unauthStaticFunc = expressStaticGzip ( path . join ( baseDir , "build/web/unauth" ) ) ;
219
+ const staticGzip = expressStaticGzip ( path . join ( baseDir , "build/web" ) ) ;
220
+
195
221
app . use ( ( req , res , next ) => {
222
+ logger . trace ( `\u001B[1m${ req . method } ${ res . statusCode } \u001B[0m${ req . originalUrl } ` , field ( "host" , req . hostname ) , field ( "ip" , req . ip ) ) ;
223
+
224
+ // Force HTTPS unless allowing HTTP.
196
225
if ( ! isEncrypted ( req . socket ) && ! options . allowHttp ) {
197
- return res . redirect ( 301 , ` https:// ${ req . headers . host ! } ${ req . path } ` ) ;
226
+ return redirect ( req , res , "" , "" , 301 , " https" ) ;
198
227
}
199
228
200
- if ( isAuthed ( req ) ) {
201
- // We can serve the actual VSCode bin
202
- authStaticFunc ( req , res , next ) ;
203
- } else {
204
- // Serve only the unauthed version
205
- unauthStaticFunc ( req , res , next ) ;
206
- }
229
+ next ( ) ;
207
230
} ) ;
231
+
208
232
// @ts -ignore
209
- app . use ( ( err , req , res , next ) => {
233
+ app . use ( ( err , _req , _res , next ) => {
234
+ logger . error ( err . message ) ;
210
235
next ( ) ;
211
236
} ) ;
212
- app . get ( "/ping" , ( req , res ) => {
237
+
238
+ // If not authenticated, redirect to the login page.
239
+ app . get ( "/" , ( req , res , next ) => {
240
+ if ( ! isAuthed ( req ) ) {
241
+ return redirect ( req , res , "login" ) ;
242
+ }
243
+ next ( ) ;
244
+ } ) ;
245
+
246
+ // If already authenticated, redirect back to the root.
247
+ app . get ( "/login" , ( req , res , next ) => {
248
+ if ( isAuthed ( req ) ) {
249
+ return redirect ( req , res , "" , "login" ) ;
250
+ }
251
+ next ( ) ;
252
+ } ) ;
253
+
254
+ // For getting general server data.
255
+ app . get ( "/ping" , ( _req , res ) => {
213
256
res . json ( {
214
257
hostname : os . hostname ( ) ,
215
258
} ) ;
216
259
} ) ;
260
+
261
+ // For getting a resource on disk.
217
262
app . get ( "/resource/:url(*)" , async ( req , res ) => {
218
263
if ( ! ensureAuthed ( req , res ) ) {
219
264
return ;
@@ -254,6 +299,8 @@ export const createApp = async (options: CreateAppOptions): Promise<{
254
299
res . end ( ) ;
255
300
}
256
301
} ) ;
302
+
303
+ // For writing a resource to disk.
257
304
app . post ( "/resource/:url(*)" , async ( req , res ) => {
258
305
if ( ! ensureAuthed ( req , res ) ) {
259
306
return ;
@@ -282,6 +329,9 @@ export const createApp = async (options: CreateAppOptions): Promise<{
282
329
}
283
330
} ) ;
284
331
332
+ // Everything else just pulls from the static build directory.
333
+ app . use ( staticGzip ) ;
334
+
285
335
return {
286
336
express : app ,
287
337
server,
0 commit comments