-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
154 lines (115 loc) · 3.78 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
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
'use strict'
const http = require('http')
const net = require('net')
const fp = require('fastify-plugin')
const createList = (config, isGlobal, mustBeArray = false) => {
if (!config) return null
if (typeof config === 'string') {
config = [config]
}
if (Array.isArray(config)) {
const rules = [].concat(config).filter(Boolean)
if (!rules.length) return null
const list = new net.BlockList()
for (const rule of rules) {
const [, ip, slash] = rule.match(/^(.*?)(?:\/(\d+))?$/)
let ipType = net.isIP(ip)
if (!ipType) {
throw new Error('Invalid IP address: ' + ip)
}
ipType = 'ipv' + ipType
slash
? list.addSubnet(ip, +slash, ipType)
: list.addAddress(ip, ipType)
}
return list
}
if (config?.constructor?.name === 'Object') {
if (isGlobal) {
throw new Error('Expected list config to an array when global')
}
if (mustBeArray) {
throw new Error('Expected inner list config to be an array')
}
const allRules = {}
Object.entries(config).forEach(([key, rules]) => {
const result = createList(rules, isGlobal, true)
if (result) {
allRules[key] = result
}
})
return Object.entries(allRules).length
? allRules
: null
}
throw new Error('Expected list config to be an array or object literal')
}
async function plugin (fastify, options) {
if (options?.constructor?.name !== 'Object') {
throw new Error('Expected options to be an object literal')
}
const invalidErrorCode = (
options.errorCode &&
(!Number.isInteger(options.errorCode) || options.errorCode <= 0)
)
if (invalidErrorCode) {
throw new Error('Expected options.errorCode to be a positive integer')
}
if (options.errorMessage && typeof options.errorMessage !== 'string') {
throw new Error('Expected options.errorMessage to be a string')
}
const isGlobal = typeof options.global === 'boolean'
? options.global
: true
const allowList = createList(options.allowList, isGlobal)
const blockList = createList(options.blockList, isGlobal)
if (isGlobal && allowList && blockList) {
throw new Error('Cannot specify global options.allowList and options.blockList')
}
if (!allowList && !blockList) {
throw new Error('Must specify options.allowList or options.blockList')
}
if (allowList) {
fastify.decorate('allowList', allowList)
}
if (blockList) {
fastify.decorate('blockList', blockList)
}
const createRequestHandler = (listName = '') => {
let list
let isAllowList
if (listName) {
if (!/^(?:allow|block):\w+$/i.test(listName)) {
throw new Error('Invalid list name: ' + listName)
}
isAllowList = /^allow/i.test(listName)
listName = listName.split(/^(?:allow|block):/i).pop()
list = isAllowList
? fastify.allowList?.[listName]
: fastify.blockList?.[listName]
if (!list) {
throw new Error(`Couldn't find ${isAllowList ? 'allow' : 'block'} list: ${listName}`)
}
} else {
list = fastify.allowList || fastify.blockList
isAllowList = !!fastify.allowList
}
return (request, reply, done) => {
const ipType = 'ipv' + net.isIP(request.ip)
const isMatch = list.check(request.ip, ipType)
const allow = (isMatch && isAllowList) || (!isMatch && !isAllowList)
if (allow) return done()
reply.code(errorCode)
done(new Error(errorMessage))
}
}
const errorCode = options.errorCode || 403
const errorMessage = options.errorMessage || http.STATUS_CODES[errorCode]
isGlobal
? fastify.addHook('onRequest', createRequestHandler())
: fastify.decorate('createNetAclRequestHandler', createRequestHandler)
}
module.exports = fp(plugin, {
fastify: '3.x',
name: 'fastify-net-acl'
})