1 | const Boom = require('boom')
|
2 | const Hoek = require('hoek')
|
3 | const { Utils } = require('bak')
|
4 |
|
5 | const { realIP } = Utils
|
6 |
|
7 | function HapiRateLimit (plugin, _options, next) {
|
8 |
|
9 | const options = Hoek.applyToDefaults(defaults, _options)
|
10 |
|
11 |
|
12 |
|
13 | let Driver = options.driver
|
14 | if (typeof Driver === 'string') {
|
15 | Driver = require('./' + Driver)
|
16 | }
|
17 | const limiter = new Driver(options)
|
18 |
|
19 | const handleLimits = (request, reply) => {
|
20 | const route = request.route
|
21 |
|
22 |
|
23 | let routeLimit = route.settings.plugins && route.settings.plugins.ratelimit
|
24 |
|
25 |
|
26 | if (options.global) {
|
27 | routeLimit = Object.assign({}, options.global, routeLimit)
|
28 | }
|
29 |
|
30 |
|
31 | if (!routeLimit) {
|
32 | return reply.continue()
|
33 | }
|
34 |
|
35 |
|
36 | Promise.resolve(limiter.check(
|
37 | options.namespace + ':' + realIP(request) + ':' + (request.route.id || request.route.path),
|
38 | routeLimit.limit,
|
39 | routeLimit.duration
|
40 | )).then(rateLimit => {
|
41 | request.plugins.ratelimit = {
|
42 | limit: rateLimit.limit,
|
43 | remaining: rateLimit.remaining - 1,
|
44 | reset: rateLimit.reset
|
45 | }
|
46 |
|
47 | if (rateLimit.remaining > 0) {
|
48 | return reply.continue()
|
49 | }
|
50 |
|
51 | const error = Boom.tooManyRequests('RATE_LIMIT_EXCEEDED')
|
52 | setHeaders(error.output.headers, request.plugins.ratelimit, options.XHeaders)
|
53 | error.reformat()
|
54 |
|
55 | return reply(error)
|
56 | }).catch(reply)
|
57 | }
|
58 |
|
59 | const responseLimits = (request, reply) => {
|
60 | if (request.plugins.ratelimit) {
|
61 | const response = request.response
|
62 | if (!response.isBoom) {
|
63 | setHeaders(response.headers, request.plugins.ratelimit, options.XHeaders)
|
64 | }
|
65 | }
|
66 | reply.continue()
|
67 | }
|
68 |
|
69 |
|
70 | plugin.ext('onPreAuth', handleLimits)
|
71 | plugin.ext('onPostHandler', responseLimits)
|
72 |
|
73 | next()
|
74 | }
|
75 |
|
76 |
|
77 | function setHeaders (headers, ratelimit, XHeaders = false) {
|
78 | if (XHeaders) {
|
79 | headers['X-Rate-Limit-Limit'] = ratelimit.limit
|
80 | headers['X-Rate-Limit-Remaining'] = ratelimit.remaining > 0 ? ratelimit.remaining : 0
|
81 | headers['X-Rate-Limit-Reset'] = ratelimit.reset
|
82 | }
|
83 |
|
84 |
|
85 | headers['Retry-After'] = Math.ceil(ratelimit.reset / 1000)
|
86 | }
|
87 |
|
88 |
|
89 | const defaults = {
|
90 | namespace: 'ratelimit',
|
91 | driver: 'memory',
|
92 | XHeaders: false,
|
93 | global: {
|
94 | limit: 60,
|
95 | duration: 60000
|
96 | }
|
97 | }
|
98 |
|
99 |
|
100 | HapiRateLimit.attributes = {
|
101 | pkg: {
|
102 | name: 'bak-ratelimit'
|
103 | }
|
104 | }
|
105 |
|
106 |
|
107 | exports.register = HapiRateLimit
|