1
1
'use strict' ;
2
2
3
3
const HttpError = require ( '@starefossen/http-error' ) ;
4
- const redis = require ( '@turbasen/db-redis' ) ;
5
- const mongo = require ( '@turbasen/db-mongo' ) ;
6
4
7
- const UnauthUser = require ( './lib/User' ) . UnauthUser ;
8
- const AuthUser = require ( './lib/User' ) . AuthUser ;
9
-
10
- const CACHE_VALID = process . env . NTB_CACHE_VALID || 60 * 60 * 1000 ;
11
- const CACHE_INVALID = process . env . NTB_CACHE_INVALID || 24 * 60 * 60 * 1000 ;
12
- const API_ENV = process . env . NTB_API_ENV || 'dev' ;
13
- const RATELIMIT_UNAUTH = process . env . NTB_RATELIMIT_UNAUTH || 100 ;
5
+ const AbstractUser = require ( './lib/AbstractUser' ) ;
6
+ const UnauthUser = require ( './lib/UnauthUser' ) ;
7
+ const AuthUser = require ( './lib/AuthUser' ) ;
14
8
15
9
module . exports = ( ) => ( req , res , next ) => {
16
10
let promise ;
17
11
18
12
// API key through Authorization header
19
13
if ( req . headers . authorization ) {
20
14
const token = req . headers . authorization . split ( ' ' ) ;
21
- promise = module . exports . getUserByToken ( token [ 1 ] ) ;
15
+ promise = AuthUser . getByKey ( token [ 1 ] ) ;
22
16
23
17
// API key through URL query parameter
24
18
} else if ( req . query && req . query . api_key ) {
25
- promise = module . exports . getUserByToken ( req . query . api_key ) ;
19
+ promise = AuthUser . getByKey ( req . query . api_key ) ;
26
20
27
21
// No API key
28
22
} else {
29
- promise = module . exports . getUserByIp (
23
+ promise = UnauthUser . getByKey (
30
24
req . headers [ 'x-forwarded-for' ] || req . connection . remoteAddres
31
25
) ;
32
26
}
33
27
34
28
promise . then ( user => {
35
29
req . user = user ;
36
30
31
+ // X-User headers
37
32
res . set ( 'X-User-Auth' , user . auth ) ;
38
33
if ( user . auth ) {
39
34
res . set ( 'X-User-Provider' , user . provider ) ;
40
35
}
41
36
37
+ // X-Rate-Limit headers
42
38
res . set ( 'X-RateLimit-Limit' , user . limit ) ;
43
39
res . set ( 'X-RateLimit-Reset' , user . reset ) ;
44
40
41
+ // Check if user has remaining rate limit quota
45
42
if ( ! user . hasRemainingQuota ( ) ) {
46
43
res . set ( 'X-RateLimit-Remaining' , 0 ) ;
47
44
@@ -50,115 +47,36 @@ module.exports = () => (req, res, next) => {
50
47
) ) ;
51
48
}
52
49
50
+ // Charge user for this request
53
51
res . set ( 'X-RateLimit-Remaining' , user . charge ( ) ) ;
54
52
53
+ // Check if user can execute the HTTP method. Only authenticated users are
54
+ // allowed to execute POST, PUT, and DELETE requests.
55
55
if ( ! user . can ( req . method ) ) {
56
56
return next ( new HttpError (
57
57
401 , `API authentication required for "${ req . method } " requests`
58
58
) ) ;
59
59
}
60
60
61
- res . on ( 'finish' , function resOnFinishCb ( ) {
62
- // Uncharge user when certain cache features are used.
63
- // 304 Not Modified, and 412 Precondition Failed
64
- if ( this . statusCode === 304 || this . statusCode === 412 ) {
65
- this . req . user . uncharge ( ) ;
66
- }
67
-
68
- if ( this . req . user . getCharge ( ) > 0 ) {
69
- redis . hincrby ( this . req . user . cacheKey , 'remaining' , - 1 ) ;
70
- }
71
- } ) ;
61
+ // Attach the on finish callback which updates the user rate limit in cache.
62
+ res . on ( 'finish' , module . exports . onFinish ) ;
72
63
73
64
return next ( ) ;
74
65
} ) . catch ( next ) ;
75
66
} ;
76
67
77
- module . exports . getUserByIp = function getUserByIp ( key ) {
78
- return new Promise ( ( resolve , reject ) => {
79
- redis . hgetall ( AuthUser . getCacheKey ( key ) , ( redisErr , data ) => {
80
- if ( redisErr ) { return reject ( redisErr ) ; }
81
-
82
- if ( data && data . limit ) {
83
- return resolve ( new UnauthUser ( key , data ) ) ;
84
- } else {
85
- const expireat = module . exports . expireat ( CACHE_VALID ) ;
86
-
87
- const user = new UnauthUser ( key , {
88
- limit : RATELIMIT_UNAUTH ,
89
- remaining : RATELIMIT_UNAUTH ,
90
- reset : expireat ,
91
- } ) ;
92
-
93
- redis . hmset ( AuthUser . getCacheKey ( key ) , user . toObject ( ) ) ;
94
- redis . expireat ( AuthUser . getCacheKey ( key ) , expireat ) ;
95
-
96
- return resolve ( user ) ;
97
- }
98
- } ) ;
99
- } ) ;
100
- } ;
101
-
102
- module . exports . getUserByToken = function getUserByToken ( key ) {
103
- return new Promise ( ( resolve , reject ) => {
104
- redis . hgetall ( UnauthUser . getCacheKey ( key ) , ( redisErr , data ) => {
105
- if ( redisErr ) { return reject ( redisErr ) ; }
106
-
107
- if ( data && data . access ) {
108
- if ( data . access === 'true' ) {
109
- return resolve ( new AuthUser ( key , data ) ) ;
110
- } else {
111
- return reject ( new HttpError ( `Bad credentials for user "${ key } "` , 401 ) ) ;
112
- }
113
- }
114
-
115
- const query = {
116
- [ `apps.key.${ API_ENV } ` ] : key ,
117
- 'apps.active' : true ,
118
- } ;
119
-
120
- const opts = {
121
- fields : {
122
- provider : true ,
123
- apps : true ,
124
- } ,
125
- } ;
126
-
127
- return mongo . api . users . findOne ( query , opts , ( mongoErr , doc ) => {
128
- if ( mongoErr ) { return reject ( mongoErr ) ; }
129
-
130
- if ( ! doc ) {
131
- const expireat = module . exports . expireat ( CACHE_INVALID ) ;
132
-
133
- redis . hset ( AuthUser . getCacheKey ( key ) , 'access' , 'false' ) ;
134
- redis . expireat ( AuthUser . getCacheKey ( key ) , expireat ) ;
135
-
136
- return reject ( new HttpError ( `Bad credentials for user "${ key } "` , 401 ) ) ;
137
- }
138
-
139
- const app = doc . apps . find ( item => item . key [ API_ENV ] === key ) ;
140
- const expireat = module . exports . expireat ( CACHE_VALID ) ;
141
-
142
- const user = new AuthUser ( key , {
143
- provider : doc . provider ,
144
- app : app . name ,
145
-
146
- limit : app . limit [ API_ENV ] ,
147
- remaining : app . limit [ API_ENV ] ,
148
- reset : expireat ,
149
- } ) ;
150
-
151
- redis . hmset ( AuthUser . getCacheKey ( key ) , user . toObject ( ) ) ;
152
- redis . expireat ( AuthUser . getCacheKey ( key ) , expireat ) ;
153
-
154
- return resolve ( user ) ;
155
- } ) ;
156
- } ) ;
157
- } ) ;
158
- } ;
68
+ module . exports . onFinish = function onFinish ( ) {
69
+ // Uncharge user when certain cache features are used.
70
+ // 304 Not Modified, and 412 Precondition Failed
71
+ if ( this . statusCode === 304 || this . statusCode === 412 ) {
72
+ this . req . user . uncharge ( ) ;
73
+ }
159
74
160
- module . exports . expireat = function expireat ( seconds ) {
161
- return Math . floor ( ( new Date ( ) . getTime ( ) + seconds ) / 1000 ) ;
75
+ // Update user rate-limit in cache if it has changed.
76
+ this . req . user . update ( ) ;
162
77
} ;
163
78
164
79
module . exports . middleware = module . exports ( ) ;
80
+ module . exports . AbstractUser = AbstractUser ;
81
+ module . exports . AuthUser = AuthUser ;
82
+ module . exports . UnauthUser = UnauthUser ;
0 commit comments