Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1import express from 'express';
2
3import makeApiApp from './api.js';
4import { type Dependencies } from './iocContainer/index.js';
5
6export default async function makeWWWServer(deps: Dependencies) {
7 const { app: apiApp, shutdown } = await makeApiApp(deps);
8 const app = express();
9
10 // Traffic is routed to our app via: NLB -> ALB -> Express. The
11 // NLB terminates TLS, so the NLB -> ALB request is over HTTP. As a
12 // result, when the ALB hits express, it sets the `x-forwarded-proto` header
13 // to `http`. This causes express to think the request is insecure, which
14 // cause express-session to refuse to send a session cookie (since we mark our
15 // session cookies with the `Secure` flag, which is designed to prevent both
16 // server and client from sending the cookie in plaintext over HTTP).
17 // express-session intentionally does not have a way to force it to skip the
18 // secure check and unconditionally send the session cookie (see
19 // https://github.com/expressjs/session/issues/785), so we have two options:
20 //
21 // 1. Override `req.secure` to always return true, so express-session thinks
22 // the request is secure.
23 //
24 // 2. Actually have the request hit the ALB over HTTPS, which involves either
25 // having the NLB re-encrypt the traffic on the way to the ALB, or having
26 // the NLB re-encrypt it and pass that through.
27 //
28 // Option 2 is more complex, so we go with option 1 for now. However, the risk
29 // with option 1 is that different code/middleware will check different things
30 // to determine whether the request is secure, and those checks disagreeing
31 // could lead to subtle bugs. So, we limit that risk by patching as many
32 // things as possible (i.e, also `req.protocol` and `x-forwarded-proto`); we
33 // don't deal with the `Forwarded` header since that's more complicated and
34 // less widely supported, so less likely to be checked by middleware.
35 //
36 // Note: forcing the ALB to set `x-forwarded-proto` to `https` is not an
37 // option, as AWS doesn't support that.
38 app.set('trust proxy', true);
39 app.use((req, _res, next) => {
40 // Define our overrides as getter properties to be consistent with their
41 // definition in express, just in case.
42 Object.defineProperty(req, 'secure', { get: () => true });
43 Object.defineProperty(req, 'protocol', { get: () => 'https' });
44 // NB: we overwrite the x-forwarded-proto, rather than append to it with a
45 // comma, because x-forwarded-proto is a non-standardized header with no spec,
46 // and it's not clear if appending is actually kosher/universally supported.
47 req.headers['x-forwarded-proto'] = 'https';
48 next();
49 });
50
51 app.use('/api/v1', apiApp);
52
53 return { app, shutdown };
54}