UNPKG

2.7 kBJavaScriptView Raw
1const Boom = require('boom')
2const Hoek = require('hoek')
3const { Utils } = require('bak')
4
5const { realIP } = Utils
6
7function HapiRateLimit (plugin, _options, next) {
8 // Apply default options
9 const options = Hoek.applyToDefaults(defaults, _options)
10
11 // Create limiter instance
12 // const limiter = new RedisRateLimit(options);
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 // Get route-specific limits
23 let routeLimit = route.settings.plugins && route.settings.plugins.ratelimit
24
25 // Try to apply global if no options
26 if (options.global) {
27 routeLimit = Object.assign({}, options.global, routeLimit)
28 }
29
30 // If no limits on route
31 if (!routeLimit) {
32 return reply.continue()
33 }
34
35 // Check limits on route
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 // Ext
70 plugin.ext('onPreAuth', handleLimits)
71 plugin.ext('onPostHandler', responseLimits)
72
73 next()
74}
75
76// Set rate-limit headers
77function 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 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
85 headers['Retry-After'] = Math.ceil(ratelimit.reset / 1000)
86}
87
88// Default options
89const defaults = {
90 namespace: 'ratelimit',
91 driver: 'memory',
92 XHeaders: false,
93 global: {
94 limit: 60,
95 duration: 60000
96 }
97}
98
99// Meta
100HapiRateLimit.attributes = {
101 pkg: {
102 name: 'bak-ratelimit'
103 }
104}
105
106// Export plugin
107exports.register = HapiRateLimit