@@ -323,61 +323,73 @@ function getFirstHeader(req: http.IncomingMessage, headerName: string): string |
323
323
}
324
324
325
325
/**
326
- * Throw an error if origin checks fail. Call `next` if provided.
326
+ * Throw a forbidden error if origin checks fail. Call `next` if provided.
327
327
*/
328
328
export function ensureOrigin ( req : express . Request , _ ?: express . Response , next ?: express . NextFunction ) : void {
329
- if ( ! authenticateOrigin ( req ) ) {
329
+ try {
330
+ authenticateOrigin ( req )
331
+ if ( next ) {
332
+ next ( )
333
+ }
334
+ } catch ( error ) {
335
+ logger . debug ( `${ error instanceof Error ? error . message : error } ; blocking request to ${ req . originalUrl } ` )
330
336
throw new HttpError ( "Forbidden" , HttpCode . Forbidden )
331
337
}
332
- if ( next ) {
333
- next ( )
334
- }
335
338
}
336
339
337
340
/**
338
- * Authenticate the request origin against the host.
341
+ * Authenticate the request origin against the host. Throw if invalid.
339
342
*/
340
- export function authenticateOrigin ( req : express . Request ) : boolean {
343
+ export function authenticateOrigin ( req : express . Request ) : void {
341
344
// A missing origin probably means the source is non-browser. Not sure we
342
345
// have a use case for this but let it through.
343
346
const originRaw = getFirstHeader ( req , "origin" )
344
347
if ( ! originRaw ) {
345
- return true
348
+ return
346
349
}
347
350
348
351
let origin : string
349
352
try {
350
353
origin = new URL ( originRaw ) . host . trim ( ) . toLowerCase ( )
351
354
} catch ( error ) {
352
- return false // Malformed URL.
355
+ throw new Error ( `unable to parse malformed origin "${ originRaw } "` )
356
+ }
357
+
358
+ // A missing host likely means the reverse proxy has not been configured to
359
+ // forward the host which means we cannot perform the check. Emit a warning
360
+ // so an admin can fix the issue.
361
+ const host = getHost ( req )
362
+ if ( typeof host === "undefined" ) {
363
+ throw new Error ( "no host headers found" )
353
364
}
354
365
366
+ if ( host !== origin ) {
367
+ throw new Error ( `host "${ host } " does not match origin "${ origin } "` )
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Get the host from headers. It will be trimmed and lowercased.
373
+ */
374
+ function getHost ( req : express . Request ) : string | undefined {
355
375
// Honor Forwarded if present.
356
376
const forwardedRaw = getFirstHeader ( req , "forwarded" )
357
377
if ( forwardedRaw ) {
358
378
const parts = forwardedRaw . split ( / [ ; , ] / )
359
379
for ( let i = 0 ; i < parts . length ; ++ i ) {
360
380
const [ key , value ] = splitOnFirstEquals ( parts [ i ] )
361
381
if ( key . trim ( ) . toLowerCase ( ) === "host" && value ) {
362
- return origin === value . trim ( ) . toLowerCase ( )
382
+ return value . trim ( ) . toLowerCase ( )
363
383
}
364
384
}
365
385
}
366
386
367
387
// Honor X-Forwarded-Host if present.
368
388
const xHost = getFirstHeader ( req , "x-forwarded-host" )
369
389
if ( xHost ) {
370
- return origin === xHost . trim ( ) . toLowerCase ( )
390
+ return xHost . trim ( ) . toLowerCase ( )
371
391
}
372
392
373
- // A missing host likely means the reverse proxy has not been configured to
374
- // forward the host which means we cannot perform the check. Emit a warning
375
- // so an admin can fix the issue.
376
393
const host = getFirstHeader ( req , "host" )
377
- if ( ! host ) {
378
- logger . warn ( `no host headers found; blocking request to ${ req . originalUrl } ` )
379
- return false
380
- }
381
-
382
- return origin === host . trim ( ) . toLowerCase ( )
394
+ return host ? host . trim ( ) . toLowerCase ( ) : undefined
383
395
}
0 commit comments