@@ -5,6 +5,20 @@ const redis = require('redis'),
55 jwt = require ( 'jsonwebtoken' ) ,
66 joi = require ( '@hapi/joi' )
77
8+ const ignoredClients = [ 'zYw8u52siLqHu7PmHODYndeIpD4vGe1R' ]
9+
10+ function validatePayload ( event ) {
11+ if ( _ . isEmpty ( event [ 'body' ] ) ) {
12+ return { error : getErrorResponse ( { body : "Empty body." } ) }
13+ }
14+ const auth0Payload = typeof event [ 'body' ] === 'string' ? JSON . parse ( event [ 'body' ] ) : event [ 'body' ]
15+ const { value, error } = schema . validate ( auth0Payload )
16+ if ( error != null ) {
17+ return { error : getErrorResponse ( { statusCode : 400 , body : "Payload validation error: " + JSON . stringify ( error . details ) } ) }
18+ }
19+ return { auth0Payload }
20+ }
21+
822const schema = joi . object ( ) . keys ( {
923 client_id : joi . string ( ) . required ( ) ,
1024 grant_type : joi . string ( ) . required ( ) ,
@@ -14,129 +28,141 @@ const schema = joi.object().keys({
1428 fresh_token : joi . boolean ( )
1529} )
1630
31+ let redisClient = null
32+
33+ function acquireRedisClient ( ) {
34+ if ( redisClient == null ) {
35+ console . log ( "Creating new redis client" )
36+ redisClient = createRedisClient ( )
37+ } else {
38+ const pong = redisClient . ping ( )
39+ if ( ! pong ) {
40+ console . log ( "Redis connection lost, creating new redis client" )
41+ redisClient = createRedisClient ( )
42+ }
43+ }
44+ }
45+
46+ function createRedisClient ( ) {
47+ const redisUrl = process . env . REDIS_URL || 'redis://localhost:6379'
48+ const con = redis . createClient ( redisUrl )
49+ con . on ( "error" , function ( err ) {
50+ console . log ( err )
51+ } )
52+ return con
53+ }
54+
55+ function getCacheKey ( auth0Payload ) {
56+ const copyAuth0Payload = {
57+ "client_id" : auth0Payload . client_id ,
58+ "audience" : auth0Payload . audience ,
59+ "client_secret" : auth0Payload . client_secret ,
60+ }
61+ return `${ auth0Payload . client_id } -${ md5 ( JSON . stringify ( copyAuth0Payload ) ) } `
62+ }
63+
64+ function callAuth0 ( auth0Payload , cacheKey , callback ) {
65+ const options = {
66+ url : auth0Payload . auth0_url ,
67+ headers : { 'content-type' : 'application/json' } ,
68+ body : {
69+ grant_type : auth0Payload . grant_type ,
70+ client_id : auth0Payload . client_id ,
71+ client_secret : auth0Payload . client_secret ,
72+ audience : auth0Payload . audience
73+ } ,
74+ json : true
75+ }
76+ request . post ( options , function ( error , response , body ) {
77+ if ( error ) {
78+ const errorResponse = getErrorResponse ( { statusCode : response . statusCode , body : error } )
79+ console . log ( errorResponse )
80+ callback ( null , errorResponse )
81+ }
82+ if ( body . access_token && response . statusCode === 200 ) {
83+ console . log ( `Fetched from Auth0 for client-id: ${ cacheKey } ` )
84+ const token = body . access_token
85+ const ttl = saveToRedisCache ( cacheKey , token )
86+ callback ( null , getSuccessResponse ( { body : JSON . stringify ( { access_token : token , expires_in : ttl } ) } ) )
87+ } else {
88+ const errorResponse = getErrorResponse ( { statusCode : response . statusCode , body : JSON . stringify ( body ) } )
89+ console . log ( errorResponse )
90+ callback ( null , errorResponse )
91+ }
92+ } )
93+ }
94+
95+ function getFromRedisCache ( auth0Payload , cacheKey , callback ) {
96+ redisClient . get ( cacheKey , function ( err , token ) {
97+ if ( err ) {
98+ console . log ( err )
99+ callAuth0 ( auth0Payload , cacheKey , callback )
100+ } else {
101+ const ttl = getTokenExpiryTime ( token )
102+ if ( ttl > 0 ) {
103+ console . log ( `Fetched from Redis Cache for cache key: ${ cacheKey } ` )
104+ callback ( null , getSuccessResponse ( { body : JSON . stringify ( { access_token : token , expires_in : ttl } ) } ) )
105+ } else {
106+ console . log ( "Token expired in cache" )
107+ callAuth0 ( auth0Payload , cacheKey , callback )
108+ }
109+ }
110+ } )
111+ }
112+
113+ function saveToRedisCache ( cacheKey , token ) {
114+ const ttl = getTokenExpiryTime ( token )
115+ redisClient . set ( cacheKey , token , 'EX' , ttl )
116+ return ttl
117+ }
118+
17119/**
18120 *
19121 * @param String token
20122 * @returns expiryTime in seconds
21123 */
22- function getTokenExipryTime ( token ) {
23- let expiryTime = 0
24- if ( token ) {
25- let decodedToken = jwt . decode ( token )
26- let expiryTimeInMilliSeconds = ( decodedToken . exp - 60 ) * 1000 - ( new Date ( ) . getTime ( ) )
27- expiryTime = Math . floor ( expiryTimeInMilliSeconds / 1000 )
124+ function getTokenExpiryTime ( token ) {
125+ if ( ! token ) {
126+ return 0
127+ } else {
128+ const decodedToken = jwt . decode ( token )
129+ const expiryTimeInMilliSeconds = ( decodedToken . exp - 60 ) * 1000 - ( new Date ( ) . getTime ( ) )
130+ return Math . floor ( expiryTimeInMilliSeconds / 1000 )
28131 }
29- return expiryTime
30132}
31133
32- exports . handler = ( event , context , callback ) => {
33- let redisUrl = process . env . REDIS_URL || 'redis://localhost:6379'
34- let auth0Payload = { }
35- let cacheKey = ''
36- let clientId = ''
37- let options = { }
38- let redisClient = null
39- let errorResponse = {
134+ function getErrorResponse ( error ) {
135+ const errorResponse = {
40136 statusCode : 500 ,
41137 body : 'something went wrong.'
42138 }
43- let successResponse = {
139+ return _ . assign ( errorResponse , error )
140+ }
141+
142+ function getSuccessResponse ( response ) {
143+ const successResponse = {
44144 statusCode : 200 ,
45145 body : "Bye!"
46146 }
47- let freshToken = false
48- let payloadValidationError = false
49- let audience = ''
50- let copyAuth0Payload = { }
51-
52- if ( ! _ . isEmpty ( event [ 'body' ] ) ) {
53- auth0Payload = typeof event [ 'body' ] === 'string' ? JSON . parse ( event [ 'body' ] ) : event [ 'body' ]
54- // cache key is combination of : clientid
55- const { value, error } = schema . validate ( auth0Payload )
56- if ( error != null ) {
57- payloadValidationError = true
58- errorResponse . statusCode = 400
59- errorResponse . body = "Payload validation error: " + JSON . stringify ( error . details )
60- }
61- clientId = auth0Payload . client_id || ''
62- secret = _ . get ( auth0Payload , 'client_secret' , '' )
63- audience = _ . get ( auth0Payload , 'audience' , '' )
64-
65- /**
66- * create cache key
67- */
68- Object . assign ( copyAuth0Payload , auth0Payload )
69- if ( copyAuth0Payload . hasOwnProperty ( 'fresh_token' ) ) {
70- delete copyAuth0Payload . fresh_token
71- }
72- cacheKey = `${ clientId } -${ md5 ( JSON . stringify ( copyAuth0Payload ) ) } `
73-
74- options = {
75- url : auth0Payload . auth0_url ,
76- headers : { 'content-type' : 'application/json' } ,
77- body : auth0Payload ,
78- json : true
79- }
80- freshToken = JSON . parse ( auth0Payload . fresh_token ? auth0Payload . fresh_token : 0 )
81- && clientId != 'zYw8u52siLqHu7PmHODYndeIpD4vGe1R'
82-
83- } else {
84- errorResponse . body = "Empty body."
85- callback ( null , errorResponse )
86- }
147+ return _ . assign ( successResponse , response )
148+ }
87149
88- if ( ! _ . isEmpty ( redisUrl ) && ! payloadValidationError ) {
89- redisClient = redis . createClient ( redisUrl )
90- redisClient . on ( "error" , function ( err ) {
91- errorResponse . body = "redis client connecting error: " + err
92- callback ( null , errorResponse )
93- redisClient . quit ( )
94- } )
95- redisClient . on ( "ready" , ( ) => {
96- // try to get token from cache first
97- redisClient . get ( cacheKey , function ( err , token ) {
98- // todo err implementation
99- if ( token != null && ! freshToken && getTokenExipryTime ( token . toString ( ) ) > 0 ) {
100- console . log ( `Fetched from Redis Cache for cache key: ${ cacheKey } ` )
101- successResponse . body = JSON . stringify ( {
102- access_token : token . toString ( ) ,
103- expires_in : getTokenExipryTime ( token . toString ( ) )
104- } )
105- callback ( null , successResponse )
106- redisClient . quit ( )
107- }
108- else {
109- request . post ( options , function ( error , response , body ) {
110- if ( error ) {
111- errorResponse . statusCode = response . statusCode
112- errorResponse . body = error
113- callback ( null , errorResponse )
114- }
115- if ( body . access_token && response . statusCode === 200 ) {
116- let token = body . access_token
117- // Time to live in cache
118- let ttl = getTokenExipryTime ( token )
119- redisClient . set ( cacheKey , token , 'EX' , ttl )
120- console . log ( `Fetched from Auth0 for client-id: ${ cacheKey } ` )
121- successResponse . body = JSON . stringify ( {
122- access_token : token . toString ( ) ,
123- expires_in : ttl
124- } )
125- callback ( null , successResponse )
126- } else {
127- errorResponse . statusCode = response . statusCode
128- errorResponse . body = JSON . stringify ( body )
129- callback ( null , errorResponse )
130- }
131- redisClient . quit ( )
132- } )
133- }
134- } )
135- } )
136- } else if ( payloadValidationError ) {
137- callback ( null , errorResponse )
150+ exports . handler = ( event , context , callback ) => {
151+ context . callbackWaitsForEmptyEventLoop = false
152+ const { error, auth0Payload } = validatePayload ( event )
153+ if ( error ) {
154+ callback ( null , error )
138155 } else {
139- errorResponse . body = "Empty redis url or payload validation error."
140- callback ( null , errorResponse )
156+ acquireRedisClient ( )
157+ const cacheKey = getCacheKey ( auth0Payload )
158+ console . log ( `Request for ${ cacheKey } ` )
159+ const freshToken = JSON . parse ( auth0Payload . fresh_token ? auth0Payload . fresh_token : 0 )
160+ && ! _ . includes ( ignoredClients , auth0Payload . clientId )
161+ if ( freshToken ) {
162+ console . log ( "Requested fresh token" )
163+ callAuth0 ( auth0Payload , cacheKey , callback )
164+ } else {
165+ getFromRedisCache ( auth0Payload , cacheKey , callback )
166+ }
141167 }
142- } ;
168+ }
0 commit comments