@@ -3,7 +3,6 @@ import * as express from "express"
3
3
import * as expressCore from "express-serve-static-core"
4
4
import * as http from "http"
5
5
import * as net from "net"
6
- import path from "path"
7
6
import * as qs from "qs"
8
7
import { Disposable } from "../common/emitter"
9
8
import { CookieKeys , HttpCode , HttpError } from "../common/http"
@@ -18,7 +17,9 @@ import { getPasswordMethod, IsCookieValidArgs, isCookieValid, sanitizeString, es
18
17
*/
19
18
export interface ClientConfiguration {
20
19
codeServerVersion : string
20
+ /** Relative path from this page to the root. No trailing slash. */
21
21
base : string
22
+ /** Relative path from this page to the static root. No trailing slash. */
22
23
csStaticBase : string
23
24
}
24
25
@@ -33,11 +34,11 @@ declare global {
33
34
}
34
35
35
36
export const createClientConfiguration = ( req : express . Request ) : ClientConfiguration => {
36
- const base = relativeRoot ( req )
37
+ const base = relativeRoot ( req . originalUrl )
37
38
38
39
return {
39
40
base,
40
- csStaticBase : normalize ( path . posix . join ( base , "_static/" ) ) ,
41
+ csStaticBase : base + "/_static" ,
41
42
codeServerVersion,
42
43
}
43
44
}
@@ -108,15 +109,28 @@ export const authenticated = async (req: express.Request): Promise<boolean> => {
108
109
109
110
/**
110
111
* Get the relative path that will get us to the root of the page. For each
111
- * slash we need to go up a directory. For example:
112
+ * slash we need to go up a directory. Will not have a trailing slash.
113
+ *
114
+ * For example:
115
+ *
112
116
* / => .
113
117
* /foo => .
114
118
* /foo/ => ./..
115
119
* /foo/bar => ./..
116
120
* /foo/bar/ => ./../..
121
+ *
122
+ * All paths must be relative in order to work behind a reverse proxy since we
123
+ * we do not know the base path. Anything that needs to be absolute (for
124
+ * example cookies) must get the base path from the frontend.
125
+ *
126
+ * All relative paths must be prefixed with the relative root to ensure they
127
+ * work no matter the depth at which they happen to appear.
128
+ *
129
+ * For Express `req.originalUrl` should be used as they remove the base from the
130
+ * standard `url` property making it impossible to get the true depth.
117
131
*/
118
- export const relativeRoot = ( req : express . Request ) : string => {
119
- const depth = ( req . originalUrl . split ( "?" , 1 ) [ 0 ] . match ( / \/ / g) || [ ] ) . length
132
+ export const relativeRoot = ( originalUrl : string ) : string => {
133
+ const depth = ( originalUrl . split ( "?" , 1 ) [ 0 ] . match ( / \/ / g) || [ ] ) . length
120
134
return normalize ( "./" + ( depth > 1 ? "../" . repeat ( depth - 1 ) : "" ) )
121
135
}
122
136
@@ -138,7 +152,7 @@ export const redirect = (
138
152
}
139
153
} )
140
154
141
- const relativePath = normalize ( `${ relativeRoot ( req ) } /${ to } ` , true )
155
+ const relativePath = normalize ( `${ relativeRoot ( req . originalUrl ) } /${ to } ` , true )
142
156
const queryString = qs . stringify ( query )
143
157
const redirectPath = `${ relativePath } ${ queryString ? `?${ queryString } ` : "" } `
144
158
logger . debug ( `redirecting from ${ req . originalUrl } to ${ redirectPath } ` )
@@ -241,3 +255,32 @@ export function disposer(server: http.Server): Disposable["dispose"] {
241
255
} )
242
256
}
243
257
}
258
+
259
+ /**
260
+ * Get the options for setting a cookie. The options must be identical for
261
+ * setting and unsetting cookies otherwise they are considered separate.
262
+ */
263
+ export const getCookieOptions = ( req : express . Request ) : express . CookieOptions => {
264
+ // Normally we set paths relatively. However browsers do not appear to allow
265
+ // cookies to be set relatively which means we need an absolute path. We
266
+ // cannot be guaranteed we know the path since a reverse proxy might have
267
+ // rewritten it. That means we need to get the path from the frontend.
268
+
269
+ // The reason we need to set the path (as opposed to defaulting to /) is to
270
+ // avoid code-server instances on different sub-paths clobbering each other or
271
+ // from accessing each other's tokens (and to prevent other services from
272
+ // accessing code-server's tokens).
273
+
274
+ // When logging in or out the request must include the href (the full current
275
+ // URL of that page) and the relative path to the root as given to it by the
276
+ // backend. Using these two we can determine the true absolute root.
277
+ const url = new URL (
278
+ req . query . base || req . body . base || "/" ,
279
+ req . query . href || req . body . href || "http://" + ( req . headers . host || "localhost" ) ,
280
+ )
281
+ return {
282
+ domain : getCookieDomain ( url . host , req . args [ "proxy-domain" ] ) ,
283
+ path : normalize ( url . pathname ) || "/" ,
284
+ sameSite : "lax" ,
285
+ }
286
+ }
0 commit comments