11'use strict' ;
22
33const HttpError = require ( '@starefossen/http-error' ) ;
4- const redis = require ( '@turbasen/db-redis' ) ;
5- const mongo = require ( '@turbasen/db-mongo' ) ;
64
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' ) ;
148
159module . exports = ( ) => ( req , res , next ) => {
1610 let promise ;
1711
1812 // API key through Authorization header
1913 if ( req . headers . authorization ) {
2014 const token = req . headers . authorization . split ( ' ' ) ;
21- promise = module . exports . getUserByToken ( token [ 1 ] ) ;
15+ promise = AuthUser . getByKey ( token [ 1 ] ) ;
2216
2317 // API key through URL query parameter
2418 } 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 ) ;
2620
2721 // No API key
2822 } else {
29- promise = module . exports . getUserByIp (
23+ promise = UnauthUser . getByKey (
3024 req . headers [ 'x-forwarded-for' ] || req . connection . remoteAddres
3125 ) ;
3226 }
3327
3428 promise . then ( user => {
3529 req . user = user ;
3630
31+ // X-User headers
3732 res . set ( 'X-User-Auth' , user . auth ) ;
3833 if ( user . auth ) {
3934 res . set ( 'X-User-Provider' , user . provider ) ;
4035 }
4136
37+ // X-Rate-Limit headers
4238 res . set ( 'X-RateLimit-Limit' , user . limit ) ;
4339 res . set ( 'X-RateLimit-Reset' , user . reset ) ;
4440
41+ // Check if user has remaining rate limit quota
4542 if ( ! user . hasRemainingQuota ( ) ) {
4643 res . set ( 'X-RateLimit-Remaining' , 0 ) ;
4744
@@ -50,115 +47,36 @@ module.exports = () => (req, res, next) => {
5047 ) ) ;
5148 }
5249
50+ // Charge user for this request
5351 res . set ( 'X-RateLimit-Remaining' , user . charge ( ) ) ;
5452
53+ // Check if user can execute the HTTP method. Only authenticated users are
54+ // allowed to execute POST, PUT, and DELETE requests.
5555 if ( ! user . can ( req . method ) ) {
5656 return next ( new HttpError (
5757 401 , `API authentication required for "${ req . method } " requests`
5858 ) ) ;
5959 }
6060
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 ) ;
7263
7364 return next ( ) ;
7465 } ) . catch ( next ) ;
7566} ;
7667
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+ }
15974
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 ( ) ;
16277} ;
16378
16479module . exports . middleware = module . exports ( ) ;
80+ module . exports . AbstractUser = AbstractUser ;
81+ module . exports . AuthUser = AuthUser ;
82+ module . exports . UnauthUser = UnauthUser ;
0 commit comments