Skip to content

Commit 822a2b2

Browse files
authored
Merge pull request #21 from topcoder-platform/qa
Qa
2 parents 60b4560 + ddc3ba0 commit 822a2b2

2 files changed

Lines changed: 150 additions & 109 deletions

File tree

.circleci/config.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ jobs:
6161
# deploy app
6262
steps: *build_steps
6363

64+
"build-qa":
65+
<<: *default
66+
environment:
67+
DEPLOY_ENV: "QA"
68+
LOGICAL_ENV: "qa"
69+
GIT_REPO: "tc-lambda-auth0-proxy-server"
70+
# deploy app
71+
steps: *build_steps
6472

6573
"build-prod":
6674
<<: *default
@@ -82,6 +90,13 @@ workflows:
8290
branches:
8391
only:
8492
- dev
93+
# QA builds are executed on "develop" branch only.
94+
- "build-qa":
95+
context : org-global
96+
filters:
97+
branches:
98+
only:
99+
- qa
85100
# production builds are executed on "master" branch only.
86101
- "build-prod":
87102
context : org-global

lambda.js

Lines changed: 135 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
822
const 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

Comments
 (0)