-
Notifications
You must be signed in to change notification settings - Fork 585
/
Copy pathproxy.js
160 lines (135 loc) · 4.94 KB
/
proxy.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
const { createProxyMiddleware } = require("http-proxy-middleware");
const { StatusCodes } = require("http-status-codes");
const url = require("url");
const expressUtils = require("./express.js");
const tokenUtils = require("./token.js");
const torUtils = require("./tor.js");
const CONSTANTS = require("./const.js");
const safeHandler = require("./safe_handler.js");
function onProxyReq(proxyReq, req, res, config) {
// "Value may be undefined if the socket is destroyed (for example, if the client disconnected)."
// More details here: https://nodejs.org/api/net.html#socketremoteaddress
if (req.socket.remoteAddress === undefined) {
return res.end();
}
// If we don't trust the upstream, we'll set the x-forwarded headers
// Upstream could be a proxy and therefore trusted
// So we'll accept the incoming x-forwarded headers
if (!CONSTANTS.PROXY_TRUST_UPSTREAM) {
proxyReq.setHeader("x-forwarded-proto", req.protocol);
proxyReq.setHeader("x-forwarded-host", req.headers.host);
proxyReq.setHeader("x-forwarded-for", req.socket.remoteAddress);
}
// Remove umbrel session cookie from proxied request
const cookies = expressUtils.removeCookie(req, CONSTANTS.UMBREL_COOKIE_NAME);
if (cookies.trim().length === 0) {
// "the user agent sends a Cookie request header to the origin server if it has cookies"
// More info: https://datatracker.ietf.org/doc/html/rfc2109#section-4.3.4
proxyReq.removeHeader("cookie");
} else {
proxyReq.setHeader("cookie", cookies);
}
}
function onError(err, req, res, target) {
// ENOTFOUND = The proxy could not reach the target (check APP_HOST and APP_PORT)
// ETIMEDOUT = The proxy could reach the target, but the target was too slow to respond (potentially PROXY_TIMEOUT is too low)
console.error(`Proxy error: ${err.message}`);
if (typeof res.status === "function") {
res.status(StatusCodes.BAD_GATEWAY).render("pages/error", {
app: CONSTANTS.APP,
err,
});
}
}
function proxy() {
const proxyTarget = `${CONSTANTS.APP_PROTOCOL}://${CONSTANTS.APP_HOST}:${CONSTANTS.APP_PORT}`;
const proxyConfig = {
onProxyReq: onProxyReq,
onError: onError,
target: proxyTarget,
// Don't change the origin
// Pass through the origin ('host' header) from the browser
changeOrigin: false,
// Add websocket support, but this option assumes that
// an initial http request is made before the websocket connection
ws: true,
// If this is true, this will chain the x-forwarded header values
// Many applications don't handle multiple header values (e.g. BTC Pay Server)
xfwd: false,
logLevel: CONSTANTS.LOG_LEVEL,
proxyTimeout: CONSTANTS.PROXY_TIMEOUT,
// The proxy shouldn't follow redirect
// The browser should, therefore this must be off
followRedirects: false,
};
return createProxyMiddleware(proxyConfig);
}
function whitelist() {
return function (req, res, next) {
req.ignoreAuth = true;
next();
};
}
function blacklist() {
return function (req, res, next) {
req.ignoreAuth = false;
next();
};
}
function apply(app) {
if (CONSTANTS.PROXY_AUTH_ADD) {
if (CONSTANTS.PROXY_AUTH_WHITELIST.length > 0)
app.use(CONSTANTS.PROXY_AUTH_WHITELIST, whitelist());
if (CONSTANTS.PROXY_AUTH_BLACKLIST.length > 0)
app.use(CONSTANTS.PROXY_AUTH_BLACKLIST, blacklist());
}
const middleware = proxy();
app.use(
safeHandler(async (req, res, next) => {
// If route is part of the auth whitelist
// Then we ignore handling auth
if (CONSTANTS.PROXY_AUTH_ADD && req.ignoreAuth !== true) {
const token = req.cookies.UMBREL_PROXY_TOKEN;
// token could be false if hmac fails (ie. someone tampered with the token)
if (typeof token !== "string" || !(await tokenUtils.validate(token))) {
const origin = req.hostname.endsWith(".onion") ? "tor" : "host";
// Get the raw query string
// This could be null if there is no query string
let query = url.parse(req.url).query;
if (typeof query == "string") {
query = `?${query}`;
} else {
query = "";
}
const searchParams = new URLSearchParams({
origin: origin,
app: CONSTANTS.APP.id,
path: `${req.path}${query}`,
});
// If request came over Tor
// Then redirect to auth HS hosted on Tor
if (origin === "tor") {
const authHsUrl = await torUtils.authHsUrl();
return res.redirect(
`${req.protocol}://${authHsUrl}/?${searchParams.toString()}`
);
} else {
return res.redirect(
`${req.protocol}://${req.hostname}:${
CONSTANTS.UMBREL_AUTH_PORT
}/?${searchParams.toString()}`
);
}
}
}
middleware(req, res, next);
})
);
return middleware;
}
module.exports = {
proxy,
whitelist,
blacklist,
apply,
};