parse-dashboard 9.3.0-alpha.4 → 9.3.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -87,13 +87,52 @@ module.exports = function(config, options) {
87
87
  cookieSessionStore: options.cookieSessionStore
88
88
  });
89
89
 
90
+ // Headers set by reverse proxies to describe the original client. Their
91
+ // presence means the request was relayed and did not originate on this host.
92
+ // The `x-forwarded-` prefix is matched as a whole because proxies vary in
93
+ // which of them they set, and any single one is enough to reveal a relay.
94
+ const forwardingHeaderPrefix = 'x-forwarded-';
95
+ const forwardingHeaders = ['x-real-ip', 'forwarded'];
96
+
90
97
  /**
91
- * Checks whether a request is from localhost.
98
+ * Checks whether the connection was opened from this host.
99
+ *
100
+ * This describes the connection, not the client: a reverse proxy running on
101
+ * the same host also connects over loopback, so a true result does not mean
102
+ * the client is local. Use it only to decide whether the connection itself
103
+ * needs encrypting, never to grant access.
104
+ */
105
+ function isLoopbackPeer(req) {
106
+ // The whole of 127.0.0.0/8 is loopback, not just 127.0.0.1. The address is
107
+ // absent on a socket that has already been destroyed.
108
+ const address = req.socket.remoteAddress;
109
+ return typeof address === 'string' && (
110
+ address.startsWith('127.') ||
111
+ address.startsWith('::ffff:127.') ||
112
+ address === '::1'
113
+ );
114
+ }
115
+
116
+ /**
117
+ * Checks whether a request originated on this host.
118
+ *
119
+ * Stricter than `isLoopbackPeer`, because a same-host reverse proxy would
120
+ * otherwise make every request it relays look local. A request counts as
121
+ * local only when nothing indicates it was relayed.
92
122
  */
93
123
  function isLocalRequest(req) {
94
- return req.connection.remoteAddress === '127.0.0.1' ||
95
- req.connection.remoteAddress === '::ffff:127.0.0.1' ||
96
- req.connection.remoteAddress === '::1';
124
+ // A configured proxy relays every request, so no request is local.
125
+ if (config.trustProxy) {
126
+ return false;
127
+ }
128
+ // Forwarding headers are never sent by a client on this host.
129
+ const isForwarded = Object.keys(req.headers).some(header =>
130
+ header.startsWith(forwardingHeaderPrefix) || forwardingHeaders.includes(header)
131
+ );
132
+ if (isForwarded) {
133
+ return false;
134
+ }
135
+ return isLoopbackPeer(req);
97
136
  }
98
137
 
99
138
  /**
@@ -102,11 +141,15 @@ module.exports = function(config, options) {
102
141
  * - Requires users to be configured for remote access (unless dev mode is enabled)
103
142
  */
104
143
  function enforceRemoteAccessRestrictions(req, res, next) {
105
- if (!options.dev && !isLocalRequest(req)) {
106
- if (!req.secure && !options.allowInsecureHTTP) {
144
+ if (!options.dev) {
145
+ // Keyed on the connection: a loopback connection cannot be observed off
146
+ // this host, so it needs no encryption of its own.
147
+ if (!isLoopbackPeer(req) && !req.secure && !options.allowInsecureHTTP) {
107
148
  return res.status(403).json({ error: 'Parse Dashboard can only be remotely accessed via HTTPS' });
108
149
  }
109
- if (!users) {
150
+ // Keyed on the client: granting access requires knowing where the
151
+ // request came from, which a relayed request cannot establish.
152
+ if (!isLocalRequest(req) && !users) {
110
153
  return res.status(401).json({ error: 'Configure a user to access Parse Dashboard remotely' });
111
154
  }
112
155
  }
@@ -135,13 +178,13 @@ module.exports = function(config, options) {
135
178
  agent: config.agent,
136
179
  };
137
180
 
138
- if (!options.dev && !isLocalRequest(req)) {
139
- if (!req.secure && !options.allowInsecureHTTP) {
181
+ if (!options.dev) {
182
+ if (!isLoopbackPeer(req) && !req.secure && !options.allowInsecureHTTP) {
140
183
  //Disallow HTTP requests except on localhost, to prevent the master key from being transmitted in cleartext
141
184
  return res.send({ success: false, error: 'Parse Dashboard can only be remotely accessed via HTTPS' });
142
185
  }
143
186
 
144
- if (!users) {
187
+ if (!isLocalRequest(req) && !users) {
145
188
  //Accessing the dashboard over the internet can only be done with username and password
146
189
  return res.send({ success: false, error: 'Configure a user to access Parse Dashboard remotely' });
147
190
  }