· 8 years ago · Nov 21, 2017, 02:50 PM
1'use strict';
2
3const Promise = require('bluebird');
4const jwt = require('jsonwebtoken');
5const verifyAsync = Promise.promisify(jwt.verify, jwt);
6const signAsync = Promise.promisify(jwt.sign, jwt);
7
8const auth0lib = require('./lib/auth0lib');
9const atdbhelper = require('./lib/atdbhelper');
10const utdbhelper = require('./lib/utdbhelper');
11const authhelper = require('./lib/authhelper');
12
13const moment = require('moment');
14const AWS = require('aws-sdk');
15
16const utils = require('../lib/utils');
17const logger = require('../lib/utils').log;
18
19/**
20 * Signs a user token with the app secret.
21 *
22 * @param {object}
23 */
24const signUserToken = ({ userInfo, appToken, app_metadata }) => {
25 const tokenValidityInDays = process.env.TOKEN_VALIDITY_IN_DAYS;
26
27 return authhelper.getSecretFromAppToken(appToken)
28 .then((clientSecret) => {
29 return signAsync({
30 userInfo,
31 appToken,
32 app_metadata
33 }, clientSecret, {
34 expiresIn: `${tokenValidityInDays}d`,
35 });
36 });
37};
38
39/**
40 * Get the UserId to store in the auth db
41 *
42 * @param {string} uuid
43 * @param {string} clientName
44 */
45const getUserId = (uuid, clientName) => {
46 return `${uuid}|${clientName}`;
47};
48
49/**
50 * Login the user via Auth0 and check for a valid appToken
51 *
52 * @param {string} username
53 * @param {string} password
54 * @param {string} appToken
55 *
56 * @returns {Promise}
57 */
58module.exports.loginWithCredentials = ({ username, password, appToken }) => {
59 if (!username || !password) {
60 return Promise.reject('[400] Invalid param: "username" or "password"');
61 }
62
63 // Check appToken against db for validity
64 // The user will login via the client. App/Client level authorization is done here.
65 // A jwt is returned (user jwt) that contains the user information as well as the appToken
66 let clientId;
67
68 return authhelper.decodeAppToken(appToken)
69 .then(decodedToken => {
70 clientId = decodedToken.clientId;
71 return atdbhelper.getItem(decodedToken.clientName);
72 })
73 .then(result => {
74 if (result.Count > 0) {
75 return auth0lib.login({ data: { username, password, appToken }, clientId: clientId });
76 }
77
78 return Promise.reject('[404] Non existing client');
79 })
80 .then((auth0UserInfo) => {
81 // A userToken is given back to the user which will essentially act as a session token.
82 return authhelper.getUserInfo(auth0UserInfo.identities[0].user_id)
83 .then((data) => {
84 const userInfo = JSON.parse(data.Payload);
85 return signUserToken({ userInfo, appToken, app_metadata: auth0UserInfo.app_metadata || {} })
86 .then(token => {
87 return Promise.resolve([auth0UserInfo, token]);
88 });
89 });
90 })
91 .spread((userInfo, token) => {
92 return utdbhelper.createItem({
93 userId: userInfo.user_id,
94 userToken: token,
95 })
96 .then(() => { return token; });
97 });
98};
99
100/**
101 * Login to Auth0 using an Access Token
102 *
103 * @param {string} accessToken - The access token to validate
104 * @param {string} appToken - The appToken issued to the application that identifies the vendor
105 *
106 * @returns {Promise}
107 */
108module.exports.loginWithAccessToken = ({ accessToken, appToken }) => {
109 logger('loginWithAccessToken', accessToken, appToken);
110 if (!accessToken) {
111 return Promise.reject('[400] Invalid param: "accessToken"');
112 }
113
114 return authhelper.decodeAppToken(appToken)
115 .then(decodedToken => {
116 return atdbhelper.getItem(decodedToken.clientName);
117 })
118 .then(result => {
119 if (result.Count > 0) {
120 // Get user details from accessToken
121 return auth0lib.getUserInfo(accessToken);
122 }
123
124 return Promise.reject('[404] Non existing client');
125 })
126 .then((data) => JSON.parse(data))
127 .then((data) => authhelper.getUserInfo(data.identities[0].user_id))
128 .then((data) => {
129 logger('loginWithAccessToken: got userInfo from manage.', data);
130 return {
131 ...data,
132 app_metadata: data.app_metadata || {},
133 userInfo: JSON.parse(data.Payload),
134 };
135 })
136 .then((data) => {
137 // A userToken is given back to the user which will essentially act as a session token.
138 return signUserToken({ userInfo: data.userInfo, appToken, app_metadata: data.app_metadata || {} })
139 .then(token => {
140 return Promise.resolve([data.userInfo, token]);
141 });
142 })
143 .spread((userInfo, token) => {
144 logger('loginWithAccessToken', 'Attempting to create item', userInfo, token);
145 return utdbhelper.createItem({
146 userId: userInfo.uuid,
147 userToken: token,
148 })
149 .then(() => token);
150 });
151};
152
153/**
154 * Login to Auth0 via a redirection code
155 *
156 * @param {string} code - The redirection code
157 * @param {string} redirectUri - The URI to redirect to on success (callbackUrl)
158 * @param {string} appToken - The appToken issued to the application that identifies the vendor
159 *
160 * @returns {Promise}
161 */
162module.exports.loginWithCode = ({ code, redirectUri, appToken }) => {
163 if (!code) {
164 return Promise.reject('[400] Invalid param: "code"');
165 }
166
167 if (!redirectUri) {
168 return Promise.reject('[400] Invalid param: "redirectUri"');
169 }
170
171 logger('loginWithCode', code, redirectUri);
172
173 return authhelper.decodeAppToken(appToken)
174 .then(decodedToken => {
175 return atdbhelper.getItem(decodedToken.clientName);
176 })
177 .then(result => {
178 if (result.Count > 0) {
179 // Attempt to get accessToken from code
180 return auth0lib.getAccessToken(code, redirectUri);
181 }
182
183 return Promise.reject('[404] Non existing client');
184 })
185 .then((response) => response.access_token)
186 .then((accessToken) => module.exports.loginWithAccessToken({ accessToken, appToken }));
187};
188
189module.exports.ssoLogin = (event, context, cb) => {
190 const { appToken } = event.data;
191 if (!appToken) {
192 cb(new Error('[400] Invalid param: "appToken"'));
193 return;
194 }
195
196 const { code, redirectUri } = event.data;
197 if (code) {
198 module.exports.loginWithCode(event.data)
199 .then((token) => cb(null, { userToken: token, success: true, }))
200 .catch((error) => {
201 logger('loginWithCode', `Error: ${error}`);
202 cb(new Error(error));
203 });
204 } else {
205 module.exports.loginWithAccessToken(event.data)
206 .then((token) => cb(null, { userToken: token, success: true, }))
207 .catch((error) => {
208 logger('loginWithAccessToken', `Error: ${error}`);
209 cb(new Error(error));
210 });
211 }
212};
213
214/**
215 * Login the user with credentials
216 *
217 * @param event - The API Gateway event
218 * @param context - The Lambda context object
219 * @param cb - The Lambda callback pointer
220 */
221module.exports.login = (event, context, cb) => {
222 const { appToken } = event.data;
223 if (!appToken) {
224 cb(new Error('[400] Invalid param: "appToken"'));
225 return;
226 }
227
228 module.exports.loginWithCredentials(event.data)
229 .then((token) => cb(null, { userToken: token, success: true, }))
230 .catch((error) => {
231 logger('loginWithCredentials', `Error: ${error}, Username: [${event.data.username}], AppToken: [${event.data.appToken}]`);
232 cb(new Error(error));
233 });
234};
235
236/**
237 * Logout the user
238 *
239 * @param event - The API Gateway event
240 * @param context - The Lambda context object
241 * @param cb - The Lambda callback pointer
242 */
243module.exports.logout = (event, context, cb) => {
244 // Remove the users userToken from the database
245 const { userToken } = event.data;
246
247 if (!event.data && !userToken) {
248 cb(new Error('[400] Invalid param: "userToken"'));
249 return;
250 }
251
252 logger('logout', `Logging out for: ${userToken}`);
253
254 return authhelper.getSecretFromAppName(event.clientName)
255 .then(clientSecret => {
256 return verifyAsync(userToken, clientSecret)
257 })
258 .then(decodedToken => {
259 let userInfo = decodedToken.userInfo;
260 return utdbhelper.getItem(userInfo.uuid)
261 .then(result => {
262 return [userInfo, result];
263 });
264 })
265 .spread((userInfo, result) => {
266 if (result.Count === 0) throw '[404] userToken not found';
267
268 // It exists, so let us delete it.
269 return utdbhelper.removeItem({
270 userId: `auth0|${userInfo.uuid}`,
271 });
272 })
273 .then(result => cb(null, { success: true }))
274 .catch(error => {
275 logger('logout', `Error: ${error}`);
276 cb(new Error(error));
277 });
278};
279
280/**
281 * Refresh the userToken if it is still valid
282 *
283 * @param event - The API Gateway event
284 * @param context - The Lambda context object
285 * @param cb - The Lambda callback pointer
286 */
287module.exports.refreshUserToken = (event, context, cb) => {
288 const { userToken } = event.data;
289
290 if (!userToken) {
291 cb(new Error('[400] Invalid param: "userToken"'));
292 return;
293 }
294
295 logger('refreshUserToken', `Attempting refresh for: ${userToken}`);
296
297 return authhelper.getSecretFromAppName(event.clientName)
298 .then(clientSecret => {
299 return verifyAsync(userToken, clientSecret);
300 })
301 .then(decodedToken => {
302 const userInfo = decodedToken.userInfo;
303 return [utdbhelper.getItem(`auth0|${userInfo.uuid}`), userInfo, decodedToken];
304 })
305 .spread((result, userInfo, decodedToken) => {
306 if (result.Count > 0) {
307 // It does actually exist and thus has not been revoked. Continue.
308 return [decodedToken, userInfo, result];
309 }
310
311 return Promise.reject('[404] userToken not found');
312 })
313 .spread((decodedToken, userInfo, result) => {
314 return authhelper.getUserInfo(userInfo.uuid)
315 .then((data) => {
316 return signUserToken({ userInfo: JSON.parse(data.Payload), appToken: decodedToken.appToken, app_metadata: decodedToken.app_metadata || {} })
317 .then(token => {
318 return Promise.resolve([userInfo, token]);
319 });
320 });
321 })
322 .spread((userInfo, token) => {
323 // Save token to the database against the users userId (user_id)
324 return utdbhelper.updateItem({
325 userId: `auth0|${userInfo.uuid}`,
326 userToken: token,
327 }).then(result => {
328 return [token, result];
329 });
330 })
331 .spread((token, result) => cb(null, { userToken: token, success: true }))
332 .catch(error => {
333 logger('refreshUserToken', `Error: ${error}`);
334 cb(new Error(error));
335 });
336};
337
338// Authenticated via x-api-key. Only administrator validated app
339/**
340 * Refresh the userToken with the specified appToken if both are valid
341 *
342 * @param event - The API Gateway event
343 * @param context - The Lambda context object
344 * @param cb - The Lambda callback pointer
345 */
346module.exports.refreshUserTokenWithAppToken = (event, context, cb) => {
347 const { userToken, appToken, appTokenNew } = event.data;
348
349 if (!userToken) cb(new Error('[400] Invalid params: "userToken"'));
350
351 // Decode token
352 return authhelper.decodeUserToken(userToken, appToken)
353 .then((decodedToken) => {
354 return signUserToken({ userInfo: decodedToken.userInfo
355 , appToken: appTokenNew, app_metadata: decodedToken.app_metadata || {} });
356 })
357 .then((signedToken) => cb(null, { userToken: signedToken, success: true }))
358 .catch((error) => {
359 logger(`refreshUserTokenWithAppToken: unable to re-sign token ${JSON.stringify(error)}`);
360 cb(new Error(error));
361 });
362};
363
364// TODO: Update userToken table with uuid|appToken as ID