UNPKG

63.2 kBJavaScriptView Raw
1/*!
2 * Copyright 2016 Amazon.com,
3 * Inc. or its affiliates. All Rights Reserved.
4 *
5 * Licensed under the Amazon Software License (the "License").
6 * You may not use this file except in compliance with the
7 * License. A copy of the License is located at
8 *
9 * http://aws.amazon.com/asl/
10 *
11 * or in the "license" file accompanying this file. This file is
12 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
13 * CONDITIONS OF ANY KIND, express or implied. See the License
14 * for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18import { Buffer } from 'buffer';
19import CryptoJS from 'crypto-js/core';
20import TypedArrays from 'crypto-js/lib-typedarrays'; // necessary for crypto js
21import Base64 from 'crypto-js/enc-base64';
22import HmacSHA256 from 'crypto-js/hmac-sha256';
23
24import BigInteger from './BigInteger';
25import AuthenticationHelper from './AuthenticationHelper';
26import CognitoAccessToken from './CognitoAccessToken';
27import CognitoIdToken from './CognitoIdToken';
28import CognitoRefreshToken from './CognitoRefreshToken';
29import CognitoUserSession from './CognitoUserSession';
30import DateHelper from './DateHelper';
31import CognitoUserAttribute from './CognitoUserAttribute';
32import StorageHelper from './StorageHelper';
33
34/**
35 * @callback nodeCallback
36 * @template T result
37 * @param {*} err The operation failure reason, or null.
38 * @param {T} result The operation result.
39 */
40
41/**
42 * @callback onFailure
43 * @param {*} err Failure reason.
44 */
45
46/**
47 * @callback onSuccess
48 * @template T result
49 * @param {T} result The operation result.
50 */
51
52/**
53 * @callback mfaRequired
54 * @param {*} details MFA challenge details.
55 */
56
57/**
58 * @callback customChallenge
59 * @param {*} details Custom challenge details.
60 */
61
62/**
63 * @callback inputVerificationCode
64 * @param {*} data Server response.
65 */
66
67/**
68 * @callback authSuccess
69 * @param {CognitoUserSession} session The new session.
70 * @param {bool=} userConfirmationNecessary User must be confirmed.
71 */
72
73const isBrowser = typeof navigator !== 'undefined';
74const userAgent = isBrowser ? navigator.userAgent : 'nodejs';
75
76/** @class */
77export default class CognitoUser {
78 /**
79 * Constructs a new CognitoUser object
80 * @param {object} data Creation options
81 * @param {string} data.Username The user's username.
82 * @param {CognitoUserPool} data.Pool Pool containing the user.
83 * @param {object} data.Storage Optional storage object.
84 */
85 constructor(data) {
86 if (data == null || data.Username == null || data.Pool == null) {
87 throw new Error('Username and pool information are required.');
88 }
89
90 this.username = data.Username || '';
91 this.pool = data.Pool;
92 this.Session = null;
93
94 this.client = data.Pool.client;
95
96 this.signInUserSession = null;
97 this.authenticationFlowType = 'USER_SRP_AUTH';
98
99 this.storage = data.Storage || new StorageHelper().getStorage();
100
101 this.keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`;
102 this.userDataKey = `${this.keyPrefix}.${this.username}.userData`;
103 }
104
105 /**
106 * Sets the session for this user
107 * @param {CognitoUserSession} signInUserSession the session
108 * @returns {void}
109 */
110 setSignInUserSession(signInUserSession) {
111 this.clearCachedUserData();
112 this.signInUserSession = signInUserSession;
113 this.cacheTokens();
114 }
115
116 /**
117 * @returns {CognitoUserSession} the current session for this user
118 */
119 getSignInUserSession() {
120 return this.signInUserSession;
121 }
122
123 /**
124 * @returns {string} the user's username
125 */
126 getUsername() {
127 return this.username;
128 }
129
130 /**
131 * @returns {String} the authentication flow type
132 */
133 getAuthenticationFlowType() {
134 return this.authenticationFlowType;
135 }
136
137 /**
138 * sets authentication flow type
139 * @param {string} authenticationFlowType New value.
140 * @returns {void}
141 */
142 setAuthenticationFlowType(authenticationFlowType) {
143 this.authenticationFlowType = authenticationFlowType;
144 }
145
146 /**
147 * This is used for authenticating the user through the custom authentication flow.
148 * @param {AuthenticationDetails} authDetails Contains the authentication data
149 * @param {object} callback Result callback map.
150 * @param {onFailure} callback.onFailure Called on any error.
151 * @param {customChallenge} callback.customChallenge Custom challenge
152 * response required to continue.
153 * @param {authSuccess} callback.onSuccess Called on success with the new session.
154 * @returns {void}
155 */
156 initiateAuth(authDetails, callback) {
157 const authParameters = authDetails.getAuthParameters();
158 authParameters.USERNAME = this.username;
159
160 const clientMetaData =
161 Object.keys(authDetails.getValidationData()).length !== 0
162 ? authDetails.getValidationData()
163 : authDetails.getClientMetadata();
164
165 const jsonReq = {
166 AuthFlow: 'CUSTOM_AUTH',
167 ClientId: this.pool.getClientId(),
168 AuthParameters: authParameters,
169 ClientMetadata: clientMetaData,
170 };
171 if (this.getUserContextData()) {
172 jsonReq.UserContextData = this.getUserContextData();
173 }
174
175 this.client.request('InitiateAuth', jsonReq, (err, data) => {
176 if (err) {
177 return callback.onFailure(err);
178 }
179 const challengeName = data.ChallengeName;
180 const challengeParameters = data.ChallengeParameters;
181
182 if (challengeName === 'CUSTOM_CHALLENGE') {
183 this.Session = data.Session;
184 return callback.customChallenge(challengeParameters);
185 }
186 this.signInUserSession = this.getCognitoUserSession(
187 data.AuthenticationResult
188 );
189 this.cacheTokens();
190 return callback.onSuccess(this.signInUserSession);
191 });
192 }
193
194 /**
195 * This is used for authenticating the user.
196 * stuff
197 * @param {AuthenticationDetails} authDetails Contains the authentication data
198 * @param {object} callback Result callback map.
199 * @param {onFailure} callback.onFailure Called on any error.
200 * @param {newPasswordRequired} callback.newPasswordRequired new
201 * password and any required attributes are required to continue
202 * @param {mfaRequired} callback.mfaRequired MFA code
203 * required to continue.
204 * @param {customChallenge} callback.customChallenge Custom challenge
205 * response required to continue.
206 * @param {authSuccess} callback.onSuccess Called on success with the new session.
207 * @returns {void}
208 */
209 authenticateUser(authDetails, callback) {
210 if (this.authenticationFlowType === 'USER_PASSWORD_AUTH') {
211 return this.authenticateUserPlainUsernamePassword(authDetails, callback);
212 } else if (
213 this.authenticationFlowType === 'USER_SRP_AUTH' ||
214 this.authenticationFlowType === 'CUSTOM_AUTH'
215 ) {
216 return this.authenticateUserDefaultAuth(authDetails, callback);
217 }
218 return callback.onFailure(
219 new Error('Authentication flow type is invalid.')
220 );
221 }
222
223 /**
224 * PRIVATE ONLY: This is an internal only method and should not
225 * be directly called by the consumers.
226 * It calls the AuthenticationHelper for SRP related
227 * stuff
228 * @param {AuthenticationDetails} authDetails Contains the authentication data
229 * @param {object} callback Result callback map.
230 * @param {onFailure} callback.onFailure Called on any error.
231 * @param {newPasswordRequired} callback.newPasswordRequired new
232 * password and any required attributes are required to continue
233 * @param {mfaRequired} callback.mfaRequired MFA code
234 * required to continue.
235 * @param {customChallenge} callback.customChallenge Custom challenge
236 * response required to continue.
237 * @param {authSuccess} callback.onSuccess Called on success with the new session.
238 * @returns {void}
239 */
240 authenticateUserDefaultAuth(authDetails, callback) {
241 const authenticationHelper = new AuthenticationHelper(
242 this.pool.getUserPoolId().split('_')[1]
243 );
244 const dateHelper = new DateHelper();
245
246 let serverBValue;
247 let salt;
248 const authParameters = {};
249
250 if (this.deviceKey != null) {
251 authParameters.DEVICE_KEY = this.deviceKey;
252 }
253
254 authParameters.USERNAME = this.username;
255 authenticationHelper.getLargeAValue((errOnAValue, aValue) => {
256 // getLargeAValue callback start
257 if (errOnAValue) {
258 callback.onFailure(errOnAValue);
259 }
260
261 authParameters.SRP_A = aValue.toString(16);
262
263 if (this.authenticationFlowType === 'CUSTOM_AUTH') {
264 authParameters.CHALLENGE_NAME = 'SRP_A';
265 }
266
267 const clientMetaData =
268 Object.keys(authDetails.getValidationData()).length !== 0
269 ? authDetails.getValidationData()
270 : authDetails.getClientMetadata();
271
272 const jsonReq = {
273 AuthFlow: this.authenticationFlowType,
274 ClientId: this.pool.getClientId(),
275 AuthParameters: authParameters,
276 ClientMetadata: clientMetaData,
277 };
278 if (this.getUserContextData(this.username)) {
279 jsonReq.UserContextData = this.getUserContextData(this.username);
280 }
281
282 this.client.request('InitiateAuth', jsonReq, (err, data) => {
283 if (err) {
284 return callback.onFailure(err);
285 }
286
287 const challengeParameters = data.ChallengeParameters;
288
289 this.username = challengeParameters.USER_ID_FOR_SRP;
290 serverBValue = new BigInteger(challengeParameters.SRP_B, 16);
291 salt = new BigInteger(challengeParameters.SALT, 16);
292 this.getCachedDeviceKeyAndPassword();
293
294 authenticationHelper.getPasswordAuthenticationKey(
295 this.username,
296 authDetails.getPassword(),
297 serverBValue,
298 salt,
299 (errOnHkdf, hkdf) => {
300 // getPasswordAuthenticationKey callback start
301 if (errOnHkdf) {
302 callback.onFailure(errOnHkdf);
303 }
304
305 const dateNow = dateHelper.getNowString();
306
307 const message = CryptoJS.lib.WordArray.create(
308 Buffer.concat([
309 Buffer.from(this.pool.getUserPoolId().split('_')[1], 'utf8'),
310 Buffer.from(this.username, 'utf8'),
311 Buffer.from(challengeParameters.SECRET_BLOCK, 'base64'),
312 Buffer.from(dateNow, 'utf8'),
313 ])
314 );
315 const key = CryptoJS.lib.WordArray.create(hkdf);
316 const signatureString = Base64.stringify(HmacSHA256(message, key));
317
318 const challengeResponses = {};
319
320 challengeResponses.USERNAME = this.username;
321 challengeResponses.PASSWORD_CLAIM_SECRET_BLOCK =
322 challengeParameters.SECRET_BLOCK;
323 challengeResponses.TIMESTAMP = dateNow;
324 challengeResponses.PASSWORD_CLAIM_SIGNATURE = signatureString;
325
326 if (this.deviceKey != null) {
327 challengeResponses.DEVICE_KEY = this.deviceKey;
328 }
329
330 const respondToAuthChallenge = (challenge, challengeCallback) =>
331 this.client.request(
332 'RespondToAuthChallenge',
333 challenge,
334 (errChallenge, dataChallenge) => {
335 if (
336 errChallenge &&
337 errChallenge.code === 'ResourceNotFoundException' &&
338 errChallenge.message.toLowerCase().indexOf('device') !== -1
339 ) {
340 challengeResponses.DEVICE_KEY = null;
341 this.deviceKey = null;
342 this.randomPassword = null;
343 this.deviceGroupKey = null;
344 this.clearCachedDeviceKeyAndPassword();
345 return respondToAuthChallenge(challenge, challengeCallback);
346 }
347 return challengeCallback(errChallenge, dataChallenge);
348 }
349 );
350
351 const jsonReqResp = {
352 ChallengeName: 'PASSWORD_VERIFIER',
353 ClientId: this.pool.getClientId(),
354 ChallengeResponses: challengeResponses,
355 Session: data.Session,
356 ClientMetadata: clientMetaData,
357 };
358 if (this.getUserContextData()) {
359 jsonReqResp.UserContextData = this.getUserContextData();
360 }
361 respondToAuthChallenge(
362 jsonReqResp,
363 (errAuthenticate, dataAuthenticate) => {
364 if (errAuthenticate) {
365 return callback.onFailure(errAuthenticate);
366 }
367
368 return this.authenticateUserInternal(
369 dataAuthenticate,
370 authenticationHelper,
371 callback
372 );
373 }
374 );
375 return undefined;
376 // getPasswordAuthenticationKey callback end
377 }
378 );
379 return undefined;
380 });
381 // getLargeAValue callback end
382 });
383 }
384
385 /**
386 * PRIVATE ONLY: This is an internal only method and should not
387 * be directly called by the consumers.
388 * @param {AuthenticationDetails} authDetails Contains the authentication data.
389 * @param {object} callback Result callback map.
390 * @param {onFailure} callback.onFailure Called on any error.
391 * @param {mfaRequired} callback.mfaRequired MFA code
392 * required to continue.
393 * @param {authSuccess} callback.onSuccess Called on success with the new session.
394 * @returns {void}
395 */
396 authenticateUserPlainUsernamePassword(authDetails, callback) {
397 const authParameters = {};
398 authParameters.USERNAME = this.username;
399 authParameters.PASSWORD = authDetails.getPassword();
400 if (!authParameters.PASSWORD) {
401 callback.onFailure(new Error('PASSWORD parameter is required'));
402 return;
403 }
404 const authenticationHelper = new AuthenticationHelper(
405 this.pool.getUserPoolId().split('_')[1]
406 );
407 this.getCachedDeviceKeyAndPassword();
408 if (this.deviceKey != null) {
409 authParameters.DEVICE_KEY = this.deviceKey;
410 }
411
412 const clientMetaData =
413 Object.keys(authDetails.getValidationData()).length !== 0
414 ? authDetails.getValidationData()
415 : authDetails.getClientMetadata();
416
417 const jsonReq = {
418 AuthFlow: 'USER_PASSWORD_AUTH',
419 ClientId: this.pool.getClientId(),
420 AuthParameters: authParameters,
421 ClientMetadata: clientMetaData,
422 };
423 if (this.getUserContextData(this.username)) {
424 jsonReq.UserContextData = this.getUserContextData(this.username);
425 }
426 // USER_PASSWORD_AUTH happens in a single round-trip: client sends userName and password,
427 // Cognito UserPools verifies password and returns tokens.
428 this.client.request('InitiateAuth', jsonReq, (err, authResult) => {
429 if (err) {
430 return callback.onFailure(err);
431 }
432 return this.authenticateUserInternal(
433 authResult,
434 authenticationHelper,
435 callback
436 );
437 });
438 }
439
440 /**
441 * PRIVATE ONLY: This is an internal only method and should not
442 * be directly called by the consumers.
443 * @param {object} dataAuthenticate authentication data
444 * @param {object} authenticationHelper helper created
445 * @param {callback} callback passed on from caller
446 * @returns {void}
447 */
448 authenticateUserInternal(dataAuthenticate, authenticationHelper, callback) {
449 const challengeName = dataAuthenticate.ChallengeName;
450 const challengeParameters = dataAuthenticate.ChallengeParameters;
451
452 if (challengeName === 'SMS_MFA') {
453 this.Session = dataAuthenticate.Session;
454 return callback.mfaRequired(challengeName, challengeParameters);
455 }
456
457 if (challengeName === 'SELECT_MFA_TYPE') {
458 this.Session = dataAuthenticate.Session;
459 return callback.selectMFAType(challengeName, challengeParameters);
460 }
461
462 if (challengeName === 'MFA_SETUP') {
463 this.Session = dataAuthenticate.Session;
464 return callback.mfaSetup(challengeName, challengeParameters);
465 }
466
467 if (challengeName === 'SOFTWARE_TOKEN_MFA') {
468 this.Session = dataAuthenticate.Session;
469 return callback.totpRequired(challengeName, challengeParameters);
470 }
471
472 if (challengeName === 'CUSTOM_CHALLENGE') {
473 this.Session = dataAuthenticate.Session;
474 return callback.customChallenge(challengeParameters);
475 }
476
477 if (challengeName === 'NEW_PASSWORD_REQUIRED') {
478 this.Session = dataAuthenticate.Session;
479
480 let userAttributes = null;
481 let rawRequiredAttributes = null;
482 const requiredAttributes = [];
483 const userAttributesPrefix = authenticationHelper.getNewPasswordRequiredChallengeUserAttributePrefix();
484
485 if (challengeParameters) {
486 userAttributes = JSON.parse(
487 dataAuthenticate.ChallengeParameters.userAttributes
488 );
489 rawRequiredAttributes = JSON.parse(
490 dataAuthenticate.ChallengeParameters.requiredAttributes
491 );
492 }
493
494 if (rawRequiredAttributes) {
495 for (let i = 0; i < rawRequiredAttributes.length; i++) {
496 requiredAttributes[i] = rawRequiredAttributes[i].substr(
497 userAttributesPrefix.length
498 );
499 }
500 }
501 return callback.newPasswordRequired(userAttributes, requiredAttributes);
502 }
503
504 if (challengeName === 'DEVICE_SRP_AUTH') {
505 this.getDeviceResponse(callback);
506 return undefined;
507 }
508
509 this.signInUserSession = this.getCognitoUserSession(
510 dataAuthenticate.AuthenticationResult
511 );
512 this.challengeName = challengeName;
513 this.cacheTokens();
514
515 const newDeviceMetadata =
516 dataAuthenticate.AuthenticationResult.NewDeviceMetadata;
517 if (newDeviceMetadata == null) {
518 return callback.onSuccess(this.signInUserSession);
519 }
520
521 authenticationHelper.generateHashDevice(
522 dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceGroupKey,
523 dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceKey,
524 errGenHash => {
525 if (errGenHash) {
526 return callback.onFailure(errGenHash);
527 }
528
529 const deviceSecretVerifierConfig = {
530 Salt: Buffer.from(
531 authenticationHelper.getSaltDevices(),
532 'hex'
533 ).toString('base64'),
534 PasswordVerifier: Buffer.from(
535 authenticationHelper.getVerifierDevices(),
536 'hex'
537 ).toString('base64'),
538 };
539
540 this.verifierDevices = deviceSecretVerifierConfig.PasswordVerifier;
541 this.deviceGroupKey = newDeviceMetadata.DeviceGroupKey;
542 this.randomPassword = authenticationHelper.getRandomPassword();
543
544 this.client.request(
545 'ConfirmDevice',
546 {
547 DeviceKey: newDeviceMetadata.DeviceKey,
548 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
549 DeviceSecretVerifierConfig: deviceSecretVerifierConfig,
550 DeviceName: userAgent,
551 },
552 (errConfirm, dataConfirm) => {
553 if (errConfirm) {
554 return callback.onFailure(errConfirm);
555 }
556
557 this.deviceKey =
558 dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceKey;
559 this.cacheDeviceKeyAndPassword();
560 if (dataConfirm.UserConfirmationNecessary === true) {
561 return callback.onSuccess(
562 this.signInUserSession,
563 dataConfirm.UserConfirmationNecessary
564 );
565 }
566 return callback.onSuccess(this.signInUserSession);
567 }
568 );
569 return undefined;
570 }
571 );
572 return undefined;
573 }
574
575 /**
576 * This method is user to complete the NEW_PASSWORD_REQUIRED challenge.
577 * Pass the new password with any new user attributes to be updated.
578 * User attribute keys must be of format userAttributes.<attribute_name>.
579 * @param {string} newPassword new password for this user
580 * @param {object} requiredAttributeData map with values for all required attributes
581 * @param {object} callback Result callback map.
582 * @param {onFailure} callback.onFailure Called on any error.
583 * @param {mfaRequired} callback.mfaRequired MFA code required to continue.
584 * @param {customChallenge} callback.customChallenge Custom challenge
585 * response required to continue.
586 * @param {authSuccess} callback.onSuccess Called on success with the new session.
587 * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger
588 * @returns {void}
589 */
590 completeNewPasswordChallenge(
591 newPassword,
592 requiredAttributeData,
593 callback,
594 clientMetadata
595 ) {
596 if (!newPassword) {
597 return callback.onFailure(new Error('New password is required.'));
598 }
599 const authenticationHelper = new AuthenticationHelper(
600 this.pool.getUserPoolId().split('_')[1]
601 );
602 const userAttributesPrefix = authenticationHelper.getNewPasswordRequiredChallengeUserAttributePrefix();
603
604 const finalUserAttributes = {};
605 if (requiredAttributeData) {
606 Object.keys(requiredAttributeData).forEach(key => {
607 finalUserAttributes[userAttributesPrefix + key] =
608 requiredAttributeData[key];
609 });
610 }
611
612 finalUserAttributes.NEW_PASSWORD = newPassword;
613 finalUserAttributes.USERNAME = this.username;
614 const jsonReq = {
615 ChallengeName: 'NEW_PASSWORD_REQUIRED',
616 ClientId: this.pool.getClientId(),
617 ChallengeResponses: finalUserAttributes,
618 Session: this.Session,
619 ClientMetadata: clientMetadata,
620 };
621 if (this.getUserContextData()) {
622 jsonReq.UserContextData = this.getUserContextData();
623 }
624
625 this.client.request(
626 'RespondToAuthChallenge',
627 jsonReq,
628 (errAuthenticate, dataAuthenticate) => {
629 if (errAuthenticate) {
630 return callback.onFailure(errAuthenticate);
631 }
632 return this.authenticateUserInternal(
633 dataAuthenticate,
634 authenticationHelper,
635 callback
636 );
637 }
638 );
639 return undefined;
640 }
641
642 /**
643 * This is used to get a session using device authentication. It is called at the end of user
644 * authentication
645 *
646 * @param {object} callback Result callback map.
647 * @param {onFailure} callback.onFailure Called on any error.
648 * @param {authSuccess} callback.onSuccess Called on success with the new session.
649 * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger
650 * @returns {void}
651 * @private
652 */
653 getDeviceResponse(callback, clientMetadata) {
654 const authenticationHelper = new AuthenticationHelper(this.deviceGroupKey);
655 const dateHelper = new DateHelper();
656
657 const authParameters = {};
658
659 authParameters.USERNAME = this.username;
660 authParameters.DEVICE_KEY = this.deviceKey;
661 authenticationHelper.getLargeAValue((errAValue, aValue) => {
662 // getLargeAValue callback start
663 if (errAValue) {
664 callback.onFailure(errAValue);
665 }
666
667 authParameters.SRP_A = aValue.toString(16);
668
669 const jsonReq = {
670 ChallengeName: 'DEVICE_SRP_AUTH',
671 ClientId: this.pool.getClientId(),
672 ChallengeResponses: authParameters,
673 ClientMetadata: clientMetadata,
674 };
675 if (this.getUserContextData()) {
676 jsonReq.UserContextData = this.getUserContextData();
677 }
678 this.client.request('RespondToAuthChallenge', jsonReq, (err, data) => {
679 if (err) {
680 return callback.onFailure(err);
681 }
682
683 const challengeParameters = data.ChallengeParameters;
684
685 const serverBValue = new BigInteger(challengeParameters.SRP_B, 16);
686 const salt = new BigInteger(challengeParameters.SALT, 16);
687
688 authenticationHelper.getPasswordAuthenticationKey(
689 this.deviceKey,
690 this.randomPassword,
691 serverBValue,
692 salt,
693 (errHkdf, hkdf) => {
694 // getPasswordAuthenticationKey callback start
695 if (errHkdf) {
696 return callback.onFailure(errHkdf);
697 }
698
699 const dateNow = dateHelper.getNowString();
700
701 const message = CryptoJS.lib.WordArray.create(
702 Buffer.concat([
703 Buffer.from(this.deviceGroupKey, 'utf8'),
704 Buffer.from(this.deviceKey, 'utf8'),
705 Buffer.from(challengeParameters.SECRET_BLOCK, 'base64'),
706 Buffer.from(dateNow, 'utf8'),
707 ])
708 );
709 const key = CryptoJS.lib.WordArray.create(hkdf);
710 const signatureString = Base64.stringify(HmacSHA256(message, key));
711
712 const challengeResponses = {};
713
714 challengeResponses.USERNAME = this.username;
715 challengeResponses.PASSWORD_CLAIM_SECRET_BLOCK =
716 challengeParameters.SECRET_BLOCK;
717 challengeResponses.TIMESTAMP = dateNow;
718 challengeResponses.PASSWORD_CLAIM_SIGNATURE = signatureString;
719 challengeResponses.DEVICE_KEY = this.deviceKey;
720
721 const jsonReqResp = {
722 ChallengeName: 'DEVICE_PASSWORD_VERIFIER',
723 ClientId: this.pool.getClientId(),
724 ChallengeResponses: challengeResponses,
725 Session: data.Session,
726 };
727 if (this.getUserContextData()) {
728 jsonReqResp.UserContextData = this.getUserContextData();
729 }
730
731 this.client.request(
732 'RespondToAuthChallenge',
733 jsonReqResp,
734 (errAuthenticate, dataAuthenticate) => {
735 if (errAuthenticate) {
736 return callback.onFailure(errAuthenticate);
737 }
738
739 this.signInUserSession = this.getCognitoUserSession(
740 dataAuthenticate.AuthenticationResult
741 );
742 this.cacheTokens();
743
744 return callback.onSuccess(this.signInUserSession);
745 }
746 );
747 return undefined;
748 // getPasswordAuthenticationKey callback end
749 }
750 );
751 return undefined;
752 });
753 // getLargeAValue callback end
754 });
755 }
756
757 /**
758 * This is used for a certain user to confirm the registration by using a confirmation code
759 * @param {string} confirmationCode Code entered by user.
760 * @param {bool} forceAliasCreation Allow migrating from an existing email / phone number.
761 * @param {nodeCallback<string>} callback Called on success or error.
762 * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger
763 * @returns {void}
764 */
765 confirmRegistration(
766 confirmationCode,
767 forceAliasCreation,
768 callback,
769 clientMetadata
770 ) {
771 const jsonReq = {
772 ClientId: this.pool.getClientId(),
773 ConfirmationCode: confirmationCode,
774 Username: this.username,
775 ForceAliasCreation: forceAliasCreation,
776 ClientMetadata: clientMetadata,
777 };
778 if (this.getUserContextData()) {
779 jsonReq.UserContextData = this.getUserContextData();
780 }
781 this.client.request('ConfirmSignUp', jsonReq, err => {
782 if (err) {
783 return callback(err, null);
784 }
785 return callback(null, 'SUCCESS');
786 });
787 }
788
789 /**
790 * This is used by the user once he has the responses to a custom challenge
791 * @param {string} answerChallenge The custom challenge answer.
792 * @param {object} callback Result callback map.
793 * @param {onFailure} callback.onFailure Called on any error.
794 * @param {customChallenge} callback.customChallenge
795 * Custom challenge response required to continue.
796 * @param {authSuccess} callback.onSuccess Called on success with the new session.
797 * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger
798 * @returns {void}
799 */
800 sendCustomChallengeAnswer(answerChallenge, callback, clientMetadata) {
801 const challengeResponses = {};
802 challengeResponses.USERNAME = this.username;
803 challengeResponses.ANSWER = answerChallenge;
804
805 const authenticationHelper = new AuthenticationHelper(
806 this.pool.getUserPoolId().split('_')[1]
807 );
808 this.getCachedDeviceKeyAndPassword();
809 if (this.deviceKey != null) {
810 challengeResponses.DEVICE_KEY = this.deviceKey;
811 }
812
813 const jsonReq = {
814 ChallengeName: 'CUSTOM_CHALLENGE',
815 ChallengeResponses: challengeResponses,
816 ClientId: this.pool.getClientId(),
817 Session: this.Session,
818 ClientMetadata: clientMetadata,
819 };
820 if (this.getUserContextData()) {
821 jsonReq.UserContextData = this.getUserContextData();
822 }
823 this.client.request('RespondToAuthChallenge', jsonReq, (err, data) => {
824 if (err) {
825 return callback.onFailure(err);
826 }
827
828 return this.authenticateUserInternal(
829 data,
830 authenticationHelper,
831 callback
832 );
833 });
834 }
835
836 /**
837 * This is used by the user once he has an MFA code
838 * @param {string} confirmationCode The MFA code entered by the user.
839 * @param {object} callback Result callback map.
840 * @param {string} mfaType The mfa we are replying to.
841 * @param {onFailure} callback.onFailure Called on any error.
842 * @param {authSuccess} callback.onSuccess Called on success with the new session.
843 * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger
844 * @returns {void}
845 */
846 sendMFACode(confirmationCode, callback, mfaType, clientMetadata) {
847 const challengeResponses = {};
848 challengeResponses.USERNAME = this.username;
849 challengeResponses.SMS_MFA_CODE = confirmationCode;
850 const mfaTypeSelection = mfaType || 'SMS_MFA';
851 if (mfaTypeSelection === 'SOFTWARE_TOKEN_MFA') {
852 challengeResponses.SOFTWARE_TOKEN_MFA_CODE = confirmationCode;
853 }
854
855 if (this.deviceKey != null) {
856 challengeResponses.DEVICE_KEY = this.deviceKey;
857 }
858
859 const jsonReq = {
860 ChallengeName: mfaTypeSelection,
861 ChallengeResponses: challengeResponses,
862 ClientId: this.pool.getClientId(),
863 Session: this.Session,
864 ClientMetadata: clientMetadata,
865 };
866 if (this.getUserContextData()) {
867 jsonReq.UserContextData = this.getUserContextData();
868 }
869
870 this.client.request(
871 'RespondToAuthChallenge',
872 jsonReq,
873 (err, dataAuthenticate) => {
874 if (err) {
875 return callback.onFailure(err);
876 }
877
878 const challengeName = dataAuthenticate.ChallengeName;
879
880 if (challengeName === 'DEVICE_SRP_AUTH') {
881 this.getDeviceResponse(callback);
882 return undefined;
883 }
884
885 this.signInUserSession = this.getCognitoUserSession(
886 dataAuthenticate.AuthenticationResult
887 );
888 this.cacheTokens();
889
890 if (dataAuthenticate.AuthenticationResult.NewDeviceMetadata == null) {
891 return callback.onSuccess(this.signInUserSession);
892 }
893
894 const authenticationHelper = new AuthenticationHelper(
895 this.pool.getUserPoolId().split('_')[1]
896 );
897 authenticationHelper.generateHashDevice(
898 dataAuthenticate.AuthenticationResult.NewDeviceMetadata
899 .DeviceGroupKey,
900 dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceKey,
901 errGenHash => {
902 if (errGenHash) {
903 return callback.onFailure(errGenHash);
904 }
905
906 const deviceSecretVerifierConfig = {
907 Salt: Buffer.from(
908 authenticationHelper.getSaltDevices(),
909 'hex'
910 ).toString('base64'),
911 PasswordVerifier: Buffer.from(
912 authenticationHelper.getVerifierDevices(),
913 'hex'
914 ).toString('base64'),
915 };
916
917 this.verifierDevices = deviceSecretVerifierConfig.PasswordVerifier;
918 this.deviceGroupKey =
919 dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceGroupKey;
920 this.randomPassword = authenticationHelper.getRandomPassword();
921
922 this.client.request(
923 'ConfirmDevice',
924 {
925 DeviceKey:
926 dataAuthenticate.AuthenticationResult.NewDeviceMetadata
927 .DeviceKey,
928 AccessToken: this.signInUserSession
929 .getAccessToken()
930 .getJwtToken(),
931 DeviceSecretVerifierConfig: deviceSecretVerifierConfig,
932 DeviceName: userAgent,
933 },
934 (errConfirm, dataConfirm) => {
935 if (errConfirm) {
936 return callback.onFailure(errConfirm);
937 }
938
939 this.deviceKey =
940 dataAuthenticate.AuthenticationResult.NewDeviceMetadata.DeviceKey;
941 this.cacheDeviceKeyAndPassword();
942 if (dataConfirm.UserConfirmationNecessary === true) {
943 return callback.onSuccess(
944 this.signInUserSession,
945 dataConfirm.UserConfirmationNecessary
946 );
947 }
948 return callback.onSuccess(this.signInUserSession);
949 }
950 );
951 return undefined;
952 }
953 );
954 return undefined;
955 }
956 );
957 }
958
959 /**
960 * This is used by an authenticated user to change the current password
961 * @param {string} oldUserPassword The current password.
962 * @param {string} newUserPassword The requested new password.
963 * @param {nodeCallback<string>} callback Called on success or error.
964 * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger
965 * @returns {void}
966 */
967 changePassword(oldUserPassword, newUserPassword, callback, clientMetadata) {
968 if (!(this.signInUserSession != null && this.signInUserSession.isValid())) {
969 return callback(new Error('User is not authenticated'), null);
970 }
971
972 this.client.request(
973 'ChangePassword',
974 {
975 PreviousPassword: oldUserPassword,
976 ProposedPassword: newUserPassword,
977 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
978 ClientMetadata: clientMetadata,
979 },
980 err => {
981 if (err) {
982 return callback(err, null);
983 }
984 return callback(null, 'SUCCESS');
985 }
986 );
987 return undefined;
988 }
989
990 /**
991 * This is used by an authenticated user to enable MFA for itself
992 * @deprecated
993 * @param {nodeCallback<string>} callback Called on success or error.
994 * @returns {void}
995 */
996 enableMFA(callback) {
997 if (this.signInUserSession == null || !this.signInUserSession.isValid()) {
998 return callback(new Error('User is not authenticated'), null);
999 }
1000
1001 const mfaOptions = [];
1002 const mfaEnabled = {
1003 DeliveryMedium: 'SMS',
1004 AttributeName: 'phone_number',
1005 };
1006 mfaOptions.push(mfaEnabled);
1007
1008 this.client.request(
1009 'SetUserSettings',
1010 {
1011 MFAOptions: mfaOptions,
1012 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1013 },
1014 err => {
1015 if (err) {
1016 return callback(err, null);
1017 }
1018 return callback(null, 'SUCCESS');
1019 }
1020 );
1021 return undefined;
1022 }
1023
1024 /**
1025 * This is used by an authenticated user to enable MFA for itself
1026 * @param {IMfaSettings} smsMfaSettings the sms mfa settings
1027 * @param {IMFASettings} softwareTokenMfaSettings the software token mfa settings
1028 * @param {nodeCallback<string>} callback Called on success or error.
1029 * @returns {void}
1030 */
1031 setUserMfaPreference(smsMfaSettings, softwareTokenMfaSettings, callback) {
1032 if (this.signInUserSession == null || !this.signInUserSession.isValid()) {
1033 return callback(new Error('User is not authenticated'), null);
1034 }
1035
1036 this.client.request(
1037 'SetUserMFAPreference',
1038 {
1039 SMSMfaSettings: smsMfaSettings,
1040 SoftwareTokenMfaSettings: softwareTokenMfaSettings,
1041 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1042 },
1043 err => {
1044 if (err) {
1045 return callback(err, null);
1046 }
1047 return callback(null, 'SUCCESS');
1048 }
1049 );
1050 return undefined;
1051 }
1052
1053 /**
1054 * This is used by an authenticated user to disable MFA for itself
1055 * @deprecated
1056 * @param {nodeCallback<string>} callback Called on success or error.
1057 * @returns {void}
1058 */
1059 disableMFA(callback) {
1060 if (this.signInUserSession == null || !this.signInUserSession.isValid()) {
1061 return callback(new Error('User is not authenticated'), null);
1062 }
1063
1064 const mfaOptions = [];
1065
1066 this.client.request(
1067 'SetUserSettings',
1068 {
1069 MFAOptions: mfaOptions,
1070 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1071 },
1072 err => {
1073 if (err) {
1074 return callback(err, null);
1075 }
1076 return callback(null, 'SUCCESS');
1077 }
1078 );
1079 return undefined;
1080 }
1081
1082 /**
1083 * This is used by an authenticated user to delete itself
1084 * @param {nodeCallback<string>} callback Called on success or error.
1085 * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger
1086 * @returns {void}
1087 */
1088 deleteUser(callback, clientMetadata) {
1089 if (this.signInUserSession == null || !this.signInUserSession.isValid()) {
1090 return callback(new Error('User is not authenticated'), null);
1091 }
1092
1093 this.client.request(
1094 'DeleteUser',
1095 {
1096 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1097 ClientMetadata: clientMetadata,
1098 },
1099 err => {
1100 if (err) {
1101 return callback(err, null);
1102 }
1103 this.clearCachedUser();
1104 return callback(null, 'SUCCESS');
1105 }
1106 );
1107 return undefined;
1108 }
1109
1110 /**
1111 * @typedef {CognitoUserAttribute | { Name:string, Value:string }} AttributeArg
1112 */
1113 /**
1114 * This is used by an authenticated user to change a list of attributes
1115 * @param {AttributeArg[]} attributes A list of the new user attributes.
1116 * @param {nodeCallback<string>} callback Called on success or error.
1117 * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger
1118 * @returns {void}
1119 */
1120 updateAttributes(attributes, callback, clientMetadata) {
1121 if (this.signInUserSession == null || !this.signInUserSession.isValid()) {
1122 return callback(new Error('User is not authenticated'), null);
1123 }
1124
1125 this.client.request(
1126 'UpdateUserAttributes',
1127 {
1128 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1129 UserAttributes: attributes,
1130 ClientMetadata: clientMetadata,
1131 },
1132 err => {
1133 if (err) {
1134 return callback(err, null);
1135 }
1136
1137 // update cached user
1138 return this.getUserData(() => callback(null, 'SUCCESS'), {
1139 bypassCache: true,
1140 });
1141 }
1142 );
1143 return undefined;
1144 }
1145
1146 /**
1147 * This is used by an authenticated user to get a list of attributes
1148 * @param {nodeCallback<CognitoUserAttribute[]>} callback Called on success or error.
1149 * @returns {void}
1150 */
1151 getUserAttributes(callback) {
1152 if (!(this.signInUserSession != null && this.signInUserSession.isValid())) {
1153 return callback(new Error('User is not authenticated'), null);
1154 }
1155
1156 this.client.request(
1157 'GetUser',
1158 {
1159 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1160 },
1161 (err, userData) => {
1162 if (err) {
1163 return callback(err, null);
1164 }
1165
1166 const attributeList = [];
1167
1168 for (let i = 0; i < userData.UserAttributes.length; i++) {
1169 const attribute = {
1170 Name: userData.UserAttributes[i].Name,
1171 Value: userData.UserAttributes[i].Value,
1172 };
1173 const userAttribute = new CognitoUserAttribute(attribute);
1174 attributeList.push(userAttribute);
1175 }
1176
1177 return callback(null, attributeList);
1178 }
1179 );
1180 return undefined;
1181 }
1182
1183 /**
1184 * This is used by an authenticated user to get the MFAOptions
1185 * @param {nodeCallback<MFAOptions>} callback Called on success or error.
1186 * @returns {void}
1187 */
1188 getMFAOptions(callback) {
1189 if (!(this.signInUserSession != null && this.signInUserSession.isValid())) {
1190 return callback(new Error('User is not authenticated'), null);
1191 }
1192
1193 this.client.request(
1194 'GetUser',
1195 {
1196 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1197 },
1198 (err, userData) => {
1199 if (err) {
1200 return callback(err, null);
1201 }
1202
1203 return callback(null, userData.MFAOptions);
1204 }
1205 );
1206 return undefined;
1207 }
1208
1209 /**
1210 * PRIVATE ONLY: This is an internal only method and should not
1211 * be directly called by the consumers.
1212 */
1213 createGetUserRequest() {
1214 return this.client.promisifyRequest('GetUser', {
1215 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1216 });
1217 }
1218
1219 /**
1220 * PRIVATE ONLY: This is an internal only method and should not
1221 * be directly called by the consumers.
1222 */
1223 refreshSessionIfPossible() {
1224 // best effort, if not possible
1225 return new Promise(resolve => {
1226 const refresh = this.signInUserSession.getRefreshToken();
1227 if (refresh && refresh.getToken()) {
1228 this.refreshSession(refresh, resolve);
1229 } else {
1230 resolve();
1231 }
1232 });
1233 }
1234
1235 /**
1236 * This is used by an authenticated users to get the userData
1237 * @param {nodeCallback<UserData>} callback Called on success or error.
1238 * @param {GetUserDataOptions} params
1239 * @returns {void}
1240 */
1241 getUserData(callback, params) {
1242 if (!(this.signInUserSession != null && this.signInUserSession.isValid())) {
1243 this.clearCachedUserData();
1244 return callback(new Error('User is not authenticated'), null);
1245 }
1246
1247 const userData = this.getUserDataFromCache();
1248
1249 if (!userData) {
1250 this.fetchUserData()
1251 .then(data => {
1252 callback(null, data);
1253 })
1254 .catch(callback);
1255 return;
1256 }
1257
1258 if (this.isFetchUserDataAndTokenRequired(params)) {
1259 this.fetchUserData()
1260 .then(data => {
1261 return this.refreshSessionIfPossible().then(() => data);
1262 })
1263 .then(data => callback(null, data))
1264 .catch(callback);
1265 return;
1266 }
1267
1268 try {
1269 callback(null, JSON.parse(userData));
1270 return;
1271 } catch (err) {
1272 this.clearCachedUserData();
1273 callback(err, null);
1274 return;
1275 }
1276 }
1277
1278 /**
1279 *
1280 * PRIVATE ONLY: This is an internal only method and should not
1281 * be directly called by the consumers.
1282 */
1283 getUserDataFromCache() {
1284 const userData = this.storage.getItem(this.userDataKey);
1285
1286 return userData;
1287 }
1288
1289 /**
1290 *
1291 * PRIVATE ONLY: This is an internal only method and should not
1292 * be directly called by the consumers.
1293 */
1294 isFetchUserDataAndTokenRequired(params) {
1295 const { bypassCache = false } = params || {};
1296
1297 return bypassCache;
1298 }
1299 /**
1300 *
1301 * PRIVATE ONLY: This is an internal only method and should not
1302 * be directly called by the consumers.
1303 */
1304 fetchUserData() {
1305 return this.createGetUserRequest().then(data => {
1306 this.cacheUserData(data);
1307 return data;
1308 });
1309 }
1310
1311 /**
1312 * This is used by an authenticated user to delete a list of attributes
1313 * @param {string[]} attributeList Names of the attributes to delete.
1314 * @param {nodeCallback<string>} callback Called on success or error.
1315 * @returns {void}
1316 */
1317 deleteAttributes(attributeList, callback) {
1318 if (!(this.signInUserSession != null && this.signInUserSession.isValid())) {
1319 return callback(new Error('User is not authenticated'), null);
1320 }
1321
1322 this.client.request(
1323 'DeleteUserAttributes',
1324 {
1325 UserAttributeNames: attributeList,
1326 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1327 },
1328 err => {
1329 if (err) {
1330 return callback(err, null);
1331 }
1332 return callback(null, 'SUCCESS');
1333 }
1334 );
1335 return undefined;
1336 }
1337
1338 /**
1339 * This is used by a user to resend a confirmation code
1340 * @param {nodeCallback<string>} callback Called on success or error.
1341 * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger
1342 * @returns {void}
1343 */
1344 resendConfirmationCode(callback, clientMetadata) {
1345 const jsonReq = {
1346 ClientId: this.pool.getClientId(),
1347 Username: this.username,
1348 ClientMetadata: clientMetadata,
1349 };
1350
1351 this.client.request('ResendConfirmationCode', jsonReq, (err, result) => {
1352 if (err) {
1353 return callback(err, null);
1354 }
1355 return callback(null, result);
1356 });
1357 }
1358
1359 /**
1360 * This is used to get a session, either from the session object
1361 * or from the local storage, or by using a refresh token
1362 *
1363 * @param {nodeCallback<CognitoUserSession>} callback Called on success or error.
1364 * @returns {void}
1365 */
1366 getSession(callback) {
1367 if (this.username == null) {
1368 return callback(
1369 new Error('Username is null. Cannot retrieve a new session'),
1370 null
1371 );
1372 }
1373
1374 if (this.signInUserSession != null && this.signInUserSession.isValid()) {
1375 return callback(null, this.signInUserSession);
1376 }
1377
1378 const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}.${
1379 this.username
1380 }`;
1381 const idTokenKey = `${keyPrefix}.idToken`;
1382 const accessTokenKey = `${keyPrefix}.accessToken`;
1383 const refreshTokenKey = `${keyPrefix}.refreshToken`;
1384 const clockDriftKey = `${keyPrefix}.clockDrift`;
1385
1386 if (this.storage.getItem(idTokenKey)) {
1387 const idToken = new CognitoIdToken({
1388 IdToken: this.storage.getItem(idTokenKey),
1389 });
1390 const accessToken = new CognitoAccessToken({
1391 AccessToken: this.storage.getItem(accessTokenKey),
1392 });
1393 const refreshToken = new CognitoRefreshToken({
1394 RefreshToken: this.storage.getItem(refreshTokenKey),
1395 });
1396 const clockDrift = parseInt(this.storage.getItem(clockDriftKey), 0) || 0;
1397
1398 const sessionData = {
1399 IdToken: idToken,
1400 AccessToken: accessToken,
1401 RefreshToken: refreshToken,
1402 ClockDrift: clockDrift,
1403 };
1404 const cachedSession = new CognitoUserSession(sessionData);
1405
1406 if (cachedSession.isValid()) {
1407 this.signInUserSession = cachedSession;
1408 return callback(null, this.signInUserSession);
1409 }
1410
1411 if (!refreshToken.getToken()) {
1412 return callback(
1413 new Error('Cannot retrieve a new session. Please authenticate.'),
1414 null
1415 );
1416 }
1417
1418 this.refreshSession(refreshToken, callback);
1419 } else {
1420 callback(
1421 new Error('Local storage is missing an ID Token, Please authenticate'),
1422 null
1423 );
1424 }
1425
1426 return undefined;
1427 }
1428
1429 /**
1430 * This uses the refreshToken to retrieve a new session
1431 * @param {CognitoRefreshToken} refreshToken A previous session's refresh token.
1432 * @param {nodeCallback<CognitoUserSession>} callback Called on success or error.
1433 * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger
1434 * @returns {void}
1435 */
1436 refreshSession(refreshToken, callback, clientMetadata) {
1437 const authParameters = {};
1438 authParameters.REFRESH_TOKEN = refreshToken.getToken();
1439 const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`;
1440 const lastUserKey = `${keyPrefix}.LastAuthUser`;
1441
1442 if (this.storage.getItem(lastUserKey)) {
1443 this.username = this.storage.getItem(lastUserKey);
1444 const deviceKeyKey = `${keyPrefix}.${this.username}.deviceKey`;
1445 this.deviceKey = this.storage.getItem(deviceKeyKey);
1446 authParameters.DEVICE_KEY = this.deviceKey;
1447 }
1448
1449 const jsonReq = {
1450 ClientId: this.pool.getClientId(),
1451 AuthFlow: 'REFRESH_TOKEN_AUTH',
1452 AuthParameters: authParameters,
1453 ClientMetadata: clientMetadata,
1454 };
1455 if (this.getUserContextData()) {
1456 jsonReq.UserContextData = this.getUserContextData();
1457 }
1458 this.client.request('InitiateAuth', jsonReq, (err, authResult) => {
1459 if (err) {
1460 if (err.code === 'NotAuthorizedException') {
1461 this.clearCachedUser();
1462 }
1463 return callback(err, null);
1464 }
1465 if (authResult) {
1466 const authenticationResult = authResult.AuthenticationResult;
1467 if (
1468 !Object.prototype.hasOwnProperty.call(
1469 authenticationResult,
1470 'RefreshToken'
1471 )
1472 ) {
1473 authenticationResult.RefreshToken = refreshToken.getToken();
1474 }
1475 this.signInUserSession = this.getCognitoUserSession(
1476 authenticationResult
1477 );
1478 this.cacheTokens();
1479 return callback(null, this.signInUserSession);
1480 }
1481 return undefined;
1482 });
1483 }
1484
1485 /**
1486 * This is used to save the session tokens to local storage
1487 * @returns {void}
1488 */
1489 cacheTokens() {
1490 const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`;
1491 const idTokenKey = `${keyPrefix}.${this.username}.idToken`;
1492 const accessTokenKey = `${keyPrefix}.${this.username}.accessToken`;
1493 const refreshTokenKey = `${keyPrefix}.${this.username}.refreshToken`;
1494 const clockDriftKey = `${keyPrefix}.${this.username}.clockDrift`;
1495 const lastUserKey = `${keyPrefix}.LastAuthUser`;
1496
1497 this.storage.setItem(
1498 idTokenKey,
1499 this.signInUserSession.getIdToken().getJwtToken()
1500 );
1501 this.storage.setItem(
1502 accessTokenKey,
1503 this.signInUserSession.getAccessToken().getJwtToken()
1504 );
1505 this.storage.setItem(
1506 refreshTokenKey,
1507 this.signInUserSession.getRefreshToken().getToken()
1508 );
1509 this.storage.setItem(
1510 clockDriftKey,
1511 `${this.signInUserSession.getClockDrift()}`
1512 );
1513 this.storage.setItem(lastUserKey, this.username);
1514 }
1515
1516 /**
1517 * This is to cache user data
1518 */
1519 cacheUserData(userData) {
1520 this.storage.setItem(this.userDataKey, JSON.stringify(userData));
1521 }
1522
1523 /**
1524 * This is to remove cached user data
1525 */
1526 clearCachedUserData() {
1527 this.storage.removeItem(this.userDataKey);
1528 }
1529
1530 clearCachedUser() {
1531 this.clearCachedTokens();
1532 this.clearCachedUserData();
1533 }
1534
1535 /**
1536 * This is used to cache the device key and device group and device password
1537 * @returns {void}
1538 */
1539 cacheDeviceKeyAndPassword() {
1540 const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}.${
1541 this.username
1542 }`;
1543 const deviceKeyKey = `${keyPrefix}.deviceKey`;
1544 const randomPasswordKey = `${keyPrefix}.randomPasswordKey`;
1545 const deviceGroupKeyKey = `${keyPrefix}.deviceGroupKey`;
1546
1547 this.storage.setItem(deviceKeyKey, this.deviceKey);
1548 this.storage.setItem(randomPasswordKey, this.randomPassword);
1549 this.storage.setItem(deviceGroupKeyKey, this.deviceGroupKey);
1550 }
1551
1552 /**
1553 * This is used to get current device key and device group and device password
1554 * @returns {void}
1555 */
1556 getCachedDeviceKeyAndPassword() {
1557 const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}.${
1558 this.username
1559 }`;
1560 const deviceKeyKey = `${keyPrefix}.deviceKey`;
1561 const randomPasswordKey = `${keyPrefix}.randomPasswordKey`;
1562 const deviceGroupKeyKey = `${keyPrefix}.deviceGroupKey`;
1563
1564 if (this.storage.getItem(deviceKeyKey)) {
1565 this.deviceKey = this.storage.getItem(deviceKeyKey);
1566 this.randomPassword = this.storage.getItem(randomPasswordKey);
1567 this.deviceGroupKey = this.storage.getItem(deviceGroupKeyKey);
1568 }
1569 }
1570
1571 /**
1572 * This is used to clear the device key info from local storage
1573 * @returns {void}
1574 */
1575 clearCachedDeviceKeyAndPassword() {
1576 const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}.${
1577 this.username
1578 }`;
1579 const deviceKeyKey = `${keyPrefix}.deviceKey`;
1580 const randomPasswordKey = `${keyPrefix}.randomPasswordKey`;
1581 const deviceGroupKeyKey = `${keyPrefix}.deviceGroupKey`;
1582
1583 this.storage.removeItem(deviceKeyKey);
1584 this.storage.removeItem(randomPasswordKey);
1585 this.storage.removeItem(deviceGroupKeyKey);
1586 }
1587
1588 /**
1589 * This is used to clear the session tokens from local storage
1590 * @returns {void}
1591 */
1592 clearCachedTokens() {
1593 const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`;
1594 const idTokenKey = `${keyPrefix}.${this.username}.idToken`;
1595 const accessTokenKey = `${keyPrefix}.${this.username}.accessToken`;
1596 const refreshTokenKey = `${keyPrefix}.${this.username}.refreshToken`;
1597 const lastUserKey = `${keyPrefix}.LastAuthUser`;
1598 const clockDriftKey = `${keyPrefix}.${this.username}.clockDrift`;
1599
1600 this.storage.removeItem(idTokenKey);
1601 this.storage.removeItem(accessTokenKey);
1602 this.storage.removeItem(refreshTokenKey);
1603 this.storage.removeItem(lastUserKey);
1604 this.storage.removeItem(clockDriftKey);
1605 }
1606
1607 /**
1608 * This is used to build a user session from tokens retrieved in the authentication result
1609 * @param {object} authResult Successful auth response from server.
1610 * @returns {CognitoUserSession} The new user session.
1611 * @private
1612 */
1613 getCognitoUserSession(authResult) {
1614 const idToken = new CognitoIdToken(authResult);
1615 const accessToken = new CognitoAccessToken(authResult);
1616 const refreshToken = new CognitoRefreshToken(authResult);
1617
1618 const sessionData = {
1619 IdToken: idToken,
1620 AccessToken: accessToken,
1621 RefreshToken: refreshToken,
1622 };
1623
1624 return new CognitoUserSession(sessionData);
1625 }
1626
1627 /**
1628 * This is used to initiate a forgot password request
1629 * @param {object} callback Result callback map.
1630 * @param {onFailure} callback.onFailure Called on any error.
1631 * @param {inputVerificationCode?} callback.inputVerificationCode
1632 * Optional callback raised instead of onSuccess with response data.
1633 * @param {onSuccess} callback.onSuccess Called on success.
1634 * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger
1635 * @returns {void}
1636 */
1637 forgotPassword(callback, clientMetadata) {
1638 const jsonReq = {
1639 ClientId: this.pool.getClientId(),
1640 Username: this.username,
1641 ClientMetadata: clientMetadata,
1642 };
1643 if (this.getUserContextData()) {
1644 jsonReq.UserContextData = this.getUserContextData();
1645 }
1646 this.client.request('ForgotPassword', jsonReq, (err, data) => {
1647 if (err) {
1648 return callback.onFailure(err);
1649 }
1650 if (typeof callback.inputVerificationCode === 'function') {
1651 return callback.inputVerificationCode(data);
1652 }
1653 return callback.onSuccess(data);
1654 });
1655 }
1656
1657 /**
1658 * This is used to confirm a new password using a confirmationCode
1659 * @param {string} confirmationCode Code entered by user.
1660 * @param {string} newPassword Confirm new password.
1661 * @param {object} callback Result callback map.
1662 * @param {onFailure} callback.onFailure Called on any error.
1663 * @param {onSuccess<void>} callback.onSuccess Called on success.
1664 * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger
1665 * @returns {void}
1666 */
1667 confirmPassword(confirmationCode, newPassword, callback, clientMetadata) {
1668 const jsonReq = {
1669 ClientId: this.pool.getClientId(),
1670 Username: this.username,
1671 ConfirmationCode: confirmationCode,
1672 Password: newPassword,
1673 ClientMetadata: clientMetadata,
1674 };
1675 if (this.getUserContextData()) {
1676 jsonReq.UserContextData = this.getUserContextData();
1677 }
1678 this.client.request('ConfirmForgotPassword', jsonReq, err => {
1679 if (err) {
1680 return callback.onFailure(err);
1681 }
1682 return callback.onSuccess();
1683 });
1684 }
1685
1686 /**
1687 * This is used to initiate an attribute confirmation request
1688 * @param {string} attributeName User attribute that needs confirmation.
1689 * @param {object} callback Result callback map.
1690 * @param {onFailure} callback.onFailure Called on any error.
1691 * @param {inputVerificationCode} callback.inputVerificationCode Called on success.
1692 * @param {ClientMetadata} clientMetadata object which is passed from client to Cognito Lambda trigger
1693 * @returns {void}
1694 */
1695 getAttributeVerificationCode(attributeName, callback, clientMetadata) {
1696 if (this.signInUserSession == null || !this.signInUserSession.isValid()) {
1697 return callback.onFailure(new Error('User is not authenticated'));
1698 }
1699
1700 this.client.request(
1701 'GetUserAttributeVerificationCode',
1702 {
1703 AttributeName: attributeName,
1704 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1705 ClientMetadata: clientMetadata,
1706 },
1707 (err, data) => {
1708 if (err) {
1709 return callback.onFailure(err);
1710 }
1711 if (typeof callback.inputVerificationCode === 'function') {
1712 return callback.inputVerificationCode(data);
1713 }
1714 return callback.onSuccess();
1715 }
1716 );
1717 return undefined;
1718 }
1719
1720 /**
1721 * This is used to confirm an attribute using a confirmation code
1722 * @param {string} attributeName Attribute being confirmed.
1723 * @param {string} confirmationCode Code entered by user.
1724 * @param {object} callback Result callback map.
1725 * @param {onFailure} callback.onFailure Called on any error.
1726 * @param {onSuccess<string>} callback.onSuccess Called on success.
1727 * @returns {void}
1728 */
1729 verifyAttribute(attributeName, confirmationCode, callback) {
1730 if (this.signInUserSession == null || !this.signInUserSession.isValid()) {
1731 return callback.onFailure(new Error('User is not authenticated'));
1732 }
1733
1734 this.client.request(
1735 'VerifyUserAttribute',
1736 {
1737 AttributeName: attributeName,
1738 Code: confirmationCode,
1739 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1740 },
1741 err => {
1742 if (err) {
1743 return callback.onFailure(err);
1744 }
1745 return callback.onSuccess('SUCCESS');
1746 }
1747 );
1748 return undefined;
1749 }
1750
1751 /**
1752 * This is used to get the device information using the current device key
1753 * @param {object} callback Result callback map.
1754 * @param {onFailure} callback.onFailure Called on any error.
1755 * @param {onSuccess<*>} callback.onSuccess Called on success with device data.
1756 * @returns {void}
1757 */
1758 getDevice(callback) {
1759 if (this.signInUserSession == null || !this.signInUserSession.isValid()) {
1760 return callback.onFailure(new Error('User is not authenticated'));
1761 }
1762
1763 this.client.request(
1764 'GetDevice',
1765 {
1766 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1767 DeviceKey: this.deviceKey,
1768 },
1769 (err, data) => {
1770 if (err) {
1771 return callback.onFailure(err);
1772 }
1773 return callback.onSuccess(data);
1774 }
1775 );
1776 return undefined;
1777 }
1778
1779 /**
1780 * This is used to forget a specific device
1781 * @param {string} deviceKey Device key.
1782 * @param {object} callback Result callback map.
1783 * @param {onFailure} callback.onFailure Called on any error.
1784 * @param {onSuccess<string>} callback.onSuccess Called on success.
1785 * @returns {void}
1786 */
1787 forgetSpecificDevice(deviceKey, callback) {
1788 if (this.signInUserSession == null || !this.signInUserSession.isValid()) {
1789 return callback.onFailure(new Error('User is not authenticated'));
1790 }
1791
1792 this.client.request(
1793 'ForgetDevice',
1794 {
1795 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1796 DeviceKey: deviceKey,
1797 },
1798 err => {
1799 if (err) {
1800 return callback.onFailure(err);
1801 }
1802 return callback.onSuccess('SUCCESS');
1803 }
1804 );
1805 return undefined;
1806 }
1807
1808 /**
1809 * This is used to forget the current device
1810 * @param {object} callback Result callback map.
1811 * @param {onFailure} callback.onFailure Called on any error.
1812 * @param {onSuccess<string>} callback.onSuccess Called on success.
1813 * @returns {void}
1814 */
1815 forgetDevice(callback) {
1816 this.forgetSpecificDevice(this.deviceKey, {
1817 onFailure: callback.onFailure,
1818 onSuccess: result => {
1819 this.deviceKey = null;
1820 this.deviceGroupKey = null;
1821 this.randomPassword = null;
1822 this.clearCachedDeviceKeyAndPassword();
1823 return callback.onSuccess(result);
1824 },
1825 });
1826 }
1827
1828 /**
1829 * This is used to set the device status as remembered
1830 * @param {object} callback Result callback map.
1831 * @param {onFailure} callback.onFailure Called on any error.
1832 * @param {onSuccess<string>} callback.onSuccess Called on success.
1833 * @returns {void}
1834 */
1835 setDeviceStatusRemembered(callback) {
1836 if (this.signInUserSession == null || !this.signInUserSession.isValid()) {
1837 return callback.onFailure(new Error('User is not authenticated'));
1838 }
1839
1840 this.client.request(
1841 'UpdateDeviceStatus',
1842 {
1843 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1844 DeviceKey: this.deviceKey,
1845 DeviceRememberedStatus: 'remembered',
1846 },
1847 err => {
1848 if (err) {
1849 return callback.onFailure(err);
1850 }
1851 return callback.onSuccess('SUCCESS');
1852 }
1853 );
1854 return undefined;
1855 }
1856
1857 /**
1858 * This is used to set the device status as not remembered
1859 * @param {object} callback Result callback map.
1860 * @param {onFailure} callback.onFailure Called on any error.
1861 * @param {onSuccess<string>} callback.onSuccess Called on success.
1862 * @returns {void}
1863 */
1864 setDeviceStatusNotRemembered(callback) {
1865 if (this.signInUserSession == null || !this.signInUserSession.isValid()) {
1866 return callback.onFailure(new Error('User is not authenticated'));
1867 }
1868
1869 this.client.request(
1870 'UpdateDeviceStatus',
1871 {
1872 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1873 DeviceKey: this.deviceKey,
1874 DeviceRememberedStatus: 'not_remembered',
1875 },
1876 err => {
1877 if (err) {
1878 return callback.onFailure(err);
1879 }
1880 return callback.onSuccess('SUCCESS');
1881 }
1882 );
1883 return undefined;
1884 }
1885
1886 /**
1887 * This is used to list all devices for a user
1888 *
1889 * @param {int} limit the number of devices returned in a call
1890 * @param {string | null} paginationToken the pagination token in case any was returned before
1891 * @param {object} callback Result callback map.
1892 * @param {onFailure} callback.onFailure Called on any error.
1893 * @param {onSuccess<*>} callback.onSuccess Called on success with device list.
1894 * @returns {void}
1895 */
1896 listDevices(limit, paginationToken, callback) {
1897 if (this.signInUserSession == null || !this.signInUserSession.isValid()) {
1898 return callback.onFailure(new Error('User is not authenticated'));
1899 }
1900 const requestParams = {
1901 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1902 Limit: limit,
1903 };
1904
1905 if (paginationToken) {
1906 requestParams.PaginationToken = paginationToken;
1907 }
1908
1909 this.client.request('ListDevices', requestParams, (err, data) => {
1910 if (err) {
1911 return callback.onFailure(err);
1912 }
1913 return callback.onSuccess(data);
1914 });
1915 return undefined;
1916 }
1917
1918 /**
1919 * This is used to globally revoke all tokens issued to a user
1920 * @param {object} callback Result callback map.
1921 * @param {onFailure} callback.onFailure Called on any error.
1922 * @param {onSuccess<string>} callback.onSuccess Called on success.
1923 * @returns {void}
1924 */
1925 globalSignOut(callback) {
1926 if (this.signInUserSession == null || !this.signInUserSession.isValid()) {
1927 return callback.onFailure(new Error('User is not authenticated'));
1928 }
1929
1930 this.client.request(
1931 'GlobalSignOut',
1932 {
1933 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
1934 },
1935 err => {
1936 if (err) {
1937 return callback.onFailure(err);
1938 }
1939 this.clearCachedUser();
1940 return callback.onSuccess('SUCCESS');
1941 }
1942 );
1943 return undefined;
1944 }
1945
1946 /**
1947 * This is used for the user to signOut of the application and clear the cached tokens.
1948 * @returns {void}
1949 */
1950 signOut() {
1951 this.signInUserSession = null;
1952 this.clearCachedUser();
1953 }
1954
1955 /**
1956 * This is used by a user trying to select a given MFA
1957 * @param {string} answerChallenge the mfa the user wants
1958 * @param {nodeCallback<string>} callback Called on success or error.
1959 * @returns {void}
1960 */
1961 sendMFASelectionAnswer(answerChallenge, callback) {
1962 const challengeResponses = {};
1963 challengeResponses.USERNAME = this.username;
1964 challengeResponses.ANSWER = answerChallenge;
1965
1966 const jsonReq = {
1967 ChallengeName: 'SELECT_MFA_TYPE',
1968 ChallengeResponses: challengeResponses,
1969 ClientId: this.pool.getClientId(),
1970 Session: this.Session,
1971 };
1972 if (this.getUserContextData()) {
1973 jsonReq.UserContextData = this.getUserContextData();
1974 }
1975 this.client.request('RespondToAuthChallenge', jsonReq, (err, data) => {
1976 if (err) {
1977 return callback.onFailure(err);
1978 }
1979 this.Session = data.Session;
1980 if (answerChallenge === 'SMS_MFA') {
1981 return callback.mfaRequired(
1982 data.ChallengeName,
1983 data.ChallengeParameters
1984 );
1985 }
1986 if (answerChallenge === 'SOFTWARE_TOKEN_MFA') {
1987 return callback.totpRequired(
1988 data.ChallengeName,
1989 data.ChallengeParameters
1990 );
1991 }
1992 return undefined;
1993 });
1994 }
1995
1996 /**
1997 * This returns the user context data for advanced security feature.
1998 * @returns {void}
1999 */
2000 getUserContextData() {
2001 const pool = this.pool;
2002 return pool.getUserContextData(this.username);
2003 }
2004
2005 /**
2006 * This is used by an authenticated or a user trying to authenticate to associate a TOTP MFA
2007 * @param {nodeCallback<string>} callback Called on success or error.
2008 * @returns {void}
2009 */
2010 associateSoftwareToken(callback) {
2011 if (!(this.signInUserSession != null && this.signInUserSession.isValid())) {
2012 this.client.request(
2013 'AssociateSoftwareToken',
2014 {
2015 Session: this.Session,
2016 },
2017 (err, data) => {
2018 if (err) {
2019 return callback.onFailure(err);
2020 }
2021 this.Session = data.Session;
2022 return callback.associateSecretCode(data.SecretCode);
2023 }
2024 );
2025 } else {
2026 this.client.request(
2027 'AssociateSoftwareToken',
2028 {
2029 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
2030 },
2031 (err, data) => {
2032 if (err) {
2033 return callback.onFailure(err);
2034 }
2035 return callback.associateSecretCode(data.SecretCode);
2036 }
2037 );
2038 }
2039 }
2040
2041 /**
2042 * This is used by an authenticated or a user trying to authenticate to verify a TOTP MFA
2043 * @param {string} totpCode The MFA code entered by the user.
2044 * @param {string} friendlyDeviceName The device name we are assigning to the device.
2045 * @param {nodeCallback<string>} callback Called on success or error.
2046 * @returns {void}
2047 */
2048 verifySoftwareToken(totpCode, friendlyDeviceName, callback) {
2049 if (!(this.signInUserSession != null && this.signInUserSession.isValid())) {
2050 this.client.request(
2051 'VerifySoftwareToken',
2052 {
2053 Session: this.Session,
2054 UserCode: totpCode,
2055 FriendlyDeviceName: friendlyDeviceName,
2056 },
2057 (err, data) => {
2058 if (err) {
2059 return callback.onFailure(err);
2060 }
2061 this.Session = data.Session;
2062 const challengeResponses = {};
2063 challengeResponses.USERNAME = this.username;
2064 const jsonReq = {
2065 ChallengeName: 'MFA_SETUP',
2066 ClientId: this.pool.getClientId(),
2067 ChallengeResponses: challengeResponses,
2068 Session: this.Session,
2069 };
2070 if (this.getUserContextData()) {
2071 jsonReq.UserContextData = this.getUserContextData();
2072 }
2073 this.client.request(
2074 'RespondToAuthChallenge',
2075 jsonReq,
2076 (errRespond, dataRespond) => {
2077 if (errRespond) {
2078 return callback.onFailure(errRespond);
2079 }
2080 this.signInUserSession = this.getCognitoUserSession(
2081 dataRespond.AuthenticationResult
2082 );
2083 this.cacheTokens();
2084 return callback.onSuccess(this.signInUserSession);
2085 }
2086 );
2087 return undefined;
2088 }
2089 );
2090 } else {
2091 this.client.request(
2092 'VerifySoftwareToken',
2093 {
2094 AccessToken: this.signInUserSession.getAccessToken().getJwtToken(),
2095 UserCode: totpCode,
2096 FriendlyDeviceName: friendlyDeviceName,
2097 },
2098 (err, data) => {
2099 if (err) {
2100 return callback.onFailure(err);
2101 }
2102 return callback.onSuccess(data);
2103 }
2104 );
2105 }
2106 }
2107}