Files
OpenParty/core/lib/ipResolver.js

206 lines
6.6 KiB
JavaScript

const is = require('./is');
//taken from https://github.com/pbojinov/request-ip
/**
* Parse x-forwarded-for headers.
*
* @param {string} value - The value to be parsed.
* @return {string|null} First known IP address, if any.
*/
function getClientIpFromXForwardedFor(value) {
if (!is.existy(value)) {
return null;
}
if (is.not.string(value)) {
throw new TypeError(`Expected a string, got "${typeof value}"`);
}
// x-forwarded-for may return multiple IP addresses in the format:
// "client IP, proxy 1 IP, proxy 2 IP"
// Therefore, the right-most IP address is the IP address of the most recent proxy
// and the left-most IP address is the IP address of the originating client.
// source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
// Azure Web App's also adds a port for some reason, so we'll only use the first part (the IP)
const forwardedIps = value.split(',').map((e) => {
const ip = e.trim();
if (ip.includes(':')) {
const splitted = ip.split(':');
// make sure we only use this if it's ipv4 (ip:port)
if (splitted.length === 2) {
return splitted[0];
}
}
return ip;
});
// Sometimes IP addresses in this header can be 'unknown' (http://stackoverflow.com/a/11285650).
// Therefore taking the right-most IP address that is not unknown
// A Squid configuration directive can also set the value to "unknown" (http://www.squid-cache.org/Doc/config/forwarded_for/)
for (let i = 0; i < forwardedIps.length; i++) {
if (is.ip(forwardedIps[i])) {
return forwardedIps[i];
}
}
// If no value in the split list is an ip, return null
return null;
}
/**
* Determine client IP address.
*
* @param req
* @returns {string} ip - The IP address if known, defaulting to empty string if unknown.
*/
function getClientIp(req) {
// Server is probably behind a proxy.
if (req.headers) {
// Standard headers used by Amazon EC2, Heroku, and others.
if (is.ip(req.headers['x-client-ip'])) {
return req.headers['x-client-ip'];
}
// Load-balancers (AWS ELB) or proxies.
const xForwardedFor = getClientIpFromXForwardedFor(
req.headers['x-forwarded-for'],
);
if (is.ip(xForwardedFor)) {
return xForwardedFor;
}
// Cloudflare.
// @see https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-
// CF-Connecting-IP - applied to every request to the origin.
if (is.ip(req.headers['cf-connecting-ip'])) {
return req.headers['cf-connecting-ip'];
}
// DigitalOcean.
// @see https://www.digitalocean.com/community/questions/app-platform-client-ip
// DO-Connecting-IP - applied to app platform servers behind a proxy.
if (is.ip(req.headers['do-connecting-ip'])) {
return req.headers['do-connecting-ip'];
}
// Fastly and Firebase hosting header (When forwared to cloud function)
if (is.ip(req.headers['fastly-client-ip'])) {
return req.headers['fastly-client-ip'];
}
// Akamai and Cloudflare: True-Client-IP.
if (is.ip(req.headers['true-client-ip'])) {
return req.headers['true-client-ip'];
}
// Default nginx proxy/fcgi; alternative to x-forwarded-for, used by some proxies.
if (is.ip(req.headers['x-real-ip'])) {
return req.headers['x-real-ip'];
}
// (Rackspace LB and Riverbed's Stingray)
// http://www.rackspace.com/knowledge_center/article/controlling-access-to-linux-cloud-sites-based-on-the-client-ip-address
// https://splash.riverbed.com/docs/DOC-1926
if (is.ip(req.headers['x-cluster-client-ip'])) {
return req.headers['x-cluster-client-ip'];
}
if (is.ip(req.headers['x-forwarded'])) {
return req.headers['x-forwarded'];
}
if (is.ip(req.headers['forwarded-for'])) {
return req.headers['forwarded-for'];
}
if (is.ip(req.headers.forwarded)) {
return req.headers.forwarded;
}
// Google Cloud App Engine
// https://cloud.google.com/appengine/docs/standard/go/reference/request-response-headers
if (is.ip(req.headers['x-appengine-user-ip'])) {
return req.headers['x-appengine-user-ip'];
}
}
// Remote address checks.
// Deprecated
if (is.existy(req.connection)) {
if (is.ip(req.connection.remoteAddress)) {
return req.connection.remoteAddress;
}
if (
is.existy(req.connection.socket) &&
is.ip(req.connection.socket.remoteAddress)
) {
return req.connection.socket.remoteAddress;
}
}
if (is.existy(req.socket) && is.ip(req.socket.remoteAddress)) {
return req.socket.remoteAddress;
}
if (is.existy(req.info) && is.ip(req.info.remoteAddress)) {
return req.info.remoteAddress;
}
// AWS Api Gateway + Lambda
if (
is.existy(req.requestContext) &&
is.existy(req.requestContext.identity) &&
is.ip(req.requestContext.identity.sourceIp)
) {
return req.requestContext.identity.sourceIp;
}
// Cloudflare fallback
// https://blog.cloudflare.com/eliminating-the-last-reasons-to-not-enable-ipv6/#introducingpseudoipv4
if (req.headers) {
if (is.ip(req.headers['Cf-Pseudo-IPv4'])) {
return req.headers['Cf-Pseudo-IPv4'];
}
}
// Fastify https://www.fastify.io/docs/latest/Reference/Request/
if (is.existy(req.raw)) {
return getClientIp(req.raw);
}
return null;
}
/**
* Expose request IP as a middleware.
*
* @param {object} [options] - Configuration.
* @param {string} [options.attributeName] - Name of attribute to augment request object with.
* @return {*}
*/
function mw(options) {
// Defaults.
const configuration = is.not.existy(options) ? {} : options;
// Validation.
if (is.not.object(configuration)) {
throw new TypeError('Options must be an object!');
}
const attributeName = configuration.attributeName || 'clientIp';
return (req, res, next) => {
const ip = getClientIp(req);
Object.defineProperty(req, attributeName, {
get: () => ip,
configurable: true,
});
next();
};
}
module.exports = {
getClientIpFromXForwardedFor,
getClientIp,
mw,
};