1
/*
2
 * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
5
 * the License. A copy of the License is located at
6
 *
7
 *	 http://aws.amazon.com/apache2.0/
8
 *
9
 * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
10
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
11
 * and limitations under the License.
12
 */
13

14 1
import {
15
	AuthOptions,
16
	FederatedResponse,
17
	SignUpParams,
18
	FederatedUser,
19
	ConfirmSignUpOptions,
20
	SignOutOpts,
21
	CurrentUserOpts,
22
	GetPreferredMFAOpts,
23
	SignInOpts,
24
	isUsernamePasswordOpts,
25
	isCognitoHostedOpts,
26
	isFederatedSignInOptions,
27
	isFederatedSignInOptionsCustom,
28
	hasCustomState,
29
	FederatedSignInOptionsCustom,
30
	LegacyProvider,
31
	FederatedSignInOptions,
32
	AwsCognitoOAuthOpts,
33
	ClientMetaData,
34
} from './types';
35

36 1
import {
37
	Amplify,
38
	ConsoleLogger as Logger,
39
	Credentials,
40
	Hub,
41
	StorageHelper,
42
	ICredentials,
43
	Parser,
44
	JS,
45
	UniversalStorage,
46
} from '@aws-amplify/core';
47 1
import {
48
	CookieStorage,
49
	CognitoUserPool,
50
	AuthenticationDetails,
51
	ICognitoUserPoolData,
52
	ICognitoUserData,
53
	ISignUpResult,
54
	CognitoUser,
55
	MFAOption,
56
	CognitoUserSession,
57
	IAuthenticationCallback,
58
	ICognitoUserAttributeData,
59
	CognitoUserAttribute,
60
	CognitoIdToken,
61
	CognitoRefreshToken,
62
	CognitoAccessToken,
63
} from 'amazon-cognito-identity-js';
64

65 1
import { parse } from 'url';
66 1
import OAuth from './OAuth/OAuth';
67 1
import { default as urlListener } from './urlListener';
68 1
import { AuthError, NoUserPoolError } from './Errors';
69 1
import { AuthErrorTypes, CognitoHostedUIIdentityProvider } from './types/Auth';
70

71 1
const logger = new Logger('AuthClass');
72 1
const USER_ADMIN_SCOPE = 'aws.cognito.signin.user.admin';
73

74
// 10 sec, following this guide https://www.nngroup.com/articles/response-times-3-important-limits/
75 1
const OAUTH_FLOW_MS_TIMEOUT = 10 * 1000;
76

77 1
const AMPLIFY_SYMBOL = (typeof Symbol !== 'undefined' &&
78
typeof Symbol.for === 'function'
79 1
	? Symbol.for('amplify_default')
80
	: '@@amplify_default') as Symbol;
81

82 1
const dispatchAuthEvent = (event: string, data: any, message: string) => {
83 1
	Hub.dispatch('auth', { event, data, message }, 'Auth', AMPLIFY_SYMBOL);
84
};
85

86
/**
87
 * Provide authentication steps
88
 */
89 1
export class AuthClass {
90
	private _config: AuthOptions;
91 1
	private userPool = null;
92 1
	private user: any = null;
93
	private _oAuthHandler: OAuth;
94
	private _storage;
95
	private _storageSync;
96 1
	private oAuthFlowInProgress: boolean = false;
97

98 1
	Credentials = Credentials;
99

100
	/**
101
	 * Initialize Auth with AWS configurations
102
	 * @param {Object} config - Configuration of the Auth
103
	 */
104 1
	constructor(config: AuthOptions) {
105 1
		this.configure(config);
106 1
		this.currentCredentials = this.currentCredentials.bind(this);
107 1
		this.currentUserCredentials = this.currentUserCredentials.bind(this);
108

109 1
		Hub.listen('auth', ({ payload }) => {
110 1
			const { event } = payload;
111 1
			switch (event) {
112 1
				case 'signIn':
113 1
					this._storage.setItem('amplify-signin-with-hostedUI', 'false');
114 1
					break;
115
				case 'signOut':
116 1
					this._storage.removeItem('amplify-signin-with-hostedUI');
117 1
					break;
118
				case 'cognitoHostedUI':
119 1
					this._storage.setItem('amplify-signin-with-hostedUI', 'true');
120 1
					break;
121
			}
122
		});
123
	}
124

125 1
	public getModuleName() {
126 1
		return 'Auth';
127
	}
128

129 1
	configure(config?) {
130 1
		if (!config) return this._config || {};
131 1
		logger.debug('configure Auth');
132 1
		const conf = Object.assign(
133
			{},
134
			this._config,
135
			Parser.parseMobilehubConfig(config).Auth,
136
			config
137
		);
138 1
		this._config = conf;
139 1
		const {
140 1
			userPoolId,
141 1
			userPoolWebClientId,
142 1
			cookieStorage,
143 1
			oauth,
144 1
			region,
145 1
			identityPoolId,
146 1
			mandatorySignIn,
147 1
			refreshHandlers,
148 1
			identityPoolRegion,
149 1
			clientMetadata,
150 1
			endpoint,
151
		} = this._config;
152

153 1
		if (!this._config.storage) {
154
			// backward compatability
155 1
			if (cookieStorage) this._storage = new CookieStorage(cookieStorage);
156
			else {
157 1
				this._storage = config.ssr
158 1
					? new UniversalStorage()
159
					: new StorageHelper().getStorage();
160
			}
161
		} else {
162 1
			if (!this._isValidAuthStorage(this._config.storage)) {
163 1
				logger.error('The storage in the Auth config is not valid!');
164 1
				throw new Error('Empty storage object');
165
			}
166 0
			this._storage = this._config.storage;
167
		}
168

169 1
		this._storageSync = Promise.resolve();
170 1
		if (typeof this._storage['sync'] === 'function') {
171 0
			this._storageSync = this._storage['sync']();
172
		}
173

174 1
		if (userPoolId) {
175 1
			const userPoolData: ICognitoUserPoolData = {
176
				UserPoolId: userPoolId,
177
				ClientId: userPoolWebClientId,
178
				endpoint,
179
			};
180 1
			userPoolData.Storage = this._storage;
181

182 1
			this.userPool = new CognitoUserPool(userPoolData);
183
		}
184

185 1
		this.Credentials.configure({
186
			mandatorySignIn,
187 1
			region: identityPoolRegion || region,
188
			userPoolId,
189
			identityPoolId,
190
			refreshHandlers,
191
			storage: this._storage,
192
		});
193

194
		// initiailize cognitoauth client if hosted ui options provided
195
		// to keep backward compatibility:
196 1
		const cognitoHostedUIConfig = oauth
197 1
			? isCognitoHostedOpts(this._config.oauth)
198 1
				? oauth
199
				: (<any>oauth).awsCognito
200
			: undefined;
201

202 1
		if (cognitoHostedUIConfig) {
203 1
			const cognitoAuthParams = Object.assign(
204
				{
205
					cognitoClientId: userPoolWebClientId,
206
					UserPoolId: userPoolId,
207
					domain: cognitoHostedUIConfig['domain'],
208
					scopes: cognitoHostedUIConfig['scope'],
209
					redirectSignIn: cognitoHostedUIConfig['redirectSignIn'],
210
					redirectSignOut: cognitoHostedUIConfig['redirectSignOut'],
211
					responseType: cognitoHostedUIConfig['responseType'],
212
					Storage: this._storage,
213
					urlOpener: cognitoHostedUIConfig['urlOpener'],
214
					clientMetadata,
215
				},
216
				cognitoHostedUIConfig['options']
217
			);
218

219 1
			this._oAuthHandler = new OAuth({
220
				scopes: cognitoAuthParams.scopes,
221
				config: cognitoAuthParams,
222
				cognitoClientId: cognitoAuthParams.cognitoClientId,
223
			});
224

225
			// **NOTE** - Remove this in a future major release as it is a breaking change
226
			// Prevents _handleAuthResponse from being called multiple times in Expo
227
			// See https://github.com/aws-amplify/amplify-js/issues/4388
228 1
			const usedResponseUrls = {};
229 1
			urlListener(({ url }) => {
230 1
				if (usedResponseUrls[url]) {
231 0
					return;
232
				}
233

234 1
				usedResponseUrls[url] = true;
235 1
				this._handleAuthResponse(url);
236
			});
237
		}
238

239 1
		dispatchAuthEvent(
240
			'configured',
241
			null,
242
			`The Auth category has been configured successfully`
243
		);
244 1
		return this._config;
245
	}
246

247
	/**
248
	 * Sign up with username, password and other attributes like phone, email
249
	 * @param {String | object} params - The user attributes used for signin
250
	 * @param {String[]} restOfAttrs - for the backward compatability
251
	 * @return - A promise resolves callback data if success
252
	 */
253 1
	public signUp(
254
		params: string | SignUpParams,
255 1
		...restOfAttrs: string[]
256
	): Promise<ISignUpResult> {
257 1
		if (!this.userPool) {
258 1
			return this.rejectNoUserPool();
259
		}
260

261 1
		let username: string = null;
262 1
		let password: string = null;
263 1
		const attributes: object[] = [];
264 1
		let validationData: object[] = null;
265
		let clientMetadata;
266

267 1
		if (params && typeof params === 'string') {
268 1
			username = params;
269 1
			password = restOfAttrs ? restOfAttrs[0] : null;
270 1
			const email: string = restOfAttrs ? restOfAttrs[1] : null;
271 1
			const phone_number: string = restOfAttrs ? restOfAttrs[2] : null;
272 1
			if (email) attributes.push({ Name: 'email', Value: email });
273 1
			if (phone_number)
274 1
				attributes.push({ Name: 'phone_number', Value: phone_number });
275 1
		} else if (params && typeof params === 'object') {
276 1
			username = params['username'];
277 1
			password = params['password'];
278

279 1
			if (params && params.clientMetadata) {
280 1
				clientMetadata = params.clientMetadata;
281 1
			} else if (this._config.clientMetadata) {
282 1
				clientMetadata = this._config.clientMetadata;
283
			}
284

285 1
			const attrs = params['attributes'];
286 1
			if (attrs) {
287 1
				Object.keys(attrs).map(key => {
288 1
					const ele: object = { Name: key, Value: attrs[key] };
289 1
					attributes.push(ele);
290
				});
291
			}
292 1
			validationData = params['validationData'] || null;
293
		} else {
294 1
			return this.rejectAuthError(AuthErrorTypes.SignUpError);
295
		}
296

297 1
		if (!username) {
298 1
			return this.rejectAuthError(AuthErrorTypes.EmptyUsername);
299
		}
300 1
		if (!password) {
301 1
			return this.rejectAuthError(AuthErrorTypes.EmptyPassword);
302
		}
303

304 1
		logger.debug('signUp attrs:', attributes);
305 1
		logger.debug('signUp validation data:', validationData);
306

307 1
		return new Promise((resolve, reject) => {
308 1
			this.userPool.signUp(
309
				username,
310
				password,
311
				attributes,
312
				validationData,
313
				(err, data) => {
314 1
					if (err) {
315 1
						dispatchAuthEvent(
316
							'signUp_failure',
317
							err,
318
							`${username} failed to signup`
319
						);
320 1
						reject(err);
321
					} else {
322 1
						dispatchAuthEvent(
323
							'signUp',
324
							data,
325
							`${username} has signed up successfully`
326
						);
327 1
						resolve(data);
328
					}
329
				},
330
				clientMetadata
331
			);
332
		});
333
	}
334

335
	/**
336
	 * Send the verfication code to confirm sign up
337
	 * @param {String} username - The username to be confirmed
338
	 * @param {String} code - The verification code
339
	 * @param {ConfirmSignUpOptions} options - other options for confirm signup
340
	 * @return - A promise resolves callback data if success
341
	 */
342 1
	public confirmSignUp(
343
		username: string,
344
		code: string,
345
		options?: ConfirmSignUpOptions
346
	): Promise<any> {
347 1
		if (!this.userPool) {
348 1
			return this.rejectNoUserPool();
349
		}
350 1
		if (!username) {
351 1
			return this.rejectAuthError(AuthErrorTypes.EmptyUsername);
352
		}
353 1
		if (!code) {
354 1
			return this.rejectAuthError(AuthErrorTypes.EmptyCode);
355
		}
356

357 1
		const user = this.createCognitoUser(username);
358
		const forceAliasCreation =
359 1
			options && typeof options.forceAliasCreation === 'boolean'
360 1
				? options.forceAliasCreation
361
				: true;
362

363
		let clientMetadata;
364 1
		if (options && options.clientMetadata) {
365 1
			clientMetadata = options.clientMetadata;
366 1
		} else if (this._config.clientMetadata) {
367 1
			clientMetadata = this._config.clientMetadata;
368
		}
369 1
		return new Promise((resolve, reject) => {
370 1
			user.confirmRegistration(
371
				code,
372
				forceAliasCreation,
373
				(err, data) => {
374 1
					if (err) {
375 1
						reject(err);
376
					} else {
377 1
						resolve(data);
378
					}
379
				},
380
				clientMetadata
381
			);
382
		});
383
	}
384

385
	/**
386
	 * Resend the verification code
387
	 * @param {String} username - The username to be confirmed
388
	 * @param {ClientMetadata} clientMetadata - Metadata to be passed to Cognito Lambda triggers
389
	 * @return - A promise resolves code delivery details if successful
390
	 */
391 1
	public resendSignUp(
392
		username: string,
393 1
		clientMetadata: ClientMetaData = this._config.clientMetadata
394
	): Promise<any> {
395 1
		if (!this.userPool) {
396 1
			return this.rejectNoUserPool();
397
		}
398 1
		if (!username) {
399 1
			return this.rejectAuthError(AuthErrorTypes.EmptyUsername);
400
		}
401

402 1
		const user = this.createCognitoUser(username);
403 1
		return new Promise((resolve, reject) => {
404 1
			user.resendConfirmationCode((err, data) => {
405 1
				if (err) {
406 1
					reject(err);
407
				} else {
408 1
					resolve(data);
409
				}
410
			}, clientMetadata);
411
		});
412
	}
413

414
	/**
415
	 * Sign in
416
	 * @param {String | SignInOpts} usernameOrSignInOpts - The username to be signed in or the sign in options
417
	 * @param {String} password - The password of the username
418
	 * @return - A promise resolves the CognitoUser
419
	 */
420 1
	public signIn(
421
		usernameOrSignInOpts: string | SignInOpts,
422
		pw?: string,
423 1
		clientMetadata: ClientMetaData = this._config.clientMetadata
424
	): Promise<CognitoUser | any> {
425 1
		if (!this.userPool) {
426 1
			return this.rejectNoUserPool();
427
		}
428

429 1
		let username = null;
430 1
		let password = null;
431 1
		let validationData = {};
432

433
		// for backward compatibility
434 1
		if (typeof usernameOrSignInOpts === 'string') {
435 1
			username = usernameOrSignInOpts;
436 1
			password = pw;
437 1
		} else if (isUsernamePasswordOpts(usernameOrSignInOpts)) {
438 1
			if (typeof pw !== 'undefined') {
439 0
				logger.warn(
440
					'The password should be defined under the first parameter object!'
441
				);
442
			}
443 1
			username = usernameOrSignInOpts.username;
444 1
			password = usernameOrSignInOpts.password;
445 1
			validationData = usernameOrSignInOpts.validationData;
446
		} else {
447 0
			return this.rejectAuthError(AuthErrorTypes.InvalidUsername);
448
		}
449 1
		if (!username) {
450 0
			return this.rejectAuthError(AuthErrorTypes.EmptyUsername);
451
		}
452 1
		const authDetails = new AuthenticationDetails({
453
			Username: username,
454
			Password: password,
455
			ValidationData: validationData,
456
			ClientMetadata: clientMetadata,
457
		});
458 1
		if (password) {
459 1
			return this.signInWithPassword(authDetails);
460
		} else {
461 1
			return this.signInWithoutPassword(authDetails);
462
		}
463
	}
464

465
	/**
466
	 * Return an object with the authentication callbacks
467
	 * @param {CognitoUser} user - the cognito user object
468
	 * @param {} resolve - function called when resolving the current step
469
	 * @param {} reject - function called when rejecting the current step
470
	 * @return - an object with the callback methods for user authentication
471
	 */
472 1
	private authCallbacks(
473
		user: CognitoUser,
474
		resolve: (value?: CognitoUser | any) => void,
475
		reject: (value?: any) => void
476
	): IAuthenticationCallback {
477 1
		const that = this;
478 1
		return {
479 1
			onSuccess: async session => {
480 1
				logger.debug(session);
481 1
				delete user['challengeName'];
482 1
				delete user['challengeParam'];
483
				try {
484 1
					await this.Credentials.clear();
485 1
					const cred = await this.Credentials.set(session, 'session');
486 1
					logger.debug('succeed to get cognito credentials', cred);
487
				} catch (e) {
488 1
					logger.debug('cannot get cognito credentials', e);
489
				} finally {
490
					try {
491
						// In order to get user attributes and MFA methods
492
						// We need to trigger currentUserPoolUser again
493 1
						const currentUser = await this.currentUserPoolUser();
494 1
						that.user = currentUser;
495 1
						dispatchAuthEvent(
496
							'signIn',
497
							currentUser,
498
							`A user ${user.getUsername()} has been signed in`
499
						);
500 1
						resolve(currentUser);
501
					} catch (e) {
502 1
						logger.error('Failed to get the signed in user', e);
503 1
						reject(e);
504
					}
505
				}
506
			},
507
			onFailure: err => {
508 1
				logger.debug('signIn failure', err);
509 1
				dispatchAuthEvent(
510
					'signIn_failure',
511
					err,
512
					`${user.getUsername()} failed to signin`
513
				);
514 1
				reject(err);
515
			},
516
			customChallenge: challengeParam => {
517 1
				logger.debug('signIn custom challenge answer required');
518 1
				user['challengeName'] = 'CUSTOM_CHALLENGE';
519 1
				user['challengeParam'] = challengeParam;
520 1
				resolve(user);
521
			},
522
			mfaRequired: (challengeName, challengeParam) => {
523 1
				logger.debug('signIn MFA required');
524 1
				user['challengeName'] = challengeName;
525 1
				user['challengeParam'] = challengeParam;
526 1
				resolve(user);
527
			},
528
			mfaSetup: (challengeName, challengeParam) => {
529 1
				logger.debug('signIn mfa setup', challengeName);
530 1
				user['challengeName'] = challengeName;
531 1
				user['challengeParam'] = challengeParam;
532 1
				resolve(user);
533
			},
534
			newPasswordRequired: (userAttributes, requiredAttributes) => {
535 1
				logger.debug('signIn new password');
536 1
				user['challengeName'] = 'NEW_PASSWORD_REQUIRED';
537 1
				user['challengeParam'] = {
538
					userAttributes,
539
					requiredAttributes,
540
				};
541 1
				resolve(user);
542
			},
543
			totpRequired: (challengeName, challengeParam) => {
544 1
				logger.debug('signIn totpRequired');
545 1
				user['challengeName'] = challengeName;
546 1
				user['challengeParam'] = challengeParam;
547 1
				resolve(user);
548
			},
549
			selectMFAType: (challengeName, challengeParam) => {
550 1
				logger.debug('signIn selectMFAType', challengeName);
551 1
				user['challengeName'] = challengeName;
552 1
				user['challengeParam'] = challengeParam;
553 1
				resolve(user);
554
			},
555
		};
556
	}
557

558
	/**
559
	 * Sign in with a password
560
	 * @private
561
	 * @param {AuthenticationDetails} authDetails - the user sign in data
562
	 * @return - A promise resolves the CognitoUser object if success or mfa required
563
	 */
564 1
	private signInWithPassword(
565
		authDetails: AuthenticationDetails
566
	): Promise<CognitoUser | any> {
567 1
		const user = this.createCognitoUser(authDetails.getUsername());
568

569 1
		return new Promise((resolve, reject) => {
570 1
			user.authenticateUser(
571
				authDetails,
572
				this.authCallbacks(user, resolve, reject)
573
			);
574
		});
575
	}
576

577
	/**
578
	 * Sign in without a password
579
	 * @private
580
	 * @param {AuthenticationDetails} authDetails - the user sign in data
581
	 * @return - A promise resolves the CognitoUser object if success or mfa required
582
	 */
583 1
	private signInWithoutPassword(
584
		authDetails: AuthenticationDetails
585
	): Promise<CognitoUser | any> {
586 1
		const user = this.createCognitoUser(authDetails.getUsername());
587 1
		user.setAuthenticationFlowType('CUSTOM_AUTH');
588

589 1
		return new Promise((resolve, reject) => {
590 1
			user.initiateAuth(authDetails, this.authCallbacks(user, resolve, reject));
591
		});
592
	}
593

594
	/**
595
	 * get user current preferred mfa option
596
	 * this method doesn't work with totp, we need to deprecate it.
597
	 * @deprecated
598
	 * @param {CognitoUser} user - the current user
599
	 * @return - A promise resolves the current preferred mfa option if success
600
	 */
601 1
	public getMFAOptions(user: CognitoUser | any): Promise<MFAOption[]> {
602 1
		return new Promise((res, rej) => {
603 1
			user.getMFAOptions((err, mfaOptions) => {
604 1
				if (err) {
605 1
					logger.debug('get MFA Options failed', err);
606 1
					rej(err);
607 1
					return;
608
				}
609 1
				logger.debug('get MFA options success', mfaOptions);
610 1
				res(mfaOptions);
611 1
				return;
612
			});
613
		});
614
	}
615

616
	/**
617
	 * get preferred mfa method
618
	 * @param {CognitoUser} user - the current cognito user
619
	 * @param {GetPreferredMFAOpts} params - options for getting the current user preferred MFA
620
	 */
621 1
	public getPreferredMFA(
622
		user: CognitoUser | any,
623
		params?: GetPreferredMFAOpts
624
	): Promise<string> {
625 0
		const that = this;
626 0
		return new Promise((res, rej) => {
627 1
			const bypassCache = params ? params.bypassCache : false;
628 0
			user.getUserData(
629
				(err, data) => {
630 1
					if (err) {
631 0
						logger.debug('getting preferred mfa failed', err);
632 0
						rej(err);
633 0
						return;
634
					}
635

636 0
					const mfaType = that._getMfaTypeFromUserData(data);
637 1
					if (!mfaType) {
638 0
						rej('invalid MFA Type');
639 0
						return;
640
					} else {
641 0
						res(mfaType);
642 0
						return;
643
					}
644
				},
645
				{ bypassCache }
646
			);
647
		});
648
	}
649

650 1
	private _getMfaTypeFromUserData(data) {
651 0
		let ret = null;
652 0
		const preferredMFA = data.PreferredMfaSetting;
653
		// if the user has used Auth.setPreferredMFA() to setup the mfa type
654
		// then the "PreferredMfaSetting" would exist in the response
655 1
		if (preferredMFA) {
656 0
			ret = preferredMFA;
657
		} else {
658
			// if mfaList exists but empty, then its noMFA
659 0
			const mfaList = data.UserMFASettingList;
660 1
			if (!mfaList) {
661
				// if SMS was enabled by using Auth.enableSMS(),
662
				// the response would contain MFAOptions
663
				// as for now Cognito only supports for SMS, so we will say it is 'SMS_MFA'
664
				// if it does not exist, then it should be NOMFA
665 0
				const MFAOptions = data.MFAOptions;
666 1
				if (MFAOptions) {
667 0
					ret = 'SMS_MFA';
668
				} else {
669 0
					ret = 'NOMFA';
670
				}
671 1
			} else if (mfaList.length === 0) {
672 0
				ret = 'NOMFA';
673
			} else {
674 0
				logger.debug('invalid case for getPreferredMFA', data);
675
			}
676
		}
677 0
		return ret;
678
	}
679

680 1
	private _getUserData(user, params) {
681 1
		return new Promise((res, rej) => {
682 1
			user.getUserData((err, data) => {
683 1
				if (err) {
684 0
					logger.debug('getting user data failed', err);
685 0
					rej(err);
686 0
					return;
687
				} else {
688 1
					res(data);
689 1
					return;
690
				}
691
			}, params);
692
		});
693
	}
694

695
	/**
696
	 * set preferred MFA method
697
	 * @param {CognitoUser} user - the current Cognito user
698
	 * @param {string} mfaMethod - preferred mfa method
699
	 * @return - A promise resolve if success
700
	 */
701 1
	public async setPreferredMFA(
702
		user: CognitoUser | any,
703
		mfaMethod: 'TOTP' | 'SMS' | 'NOMFA'
704
	): Promise<string> {
705 1
		const userData = await this._getUserData(user, {
706
			bypassCache: true,
707
		});
708 1
		let smsMfaSettings = null;
709 1
		let totpMfaSettings = null;
710

711 1
		switch (mfaMethod) {
712 1
			case 'TOTP' || 'SOFTWARE_TOKEN_MFA':
713 1
				totpMfaSettings = {
714
					PreferredMfa: true,
715
					Enabled: true,
716
				};
717 1
				break;
718 1
			case 'SMS' || 'SMS_MFA':
719 0
				smsMfaSettings = {
720
					PreferredMfa: true,
721
					Enabled: true,
722
				};
723 0
				break;
724 0
			case 'NOMFA':
725 0
				const mfaList = userData['UserMFASettingList'];
726 0
				const currentMFAType = await this._getMfaTypeFromUserData(userData);
727 1
				if (currentMFAType === 'NOMFA') {
728 0
					return Promise.resolve('No change for mfa type');
729 1
				} else if (currentMFAType === 'SMS_MFA') {
730 0
					smsMfaSettings = {
731
						PreferredMfa: false,
732
						Enabled: false,
733
					};
734 1
				} else if (currentMFAType === 'SOFTWARE_TOKEN_MFA') {
735 0
					totpMfaSettings = {
736
						PreferredMfa: false,
737
						Enabled: false,
738
					};
739
				} else {
740 0
					return this.rejectAuthError(AuthErrorTypes.InvalidMFA);
741
				}
742
				// if there is a UserMFASettingList in the response
743
				// we need to disable every mfa type in that list
744 1
				if (mfaList && mfaList.length !== 0) {
745
					// to disable SMS or TOTP if exists in that list
746 0
					mfaList.forEach(mfaType => {
747 1
						if (mfaType === 'SMS_MFA') {
748 0
							smsMfaSettings = {
749
								PreferredMfa: false,
750
								Enabled: false,
751
							};
752 1
						} else if (mfaType === 'SOFTWARE_TOKEN_MFA') {
753 0
							totpMfaSettings = {
754
								PreferredMfa: false,
755
								Enabled: false,
756
							};
757
						}
758
					});
759
				}
760 0
				break;
761
			default:
762 1
				logger.debug('no validmfa method provided');
763 1
				return this.rejectAuthError(AuthErrorTypes.NoMFA);
764
		}
765

766 1
		const that = this;
767 1
		return new Promise<string>((res, rej) => {
768 1
			user.setUserMfaPreference(
769
				smsMfaSettings,
770
				totpMfaSettings,
771
				(err, result) => {
772 1
					if (err) {
773 1
						logger.debug('Set user mfa preference error', err);
774 1
						return rej(err);
775
					}
776 1
					logger.debug('Set user mfa success', result);
777 1
					logger.debug('Caching the latest user data into local');
778
					// cache the latest result into user data
779 1
					user.getUserData(
780
						(err, data) => {
781 1
							if (err) {
782 0
								logger.debug('getting user data failed', err);
783 0
								return rej(err);
784
							} else {
785 1
								return res(result);
786
							}
787
						},
788
						{ bypassCache: true }
789
					);
790
				}
791
			);
792
		});
793
	}
794

795
	/**
796
	 * diable SMS
797
	 * @deprecated
798
	 * @param {CognitoUser} user - the current user
799
	 * @return - A promise resolves is success
800
	 */
801 1
	public disableSMS(user: CognitoUser): Promise<string> {
802 1
		return new Promise((res, rej) => {
803 1
			user.disableMFA((err, data) => {
804 1
				if (err) {
805 1
					logger.debug('disable mfa failed', err);
806 1
					rej(err);
807 1
					return;
808
				}
809 1
				logger.debug('disable mfa succeed', data);
810 1
				res(data);
811 1
				return;
812
			});
813
		});
814
	}
815

816
	/**
817
	 * enable SMS
818
	 * @deprecated
819
	 * @param {CognitoUser} user - the current user
820
	 * @return - A promise resolves is success
821
	 */
822 1
	public enableSMS(user: CognitoUser): Promise<string> {
823 1
		return new Promise((res, rej) => {
824 1
			user.enableMFA((err, data) => {
825 1
				if (err) {
826 1
					logger.debug('enable mfa failed', err);
827 1
					rej(err);
828 1
					return;
829
				}
830 1
				logger.debug('enable mfa succeed', data);
831 1
				res(data);
832 1
				return;
833
			});
834
		});
835
	}
836

837
	/**
838
	 * Setup TOTP
839
	 * @param {CognitoUser} user - the current user
840
	 * @return - A promise resolves with the secret code if success
841
	 */
842 1
	public setupTOTP(user: CognitoUser | any): Promise<string> {
843 1
		return new Promise((res, rej) => {
844 1
			user.associateSoftwareToken({
845
				onFailure: err => {
846 1
					logger.debug('associateSoftwareToken failed', err);
847 1
					rej(err);
848 1
					return;
849
				},
850
				associateSecretCode: secretCode => {
851 1
					logger.debug('associateSoftwareToken sucess', secretCode);
852 1
					res(secretCode);
853 1
					return;
854
				},
855
			});
856
		});
857
	}
858

859
	/**
860
	 * verify TOTP setup
861
	 * @param {CognitoUser} user - the current user
862
	 * @param {string} challengeAnswer - challenge answer
863
	 * @return - A promise resolves is success
864
	 */
865 1
	public verifyTotpToken(
866
		user: CognitoUser | any,
867
		challengeAnswer: string
868
	): Promise<CognitoUserSession> {
869 1
		logger.debug('verfication totp token', user, challengeAnswer);
870 1
		return new Promise((res, rej) => {
871 1
			user.verifySoftwareToken(challengeAnswer, 'My TOTP device', {
872
				onFailure: err => {
873 1
					logger.debug('verifyTotpToken failed', err);
874 1
					rej(err);
875 1
					return;
876
				},
877
				onSuccess: data => {
878 1
					logger.debug('verifyTotpToken success', data);
879 1
					res(data);
880 1
					return;
881
				},
882
			});
883
		});
884
	}
885

886
	/**
887
	 * Send MFA code to confirm sign in
888
	 * @param {Object} user - The CognitoUser object
889
	 * @param {String} code - The confirmation code
890
	 */
891 1
	public confirmSignIn(
892
		user: CognitoUser | any,
893
		code: string,
894
		mfaType?: 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA' | null,
895 1
		clientMetadata: ClientMetaData = this._config.clientMetadata
896
	): Promise<CognitoUser | any> {
897 1
		if (!code) {
898 1
			return this.rejectAuthError(AuthErrorTypes.EmptyCode);
899
		}
900

901 1
		const that = this;
902 1
		return new Promise((resolve, reject) => {
903 1
			user.sendMFACode(
904
				code,
905
				{
906 1
					onSuccess: async session => {
907 1
						logger.debug(session);
908
						try {
909 1
							await this.Credentials.clear();
910 1
							const cred = await this.Credentials.set(session, 'session');
911 0
							logger.debug('succeed to get cognito credentials', cred);
912
						} catch (e) {
913 1
							logger.debug('cannot get cognito credentials', e);
914
						} finally {
915 1
							that.user = user;
916

917 1
							dispatchAuthEvent('signIn', user, `${user} has signed in`);
918 1
							resolve(user);
919
						}
920
					},
921
					onFailure: err => {
922 1
						logger.debug('confirm signIn failure', err);
923 1
						reject(err);
924
					},
925
				},
926
				mfaType,
927
				clientMetadata
928
			);
929
		});
930
	}
931

932 1
	public completeNewPassword(
933
		user: CognitoUser | any,
934
		password: string,
935 1
		requiredAttributes: any = {},
936 1
		clientMetadata: ClientMetaData = this._config.clientMetadata
937
	): Promise<CognitoUser | any> {
938 1
		if (!password) {
939 1
			return this.rejectAuthError(AuthErrorTypes.EmptyPassword);
940
		}
941

942 1
		const that = this;
943 1
		return new Promise((resolve, reject) => {
944 1
			user.completeNewPasswordChallenge(
945
				password,
946
				requiredAttributes,
947
				{
948 1
					onSuccess: async session => {
949 1
						logger.debug(session);
950
						try {
951 1
							await this.Credentials.clear();
952 1
							const cred = await this.Credentials.set(session, 'session');
953 0
							logger.debug('succeed to get cognito credentials', cred);
954
						} catch (e) {
955 1
							logger.debug('cannot get cognito credentials', e);
956
						} finally {
957 1
							that.user = user;
958 1
							dispatchAuthEvent('signIn', user, `${user} has signed in`);
959 1
							resolve(user);
960
						}
961
					},
962
					onFailure: err => {
963 1
						logger.debug('completeNewPassword failure', err);
964 1
						dispatchAuthEvent(
965
							'completeNewPassword_failure',
966
							err,
967
							`${this.user} failed to complete the new password flow`
968
						);
969 1
						reject(err);
970
					},
971
					mfaRequired: (challengeName, challengeParam) => {
972 1
						logger.debug('signIn MFA required');
973 1
						user['challengeName'] = challengeName;
974 1
						user['challengeParam'] = challengeParam;
975 1
						resolve(user);
976
					},
977
					mfaSetup: (challengeName, challengeParam) => {
978 1
						logger.debug('signIn mfa setup', challengeName);
979 1
						user['challengeName'] = challengeName;
980 1
						user['challengeParam'] = challengeParam;
981 1
						resolve(user);
982
					},
983
					totpRequired: (challengeName, challengeParam) => {
984 0
						logger.debug('signIn mfa setup', challengeName);
985 0
						user['challengeName'] = challengeName;
986 0
						user['challengeParam'] = challengeParam;
987 0
						resolve(user);
988
					},
989
				},
990
				clientMetadata
991
			);
992
		});
993
	}
994

995
	/**
996
	 * Send the answer to a custom challenge
997
	 * @param {CognitoUser} user - The CognitoUser object
998
	 * @param {String} challengeResponses - The confirmation code
999
	 */
1000 1
	public sendCustomChallengeAnswer(
1001
		user: CognitoUser | any,
1002
		challengeResponses: string,
1003 1
		clientMetadata: ClientMetaData = this._config.clientMetadata
1004
	): Promise<CognitoUser | any> {
1005 1
		if (!this.userPool) {
1006 1
			return this.rejectNoUserPool();
1007
		}
1008 1
		if (!challengeResponses) {
1009 0
			return this.rejectAuthError(AuthErrorTypes.EmptyChallengeResponse);
1010
		}
1011

1012 1
		const that = this;
1013 1
		return new Promise((resolve, reject) => {
1014 1
			user.sendCustomChallengeAnswer(
1015
				challengeResponses,
1016
				this.authCallbacks(user, resolve, reject),
1017
				clientMetadata
1018
			);
1019
		});
1020
	}
1021

1022
	/**
1023
	 * Update an authenticated users' attributes
1024
	 * @param {CognitoUser} - The currently logged in user object
1025
	 * @return {Promise}
1026
	 **/
1027 1
	public updateUserAttributes(
1028
		user: CognitoUser | any,
1029
		attributes: object,
1030 1
		clientMetadata: ClientMetaData = this._config.clientMetadata
1031
	): Promise<string> {
1032 1
		const attributeList: ICognitoUserAttributeData[] = [];
1033 1
		const that = this;
1034 1
		return new Promise((resolve, reject) => {
1035 1
			that.userSession(user).then(session => {
1036 1
				for (const key in attributes) {
1037 1
					if (key !== 'sub' && key.indexOf('_verified') < 0) {
1038 1
						const attr: ICognitoUserAttributeData = {
1039
							Name: key,
1040
							Value: attributes[key],
1041
						};
1042 1
						attributeList.push(attr);
1043
					}
1044
				}
1045 1
				user.updateAttributes(
1046
					attributeList,
1047
					(err, result) => {
1048 1
						if (err) {
1049 0
							return reject(err);
1050
						} else {
1051 1
							return resolve(result);
1052
						}
1053
					},
1054
					clientMetadata
1055
				);
1056
			});
1057
		});
1058
	}
1059
	/**
1060
	 * Return user attributes
1061
	 * @param {Object} user - The CognitoUser object
1062
	 * @return - A promise resolves to user attributes if success
1063
	 */
1064 1
	public userAttributes(
1065
		user: CognitoUser | any
1066
	): Promise<CognitoUserAttribute[]> {
1067 1
		return new Promise((resolve, reject) => {
1068 1
			this.userSession(user).then(session => {
1069 1
				user.getUserAttributes((err, attributes) => {
1070 1
					if (err) {
1071 1
						reject(err);
1072
					} else {
1073 1
						resolve(attributes);
1074
					}
1075
				});
1076
			});
1077
		});
1078
	}
1079

1080 1
	public verifiedContact(user: CognitoUser | any) {
1081 1
		const that = this;
1082 1
		return this.userAttributes(user).then(attributes => {
1083 1
			const attrs = that.attributesToObject(attributes);
1084 1
			const unverified = {};
1085 1
			const verified = {};
1086 1
			if (attrs['email']) {
1087 1
				if (attrs['email_verified']) {
1088 1
					verified['email'] = attrs['email'];
1089
				} else {
1090 1
					unverified['email'] = attrs['email'];
1091
				}
1092
			}
1093 1
			if (attrs['phone_number']) {
1094 1
				if (attrs['phone_number_verified']) {
1095 1
					verified['phone_number'] = attrs['phone_number'];
1096
				} else {
1097 1
					unverified['phone_number'] = attrs['phone_number'];
1098
				}
1099
			}
1100 1
			return {
1101
				verified,
1102
				unverified,
1103
			};
1104
		});
1105
	}
1106

1107
	/**
1108
	 * Get current authenticated user
1109
	 * @return - A promise resolves to current authenticated CognitoUser if success
1110
	 */
1111 1
	public currentUserPoolUser(
1112
		params?: CurrentUserOpts
1113
	): Promise<CognitoUser | any> {
1114 1
		if (!this.userPool) {
1115 1
			return this.rejectNoUserPool();
1116
		}
1117

1118 1
		return new Promise((res, rej) => {
1119 1
			this._storageSync
1120 1
				.then(async () => {
1121 1
					if (this.isOAuthInProgress()) {
1122 1
						logger.debug('OAuth signIn in progress, waiting for resolution...');
1123

1124 1
						await new Promise(res => {
1125 1
							const timeoutId = setTimeout(() => {
1126 0
								logger.debug('OAuth signIn in progress timeout');
1127

1128 0
								Hub.remove('auth', hostedUISignCallback);
1129

1130 0
								res();
1131
							}, OAUTH_FLOW_MS_TIMEOUT);
1132

1133 1
							Hub.listen('auth', hostedUISignCallback);
1134

1135 1
							function hostedUISignCallback({ payload }) {
1136 1
								const { event } = payload;
1137

1138 1
								if (
1139 1
									event === 'cognitoHostedUI' ||
1140
									event === 'cognitoHostedUI_failure'
1141
								) {
1142 1
									logger.debug(`OAuth signIn resolved: ${event}`);
1143 1
									clearTimeout(timeoutId);
1144

1145 1
									Hub.remove('auth', hostedUISignCallback);
1146

1147 1
									res();
1148
								}
1149
							}
1150
						});
1151
					}
1152

1153 1
					const user = this.userPool.getCurrentUser();
1154

1155 1
					if (!user) {
1156 1
						logger.debug('Failed to get user from user pool');
1157 1
						rej('No current user');
1158 1
						return;
1159
					}
1160

1161
					// refresh the session if the session expired.
1162 1
					user.getSession(async (err, session) => {
1163 1
						if (err) {
1164 1
							logger.debug('Failed to get the user session', err);
1165 1
							rej(err);
1166 1
							return;
1167
						}
1168

1169
						// get user data from Cognito
1170 1
						const bypassCache = params ? params.bypassCache : false;
1171

1172 1
						if (bypassCache) {
1173 1
							await this.Credentials.clear();
1174
						}
1175

1176
						// validate the token's scope first before calling this function
1177 1
						const { scope = '' } = session.getAccessToken().decodePayload();
1178 1
						if (scope.split(' ').includes(USER_ADMIN_SCOPE)) {
1179 1
							user.getUserData(
1180
								(err, data) => {
1181 1
									if (err) {
1182 1
										logger.debug('getting user data failed', err);
1183
										// Make sure the user is still valid
1184 1
										if (
1185 1
											err.message === 'User is disabled.' ||
1186
											err.message === 'User does not exist.' ||
1187
											err.message === 'Access Token has been revoked' // Session revoked by another app
1188
										) {
1189 1
											rej(err);
1190
										} else {
1191
											// the error may also be thrown when lack of permissions to get user info etc
1192
											// in that case we just bypass the error
1193 1
											res(user);
1194
										}
1195 1
										return;
1196
									}
1197 1
									const preferredMFA = data.PreferredMfaSetting || 'NOMFA';
1198 1
									const attributeList = [];
1199

1200 1
									for (let i = 0; i < data.UserAttributes.length; i++) {
1201 1
										const attribute = {
1202
											Name: data.UserAttributes[i].Name,
1203
											Value: data.UserAttributes[i].Value,
1204
										};
1205 1
										const userAttribute = new CognitoUserAttribute(attribute);
1206 1
										attributeList.push(userAttribute);
1207
									}
1208

1209 1
									const attributes = this.attributesToObject(attributeList);
1210 1
									Object.assign(user, { attributes, preferredMFA });
1211 1
									return res(user);
1212
								},
1213
								{ bypassCache }
1214
							);
1215
						} else {
1216 1
							logger.debug(
1217
								`Unable to get the user data because the ${USER_ADMIN_SCOPE} ` +
1218
									`is not in the scopes of the access token`
1219
							);
1220 1
							return res(user);
1221
						}
1222
					});
1223
				})
1224
				.catch(e => {
1225 0
					logger.debug('Failed to sync cache info into memory', e);
1226 0
					return rej(e);
1227
				});
1228
		});
1229
	}
1230

1231 1
	private isOAuthInProgress(): boolean {
1232 1
		return this.oAuthFlowInProgress;
1233
	}
1234

1235
	/**
1236
	 * Get current authenticated user
1237
	 * @param {CurrentUserOpts} - options for getting the current user
1238
	 * @return - A promise resolves to current authenticated CognitoUser if success
1239
	 */
1240 1
	public async currentAuthenticatedUser(
1241
		params?: CurrentUserOpts
1242
	): Promise<CognitoUser | any> {
1243 1
		logger.debug('getting current authenticated user');
1244 1
		let federatedUser = null;
1245
		try {
1246 1
			await this._storageSync;
1247
		} catch (e) {
1248 0
			logger.debug('Failed to sync cache info into memory', e);
1249 0
			throw e;
1250
		}
1251

1252 1
		try {
1253 1
			federatedUser = JSON.parse(
1254
				this._storage.getItem('aws-amplify-federatedInfo')
1255
			).user;
1256
		} catch (e) {
1257 1
			logger.debug('cannot load federated user from auth storage');
1258
		}
1259

1260 1
		if (federatedUser) {
1261 1
			this.user = federatedUser;
1262 1
			logger.debug('get current authenticated federated user', this.user);
1263 1
			return this.user;
1264
		} else {
1265 1
			logger.debug('get current authenticated userpool user');
1266 1
			let user = null;
1267
			try {
1268 1
				user = await this.currentUserPoolUser(params);
1269
			} catch (e) {
1270 1
				if (e === 'No userPool') {
1271 0
					logger.error(
1272
						'Cannot get the current user because the user pool is missing. ' +
1273
							'Please make sure the Auth module is configured with a valid Cognito User Pool ID'
1274
					);
1275
				}
1276 0
				logger.debug('The user is not authenticated by the error', e);
1277 0
				throw 'not authenticated';
1278
			}
1279 1
			this.user = user;
1280 1
			return this.user;
1281
		}
1282
	}
1283

1284
	/**
1285
	 * Get current user's session
1286
	 * @return - A promise resolves to session object if success
1287
	 */
1288 1
	public currentSession(): Promise<CognitoUserSession> {
1289 1
		const that = this;
1290 1
		logger.debug('Getting current session');
1291
		// Purposely not calling the reject method here because we don't need a console error
1292 1
		if (!this.userPool) {
1293 1
			return Promise.reject();
1294
		}
1295

1296 1
		return new Promise((res, rej) => {
1297 1
			that
1298
				.currentUserPoolUser()
1299
				.then(user => {
1300 1
					that
1301
						.userSession(user)
1302
						.then(session => {
1303 1
							res(session);
1304 1
							return;
1305
						})
1306
						.catch(e => {
1307 1
							logger.debug('Failed to get the current session', e);
1308 1
							rej(e);
1309 1
							return;
1310
						});
1311
				})
1312
				.catch(e => {
1313 1
					logger.debug('Failed to get the current user', e);
1314 1
					rej(e);
1315 1
					return;
1316
				});
1317
		});
1318
	}
1319

1320
	/**
1321
	 * Get the corresponding user session
1322
	 * @param {Object} user - The CognitoUser object
1323
	 * @return - A promise resolves to the session
1324
	 */
1325 1
	public userSession(user): Promise<CognitoUserSession> {
1326 1
		if (!user) {
1327 1
			logger.debug('the user is null');
1328 1
			return this.rejectAuthError(AuthErrorTypes.NoUserSession);
1329
		}
1330 1
		return new Promise((resolve, reject) => {
1331 1
			logger.debug('Getting the session from this user:', user);
1332 1
			user.getSession((err, session) => {
1333 1
				if (err) {
1334 1
					logger.debug('Failed to get the session from user', user);
1335 1
					reject(err);
1336 1
					return;
1337
				} else {
1338 1
					logger.debug('Succeed to get the user session', session);
1339 1
					resolve(session);
1340 1
					return;
1341
				}
1342
			});
1343
		});
1344
	}
1345

1346
	/**
1347
	 * Get  authenticated credentials of current user.
1348
	 * @return - A promise resolves to be current user's credentials
1349
	 */
1350 1
	public async currentUserCredentials(): Promise<ICredentials> {
1351 1
		logger.debug('Getting current user credentials');
1352

1353
		try {
1354 1
			await this._storageSync;
1355
		} catch (e) {
1356 0
			logger.debug('Failed to sync cache info into memory', e);
1357 0
			throw e;
1358
		}
1359

1360
		// first to check whether there is federation info in the auth storage
1361 1
		let federatedInfo = null;
1362 1
		try {
1363 1
			federatedInfo = JSON.parse(
1364
				this._storage.getItem('aws-amplify-federatedInfo')
1365
			);
1366
		} catch (e) {
1367 1
			logger.debug('failed to get or parse item aws-amplify-federatedInfo', e);
1368
		}
1369

1370 1
		if (federatedInfo) {
1371
			// refresh the jwt token here if necessary
1372 1
			return this.Credentials.refreshFederatedToken(federatedInfo);
1373
		} else {
1374 1
			return this.currentSession()
1375
				.then(session => {
1376 1
					logger.debug('getting session success', session);
1377 1
					return this.Credentials.set(session, 'session');
1378
				})
1379
				.catch(error => {
1380 1
					logger.debug('getting session failed', error);
1381 1
					return this.Credentials.set(null, 'guest');
1382
				});
1383
		}
1384
	}
1385

1386 1
	public currentCredentials(): Promise<ICredentials> {
1387 1
		logger.debug('getting current credentials');
1388 1
		return this.Credentials.get();
1389
	}
1390

1391
	/**
1392
	 * Initiate an attribute confirmation request
1393
	 * @param {Object} user - The CognitoUser
1394
	 * @param {Object} attr - The attributes to be verified
1395
	 * @return - A promise resolves to callback data if success
1396
	 */
1397 1
	public verifyUserAttribute(
1398
		user: CognitoUser | any,
1399
		attr: string,
1400 1
		clientMetadata: ClientMetaData = this._config.clientMetadata
1401
	): Promise<void> {
1402 1
		return new Promise((resolve, reject) => {
1403 1
			user.getAttributeVerificationCode(attr, {
1404
				onSuccess() {
1405 1
					return resolve();
1406
				},
1407
				onFailure(err) {
1408 1
					return reject(err);
1409
				},
1410
				clientMetadata,
1411
			});
1412
		});
1413
	}
1414

1415
	/**
1416
	 * Confirm an attribute using a confirmation code
1417
	 * @param {Object} user - The CognitoUser
1418
	 * @param {Object} attr - The attribute to be verified
1419
	 * @param {String} code - The confirmation code
1420
	 * @return - A promise resolves to callback data if success
1421
	 */
1422 1
	public verifyUserAttributeSubmit(
1423
		user: CognitoUser | any,
1424
		attr: string,
1425
		code: string
1426
	): Promise<string> {
1427 1
		if (!code) {
1428 1
			return this.rejectAuthError(AuthErrorTypes.EmptyCode);
1429
		}
1430

1431 1
		return new Promise((resolve, reject) => {
1432 1
			user.verifyAttribute(attr, code, {
1433
				onSuccess(data) {
1434 1
					resolve(data);
1435 1
					return;
1436
				},
1437
				onFailure(err) {
1438 1
					reject(err);
1439 1
					return;
1440
				},
1441
			});
1442
		});
1443
	}
1444

1445 1
	public verifyCurrentUserAttribute(attr: string): Promise<void> {
1446 1
		const that = this;
1447 1
		return that
1448
			.currentUserPoolUser()
1449 1
			.then(user => that.verifyUserAttribute(user, attr));
1450
	}
1451

1452
	/**
1453
	 * Confirm current user's attribute using a confirmation code
1454
	 * @param {Object} attr - The attribute to be verified
1455
	 * @param {String} code - The confirmation code
1456
	 * @return - A promise resolves to callback data if success
1457
	 */
1458 1
	verifyCurrentUserAttributeSubmit(
1459
		attr: string,
1460
		code: string
1461
	): Promise<string> {
1462 1
		const that = this;
1463 1
		return that
1464
			.currentUserPoolUser()
1465 1
			.then(user => that.verifyUserAttributeSubmit(user, attr, code));
1466
	}
1467

1468 1
	private async cognitoIdentitySignOut(
1469
		opts: SignOutOpts,
1470
		user: CognitoUser | any
1471
	) {
1472
		try {
1473 1
			await this._storageSync;
1474
		} catch (e) {
1475 0
			logger.debug('Failed to sync cache info into memory', e);
1476 0
			throw e;
1477
		}
1478

1479 1
		const isSignedInHostedUI =
1480 1
			this._oAuthHandler &&
1481
			this._storage.getItem('amplify-signin-with-hostedUI') === 'true';
1482

1483 1
		return new Promise((res, rej) => {
1484 1
			if (opts && opts.global) {
1485 1
				logger.debug('user global sign out', user);
1486
				// in order to use global signout
1487
				// we must validate the user as an authenticated user by using getSession
1488 1
				user.getSession((err, result) => {
1489 1
					if (err) {
1490 0
						logger.debug('failed to get the user session', err);
1491 0
						return rej(err);
1492
					}
1493 1
					user.globalSignOut({
1494
						onSuccess: data => {
1495 1
							logger.debug('global sign out success');
1496 1
							if (isSignedInHostedUI) {
1497 1
								this.oAuthSignOutRedirect(res, rej);
1498
							} else {
1499 1
								return res();
1500
							}
1501
						},
1502
						onFailure: err => {
1503 0
							logger.debug('global sign out failed', err);
1504 0
							return rej(err);
1505
						},
1506
					});
1507
				});
1508
			} else {
1509 1
				logger.debug('user sign out', user);
1510 1
				user.signOut();
1511 1
				if (isSignedInHostedUI) {
1512 1
					this.oAuthSignOutRedirect(res, rej);
1513
				} else {
1514 1
					return res();
1515
				}
1516
			}
1517
		});
1518
	}
1519

1520 1
	private oAuthSignOutRedirect(
1521
		resolve: () => void,
1522
		reject: (reason?: any) => void
1523
	) {
1524 1
		const { isBrowser } = JS.browserOrNode();
1525

1526 1
		if (isBrowser) {
1527 1
			this.oAuthSignOutRedirectOrReject(reject);
1528
		} else {
1529 1
			this.oAuthSignOutAndResolve(resolve);
1530
		}
1531
	}
1532

1533 1
	private oAuthSignOutAndResolve(resolve: () => void) {
1534 1
		this._oAuthHandler.signOut();
1535 1
		resolve();
1536
	}
1537

1538 1
	private oAuthSignOutRedirectOrReject(reject: (reason?: any) => void) {
1539 1
		this._oAuthHandler.signOut(); // this method redirects url
1540

1541
		// App should be redirected to another url otherwise it will reject
1542 1
		setTimeout(() => reject('Signout timeout fail'), 3000);
1543
	}
1544

1545
	/**
1546
	 * Sign out method
1547
	 * @
1548
	 * @return - A promise resolved if success
1549
	 */
1550 1
	public async signOut(opts?: SignOutOpts): Promise<any> {
1551
		try {
1552 1
			await this.cleanCachedItems();
1553
		} catch (e) {
1554 0
			logger.debug('failed to clear cached items');
1555
		}
1556

1557 1
		if (this.userPool) {
1558 1
			const user = this.userPool.getCurrentUser();
1559 1
			if (user) {
1560 1
				await this.cognitoIdentitySignOut(opts, user);
1561
			} else {
1562 1
				logger.debug('no current Cognito user');
1563
			}
1564
		} else {
1565 1
			logger.debug('no Congito User pool');
1566
		}
1567

1568
		/**
1569
		 * Note for future refactor - no reliable way to get username with
1570
		 * Cognito User Pools vs Identity when federating with Social Providers
1571
		 * This is why we need a well structured session object that can be inspected
1572
		 * and information passed back in the message below for Hub dispatch
1573
		 */
1574 1
		dispatchAuthEvent('signOut', this.user, `A user has been signed out`);
1575 1
		this.user = null;
1576
	}
1577

1578 1
	private async cleanCachedItems() {
1579
		// clear cognito cached item
1580 1
		await this.Credentials.clear();
1581
	}
1582

1583
	/**
1584
	 * Change a password for an authenticated user
1585
	 * @param {Object} user - The CognitoUser object
1586
	 * @param {String} oldPassword - the current password
1587
	 * @param {String} newPassword - the requested new password
1588
	 * @return - A promise resolves if success
1589
	 */
1590 1
	public changePassword(
1591
		user: CognitoUser | any,
1592
		oldPassword: string,
1593
		newPassword: string,
1594 1
		clientMetadata: ClientMetaData = this._config.clientMetadata
1595
	): Promise<'SUCCESS'> {
1596 1
		return new Promise((resolve, reject) => {
1597 1
			this.userSession(user).then(session => {
1598 1
				user.changePassword(
1599
					oldPassword,
1600
					newPassword,
1601
					(err, data) => {
1602 1
						if (err) {
1603 0
							logger.debug('change password failure', err);
1604 0
							return reject(err);
1605
						} else {
1606 1
							return resolve(data);
1607
						}
1608
					},
1609
					clientMetadata
1610
				);
1611
			});
1612
		});
1613
	}
1614

1615
	/**
1616
	 * Initiate a forgot password request
1617
	 * @param {String} username - the username to change password
1618
	 * @return - A promise resolves if success
1619
	 */
1620 1
	public forgotPassword(
1621
		username: string,
1622 1
		clientMetadata: ClientMetaData = this._config.clientMetadata
1623
	): Promise<any> {
1624 1
		if (!this.userPool) {
1625 1
			return this.rejectNoUserPool();
1626
		}
1627 1
		if (!username) {
1628 1
			return this.rejectAuthError(AuthErrorTypes.EmptyUsername);
1629
		}
1630

1631 1
		const user = this.createCognitoUser(username);
1632 1
		return new Promise((resolve, reject) => {
1633 1
			user.forgotPassword(
1634
				{
1635
					onSuccess: () => {
1636 1
						resolve();
1637 1
						return;
1638
					},
1639
					onFailure: err => {
1640 1
						logger.debug('forgot password failure', err);
1641 1
						dispatchAuthEvent(
1642
							'forgotPassword_failure',
1643
							err,
1644
							`${username} forgotPassword failed`
1645
						);
1646 1
						reject(err);
1647 1
						return;
1648
					},
1649
					inputVerificationCode: data => {
1650 1
						dispatchAuthEvent(
1651
							'forgotPassword',
1652
							user,
1653
							`${username} has initiated forgot password flow`
1654
						);
1655 1
						resolve(data);
1656 1
						return;
1657
					},
1658
				},
1659
				clientMetadata
1660
			);
1661
		});
1662
	}
1663

1664
	/**
1665
	 * Confirm a new password using a confirmation Code
1666
	 * @param {String} username - The username
1667
	 * @param {String} code - The confirmation code
1668
	 * @param {String} password - The new password
1669
	 * @return - A promise that resolves if success
1670
	 */
1671 1
	public forgotPasswordSubmit(
1672
		username: string,
1673
		code: string,
1674
		password: string,
1675 1
		clientMetadata: ClientMetaData = this._config.clientMetadata
1676
	): Promise<void> {
1677 1
		if (!this.userPool) {
1678 1
			return this.rejectNoUserPool();
1679
		}
1680 1
		if (!username) {
1681 1
			return this.rejectAuthError(AuthErrorTypes.EmptyUsername);
1682
		}
1683 1
		if (!code) {
1684 1
			return this.rejectAuthError(AuthErrorTypes.EmptyCode);
1685
		}
1686 1
		if (!password) {
1687 1
			return this.rejectAuthError(AuthErrorTypes.EmptyPassword);
1688
		}
1689

1690 1
		const user = this.createCognitoUser(username);
1691 1
		return new Promise((resolve, reject) => {
1692 1
			user.confirmPassword(
1693
				code,
1694
				password,
1695
				{
1696
					onSuccess: () => {
1697 1
						dispatchAuthEvent(
1698
							'forgotPasswordSubmit',
1699
							user,
1700
							`${username} forgotPasswordSubmit successful`
1701
						);
1702 1
						resolve();
1703 1
						return;
1704
					},
1705
					onFailure: err => {
1706 1
						dispatchAuthEvent(
1707
							'forgotPasswordSubmit_failure',
1708
							err,
1709
							`${username} forgotPasswordSubmit failed`
1710
						);
1711 1
						reject(err);
1712 1
						return;
1713
					},
1714
				},
1715
				clientMetadata
1716
			);
1717
		});
1718
	}
1719

1720
	/**
1721
	 * Get user information
1722
	 * @async
1723
	 * @return {Object }- current User's information
1724
	 */
1725 1
	public async currentUserInfo() {
1726 1
		const source = this.Credentials.getCredSource();
1727

1728 1
		if (!source || source === 'aws' || source === 'userPool') {
1729 1
			const user = await this.currentUserPoolUser().catch(err =>
1730 0
				logger.debug(err)
1731
			);
1732 1
			if (!user) {
1733 1
				return null;
1734
			}
1735

1736
			try {
1737 1
				const attributes = await this.userAttributes(user);
1738 1
				const userAttrs: object = this.attributesToObject(attributes);
1739 1
				let credentials = null;
1740
				try {
1741 1
					credentials = await this.currentCredentials();
1742
				} catch (e) {
1743 0
					logger.debug(
1744
						'Failed to retrieve credentials while getting current user info',
1745
						e
1746
					);
1747
				}
1748

1749 1
				const info = {
1750 1
					id: credentials ? credentials.identityId : undefined,
1751
					username: user.getUsername(),
1752
					attributes: userAttrs,
1753
				};
1754 1
				return info;
1755
			} catch (err) {
1756 1
				logger.debug('currentUserInfo error', err);
1757 1
				return {};
1758
			}
1759
		}
1760

1761 1
		if (source === 'federated') {
1762 1
			const user = this.user;
1763 1
			return user ? user : {};
1764
		}
1765
	}
1766

1767
	public async federatedSignIn(
1768
		options?: FederatedSignInOptions
1769
	): Promise<ICredentials>;
1770
	public async federatedSignIn(
1771
		provider: LegacyProvider,
1772
		response: FederatedResponse,
1773
		user: FederatedUser
1774
	): Promise<ICredentials>;
1775
	public async federatedSignIn(
1776
		options?: FederatedSignInOptionsCustom
1777
	): Promise<ICredentials>;
1778 1
	public async federatedSignIn(
1779
		providerOrOptions:
1780
			| LegacyProvider
1781
			| FederatedSignInOptions
1782
			| FederatedSignInOptionsCustom,
1783
		response?: FederatedResponse,
1784
		user?: FederatedUser
1785
	): Promise<ICredentials> {
1786 1
		if (!this._config.identityPoolId && !this._config.userPoolId) {
1787 1
			throw new Error(
1788
				`Federation requires either a User Pool or Identity Pool in config`
1789
			);
1790
		}
1791

1792
		// Ensure backwards compatability
1793 1
		if (typeof providerOrOptions === 'undefined') {
1794 1
			if (this._config.identityPoolId && !this._config.userPoolId) {
1795 1
				throw new Error(
1796
					`Federation with Identity Pools requires tokens passed as arguments`
1797
				);
1798
			}
1799
		}
1800

1801
		if (
1802 1
			isFederatedSignInOptions(providerOrOptions) ||
1803
			isFederatedSignInOptionsCustom(providerOrOptions) ||
1804
			hasCustomState(providerOrOptions) ||
1805
			typeof providerOrOptions === 'undefined'
1806
		) {
1807 1
			const options = providerOrOptions || {
1808
				provider: CognitoHostedUIIdentityProvider.Cognito,
1809
			};
1810 1
			const provider = isFederatedSignInOptions(options)
1811 1
				? options.provider
1812
				: (options as FederatedSignInOptionsCustom).customProvider;
1813

1814 1
			const customState = isFederatedSignInOptions(options)
1815 1
				? options.customState
1816
				: (options as FederatedSignInOptionsCustom).customState;
1817

1818 1
			if (this._config.userPoolId) {
1819 1
				const client_id = isCognitoHostedOpts(this._config.oauth)
1820 1
					? this._config.userPoolWebClientId
1821
					: this._config.oauth.clientID;
1822
				/*Note: Invenstigate automatically adding trailing slash */
1823 1
				const redirect_uri = isCognitoHostedOpts(this._config.oauth)
1824 1
					? this._config.oauth.redirectSignIn
1825
					: this._config.oauth.redirectUri;
1826

1827 1
				this._oAuthHandler.oauthSignIn(
1828
					this._config.oauth.responseType,
1829
					this._config.oauth.domain,
1830
					redirect_uri,
1831
					client_id,
1832
					provider,
1833
					customState
1834
				);
1835
			}
1836
		} else {
1837 1
			const provider = providerOrOptions;
1838
			// To check if the user is already logged in
1839 1
			try {
1840 1
				const loggedInUser = JSON.stringify(
1841
					JSON.parse(this._storage.getItem('aws-amplify-federatedInfo')).user
1842
				);
1843 1
				if (loggedInUser) {
1844 0
					logger.warn(`There is already a signed in user: ${loggedInUser} in your app.
1845
																	You should not call Auth.federatedSignIn method again as it may cause unexpected behavior.`);
1846
				}
1847
			} catch (e) {}
1848

1849 1
			const { token, identity_id, expires_at } = response;
1850
			// Because this.Credentials.set would update the user info with identity id
1851
			// So we need to retrieve the user again.
1852 1
			const credentials = await this.Credentials.set(
1853
				{ provider, token, identity_id, user, expires_at },
1854
				'federation'
1855
			);
1856 1
			const currentUser = await this.currentAuthenticatedUser();
1857 1
			dispatchAuthEvent(
1858
				'signIn',
1859
				currentUser,
1860
				`A user ${currentUser.username} has been signed in`
1861
			);
1862 1
			logger.debug('federated sign in credentials', credentials);
1863 1
			return credentials;
1864
		}
1865
	}
1866

1867
	/**
1868
	 * Used to complete the OAuth flow with or without the Cognito Hosted UI
1869
	 * @param {String} URL - optional parameter for customers to pass in the response URL
1870
	 */
1871 1
	private async _handleAuthResponse(URL?: string) {
1872 1
		if (this.oAuthFlowInProgress) {
1873 0
			logger.debug(`Skipping URL ${URL} current flow in progress`);
1874 0
			return;
1875
		}
1876

1877
		try {
1878 1
			this.oAuthFlowInProgress = true;
1879 1
			if (!this._config.userPoolId) {
1880 1
				throw new Error(
1881
					`OAuth responses require a User Pool defined in config`
1882
				);
1883
			}
1884

1885 1
			dispatchAuthEvent(
1886
				'parsingCallbackUrl',
1887
				{ url: URL },
1888
				`The callback url is being parsed`
1889
			);
1890

1891 1
			const currentUrl =
1892 1
				URL || (JS.browserOrNode().isBrowser ? window.location.href : '');
1893

1894 1
			const hasCodeOrError = !!(parse(currentUrl).query || '')
1895
				.split('&')
1896 1
				.map(entry => entry.split('='))
1897 1
				.find(([k]) => k === 'code' || k === 'error');
1898

1899 1
			const hasTokenOrError = !!(parse(currentUrl).hash || '#')
1900
				.substr(1)
1901
				.split('&')
1902 1
				.map(entry => entry.split('='))
1903 1
				.find(([k]) => k === 'access_token' || k === 'error');
1904

1905 1
			if (hasCodeOrError || hasTokenOrError) {
1906 1
				this._storage.setItem('amplify-redirected-from-hosted-ui', 'true');
1907
				try {
1908 1
					const {
1909
						accessToken,
1910
						idToken,
1911
						refreshToken,
1912
						state,
1913 1
					} = await this._oAuthHandler.handleAuthResponse(currentUrl);
1914 1
					const session = new CognitoUserSession({
1915
						IdToken: new CognitoIdToken({ IdToken: idToken }),
1916
						RefreshToken: new CognitoRefreshToken({
1917
							RefreshToken: refreshToken,
1918
						}),
1919
						AccessToken: new CognitoAccessToken({
1920
							AccessToken: accessToken,
1921
						}),
1922
					});
1923

1924 1
					let credentials;
1925
					// Get AWS Credentials & store if Identity Pool is defined
1926 1
					if (this._config.identityPoolId) {
1927 1
						credentials = await this.Credentials.set(session, 'session');
1928 1
						logger.debug('AWS credentials', credentials);
1929
					}
1930

1931
					/* 
1932
				Prior to the request we do sign the custom state along with the state we set. This check will verify
1933
				if there is a dash indicated when setting custom state from the request. If a dash is contained
1934
				then there is custom state present on the state string.
1935
				*/
1936 1
					const isCustomStateIncluded = /-/.test(state);
1937

1938
					/*
1939
				The following is to create a user for the Cognito Identity SDK to store the tokens
1940
				When we remove this SDK later that logic will have to be centralized in our new version
1941
				*/
1942
					//#region
1943 1
					const currentUser = this.createCognitoUser(
1944
						session.getIdToken().decodePayload()['cognito:username']
1945
					);
1946

1947
					// This calls cacheTokens() in Cognito SDK
1948 1
					currentUser.setSignInUserSession(session);
1949

1950 1
					if (window && typeof window.history !== 'undefined') {
1951 1
						window.history.replaceState(
1952
							{},
1953
							null,
1954
							(this._config.oauth as AwsCognitoOAuthOpts).redirectSignIn
1955
						);
1956
					}
1957

1958 1
					dispatchAuthEvent(
1959
						'signIn',
1960
						currentUser,
1961
						`A user ${currentUser.getUsername()} has been signed in`
1962
					);
1963 1
					dispatchAuthEvent(
1964
						'cognitoHostedUI',
1965
						currentUser,
1966
						`A user ${currentUser.getUsername()} has been signed in via Cognito Hosted UI`
1967
					);
1968

1969 1
					if (isCustomStateIncluded) {
1970 0
						const customState = state
1971
							.split('-')
1972
							.splice(1)
1973
							.join('-');
1974

1975 0
						dispatchAuthEvent(
1976
							'customOAuthState',
1977
							customState,
1978
							`State for user ${currentUser.getUsername()}`
1979
						);
1980
					}
1981
					//#endregion
1982

1983 1
					return credentials;
1984
				} catch (err) {
1985 0
					logger.debug('Error in cognito hosted auth response', err);
1986 0
					dispatchAuthEvent(
1987
						'signIn_failure',
1988
						err,
1989
						`The OAuth response flow failed`
1990
					);
1991 0
					dispatchAuthEvent(
1992
						'cognitoHostedUI_failure',
1993
						err,
1994
						`A failure occurred when returning to the Cognito Hosted UI`
1995
					);
1996 0
					dispatchAuthEvent(
1997
						'customState_failure',
1998
						err,
1999
						`A failure occurred when returning state`
2000
					);
2001
				}
2002
			}
2003
		} finally {
2004 1
			this.oAuthFlowInProgress = false;
2005
		}
2006
	}
2007

2008
	/**
2009
	 * Compact version of credentials
2010
	 * @param {Object} credentials
2011
	 * @return {Object} - Credentials
2012
	 */
2013 1
	public essentialCredentials(credentials): ICredentials {
2014 0
		return {
2015
			accessKeyId: credentials.accessKeyId,
2016
			sessionToken: credentials.sessionToken,
2017
			secretAccessKey: credentials.secretAccessKey,
2018
			identityId: credentials.identityId,
2019
			authenticated: credentials.authenticated,
2020
		};
2021
	}
2022

2023 1
	private attributesToObject(attributes) {
2024 1
		const obj = {};
2025 1
		if (attributes) {
2026 1
			attributes.map(attribute => {
2027 1
				if (
2028 1
					attribute.Name === 'email_verified' ||
2029
					attribute.Name === 'phone_number_verified'
2030
				) {
2031 1
					obj[attribute.Name] =
2032 1
						attribute.Value === 'true' || attribute.Value === true;
2033
				} else {
2034 1
					obj[attribute.Name] = attribute.Value;
2035
				}
2036
			});
2037
		}
2038 1
		return obj;
2039
	}
2040

2041 1
	private createCognitoUser(username: string): CognitoUser {
2042 1
		const userData: ICognitoUserData = {
2043
			Username: username,
2044
			Pool: this.userPool,
2045
		};
2046 1
		userData.Storage = this._storage;
2047

2048 1
		const { authenticationFlowType } = this._config;
2049

2050 1
		const user = new CognitoUser(userData);
2051 1
		if (authenticationFlowType) {
2052 0
			user.setAuthenticationFlowType(authenticationFlowType);
2053
		}
2054 1
		return user;
2055
	}
2056

2057 1
	private _isValidAuthStorage(obj) {
2058
		// We need to check if the obj has the functions of Storage
2059 1
		return (
2060 1
			!!obj &&
2061
			typeof obj.getItem === 'function' &&
2062
			typeof obj.setItem === 'function' &&
2063
			typeof obj.removeItem === 'function' &&
2064
			typeof obj.clear === 'function'
2065
		);
2066
	}
2067

2068 1
	private noUserPoolErrorHandler(config: AuthOptions): AuthErrorTypes {
2069 1
		if (config) {
2070 1
			if (!config.userPoolId || !config.identityPoolId) {
2071 1
				return AuthErrorTypes.MissingAuthConfig;
2072
			}
2073
		}
2074 1
		return AuthErrorTypes.NoConfig;
2075
	}
2076

2077 1
	private rejectAuthError(type: AuthErrorTypes): Promise<never> {
2078 1
		return Promise.reject(new AuthError(type));
2079
	}
2080

2081 1
	private rejectNoUserPool(): Promise<never> {
2082 1
		const type = this.noUserPoolErrorHandler(this._config);
2083 1
		return Promise.reject(new NoUserPoolError(type));
2084
	}
2085 1
}
2086

2087 1
export const Auth = new AuthClass(null);
2088

2089 1
Amplify.register(Auth);

Read our documentation on viewing source code .

Loading