-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
127 lines (113 loc) · 4.47 KB
/
index.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
'use strict'
const Address = require('ipaddr.js')
const dns = require('dns')
/**
* Checks if the client IP/host is in the list of IPs allowed for access.
*
* @param clientIp Client IP address requesting access
* @param whitelist list of allowed ips
* @return {boolean}
*/
async function clientMatch(clientIp, whitelist) {
if (whitelist.includes('*') || whitelist.includes('0.0.0.0')) return Promise.resolve(true)
let isMatch = false
if ((clientIp && Address.isValid(clientIp)) && whitelist.length) {
// Get the host IP provided in the whitelist.
whitelist = await Promise.all(whitelist.map(getIPFromHost))
if (clientIp === '::1' || clientIp === '::ffff:127.0.0.1') clientIp = '127.0.0.1'
// `Address.process` return the IP instance in IPv4 or IPv6 form.
// It will return IPv4 instance if it's a IPv4 mapped IPv6 address
clientIp = Address.process(clientIp)
isMatch = whitelist.some((result) => {
// IPv6 address has 128 bits and IPv4 has 32 bits.
// Setting the routing prefix to all bits in a CIDR address means only the specified address is allowed.
result = (result && result.indexOf('/') === -1) ? result + '/128' : result || ''
const range = result.split('/')
if (range.length === 2 && Address.isValid(range[0]) && isNumeric(range[1])) {
const ip = Address.process(range[0])
const bit = parseInt(range[1], 10)
// `IP.kind()` return `'ipv4'` or `'ipv6'`. Only same type can be `match`.
if (clientIp.kind() === ip.kind()) return clientIp.match(ip, bit)
}
return false
})
}
return Promise.resolve(isMatch)
}
/**
* Check if it's a number.
*
* @param number
* @return {boolean}
*/
function isNumeric(number) {
return !isNaN(parseFloat(number)) && isFinite(number)
}
/**
* Get DNS from a host name.
*
* @private
* @param host
* @return Promise<void>
*/
async function getIPFromHost(host) {
return new Promise((resolve) => {
if (host === '::1') return resolve('127.0.0.1')
dns.lookup(host, async (err, ip) => {
if (err) return resolve()
return resolve(ip)
})
})
}
/**
* Middleware to validate allowed Hosts/IPs and block access to those that are not on the whitelist.
*
* @param allowedList Allowed Hosts/IPs that the middleware should use in the validation.
* @param options Middleware settings.
* @return {Function}
*/
function ipAllowed(allowedList, options) {
const _options = {
// log: Pass a log function or `false` to disable log.
// `Function(String clientIp, Boolean access)`
log: options && options.log !== undefined ? options.log : (clientIp, accessDenied) => {
console.log(`Access ${accessDenied ? 'denied' : 'allowed'} for ip address ${clientIp}`)
},
// Message sent when the request is denied, can be a string or JSON.
// `Function(String clientIp)`
message: options && options.message ? options.message : (err, clientIp) => {
return {
code: 401,
message: 'Unauthorized',
description: `Access denied for IP address ${clientIp}`
}
},
statusCode: options && options.statusCode ? parseInt(options.statusCode, 10) : 401,
redirectTo: options && options.redirectTo ? options.redirectTo : ''
}
// Express middleware.
return (req, res, next) => {
const clientIp = req.ip || req.connection.remoteAddress
// Treats the whitelist when it is string.
// Useful when hosts / ips are provided by environment variable.
if (typeof allowedList === 'string') {
allowedList = allowedList
.replace(/[\[\]]/g, '')
.split(',')
.map(item => item.trim())
}
clientMatch(clientIp, allowedList)
.then(isMatch => {
if (typeof _options.log === 'function') {
_options.log.apply(null, [clientIp, !isMatch])
}
if (!isMatch && typeof _options.message === 'function') {
if (_options.redirectTo.length > 0) res.redirect(_options.statusCode, _options.redirectTo)
else res.status(_options.statusCode).send(_options.message(null, clientIp))
} else {
next()
}
})
}
}
module.exports = ipAllowed