@aws-amplify/auth 5.6.0 → 5.6.1-unstable.50675e7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2901 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import {
5
+ AuthOptions,
6
+ FederatedResponse,
7
+ SignUpParams,
8
+ FederatedUser,
9
+ ConfirmSignUpOptions,
10
+ SignOutOpts,
11
+ CurrentUserOpts,
12
+ GetPreferredMFAOpts,
13
+ SignInOpts,
14
+ isUsernamePasswordOpts,
15
+ isCognitoHostedOpts,
16
+ isFederatedSignInOptions,
17
+ isFederatedSignInOptionsCustom,
18
+ hasCustomState,
19
+ FederatedSignInOptionsCustom,
20
+ LegacyProvider,
21
+ FederatedSignInOptions,
22
+ AwsCognitoOAuthOpts,
23
+ ClientMetaData,
24
+ } from '../types';
25
+
26
+ import {
27
+ Amplify,
28
+ ConsoleLogger as Logger,
29
+ Credentials,
30
+ CustomUserAgentDetails,
31
+ Hub,
32
+ StorageHelper,
33
+ ICredentials,
34
+ Platform,
35
+ browserOrNode,
36
+ parseAWSExports,
37
+ UniversalStorage,
38
+ urlSafeDecode,
39
+ HubCallback,
40
+ } from '@aws-amplify/core';
41
+ import {
42
+ CookieStorage,
43
+ CognitoUserPool,
44
+ AuthenticationDetails,
45
+ ICognitoUserPoolData,
46
+ ICognitoUserData,
47
+ ISignUpResult,
48
+ CognitoUser,
49
+ MFAOption,
50
+ CognitoUserSession,
51
+ IAuthenticationCallback,
52
+ ICognitoUserAttributeData,
53
+ CognitoUserAttribute,
54
+ CognitoIdToken,
55
+ CognitoRefreshToken,
56
+ CognitoAccessToken,
57
+ NodeCallback,
58
+ CodeDeliveryDetails,
59
+ } from 'amazon-cognito-identity-js';
60
+ import {
61
+ addAuthCategoryToCognitoUserAgent,
62
+ addFrameworkToCognitoUserAgent,
63
+ } from 'amazon-cognito-identity-js/internals';
64
+
65
+ import { parse } from 'url';
66
+ import OAuth from '../OAuth/OAuth';
67
+ import { default as urlListener } from '../urlListener';
68
+ import { AuthError, NoUserPoolError } from '../Errors';
69
+ import {
70
+ AuthErrorTypes,
71
+ AutoSignInOptions,
72
+ CognitoHostedUIIdentityProvider,
73
+ IAuthDevice,
74
+ } from '../types/Auth';
75
+
76
+ const logger = new Logger('AuthClass');
77
+ const USER_ADMIN_SCOPE = 'aws.cognito.signin.user.admin';
78
+
79
+ // 10 sec, following this guide https://www.nngroup.com/articles/response-times-3-important-limits/
80
+ const OAUTH_FLOW_MS_TIMEOUT = 10 * 1000;
81
+
82
+ const AMPLIFY_SYMBOL = (
83
+ typeof Symbol !== 'undefined' && typeof Symbol.for === 'function'
84
+ ? Symbol.for('amplify_default')
85
+ : '@@amplify_default'
86
+ ) as Symbol;
87
+
88
+ const dispatchAuthEvent = (event: string, data: any, message: string) => {
89
+ Hub.dispatch('auth', { event, data, message }, 'Auth', AMPLIFY_SYMBOL);
90
+ };
91
+
92
+ // Cognito Documentation for max device
93
+ // tslint:disable-next-line:max-line-length
94
+ // https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ListDevices.html#API_ListDevices_RequestSyntax
95
+ const MAX_DEVICES = 60;
96
+
97
+ const MAX_AUTOSIGNIN_POLLING_MS = 3 * 60 * 1000;
98
+
99
+ /**
100
+ * Provide authentication steps
101
+ */
102
+ export class InternalAuthClass {
103
+ private _config: AuthOptions;
104
+ private userPool: CognitoUserPool = null;
105
+ private user: any = null;
106
+ private _oAuthHandler: OAuth;
107
+ private _storage;
108
+ private _storageSync;
109
+ private oAuthFlowInProgress: boolean = false;
110
+ private pendingSignIn: ReturnType<
111
+ InternalAuthClass['signInWithPassword']
112
+ > | null;
113
+ private autoSignInInitiated: boolean = false;
114
+ private inflightSessionPromise: Promise<CognitoUserSession> | null = null;
115
+ private inflightSessionPromiseCounter: number = 0;
116
+ Credentials = Credentials;
117
+
118
+ /**
119
+ * Initialize Auth with AWS configurations
120
+ * @param {Object} config - Configuration of the Auth
121
+ */
122
+ constructor(config: AuthOptions) {
123
+ this.configure(config);
124
+ this.currentCredentials = this.currentCredentials.bind(this);
125
+ this.currentUserCredentials = this.currentUserCredentials.bind(this);
126
+
127
+ Hub.listen('auth', ({ payload }) => {
128
+ const { event } = payload;
129
+ switch (event) {
130
+ case 'verify':
131
+ case 'signIn':
132
+ this._storage.setItem('amplify-signin-with-hostedUI', 'false');
133
+ break;
134
+ case 'signOut':
135
+ this._storage.removeItem('amplify-signin-with-hostedUI');
136
+ break;
137
+ case 'cognitoHostedUI':
138
+ this._storage.setItem('amplify-signin-with-hostedUI', 'true');
139
+ break;
140
+ }
141
+ });
142
+
143
+ addAuthCategoryToCognitoUserAgent();
144
+ addFrameworkToCognitoUserAgent(Platform.framework);
145
+ Platform.observeFrameworkChanges(() => {
146
+ addFrameworkToCognitoUserAgent(Platform.framework);
147
+ });
148
+ }
149
+
150
+ public getModuleName() {
151
+ return 'InternalAuth';
152
+ }
153
+
154
+ configure(config?) {
155
+ if (!config) return this._config || {};
156
+ logger.debug('configure Auth');
157
+ const conf = Object.assign(
158
+ {},
159
+ this._config,
160
+ parseAWSExports(config).Auth,
161
+ config
162
+ );
163
+ this._config = conf;
164
+ const {
165
+ userPoolId,
166
+ userPoolWebClientId,
167
+ cookieStorage,
168
+ oauth,
169
+ region,
170
+ identityPoolId,
171
+ mandatorySignIn,
172
+ refreshHandlers,
173
+ identityPoolRegion,
174
+ clientMetadata,
175
+ endpoint,
176
+ storage,
177
+ } = this._config;
178
+
179
+ if (!storage) {
180
+ // backward compatability
181
+ if (cookieStorage) this._storage = new CookieStorage(cookieStorage);
182
+ else {
183
+ this._storage = config.ssr
184
+ ? new UniversalStorage()
185
+ : new StorageHelper().getStorage();
186
+ }
187
+ } else {
188
+ if (!this._isValidAuthStorage(storage)) {
189
+ logger.error('The storage in the Auth config is not valid!');
190
+ throw new Error('Empty storage object');
191
+ }
192
+ this._storage = storage;
193
+ }
194
+
195
+ this._storageSync = Promise.resolve();
196
+ if (typeof this._storage['sync'] === 'function') {
197
+ this._storageSync = this._storage['sync']();
198
+ }
199
+
200
+ if (userPoolId) {
201
+ const userPoolData: ICognitoUserPoolData = {
202
+ UserPoolId: userPoolId,
203
+ ClientId: userPoolWebClientId,
204
+ endpoint,
205
+ };
206
+ userPoolData.Storage = this._storage;
207
+
208
+ this.userPool = new CognitoUserPool(
209
+ userPoolData,
210
+ this.wrapRefreshSessionCallback
211
+ );
212
+ }
213
+
214
+ this.Credentials.configure({
215
+ mandatorySignIn,
216
+ region,
217
+ userPoolId,
218
+ identityPoolId,
219
+ refreshHandlers,
220
+ storage: this._storage,
221
+ identityPoolRegion,
222
+ });
223
+
224
+ // initialize cognitoauth client if hosted ui options provided
225
+ // to keep backward compatibility:
226
+ const cognitoHostedUIConfig = oauth
227
+ ? isCognitoHostedOpts(this._config.oauth)
228
+ ? oauth
229
+ : (<any>oauth).awsCognito
230
+ : undefined;
231
+
232
+ if (cognitoHostedUIConfig) {
233
+ const cognitoAuthParams = Object.assign(
234
+ {
235
+ cognitoClientId: userPoolWebClientId,
236
+ UserPoolId: userPoolId,
237
+ domain: cognitoHostedUIConfig['domain'],
238
+ scopes: cognitoHostedUIConfig['scope'],
239
+ redirectSignIn: cognitoHostedUIConfig['redirectSignIn'],
240
+ redirectSignOut: cognitoHostedUIConfig['redirectSignOut'],
241
+ responseType: cognitoHostedUIConfig['responseType'],
242
+ Storage: this._storage,
243
+ urlOpener: cognitoHostedUIConfig['urlOpener'],
244
+ clientMetadata,
245
+ },
246
+ cognitoHostedUIConfig['options']
247
+ );
248
+
249
+ this._oAuthHandler = new OAuth({
250
+ scopes: cognitoAuthParams.scopes,
251
+ config: cognitoAuthParams,
252
+ cognitoClientId: cognitoAuthParams.cognitoClientId,
253
+ });
254
+
255
+ // **NOTE** - Remove this in a future major release as it is a breaking change
256
+ // Prevents _handleAuthResponse from being called multiple times in Expo
257
+ // See https://github.com/aws-amplify/amplify-js/issues/4388
258
+ const usedResponseUrls = {};
259
+ urlListener(({ url }) => {
260
+ if (usedResponseUrls[url]) {
261
+ return;
262
+ }
263
+
264
+ usedResponseUrls[url] = true;
265
+ this._handleAuthResponse(url);
266
+ });
267
+ }
268
+
269
+ dispatchAuthEvent(
270
+ 'configured',
271
+ null,
272
+ `The Auth category has been configured successfully`
273
+ );
274
+
275
+ if (
276
+ !this.autoSignInInitiated &&
277
+ typeof this._storage['getItem'] === 'function'
278
+ ) {
279
+ const pollingInitiated = this.isTrueStorageValue(
280
+ 'amplify-polling-started'
281
+ );
282
+ if (pollingInitiated) {
283
+ dispatchAuthEvent(
284
+ 'autoSignIn_failure',
285
+ null,
286
+ AuthErrorTypes.AutoSignInError
287
+ );
288
+ this._storage.removeItem('amplify-auto-sign-in');
289
+ }
290
+ this._storage.removeItem('amplify-polling-started');
291
+ }
292
+ return this._config;
293
+ }
294
+
295
+ wrapRefreshSessionCallback = (callback: NodeCallback.Any) => {
296
+ const wrapped: NodeCallback.Any = (error, data) => {
297
+ if (data) {
298
+ dispatchAuthEvent('tokenRefresh', undefined, `New token retrieved`);
299
+ } else {
300
+ dispatchAuthEvent(
301
+ 'tokenRefresh_failure',
302
+ error,
303
+ `Failed to retrieve new token`
304
+ );
305
+ }
306
+ return callback(error, data);
307
+ };
308
+ return wrapped;
309
+ } // prettier-ignore
310
+
311
+ /**
312
+ * Sign up with username, password and other attributes like phone, email
313
+ * @param {String | object} params - The user attributes used for signin
314
+ * @param {String[]} restOfAttrs - for the backward compatability
315
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
316
+ * @return - A promise resolves callback data if success
317
+ */
318
+ public signUp(
319
+ params: string | SignUpParams,
320
+ ...restOfAttrs: any
321
+ ): Promise<ISignUpResult>;
322
+ public signUp(
323
+ params: string | SignUpParams,
324
+ restOfAttrs?: string[],
325
+ customUserAgentDetails?: CustomUserAgentDetails
326
+ ): Promise<ISignUpResult> {
327
+ if (!this.userPool) {
328
+ return this.rejectNoUserPool();
329
+ }
330
+
331
+ let username: string = null;
332
+ let password: string = null;
333
+ const attributes: CognitoUserAttribute[] = [];
334
+ let validationData: CognitoUserAttribute[] = null;
335
+ let clientMetadata;
336
+ let autoSignIn: AutoSignInOptions = { enabled: false };
337
+ let autoSignInValidationData = {};
338
+ let autoSignInClientMetaData: ClientMetaData = {};
339
+
340
+ if (params && typeof params === 'string') {
341
+ username = params;
342
+ password = restOfAttrs ? restOfAttrs[0] : null;
343
+ const email: string = restOfAttrs ? restOfAttrs[1] : null;
344
+ const phone_number: string = restOfAttrs ? restOfAttrs[2] : null;
345
+
346
+ if (email)
347
+ attributes.push(
348
+ new CognitoUserAttribute({ Name: 'email', Value: email })
349
+ );
350
+
351
+ if (phone_number)
352
+ attributes.push(
353
+ new CognitoUserAttribute({
354
+ Name: 'phone_number',
355
+ Value: phone_number,
356
+ })
357
+ );
358
+ } else if (params && typeof params === 'object') {
359
+ username = params['username'];
360
+ password = params['password'];
361
+
362
+ if (params && params.clientMetadata) {
363
+ clientMetadata = params.clientMetadata;
364
+ } else if (this._config.clientMetadata) {
365
+ clientMetadata = this._config.clientMetadata;
366
+ }
367
+
368
+ const attrs = params['attributes'];
369
+ if (attrs) {
370
+ Object.keys(attrs).map(key => {
371
+ attributes.push(
372
+ new CognitoUserAttribute({ Name: key, Value: attrs[key] })
373
+ );
374
+ });
375
+ }
376
+
377
+ const validationDataObject = params['validationData'];
378
+ if (validationDataObject) {
379
+ validationData = [];
380
+ Object.keys(validationDataObject).map(key => {
381
+ validationData.push(
382
+ new CognitoUserAttribute({
383
+ Name: key,
384
+ Value: validationDataObject[key],
385
+ })
386
+ );
387
+ });
388
+ }
389
+
390
+ autoSignIn = params.autoSignIn ?? { enabled: false };
391
+ if (autoSignIn.enabled) {
392
+ this._storage.setItem('amplify-auto-sign-in', 'true');
393
+ autoSignInValidationData = autoSignIn.validationData ?? {};
394
+ autoSignInClientMetaData = autoSignIn.clientMetaData ?? {};
395
+ }
396
+ } else {
397
+ return this.rejectAuthError(AuthErrorTypes.SignUpError);
398
+ }
399
+
400
+ if (!username) {
401
+ return this.rejectAuthError(AuthErrorTypes.EmptyUsername);
402
+ }
403
+ if (!password) {
404
+ return this.rejectAuthError(AuthErrorTypes.EmptyPassword);
405
+ }
406
+
407
+ logger.debug('signUp attrs:', attributes);
408
+ logger.debug('signUp validation data:', validationData);
409
+
410
+ return new Promise((resolve, reject) => {
411
+ this.userPool.signUp(
412
+ username,
413
+ password,
414
+ attributes,
415
+ validationData,
416
+ (err, data) => {
417
+ if (err) {
418
+ dispatchAuthEvent(
419
+ 'signUp_failure',
420
+ err,
421
+ `${username} failed to signup`
422
+ );
423
+ reject(err);
424
+ } else {
425
+ dispatchAuthEvent(
426
+ 'signUp',
427
+ data,
428
+ `${username} has signed up successfully`
429
+ );
430
+ if (autoSignIn.enabled) {
431
+ this.handleAutoSignIn(
432
+ username,
433
+ password,
434
+ autoSignInValidationData,
435
+ autoSignInClientMetaData,
436
+ data
437
+ );
438
+ }
439
+ resolve(data);
440
+ }
441
+ },
442
+ clientMetadata
443
+ );
444
+ });
445
+ }
446
+
447
+ private handleAutoSignIn(
448
+ username: string,
449
+ password: string,
450
+ validationData: {},
451
+ clientMetadata: any,
452
+ data: any
453
+ ) {
454
+ this.autoSignInInitiated = true;
455
+ const authDetails = new AuthenticationDetails({
456
+ Username: username,
457
+ Password: password,
458
+ ValidationData: validationData,
459
+ ClientMetadata: clientMetadata,
460
+ });
461
+ if (data.userConfirmed) {
462
+ this.signInAfterUserConfirmed(authDetails);
463
+ } else if (this._config.signUpVerificationMethod === 'link') {
464
+ this.handleLinkAutoSignIn(authDetails);
465
+ } else {
466
+ this.handleCodeAutoSignIn(authDetails);
467
+ }
468
+ }
469
+
470
+ private handleCodeAutoSignIn(authDetails: AuthenticationDetails) {
471
+ const listenEvent = ({ payload }) => {
472
+ if (payload.event === 'confirmSignUp') {
473
+ this.signInAfterUserConfirmed(authDetails, listenEvent);
474
+ }
475
+ };
476
+ Hub.listen('auth', listenEvent);
477
+ }
478
+
479
+ private handleLinkAutoSignIn(authDetails: AuthenticationDetails) {
480
+ this._storage.setItem('amplify-polling-started', 'true');
481
+ const start = Date.now();
482
+ const autoSignInPollingIntervalId = setInterval(() => {
483
+ if (Date.now() - start > MAX_AUTOSIGNIN_POLLING_MS) {
484
+ clearInterval(autoSignInPollingIntervalId);
485
+ dispatchAuthEvent(
486
+ 'autoSignIn_failure',
487
+ null,
488
+ 'Please confirm your account and use your credentials to sign in.'
489
+ );
490
+ this._storage.removeItem('amplify-auto-sign-in');
491
+ } else {
492
+ this.signInAfterUserConfirmed(
493
+ authDetails,
494
+ null,
495
+ autoSignInPollingIntervalId
496
+ );
497
+ }
498
+ }, 5000);
499
+ }
500
+
501
+ private async signInAfterUserConfirmed(
502
+ authDetails: AuthenticationDetails,
503
+ listenEvent?: HubCallback,
504
+ autoSignInPollingIntervalId?: ReturnType<typeof setInterval>
505
+ ) {
506
+ const user = this.createCognitoUser(authDetails.getUsername());
507
+ try {
508
+ await user.authenticateUser(
509
+ authDetails,
510
+ this.authCallbacks(
511
+ user,
512
+ value => {
513
+ dispatchAuthEvent(
514
+ 'autoSignIn',
515
+ value,
516
+ `${authDetails.getUsername()} has signed in successfully`
517
+ );
518
+ if (listenEvent) {
519
+ Hub.remove('auth', listenEvent);
520
+ }
521
+ if (autoSignInPollingIntervalId) {
522
+ clearInterval(autoSignInPollingIntervalId);
523
+ this._storage.removeItem('amplify-polling-started');
524
+ }
525
+ this._storage.removeItem('amplify-auto-sign-in');
526
+ },
527
+ error => {
528
+ logger.error(error);
529
+ this._storage.removeItem('amplify-auto-sign-in');
530
+ }
531
+ )
532
+ );
533
+ } catch (error) {
534
+ logger.error(error);
535
+ }
536
+ }
537
+
538
+ /**
539
+ * Send the verification code to confirm sign up
540
+ * @param {String} username - The username to be confirmed
541
+ * @param {String} code - The verification code
542
+ * @param {ConfirmSignUpOptions} options - other options for confirm signup
543
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
544
+ * @return - A promise resolves callback data if success
545
+ */
546
+ public confirmSignUp(
547
+ username: string,
548
+ code: string,
549
+ options?: ConfirmSignUpOptions,
550
+ customUserAgentDetails?: CustomUserAgentDetails
551
+ ): Promise<any> {
552
+ if (!this.userPool) {
553
+ return this.rejectNoUserPool();
554
+ }
555
+ if (!username) {
556
+ return this.rejectAuthError(AuthErrorTypes.EmptyUsername);
557
+ }
558
+ if (!code) {
559
+ return this.rejectAuthError(AuthErrorTypes.EmptyCode);
560
+ }
561
+
562
+ const user = this.createCognitoUser(username);
563
+ const forceAliasCreation =
564
+ options && typeof options.forceAliasCreation === 'boolean'
565
+ ? options.forceAliasCreation
566
+ : true;
567
+
568
+ let clientMetadata;
569
+ if (options && options.clientMetadata) {
570
+ clientMetadata = options.clientMetadata;
571
+ } else if (this._config.clientMetadata) {
572
+ clientMetadata = this._config.clientMetadata;
573
+ }
574
+ return new Promise((resolve, reject) => {
575
+ user.confirmRegistration(
576
+ code,
577
+ forceAliasCreation,
578
+ (err, data) => {
579
+ if (err) {
580
+ reject(err);
581
+ } else {
582
+ dispatchAuthEvent(
583
+ 'confirmSignUp',
584
+ data,
585
+ `${username} has been confirmed successfully`
586
+ );
587
+ const autoSignIn = this.isTrueStorageValue('amplify-auto-sign-in');
588
+ if (autoSignIn && !this.autoSignInInitiated) {
589
+ dispatchAuthEvent(
590
+ 'autoSignIn_failure',
591
+ null,
592
+ AuthErrorTypes.AutoSignInError
593
+ );
594
+ this._storage.removeItem('amplify-auto-sign-in');
595
+ }
596
+ resolve(data);
597
+ }
598
+ },
599
+ clientMetadata
600
+ );
601
+ });
602
+ }
603
+
604
+ private isTrueStorageValue(value: string) {
605
+ const item = this._storage.getItem(value);
606
+ return item ? item === 'true' : false;
607
+ }
608
+
609
+ /**
610
+ * Resend the verification code
611
+ * @param {String} username - The username to be confirmed
612
+ * @param {ClientMetadata} clientMetadata - Metadata to be passed to Cognito Lambda triggers
613
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
614
+ * @return - A promise resolves code delivery details if successful
615
+ */
616
+ public resendSignUp(
617
+ username: string,
618
+ clientMetadata: ClientMetaData = this._config.clientMetadata,
619
+ customUserAgentDetails?: CustomUserAgentDetails
620
+ ): Promise<any> {
621
+ if (!this.userPool) {
622
+ return this.rejectNoUserPool();
623
+ }
624
+ if (!username) {
625
+ return this.rejectAuthError(AuthErrorTypes.EmptyUsername);
626
+ }
627
+
628
+ const user = this.createCognitoUser(username);
629
+ return new Promise((resolve, reject) => {
630
+ user.resendConfirmationCode((err, data) => {
631
+ if (err) {
632
+ reject(err);
633
+ } else {
634
+ resolve(data);
635
+ }
636
+ }, clientMetadata);
637
+ });
638
+ }
639
+
640
+ /**
641
+ * Sign in
642
+ * @param {String | SignInOpts} usernameOrSignInOpts - The username to be signed in or the sign in options
643
+ * @param {String} pw - The password of the username
644
+ * @param {ClientMetaData} clientMetadata - Client metadata for custom workflows
645
+ * @return - A promise resolves the CognitoUser
646
+ */
647
+ public signIn(
648
+ usernameOrSignInOpts: string | SignInOpts,
649
+ pw?: string,
650
+ clientMetadata: ClientMetaData = this._config.clientMetadata,
651
+ customUserAgentDetails?: CustomUserAgentDetails
652
+ ): Promise<CognitoUser | any> {
653
+ if (!this.userPool) {
654
+ return this.rejectNoUserPool();
655
+ }
656
+
657
+ let username = null;
658
+ let password = null;
659
+ let validationData = {};
660
+
661
+ // for backward compatibility
662
+ if (typeof usernameOrSignInOpts === 'string') {
663
+ username = usernameOrSignInOpts;
664
+ password = pw;
665
+ } else if (isUsernamePasswordOpts(usernameOrSignInOpts)) {
666
+ if (typeof pw !== 'undefined') {
667
+ logger.warn(
668
+ 'The password should be defined under the first parameter object!'
669
+ );
670
+ }
671
+ username = usernameOrSignInOpts.username;
672
+ password = usernameOrSignInOpts.password;
673
+ validationData = usernameOrSignInOpts.validationData;
674
+ } else {
675
+ return this.rejectAuthError(AuthErrorTypes.InvalidUsername);
676
+ }
677
+ if (!username) {
678
+ return this.rejectAuthError(AuthErrorTypes.EmptyUsername);
679
+ }
680
+ const authDetails = new AuthenticationDetails({
681
+ Username: username,
682
+ Password: password,
683
+ ValidationData: validationData,
684
+ ClientMetadata: clientMetadata,
685
+ });
686
+ if (password) {
687
+ return this.signInWithPassword(authDetails);
688
+ } else {
689
+ return this.signInWithoutPassword(authDetails);
690
+ }
691
+ }
692
+
693
+ /**
694
+ * Return an object with the authentication callbacks
695
+ * @param {CognitoUser} user - the cognito user object
696
+ * @param {} resolve - function called when resolving the current step
697
+ * @param {} reject - function called when rejecting the current step
698
+ * @return - an object with the callback methods for user authentication
699
+ */
700
+ private authCallbacks(
701
+ user: CognitoUser,
702
+ resolve: (value?: CognitoUser | any) => void,
703
+ reject: (value?: any) => void
704
+ ): IAuthenticationCallback {
705
+ const that = this;
706
+ return {
707
+ onSuccess: async session => {
708
+ logger.debug(session);
709
+ delete user['challengeName'];
710
+ delete user['challengeParam'];
711
+ try {
712
+ await this.Credentials.clear();
713
+ const cred = await this.Credentials.set(session, 'session');
714
+ logger.debug('succeed to get cognito credentials', cred);
715
+ } catch (e) {
716
+ logger.debug('cannot get cognito credentials', e);
717
+ } finally {
718
+ try {
719
+ // In order to get user attributes and MFA methods
720
+ // We need to trigger currentUserPoolUser again
721
+ const currentUser = await this.currentUserPoolUser();
722
+ that.user = currentUser;
723
+ dispatchAuthEvent(
724
+ 'signIn',
725
+ currentUser,
726
+ `A user ${user.getUsername()} has been signed in`
727
+ );
728
+ resolve(currentUser);
729
+ } catch (e) {
730
+ logger.error('Failed to get the signed in user', e);
731
+ reject(e);
732
+ }
733
+ }
734
+ },
735
+ onFailure: err => {
736
+ logger.debug('signIn failure', err);
737
+ dispatchAuthEvent(
738
+ 'signIn_failure',
739
+ err,
740
+ `${user.getUsername()} failed to signin`
741
+ );
742
+ reject(err);
743
+ },
744
+ customChallenge: challengeParam => {
745
+ logger.debug('signIn custom challenge answer required');
746
+ user['challengeName'] = 'CUSTOM_CHALLENGE';
747
+ user['challengeParam'] = challengeParam;
748
+ resolve(user);
749
+ },
750
+ mfaRequired: (challengeName, challengeParam) => {
751
+ logger.debug('signIn MFA required');
752
+ user['challengeName'] = challengeName;
753
+ user['challengeParam'] = challengeParam;
754
+ resolve(user);
755
+ },
756
+ mfaSetup: (challengeName, challengeParam) => {
757
+ logger.debug('signIn mfa setup', challengeName);
758
+ user['challengeName'] = challengeName;
759
+ user['challengeParam'] = challengeParam;
760
+ resolve(user);
761
+ },
762
+ newPasswordRequired: (userAttributes, requiredAttributes) => {
763
+ logger.debug('signIn new password');
764
+ user['challengeName'] = 'NEW_PASSWORD_REQUIRED';
765
+ user['challengeParam'] = {
766
+ userAttributes,
767
+ requiredAttributes,
768
+ };
769
+ resolve(user);
770
+ },
771
+ totpRequired: (challengeName, challengeParam) => {
772
+ logger.debug('signIn totpRequired');
773
+ user['challengeName'] = challengeName;
774
+ user['challengeParam'] = challengeParam;
775
+ resolve(user);
776
+ },
777
+ selectMFAType: (challengeName, challengeParam) => {
778
+ logger.debug('signIn selectMFAType', challengeName);
779
+ user['challengeName'] = challengeName;
780
+ user['challengeParam'] = challengeParam;
781
+ resolve(user);
782
+ },
783
+ };
784
+ }
785
+
786
+ /**
787
+ * Sign in with a password
788
+ * @private
789
+ * @param {AuthenticationDetails} authDetails - the user sign in data
790
+ * @return - A promise resolves the CognitoUser object if success or mfa required
791
+ */
792
+ private signInWithPassword(
793
+ authDetails: AuthenticationDetails
794
+ ): Promise<CognitoUser | any> {
795
+ if (this.pendingSignIn) {
796
+ throw new Error('Pending sign-in attempt already in progress');
797
+ }
798
+
799
+ const user = this.createCognitoUser(authDetails.getUsername());
800
+
801
+ this.pendingSignIn = new Promise((resolve, reject) => {
802
+ user.authenticateUser(
803
+ authDetails,
804
+ this.authCallbacks(
805
+ user,
806
+ value => {
807
+ this.pendingSignIn = null;
808
+ resolve(value);
809
+ },
810
+ error => {
811
+ this.pendingSignIn = null;
812
+ reject(error);
813
+ }
814
+ )
815
+ );
816
+ });
817
+
818
+ return this.pendingSignIn;
819
+ }
820
+
821
+ /**
822
+ * Sign in without a password
823
+ * @private
824
+ * @param {AuthenticationDetails} authDetails - the user sign in data
825
+ * @return - A promise resolves the CognitoUser object if success or mfa required
826
+ */
827
+ private signInWithoutPassword(
828
+ authDetails: AuthenticationDetails
829
+ ): Promise<CognitoUser | any> {
830
+ const user = this.createCognitoUser(authDetails.getUsername());
831
+ user.setAuthenticationFlowType('CUSTOM_AUTH');
832
+
833
+ return new Promise((resolve, reject) => {
834
+ user.initiateAuth(authDetails, this.authCallbacks(user, resolve, reject));
835
+ });
836
+ }
837
+
838
+ /**
839
+ * This was previously used by an authenticated user to get MFAOptions,
840
+ * but no longer returns a meaningful response. Refer to the documentation for
841
+ * how to setup and use MFA: https://docs.amplify.aws/lib/auth/mfa/q/platform/js
842
+ * @deprecated
843
+ * @param {CognitoUser} user - the current user
844
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
845
+ * @return - A promise resolves the current preferred mfa option if success
846
+ */
847
+ public getMFAOptions(
848
+ user: CognitoUser | any,
849
+ customUserAgentDetails?: CustomUserAgentDetails
850
+ ): Promise<MFAOption[]> {
851
+ return new Promise((res, rej) => {
852
+ user.getMFAOptions((err, mfaOptions) => {
853
+ if (err) {
854
+ logger.debug('get MFA Options failed', err);
855
+ rej(err);
856
+ return;
857
+ }
858
+ logger.debug('get MFA options success', mfaOptions);
859
+ res(mfaOptions);
860
+ return;
861
+ });
862
+ });
863
+ }
864
+
865
+ /**
866
+ * get preferred mfa method
867
+ * @param {CognitoUser} user - the current cognito user
868
+ * @param {GetPreferredMFAOpts} params - options for getting the current user preferred MFA
869
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
870
+ */
871
+ public getPreferredMFA(
872
+ user: CognitoUser | any,
873
+ params?: GetPreferredMFAOpts,
874
+ customUserAgentDetails?: CustomUserAgentDetails
875
+ ): Promise<string> {
876
+ const that = this;
877
+ return new Promise((res, rej) => {
878
+ const clientMetadata = this._config.clientMetadata; // TODO: verify behavior if this is override during signIn
879
+
880
+ const bypassCache = params ? params.bypassCache : false;
881
+ user.getUserData(
882
+ async (err, data) => {
883
+ if (err) {
884
+ logger.debug('getting preferred mfa failed', err);
885
+ if (this.isSessionInvalid(err)) {
886
+ try {
887
+ await this.cleanUpInvalidSession(user);
888
+ } catch (cleanUpError) {
889
+ rej(
890
+ new Error(
891
+ `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}`
892
+ )
893
+ );
894
+ return;
895
+ }
896
+ }
897
+ rej(err);
898
+ return;
899
+ }
900
+
901
+ const mfaType = that._getMfaTypeFromUserData(data);
902
+ if (!mfaType) {
903
+ rej('invalid MFA Type');
904
+ return;
905
+ } else {
906
+ res(mfaType);
907
+ return;
908
+ }
909
+ },
910
+ { bypassCache, clientMetadata }
911
+ );
912
+ });
913
+ }
914
+
915
+ private _getMfaTypeFromUserData(data) {
916
+ let ret = null;
917
+ const preferredMFA = data.PreferredMfaSetting;
918
+ // if the user has used Auth.setPreferredMFA() to setup the mfa type
919
+ // then the "PreferredMfaSetting" would exist in the response
920
+ if (preferredMFA) {
921
+ ret = preferredMFA;
922
+ } else {
923
+ // if mfaList exists but empty, then its noMFA
924
+ const mfaList = data.UserMFASettingList;
925
+ if (!mfaList) {
926
+ // if SMS was enabled by using Auth.enableSMS(),
927
+ // the response would contain MFAOptions
928
+ // as for now Cognito only supports for SMS, so we will say it is 'SMS_MFA'
929
+ // if it does not exist, then it should be NOMFA
930
+ const MFAOptions = data.MFAOptions;
931
+ if (MFAOptions) {
932
+ ret = 'SMS_MFA';
933
+ } else {
934
+ ret = 'NOMFA';
935
+ }
936
+ } else if (mfaList.length === 0) {
937
+ ret = 'NOMFA';
938
+ } else {
939
+ logger.debug('invalid case for getPreferredMFA', data);
940
+ }
941
+ }
942
+ return ret;
943
+ }
944
+
945
+ private _getUserData(user, params) {
946
+ return new Promise((res, rej) => {
947
+ user.getUserData(async (err, data) => {
948
+ if (err) {
949
+ logger.debug('getting user data failed', err);
950
+ if (this.isSessionInvalid(err)) {
951
+ try {
952
+ await this.cleanUpInvalidSession(user);
953
+ } catch (cleanUpError) {
954
+ rej(
955
+ new Error(
956
+ `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}`
957
+ )
958
+ );
959
+ return;
960
+ }
961
+ }
962
+ rej(err);
963
+ return;
964
+ } else {
965
+ res(data);
966
+ }
967
+ }, params);
968
+ });
969
+ }
970
+
971
+ /**
972
+ * set preferred MFA method
973
+ * @param {CognitoUser} user - the current Cognito user
974
+ * @param {string} mfaMethod - preferred mfa method
975
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
976
+ * @return - A promise resolve if success
977
+ */
978
+ public async setPreferredMFA(
979
+ user: CognitoUser | any,
980
+ mfaMethod: 'TOTP' | 'SMS' | 'NOMFA' | 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA',
981
+ customUserAgentDetails?: CustomUserAgentDetails
982
+ ): Promise<string> {
983
+ const clientMetadata = this._config.clientMetadata; // TODO: verify behavior if this is override during signIn
984
+
985
+ const userData = await this._getUserData(user, {
986
+ bypassCache: true,
987
+ clientMetadata,
988
+ });
989
+ let smsMfaSettings = null;
990
+ let totpMfaSettings = null;
991
+
992
+ switch (mfaMethod) {
993
+ case 'TOTP':
994
+ case 'SOFTWARE_TOKEN_MFA':
995
+ totpMfaSettings = {
996
+ PreferredMfa: true,
997
+ Enabled: true,
998
+ };
999
+ break;
1000
+ case 'SMS':
1001
+ case 'SMS_MFA':
1002
+ smsMfaSettings = {
1003
+ PreferredMfa: true,
1004
+ Enabled: true,
1005
+ };
1006
+ break;
1007
+ case 'NOMFA':
1008
+ const mfaList = userData['UserMFASettingList'];
1009
+ const currentMFAType = await this._getMfaTypeFromUserData(userData);
1010
+ if (currentMFAType === 'NOMFA') {
1011
+ return Promise.resolve('No change for mfa type');
1012
+ } else if (currentMFAType === 'SMS_MFA') {
1013
+ smsMfaSettings = {
1014
+ PreferredMfa: false,
1015
+ Enabled: false,
1016
+ };
1017
+ } else if (currentMFAType === 'SOFTWARE_TOKEN_MFA') {
1018
+ totpMfaSettings = {
1019
+ PreferredMfa: false,
1020
+ Enabled: false,
1021
+ };
1022
+ } else {
1023
+ return this.rejectAuthError(AuthErrorTypes.InvalidMFA);
1024
+ }
1025
+ // if there is a UserMFASettingList in the response
1026
+ // we need to disable every mfa type in that list
1027
+ if (mfaList && mfaList.length !== 0) {
1028
+ // to disable SMS or TOTP if exists in that list
1029
+ mfaList.forEach(mfaType => {
1030
+ if (mfaType === 'SMS_MFA') {
1031
+ smsMfaSettings = {
1032
+ PreferredMfa: false,
1033
+ Enabled: false,
1034
+ };
1035
+ } else if (mfaType === 'SOFTWARE_TOKEN_MFA') {
1036
+ totpMfaSettings = {
1037
+ PreferredMfa: false,
1038
+ Enabled: false,
1039
+ };
1040
+ }
1041
+ });
1042
+ }
1043
+ break;
1044
+ default:
1045
+ logger.debug('no validmfa method provided');
1046
+ return this.rejectAuthError(AuthErrorTypes.NoMFA);
1047
+ }
1048
+
1049
+ const that = this;
1050
+ return new Promise<string>((res, rej) => {
1051
+ user.setUserMfaPreference(
1052
+ smsMfaSettings,
1053
+ totpMfaSettings,
1054
+ (err, result) => {
1055
+ if (err) {
1056
+ logger.debug('Set user mfa preference error', err);
1057
+ return rej(err);
1058
+ }
1059
+ logger.debug('Set user mfa success', result);
1060
+ logger.debug('Caching the latest user data into local');
1061
+ // cache the latest result into user data
1062
+ user.getUserData(
1063
+ async (err, data) => {
1064
+ if (err) {
1065
+ logger.debug('getting user data failed', err);
1066
+ if (this.isSessionInvalid(err)) {
1067
+ try {
1068
+ await this.cleanUpInvalidSession(user);
1069
+ } catch (cleanUpError) {
1070
+ rej(
1071
+ new Error(
1072
+ `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}`
1073
+ )
1074
+ );
1075
+ return;
1076
+ }
1077
+ }
1078
+ return rej(err);
1079
+ } else {
1080
+ return res(result);
1081
+ }
1082
+ },
1083
+ {
1084
+ bypassCache: true,
1085
+ clientMetadata,
1086
+ }
1087
+ );
1088
+ }
1089
+ );
1090
+ });
1091
+ }
1092
+
1093
+ /**
1094
+ * disable SMS
1095
+ * @deprecated
1096
+ * @param {CognitoUser} user - the current user
1097
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1098
+ * @return - A promise resolves is success
1099
+ */
1100
+ public disableSMS(
1101
+ user: CognitoUser,
1102
+ customUserAgentDetails?: CustomUserAgentDetails
1103
+ ): Promise<string> {
1104
+ return new Promise((res, rej) => {
1105
+ user.disableMFA((err, data) => {
1106
+ if (err) {
1107
+ logger.debug('disable mfa failed', err);
1108
+ rej(err);
1109
+ return;
1110
+ }
1111
+ logger.debug('disable mfa succeed', data);
1112
+ res(data);
1113
+ return;
1114
+ });
1115
+ });
1116
+ }
1117
+
1118
+ /**
1119
+ * enable SMS
1120
+ * @deprecated
1121
+ * @param {CognitoUser} user - the current user
1122
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1123
+ * @return - A promise resolves is success
1124
+ */
1125
+ public enableSMS(
1126
+ user: CognitoUser,
1127
+ customUserAgentDetails?: CustomUserAgentDetails
1128
+ ): Promise<string> {
1129
+ return new Promise((res, rej) => {
1130
+ user.enableMFA((err, data) => {
1131
+ if (err) {
1132
+ logger.debug('enable mfa failed', err);
1133
+ rej(err);
1134
+ return;
1135
+ }
1136
+ logger.debug('enable mfa succeed', data);
1137
+ res(data);
1138
+ return;
1139
+ });
1140
+ });
1141
+ }
1142
+
1143
+ /**
1144
+ * Setup TOTP
1145
+ * @param {CognitoUser} user - the current user
1146
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1147
+ * @return - A promise resolves with the secret code if success
1148
+ */
1149
+ public setupTOTP(
1150
+ user: CognitoUser | any,
1151
+ customUserAgentDetails?: CustomUserAgentDetails
1152
+ ): Promise<string> {
1153
+ return new Promise((res, rej) => {
1154
+ user.associateSoftwareToken({
1155
+ onFailure: err => {
1156
+ logger.debug('associateSoftwareToken failed', err);
1157
+ rej(err);
1158
+ return;
1159
+ },
1160
+ associateSecretCode: secretCode => {
1161
+ logger.debug('associateSoftwareToken success', secretCode);
1162
+ res(secretCode);
1163
+ return;
1164
+ },
1165
+ });
1166
+ });
1167
+ }
1168
+
1169
+ /**
1170
+ * verify TOTP setup
1171
+ * @param {CognitoUser} user - the current user
1172
+ * @param {string} challengeAnswer - challenge answer
1173
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1174
+ * @return - A promise resolves is success
1175
+ */
1176
+ public verifyTotpToken(
1177
+ user: CognitoUser | any,
1178
+ challengeAnswer: string,
1179
+ customUserAgentDetails?: CustomUserAgentDetails
1180
+ ): Promise<CognitoUserSession> {
1181
+ logger.debug('verification totp token', user, challengeAnswer);
1182
+
1183
+ let signInUserSession;
1184
+ if (user && typeof user.getSignInUserSession === 'function') {
1185
+ signInUserSession = (user as CognitoUser).getSignInUserSession();
1186
+ }
1187
+ const isLoggedIn = signInUserSession?.isValid();
1188
+
1189
+ return new Promise((res, rej) => {
1190
+ user.verifySoftwareToken(challengeAnswer, 'My TOTP device', {
1191
+ onFailure: err => {
1192
+ logger.debug('verifyTotpToken failed', err);
1193
+ rej(err);
1194
+ return;
1195
+ },
1196
+ onSuccess: data => {
1197
+ if (!isLoggedIn) {
1198
+ dispatchAuthEvent(
1199
+ 'signIn',
1200
+ user,
1201
+ `A user ${user.getUsername()} has been signed in`
1202
+ );
1203
+ }
1204
+ dispatchAuthEvent(
1205
+ 'verify',
1206
+ user,
1207
+ `A user ${user.getUsername()} has been verified`
1208
+ );
1209
+ logger.debug('verifyTotpToken success', data);
1210
+ res(data);
1211
+ return;
1212
+ },
1213
+ });
1214
+ });
1215
+ }
1216
+
1217
+ /**
1218
+ * Send MFA code to confirm sign in
1219
+ * @param {Object} user - The CognitoUser object
1220
+ * @param {String} code - The confirmation code
1221
+ * @param {string} mfaType - optional mfaType: 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA'
1222
+ * @param {ClientMetaData} clientMetadata - optional client metadata defaults to config
1223
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1224
+ */
1225
+ public confirmSignIn(
1226
+ user: CognitoUser | any,
1227
+ code: string,
1228
+ mfaType?: 'SMS_MFA' | 'SOFTWARE_TOKEN_MFA' | null,
1229
+ clientMetadata: ClientMetaData = this._config.clientMetadata,
1230
+ customUserAgentDetails?: CustomUserAgentDetails
1231
+ ): Promise<CognitoUser | any> {
1232
+ if (!code) {
1233
+ return this.rejectAuthError(AuthErrorTypes.EmptyCode);
1234
+ }
1235
+
1236
+ const that = this;
1237
+ return new Promise((resolve, reject) => {
1238
+ user.sendMFACode(
1239
+ code,
1240
+ {
1241
+ onSuccess: async session => {
1242
+ logger.debug(session);
1243
+ try {
1244
+ await this.Credentials.clear();
1245
+ const cred = await this.Credentials.set(session, 'session');
1246
+ logger.debug('succeed to get cognito credentials', cred);
1247
+ } catch (e) {
1248
+ logger.debug('cannot get cognito credentials', e);
1249
+ } finally {
1250
+ that.user = user;
1251
+ try {
1252
+ const currentUser = await this.currentUserPoolUser();
1253
+ user.attributes = currentUser.attributes;
1254
+ } catch (e) {
1255
+ logger.debug('cannot get updated Cognito User', e);
1256
+ }
1257
+ dispatchAuthEvent(
1258
+ 'signIn',
1259
+ user,
1260
+ `A user ${user.getUsername()} has been signed in`
1261
+ );
1262
+ resolve(user);
1263
+ }
1264
+ },
1265
+ onFailure: err => {
1266
+ logger.debug('confirm signIn failure', err);
1267
+ reject(err);
1268
+ },
1269
+ },
1270
+ mfaType,
1271
+ clientMetadata
1272
+ );
1273
+ });
1274
+ }
1275
+
1276
+ public completeNewPassword(
1277
+ user: CognitoUser | any,
1278
+ password: string,
1279
+ requiredAttributes: any = {},
1280
+ clientMetadata: ClientMetaData = this._config.clientMetadata,
1281
+ customUserAgentDetails?: CustomUserAgentDetails
1282
+ ): Promise<CognitoUser | any> {
1283
+ if (!password) {
1284
+ return this.rejectAuthError(AuthErrorTypes.EmptyPassword);
1285
+ }
1286
+
1287
+ const that = this;
1288
+ return new Promise((resolve, reject) => {
1289
+ user.completeNewPasswordChallenge(
1290
+ password,
1291
+ requiredAttributes,
1292
+ {
1293
+ onSuccess: async session => {
1294
+ logger.debug(session);
1295
+ try {
1296
+ await this.Credentials.clear();
1297
+ const cred = await this.Credentials.set(session, 'session');
1298
+ logger.debug('succeed to get cognito credentials', cred);
1299
+ } catch (e) {
1300
+ logger.debug('cannot get cognito credentials', e);
1301
+ } finally {
1302
+ that.user = user;
1303
+ dispatchAuthEvent(
1304
+ 'signIn',
1305
+ user,
1306
+ `A user ${user.getUsername()} has been signed in`
1307
+ );
1308
+ resolve(user);
1309
+ }
1310
+ },
1311
+ onFailure: err => {
1312
+ logger.debug('completeNewPassword failure', err);
1313
+ dispatchAuthEvent(
1314
+ 'completeNewPassword_failure',
1315
+ err,
1316
+ `${this.user} failed to complete the new password flow`
1317
+ );
1318
+ reject(err);
1319
+ },
1320
+ mfaRequired: (challengeName, challengeParam) => {
1321
+ logger.debug('signIn MFA required');
1322
+ user['challengeName'] = challengeName;
1323
+ user['challengeParam'] = challengeParam;
1324
+ resolve(user);
1325
+ },
1326
+ mfaSetup: (challengeName, challengeParam) => {
1327
+ logger.debug('signIn mfa setup', challengeName);
1328
+ user['challengeName'] = challengeName;
1329
+ user['challengeParam'] = challengeParam;
1330
+ resolve(user);
1331
+ },
1332
+ totpRequired: (challengeName, challengeParam) => {
1333
+ logger.debug('signIn mfa setup', challengeName);
1334
+ user['challengeName'] = challengeName;
1335
+ user['challengeParam'] = challengeParam;
1336
+ resolve(user);
1337
+ },
1338
+ },
1339
+ clientMetadata
1340
+ );
1341
+ });
1342
+ }
1343
+
1344
+ /**
1345
+ * Send the answer to a custom challenge
1346
+ * @param {CognitoUser} user - The CognitoUser object
1347
+ * @param {String} challengeResponses - The confirmation code
1348
+ * @param {ClientMetaData} clientMetadata - optional client metadata defaults to config
1349
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1350
+ */
1351
+ public sendCustomChallengeAnswer(
1352
+ user: CognitoUser | any,
1353
+ challengeResponses: string,
1354
+ clientMetadata: ClientMetaData = this._config.clientMetadata,
1355
+ customUserAgentDetails?: CustomUserAgentDetails
1356
+ ): Promise<CognitoUser | any> {
1357
+ if (!this.userPool) {
1358
+ return this.rejectNoUserPool();
1359
+ }
1360
+ if (!challengeResponses) {
1361
+ return this.rejectAuthError(AuthErrorTypes.EmptyChallengeResponse);
1362
+ }
1363
+
1364
+ const that = this;
1365
+ return new Promise((resolve, reject) => {
1366
+ user.sendCustomChallengeAnswer(
1367
+ challengeResponses,
1368
+ this.authCallbacks(user, resolve, reject),
1369
+ clientMetadata
1370
+ );
1371
+ });
1372
+ }
1373
+
1374
+ /**
1375
+ * Delete an authenticated users' attributes
1376
+ * @param {CognitoUser} user - The currently logged in user object
1377
+ * @param {string[]} attributeNames - Attributes to delete
1378
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1379
+ * @return {Promise}
1380
+ **/
1381
+ public deleteUserAttributes(
1382
+ user: CognitoUser | any,
1383
+ attributeNames: string[],
1384
+ customUserAgentDetails?: CustomUserAgentDetails
1385
+ ) {
1386
+ const that = this;
1387
+ return new Promise((resolve, reject) => {
1388
+ that.userSession(user).then(session => {
1389
+ user.deleteAttributes(attributeNames, (err, result) => {
1390
+ if (err) {
1391
+ return reject(err);
1392
+ } else {
1393
+ return resolve(result);
1394
+ }
1395
+ });
1396
+ });
1397
+ });
1398
+ }
1399
+
1400
+ /**
1401
+ * Delete the current authenticated user
1402
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1403
+ * @return {Promise}
1404
+ **/
1405
+ // TODO: Check return type void
1406
+ public async deleteUser(
1407
+ customUserAgentDetails?: CustomUserAgentDetails
1408
+ ): Promise<string | void> {
1409
+ try {
1410
+ await this._storageSync;
1411
+ } catch (e) {
1412
+ logger.debug('Failed to sync cache info into memory', e);
1413
+ throw new Error(e);
1414
+ }
1415
+
1416
+ const isSignedInHostedUI =
1417
+ this._oAuthHandler &&
1418
+ this._storage.getItem('amplify-signin-with-hostedUI') === 'true';
1419
+
1420
+ return new Promise(async (res, rej) => {
1421
+ if (this.userPool) {
1422
+ const user = this.userPool.getCurrentUser();
1423
+
1424
+ if (!user) {
1425
+ logger.debug('Failed to get user from user pool');
1426
+ return rej(new Error('No current user.'));
1427
+ } else {
1428
+ user.getSession(async (err, session) => {
1429
+ if (err) {
1430
+ logger.debug('Failed to get the user session', err);
1431
+ if (this.isSessionInvalid(err)) {
1432
+ try {
1433
+ await this.cleanUpInvalidSession(user);
1434
+ } catch (cleanUpError) {
1435
+ rej(
1436
+ new Error(
1437
+ `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}`
1438
+ )
1439
+ );
1440
+ return;
1441
+ }
1442
+ }
1443
+ return rej(err);
1444
+ } else {
1445
+ user.deleteUser((err, result: string) => {
1446
+ if (err) {
1447
+ rej(err);
1448
+ } else {
1449
+ dispatchAuthEvent(
1450
+ 'userDeleted',
1451
+ result,
1452
+ 'The authenticated user has been deleted.'
1453
+ );
1454
+ user.signOut();
1455
+ this.user = null;
1456
+ try {
1457
+ this.cleanCachedItems(); // clean aws credentials
1458
+ } catch (e) {
1459
+ // TODO: change to rejects in refactor
1460
+ logger.debug('failed to clear cached items');
1461
+ }
1462
+
1463
+ if (isSignedInHostedUI) {
1464
+ this.oAuthSignOutRedirect(res, rej);
1465
+ } else {
1466
+ dispatchAuthEvent(
1467
+ 'signOut',
1468
+ this.user,
1469
+ `A user has been signed out`
1470
+ );
1471
+ res(result);
1472
+ }
1473
+ }
1474
+ });
1475
+ }
1476
+ });
1477
+ }
1478
+ } else {
1479
+ logger.debug('no Congito User pool');
1480
+ rej(new Error('Cognito User pool does not exist'));
1481
+ }
1482
+ });
1483
+ }
1484
+
1485
+ /**
1486
+ * Update an authenticated users' attributes
1487
+ * @param {CognitoUser} user - The currently logged in user object
1488
+ * @param {object} attributes - attributes to update
1489
+ * @param {ClientMetaData} clientMetadata - optional client metadata, defaults to config
1490
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1491
+ * @return {Promise}
1492
+ **/
1493
+ public updateUserAttributes(
1494
+ user: CognitoUser | any,
1495
+ attributes: object,
1496
+ clientMetadata: ClientMetaData = this._config.clientMetadata,
1497
+ customUserAgentDetails?: CustomUserAgentDetails
1498
+ ): Promise<string> {
1499
+ const attributeList: ICognitoUserAttributeData[] = [];
1500
+ const that = this;
1501
+ return new Promise((resolve, reject) => {
1502
+ that.userSession(user).then(session => {
1503
+ for (const key in attributes) {
1504
+ if (key !== 'sub' && key.indexOf('_verified') < 0) {
1505
+ const attr: ICognitoUserAttributeData = {
1506
+ Name: key,
1507
+ Value: attributes[key],
1508
+ };
1509
+ attributeList.push(attr);
1510
+ }
1511
+ }
1512
+ user.updateAttributes(
1513
+ attributeList,
1514
+ (err, result, details) => {
1515
+ if (err) {
1516
+ dispatchAuthEvent(
1517
+ 'updateUserAttributes_failure',
1518
+ err,
1519
+ 'Failed to update attributes'
1520
+ );
1521
+ return reject(err);
1522
+ } else {
1523
+ const attrs = this.createUpdateAttributesResultList(
1524
+ attributes as Record<string, string>,
1525
+ details?.CodeDeliveryDetailsList
1526
+ );
1527
+ dispatchAuthEvent(
1528
+ 'updateUserAttributes',
1529
+ attrs,
1530
+ 'Attributes successfully updated'
1531
+ );
1532
+ return resolve(result);
1533
+ }
1534
+ },
1535
+ clientMetadata
1536
+ );
1537
+ });
1538
+ });
1539
+ }
1540
+
1541
+ private createUpdateAttributesResultList(
1542
+ attributes: Record<string, string>,
1543
+ codeDeliveryDetailsList?: CodeDeliveryDetails[]
1544
+ ): Record<string, string> {
1545
+ const attrs = {};
1546
+ Object.keys(attributes).forEach(key => {
1547
+ attrs[key] = {
1548
+ isUpdated: true,
1549
+ };
1550
+ const codeDeliveryDetails = codeDeliveryDetailsList?.find(
1551
+ value => value.AttributeName === key
1552
+ );
1553
+ if (codeDeliveryDetails) {
1554
+ attrs[key].isUpdated = false;
1555
+ attrs[key].codeDeliveryDetails = codeDeliveryDetails;
1556
+ }
1557
+ });
1558
+ return attrs;
1559
+ }
1560
+
1561
+ /**
1562
+ * Return user attributes
1563
+ * @param {Object} user - The CognitoUser object
1564
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1565
+ * @return - A promise resolves to user attributes if success
1566
+ */
1567
+ public userAttributes(
1568
+ user: CognitoUser | any,
1569
+ customUserAgentDetails?: CustomUserAgentDetails
1570
+ ): Promise<CognitoUserAttribute[]> {
1571
+ return new Promise((resolve, reject) => {
1572
+ this.userSession(user).then(session => {
1573
+ user.getUserAttributes((err, attributes) => {
1574
+ if (err) {
1575
+ reject(err);
1576
+ } else {
1577
+ resolve(attributes);
1578
+ }
1579
+ });
1580
+ });
1581
+ });
1582
+ }
1583
+
1584
+ public verifiedContact(
1585
+ user: CognitoUser | any,
1586
+ customUserAgentDetails?: CustomUserAgentDetails
1587
+ ) {
1588
+ const that = this;
1589
+ return this.userAttributes(user).then(attributes => {
1590
+ const attrs = that.attributesToObject(attributes);
1591
+ const unverified = {};
1592
+ const verified = {};
1593
+ if (attrs['email']) {
1594
+ if (attrs['email_verified']) {
1595
+ verified['email'] = attrs['email'];
1596
+ } else {
1597
+ unverified['email'] = attrs['email'];
1598
+ }
1599
+ }
1600
+ if (attrs['phone_number']) {
1601
+ if (attrs['phone_number_verified']) {
1602
+ verified['phone_number'] = attrs['phone_number'];
1603
+ } else {
1604
+ unverified['phone_number'] = attrs['phone_number'];
1605
+ }
1606
+ }
1607
+ return {
1608
+ verified,
1609
+ unverified,
1610
+ };
1611
+ });
1612
+ }
1613
+
1614
+ private isErrorWithMessage(err: any): err is { message: string } {
1615
+ return (
1616
+ typeof err === 'object' &&
1617
+ Object.prototype.hasOwnProperty.call(err, 'message')
1618
+ );
1619
+ }
1620
+
1621
+ // Session revoked by another app
1622
+ private isTokenRevokedError(
1623
+ err: any
1624
+ ): err is { message: 'Access Token has been revoked' } {
1625
+ return (
1626
+ this.isErrorWithMessage(err) &&
1627
+ err.message === 'Access Token has been revoked'
1628
+ );
1629
+ }
1630
+
1631
+ private isRefreshTokenRevokedError(
1632
+ err: any
1633
+ ): err is { message: 'Refresh Token has been revoked' } {
1634
+ return (
1635
+ this.isErrorWithMessage(err) &&
1636
+ err.message === 'Refresh Token has been revoked'
1637
+ );
1638
+ }
1639
+
1640
+ private isUserDisabledError(
1641
+ err: any
1642
+ ): err is { message: 'User is disabled.' } {
1643
+ return this.isErrorWithMessage(err) && err.message === 'User is disabled.';
1644
+ }
1645
+
1646
+ private isUserDoesNotExistError(
1647
+ err: any
1648
+ ): err is { message: 'User does not exist.' } {
1649
+ return (
1650
+ this.isErrorWithMessage(err) && err.message === 'User does not exist.'
1651
+ );
1652
+ }
1653
+
1654
+ private isRefreshTokenExpiredError(
1655
+ err: any
1656
+ ): err is { message: 'Refresh Token has expired' } {
1657
+ return (
1658
+ this.isErrorWithMessage(err) &&
1659
+ err.message === 'Refresh Token has expired'
1660
+ );
1661
+ }
1662
+
1663
+ private isPasswordResetRequiredError(
1664
+ err: any
1665
+ ): err is { message: 'Password reset required for the user' } {
1666
+ return (
1667
+ this.isErrorWithMessage(err) &&
1668
+ err.message === 'Password reset required for the user'
1669
+ );
1670
+ }
1671
+
1672
+ private isSignedInHostedUI() {
1673
+ return (
1674
+ this._oAuthHandler &&
1675
+ this._storage.getItem('amplify-signin-with-hostedUI') === 'true'
1676
+ );
1677
+ }
1678
+
1679
+ private isSessionInvalid(err: any) {
1680
+ return (
1681
+ this.isUserDisabledError(err) ||
1682
+ this.isUserDoesNotExistError(err) ||
1683
+ this.isTokenRevokedError(err) ||
1684
+ this.isRefreshTokenRevokedError(err) ||
1685
+ this.isRefreshTokenExpiredError(err) ||
1686
+ this.isPasswordResetRequiredError(err)
1687
+ );
1688
+ }
1689
+
1690
+ private async cleanUpInvalidSession(user: CognitoUser) {
1691
+ user.signOut();
1692
+ this.user = null;
1693
+ try {
1694
+ await this.cleanCachedItems(); // clean aws credentials
1695
+ } catch (e) {
1696
+ logger.debug('failed to clear cached items');
1697
+ }
1698
+ if (this.isSignedInHostedUI()) {
1699
+ return new Promise((res, rej) => {
1700
+ this.oAuthSignOutRedirect(res, rej);
1701
+ });
1702
+ } else {
1703
+ dispatchAuthEvent('signOut', this.user, `A user has been signed out`);
1704
+ }
1705
+ }
1706
+
1707
+ /**
1708
+ * Get current authenticated user
1709
+ * @param {CurrentUserOpts} params - options for getting the current user
1710
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1711
+ * @return - A promise resolves to current authenticated CognitoUser if success
1712
+ */
1713
+ public currentUserPoolUser(
1714
+ params?: CurrentUserOpts,
1715
+ customUserAgentDetails?: CustomUserAgentDetails
1716
+ ): Promise<CognitoUser | any> {
1717
+ if (!this.userPool) {
1718
+ return this.rejectNoUserPool();
1719
+ }
1720
+
1721
+ return new Promise((res, rej) => {
1722
+ this._storageSync
1723
+ .then(async () => {
1724
+ if (this.isOAuthInProgress()) {
1725
+ logger.debug('OAuth signIn in progress, waiting for resolution...');
1726
+
1727
+ await new Promise(res => {
1728
+ const timeoutId = setTimeout(() => {
1729
+ logger.debug('OAuth signIn in progress timeout');
1730
+
1731
+ Hub.remove('auth', hostedUISignCallback);
1732
+
1733
+ res();
1734
+ }, OAUTH_FLOW_MS_TIMEOUT);
1735
+
1736
+ Hub.listen('auth', hostedUISignCallback);
1737
+
1738
+ function hostedUISignCallback({ payload }) {
1739
+ const { event } = payload;
1740
+
1741
+ if (
1742
+ event === 'cognitoHostedUI' ||
1743
+ event === 'cognitoHostedUI_failure'
1744
+ ) {
1745
+ logger.debug(`OAuth signIn resolved: ${event}`);
1746
+ clearTimeout(timeoutId);
1747
+
1748
+ Hub.remove('auth', hostedUISignCallback);
1749
+
1750
+ res();
1751
+ }
1752
+ }
1753
+ });
1754
+ }
1755
+
1756
+ const user = this.userPool.getCurrentUser();
1757
+
1758
+ if (!user) {
1759
+ logger.debug('Failed to get user from user pool');
1760
+ rej('No current user');
1761
+ return;
1762
+ }
1763
+
1764
+ // refresh the session if the session expired.
1765
+ try {
1766
+ const session = await this._userSession(user);
1767
+
1768
+ // get user data from Cognito
1769
+ const bypassCache = params ? params.bypassCache : false;
1770
+
1771
+ if (bypassCache) {
1772
+ await this.Credentials.clear();
1773
+ }
1774
+
1775
+ const clientMetadata = this._config.clientMetadata;
1776
+
1777
+ // validate the token's scope first before calling this function
1778
+ const { scope = '' } = session.getAccessToken().decodePayload();
1779
+ if (scope.split(' ').includes(USER_ADMIN_SCOPE)) {
1780
+ user.getUserData(
1781
+ async (err, data) => {
1782
+ if (err) {
1783
+ logger.debug('getting user data failed', err);
1784
+ if (this.isSessionInvalid(err)) {
1785
+ try {
1786
+ await this.cleanUpInvalidSession(user);
1787
+ } catch (cleanUpError) {
1788
+ rej(
1789
+ new Error(
1790
+ `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}`
1791
+ )
1792
+ );
1793
+ return;
1794
+ }
1795
+ rej(err);
1796
+ } else {
1797
+ res(user);
1798
+ }
1799
+ return;
1800
+ }
1801
+ const preferredMFA = data.PreferredMfaSetting || 'NOMFA';
1802
+ const attributeList = [];
1803
+
1804
+ for (let i = 0; i < data.UserAttributes.length; i++) {
1805
+ const attribute = {
1806
+ Name: data.UserAttributes[i].Name,
1807
+ Value: data.UserAttributes[i].Value,
1808
+ };
1809
+ const userAttribute = new CognitoUserAttribute(attribute);
1810
+ attributeList.push(userAttribute);
1811
+ }
1812
+
1813
+ const attributes = this.attributesToObject(attributeList);
1814
+ Object.assign(user, { attributes, preferredMFA });
1815
+ return res(user);
1816
+ },
1817
+ { bypassCache, clientMetadata }
1818
+ );
1819
+ } else {
1820
+ logger.debug(
1821
+ `Unable to get the user data because the ${USER_ADMIN_SCOPE} ` +
1822
+ `is not in the scopes of the access token`
1823
+ );
1824
+ return res(user);
1825
+ }
1826
+ } catch (err) {
1827
+ rej(err);
1828
+ }
1829
+ })
1830
+ .catch(e => {
1831
+ logger.debug('Failed to sync cache info into memory', e);
1832
+ return rej(e);
1833
+ });
1834
+ });
1835
+ }
1836
+
1837
+ private isOAuthInProgress(): boolean {
1838
+ return this.oAuthFlowInProgress;
1839
+ }
1840
+
1841
+ /**
1842
+ * Get current authenticated user
1843
+ * @param {CurrentUserOpts} - options for getting the current user
1844
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1845
+ * @return - A promise resolves to current authenticated CognitoUser if success
1846
+ */
1847
+ public async currentAuthenticatedUser(
1848
+ params?: CurrentUserOpts,
1849
+ customUserAgentDetails?: CustomUserAgentDetails
1850
+ ): Promise<CognitoUser | any> {
1851
+ logger.debug('getting current authenticated user');
1852
+ let federatedUser = null;
1853
+ try {
1854
+ await this._storageSync;
1855
+ } catch (e) {
1856
+ logger.debug('Failed to sync cache info into memory', e);
1857
+ throw e;
1858
+ }
1859
+
1860
+ try {
1861
+ const federatedInfo = JSON.parse(
1862
+ this._storage.getItem('aws-amplify-federatedInfo')
1863
+ );
1864
+ if (federatedInfo) {
1865
+ federatedUser = {
1866
+ ...federatedInfo.user,
1867
+ token: federatedInfo.token,
1868
+ };
1869
+ }
1870
+ } catch (e) {
1871
+ logger.debug('cannot load federated user from auth storage');
1872
+ }
1873
+
1874
+ if (federatedUser) {
1875
+ this.user = federatedUser;
1876
+ logger.debug('get current authenticated federated user', this.user);
1877
+ return this.user;
1878
+ } else {
1879
+ logger.debug('get current authenticated userpool user');
1880
+ let user = null;
1881
+ try {
1882
+ user = await this.currentUserPoolUser(params);
1883
+ } catch (e) {
1884
+ if (e === 'No userPool') {
1885
+ logger.error(
1886
+ 'Cannot get the current user because the user pool is missing. ' +
1887
+ 'Please make sure the Auth module is configured with a valid Cognito User Pool ID'
1888
+ );
1889
+ }
1890
+ logger.debug('The user is not authenticated by the error', e);
1891
+ return Promise.reject('The user is not authenticated');
1892
+ }
1893
+ this.user = user;
1894
+ return this.user;
1895
+ }
1896
+ }
1897
+
1898
+ /**
1899
+ * Get current user's session
1900
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1901
+ * @return - A promise resolves to session object if success
1902
+ */
1903
+ public currentSession(
1904
+ customUserAgentDetails?: CustomUserAgentDetails
1905
+ ): Promise<CognitoUserSession> {
1906
+ const that = this;
1907
+ logger.debug('Getting current session');
1908
+ // Purposely not calling the reject method here because we don't need a console error
1909
+ if (!this.userPool) {
1910
+ return Promise.reject(new Error('No User Pool in the configuration.'));
1911
+ }
1912
+
1913
+ return new Promise((res, rej) => {
1914
+ that
1915
+ .currentUserPoolUser()
1916
+ .then(user => {
1917
+ that
1918
+ .userSession(user)
1919
+ .then(session => {
1920
+ res(session);
1921
+ return;
1922
+ })
1923
+ .catch(e => {
1924
+ logger.debug('Failed to get the current session', e);
1925
+ rej(e);
1926
+ return;
1927
+ });
1928
+ })
1929
+ .catch(e => {
1930
+ logger.debug('Failed to get the current user', e);
1931
+ rej(e);
1932
+ return;
1933
+ });
1934
+ });
1935
+ }
1936
+
1937
+ private async _userSession(user?: CognitoUser): Promise<CognitoUserSession> {
1938
+ if (!user) {
1939
+ logger.debug('the user is null');
1940
+ return this.rejectAuthError(AuthErrorTypes.NoUserSession);
1941
+ }
1942
+ const clientMetadata = this._config.clientMetadata;
1943
+ // Debouncing the concurrent userSession calls by caching the promise.
1944
+ // This solution assumes users will always call this function with the same CognitoUser instance.
1945
+ if (this.inflightSessionPromiseCounter === 0) {
1946
+ this.inflightSessionPromise = new Promise<CognitoUserSession>(
1947
+ (res, rej) => {
1948
+ user.getSession(
1949
+ async (err, session) => {
1950
+ if (err) {
1951
+ logger.debug('Failed to get the session from user', user);
1952
+ if (this.isSessionInvalid(err)) {
1953
+ try {
1954
+ await this.cleanUpInvalidSession(user);
1955
+ } catch (cleanUpError) {
1956
+ rej(
1957
+ new Error(
1958
+ `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}`
1959
+ )
1960
+ );
1961
+ return;
1962
+ }
1963
+ }
1964
+ rej(err);
1965
+ return;
1966
+ } else {
1967
+ logger.debug('Succeed to get the user session', session);
1968
+ res(session);
1969
+ return;
1970
+ }
1971
+ },
1972
+ { clientMetadata }
1973
+ );
1974
+ }
1975
+ );
1976
+ }
1977
+ this.inflightSessionPromiseCounter++;
1978
+
1979
+ try {
1980
+ const userSession = await this.inflightSessionPromise;
1981
+ // Set private member. Avoid user.setSignInUserSession() to prevent excessive localstorage refresh.
1982
+ // @ts-ignore
1983
+ user.signInUserSession = userSession;
1984
+ return userSession!;
1985
+ } finally {
1986
+ this.inflightSessionPromiseCounter--;
1987
+ }
1988
+ }
1989
+
1990
+ /**
1991
+ * Get the corresponding user session
1992
+ * @param {Object} user - The CognitoUser object
1993
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
1994
+ * @return - A promise resolves to the session
1995
+ */
1996
+ public userSession(
1997
+ user,
1998
+ customUserAgentDetails?: CustomUserAgentDetails
1999
+ ): Promise<CognitoUserSession> {
2000
+ return this._userSession(user);
2001
+ }
2002
+
2003
+ /**
2004
+ * Get authenticated credentials of current user.
2005
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
2006
+ * @return - A promise resolves to be current user's credentials
2007
+ */
2008
+ public async currentUserCredentials(
2009
+ customUserAgentDetails?: CustomUserAgentDetails
2010
+ ): Promise<ICredentials> {
2011
+ logger.debug('Getting current user credentials');
2012
+
2013
+ try {
2014
+ await this._storageSync;
2015
+ } catch (e) {
2016
+ logger.debug('Failed to sync cache info into memory', e);
2017
+ throw e;
2018
+ }
2019
+
2020
+ // first to check whether there is federation info in the auth storage
2021
+ let federatedInfo = null;
2022
+ try {
2023
+ federatedInfo = JSON.parse(
2024
+ this._storage.getItem('aws-amplify-federatedInfo')
2025
+ );
2026
+ } catch (e) {
2027
+ logger.debug('failed to get or parse item aws-amplify-federatedInfo', e);
2028
+ }
2029
+
2030
+ if (federatedInfo) {
2031
+ // refresh the jwt token here if necessary
2032
+ return this.Credentials.refreshFederatedToken(federatedInfo);
2033
+ } else {
2034
+ return this.currentSession()
2035
+ .then(session => {
2036
+ logger.debug('getting session success', session);
2037
+ return this.Credentials.set(session, 'session');
2038
+ })
2039
+ .catch(() => {
2040
+ logger.debug('getting guest credentials');
2041
+ return this.Credentials.set(null, 'guest');
2042
+ });
2043
+ }
2044
+ }
2045
+
2046
+ public currentCredentials(
2047
+ customUserAgentDetails?: CustomUserAgentDetails
2048
+ ): Promise<ICredentials> {
2049
+ logger.debug('getting current credentials');
2050
+ return this.Credentials.get();
2051
+ }
2052
+
2053
+ /**
2054
+ * Initiate an attribute confirmation request
2055
+ * @param {Object} user - The CognitoUser
2056
+ * @param {Object} attr - The attributes to be verified
2057
+ * @return - A promise resolves to callback data if success
2058
+ */
2059
+ public verifyUserAttribute(
2060
+ user: CognitoUser | any,
2061
+ attr: string,
2062
+ clientMetadata: ClientMetaData = this._config.clientMetadata,
2063
+ customUserAgentDetails?: CustomUserAgentDetails
2064
+ ): Promise<void> {
2065
+ return new Promise((resolve, reject) => {
2066
+ user.getAttributeVerificationCode(
2067
+ attr,
2068
+ {
2069
+ onSuccess(success) {
2070
+ return resolve(success);
2071
+ },
2072
+ onFailure(err) {
2073
+ return reject(err);
2074
+ },
2075
+ },
2076
+ clientMetadata
2077
+ );
2078
+ });
2079
+ }
2080
+
2081
+ /**
2082
+ * Confirm an attribute using a confirmation code
2083
+ * @param {Object} user - The CognitoUser
2084
+ * @param {Object} attr - The attribute to be verified
2085
+ * @param {String} code - The confirmation code
2086
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
2087
+ * @return - A promise resolves to callback data if success
2088
+ */
2089
+ public verifyUserAttributeSubmit(
2090
+ user: CognitoUser | any,
2091
+ attr: string,
2092
+ code: string,
2093
+ customUserAgentDetails?: CustomUserAgentDetails
2094
+ ): Promise<string> {
2095
+ if (!code) {
2096
+ return this.rejectAuthError(AuthErrorTypes.EmptyCode);
2097
+ }
2098
+
2099
+ return new Promise((resolve, reject) => {
2100
+ user.verifyAttribute(attr, code, {
2101
+ onSuccess(data) {
2102
+ resolve(data);
2103
+ return;
2104
+ },
2105
+ onFailure(err) {
2106
+ reject(err);
2107
+ return;
2108
+ },
2109
+ });
2110
+ });
2111
+ }
2112
+
2113
+ public verifyCurrentUserAttribute(
2114
+ attr: string,
2115
+ customUserAgentDetails?: CustomUserAgentDetails
2116
+ ): Promise<void> {
2117
+ const that = this;
2118
+ return that
2119
+ .currentUserPoolUser()
2120
+ .then(user => that.verifyUserAttribute(user, attr));
2121
+ }
2122
+
2123
+ /**
2124
+ * Confirm current user's attribute using a confirmation code
2125
+ * @param {Object} attr - The attribute to be verified
2126
+ * @param {String} code - The confirmation code
2127
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
2128
+ * @return - A promise resolves to callback data if success
2129
+ */
2130
+ verifyCurrentUserAttributeSubmit(
2131
+ attr: string,
2132
+ code: string,
2133
+ customUserAgentDetails?: CustomUserAgentDetails
2134
+ ): Promise<string> {
2135
+ const that = this;
2136
+ return that
2137
+ .currentUserPoolUser()
2138
+ .then(user => that.verifyUserAttributeSubmit(user, attr, code));
2139
+ }
2140
+
2141
+ private async cognitoIdentitySignOut(
2142
+ opts: SignOutOpts,
2143
+ user: CognitoUser | any
2144
+ ) {
2145
+ try {
2146
+ await this._storageSync;
2147
+ } catch (e) {
2148
+ logger.debug('Failed to sync cache info into memory', e);
2149
+ throw e;
2150
+ }
2151
+
2152
+ const isSignedInHostedUI =
2153
+ this._oAuthHandler &&
2154
+ this._storage.getItem('amplify-signin-with-hostedUI') === 'true';
2155
+
2156
+ return new Promise((res, rej) => {
2157
+ if (opts && opts.global) {
2158
+ logger.debug('user global sign out', user);
2159
+ // in order to use global signout
2160
+ // we must validate the user as an authenticated user by using getSession
2161
+ const clientMetadata = this._config.clientMetadata; // TODO: verify behavior if this is override during signIn
2162
+
2163
+ user.getSession(
2164
+ async (err, result) => {
2165
+ if (err) {
2166
+ logger.debug('failed to get the user session', err);
2167
+ if (this.isSessionInvalid(err)) {
2168
+ try {
2169
+ await this.cleanUpInvalidSession(user);
2170
+ } catch (cleanUpError) {
2171
+ rej(
2172
+ new Error(
2173
+ `Session is invalid due to: ${err.message} and failed to clean up invalid session: ${cleanUpError.message}`
2174
+ )
2175
+ );
2176
+ return;
2177
+ }
2178
+ }
2179
+ return rej(err);
2180
+ }
2181
+ user.globalSignOut({
2182
+ onSuccess: data => {
2183
+ logger.debug('global sign out success');
2184
+ if (isSignedInHostedUI) {
2185
+ this.oAuthSignOutRedirect(res, rej);
2186
+ } else {
2187
+ return res();
2188
+ }
2189
+ },
2190
+ onFailure: err => {
2191
+ logger.debug('global sign out failed', err);
2192
+ return rej(err);
2193
+ },
2194
+ });
2195
+ },
2196
+ { clientMetadata }
2197
+ );
2198
+ } else {
2199
+ logger.debug('user sign out', user);
2200
+ user.signOut(() => {
2201
+ if (isSignedInHostedUI) {
2202
+ this.oAuthSignOutRedirect(res, rej);
2203
+ } else {
2204
+ return res();
2205
+ }
2206
+ });
2207
+ }
2208
+ });
2209
+ }
2210
+
2211
+ private oAuthSignOutRedirect(
2212
+ resolve: () => void,
2213
+ reject: (reason?: any) => void
2214
+ ) {
2215
+ const { isBrowser } = browserOrNode();
2216
+
2217
+ if (isBrowser) {
2218
+ this.oAuthSignOutRedirectOrReject(reject);
2219
+ } else {
2220
+ this.oAuthSignOutAndResolve(resolve);
2221
+ }
2222
+ }
2223
+
2224
+ private oAuthSignOutAndResolve(resolve: () => void) {
2225
+ this._oAuthHandler.signOut();
2226
+ resolve();
2227
+ }
2228
+
2229
+ private oAuthSignOutRedirectOrReject(reject: (reason?: any) => void) {
2230
+ this._oAuthHandler.signOut(); // this method redirects url
2231
+
2232
+ // App should be redirected to another url otherwise it will reject
2233
+ setTimeout(() => reject(Error('Signout timeout fail')), 3000);
2234
+ }
2235
+
2236
+ /**
2237
+ * Sign out method
2238
+ * @param {SignOutOpts} opts - options for sign out
2239
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
2240
+ * @return - A promise resolved if success
2241
+ */
2242
+ public async signOut(
2243
+ opts?: SignOutOpts,
2244
+ customUserAgentDetails?: CustomUserAgentDetails
2245
+ ): Promise<any> {
2246
+ try {
2247
+ await this.cleanCachedItems();
2248
+ } catch (e) {
2249
+ logger.debug('failed to clear cached items');
2250
+ }
2251
+
2252
+ if (this.userPool) {
2253
+ const user = this.userPool.getCurrentUser();
2254
+ if (user) {
2255
+ await this.cognitoIdentitySignOut(opts, user);
2256
+ } else {
2257
+ logger.debug('no current Cognito user');
2258
+ }
2259
+ } else {
2260
+ logger.debug('no Cognito User pool');
2261
+ }
2262
+
2263
+ /**
2264
+ * Note for future refactor - no reliable way to get username with
2265
+ * Cognito User Pools vs Identity when federating with Social Providers
2266
+ * This is why we need a well structured session object that can be inspected
2267
+ * and information passed back in the message below for Hub dispatch
2268
+ */
2269
+ dispatchAuthEvent('signOut', this.user, `A user has been signed out`);
2270
+ this.user = null;
2271
+ }
2272
+
2273
+ private async cleanCachedItems() {
2274
+ // clear cognito cached item
2275
+ await this.Credentials.clear();
2276
+ }
2277
+
2278
+ /**
2279
+ * Change a password for an authenticated user
2280
+ * @param {Object} user - The CognitoUser object
2281
+ * @param {String} oldPassword - the current password
2282
+ * @param {String} newPassword - the requested new password
2283
+ * @param {ClientMetaData} clientMetadata - optional client metadata, defaults to config
2284
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
2285
+ * @return - A promise resolves if success
2286
+ */
2287
+ public changePassword(
2288
+ user: CognitoUser | any,
2289
+ oldPassword: string,
2290
+ newPassword: string,
2291
+ clientMetadata: ClientMetaData = this._config.clientMetadata,
2292
+ customUserAgentDetails?: CustomUserAgentDetails
2293
+ ): Promise<'SUCCESS'> {
2294
+ return new Promise((resolve, reject) => {
2295
+ this.userSession(user).then(session => {
2296
+ user.changePassword(
2297
+ oldPassword,
2298
+ newPassword,
2299
+ (err, data) => {
2300
+ if (err) {
2301
+ logger.debug('change password failure', err);
2302
+ return reject(err);
2303
+ } else {
2304
+ return resolve(data);
2305
+ }
2306
+ },
2307
+ clientMetadata
2308
+ );
2309
+ });
2310
+ });
2311
+ }
2312
+
2313
+ /**
2314
+ * Initiate a forgot password request
2315
+ * @param {String} username - the username to change password
2316
+ * @param {ClientMetaData} clientMetadata - optional client metadata, defaults to config
2317
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
2318
+ * @return - A promise resolves if success
2319
+ */
2320
+ public forgotPassword(
2321
+ username: string,
2322
+ clientMetadata: ClientMetaData = this._config.clientMetadata,
2323
+ customUserAgentDetails?: CustomUserAgentDetails
2324
+ ): Promise<any> {
2325
+ if (!this.userPool) {
2326
+ return this.rejectNoUserPool();
2327
+ }
2328
+ if (!username) {
2329
+ return this.rejectAuthError(AuthErrorTypes.EmptyUsername);
2330
+ }
2331
+
2332
+ const user = this.createCognitoUser(username);
2333
+ return new Promise((resolve, reject) => {
2334
+ user.forgotPassword(
2335
+ {
2336
+ onSuccess: () => {
2337
+ resolve();
2338
+ return;
2339
+ },
2340
+ onFailure: err => {
2341
+ logger.debug('forgot password failure', err);
2342
+ dispatchAuthEvent(
2343
+ 'forgotPassword_failure',
2344
+ err,
2345
+ `${username} forgotPassword failed`
2346
+ );
2347
+ reject(err);
2348
+ return;
2349
+ },
2350
+ inputVerificationCode: data => {
2351
+ dispatchAuthEvent(
2352
+ 'forgotPassword',
2353
+ user,
2354
+ `${username} has initiated forgot password flow`
2355
+ );
2356
+ resolve(data);
2357
+ return;
2358
+ },
2359
+ },
2360
+ clientMetadata
2361
+ );
2362
+ });
2363
+ }
2364
+
2365
+ /**
2366
+ * Confirm a new password using a confirmation Code
2367
+ * @param {String} username - The username
2368
+ * @param {String} code - The confirmation code
2369
+ * @param {String} password - The new password
2370
+ * @param {ClientMetaData} clientMetadata - optional client metadata, defaults to config
2371
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
2372
+ * @return - A promise that resolves if success
2373
+ */
2374
+ public forgotPasswordSubmit(
2375
+ username: string,
2376
+ code: string,
2377
+ password: string,
2378
+ clientMetadata: ClientMetaData = this._config.clientMetadata,
2379
+ customUserAgentDetails?: CustomUserAgentDetails
2380
+ ): Promise<string> {
2381
+ if (!this.userPool) {
2382
+ return this.rejectNoUserPool();
2383
+ }
2384
+ if (!username) {
2385
+ return this.rejectAuthError(AuthErrorTypes.EmptyUsername);
2386
+ }
2387
+ if (!code) {
2388
+ return this.rejectAuthError(AuthErrorTypes.EmptyCode);
2389
+ }
2390
+ if (!password) {
2391
+ return this.rejectAuthError(AuthErrorTypes.EmptyPassword);
2392
+ }
2393
+
2394
+ const user = this.createCognitoUser(username);
2395
+ return new Promise((resolve, reject) => {
2396
+ user.confirmPassword(
2397
+ code,
2398
+ password,
2399
+ {
2400
+ onSuccess: success => {
2401
+ dispatchAuthEvent(
2402
+ 'forgotPasswordSubmit',
2403
+ user,
2404
+ `${username} forgotPasswordSubmit successful`
2405
+ );
2406
+ resolve(success);
2407
+ return;
2408
+ },
2409
+ onFailure: err => {
2410
+ dispatchAuthEvent(
2411
+ 'forgotPasswordSubmit_failure',
2412
+ err,
2413
+ `${username} forgotPasswordSubmit failed`
2414
+ );
2415
+ reject(err);
2416
+ return;
2417
+ },
2418
+ },
2419
+ clientMetadata
2420
+ );
2421
+ });
2422
+ }
2423
+
2424
+ /**
2425
+ * Get user information
2426
+ * @async
2427
+ * @param {CustomUserAgentDetails} customUserAgentDetails - Optional parameter to send user agent details
2428
+ * @return {Object }- current User's information
2429
+ */
2430
+ public async currentUserInfo(
2431
+ customUserAgentDetails?: CustomUserAgentDetails
2432
+ ) {
2433
+ const source = this.Credentials.getCredSource();
2434
+
2435
+ if (!source || source === 'aws' || source === 'userPool') {
2436
+ const user = await this.currentUserPoolUser().catch(err =>
2437
+ logger.error(err)
2438
+ );
2439
+ if (!user) {
2440
+ return null;
2441
+ }
2442
+
2443
+ try {
2444
+ const attributes = await this.userAttributes(user);
2445
+ const userAttrs: object = this.attributesToObject(attributes);
2446
+ let credentials = null;
2447
+ try {
2448
+ credentials = await this.currentCredentials();
2449
+ } catch (e) {
2450
+ logger.debug(
2451
+ 'Failed to retrieve credentials while getting current user info',
2452
+ e
2453
+ );
2454
+ }
2455
+
2456
+ const info = {
2457
+ id: credentials ? credentials.identityId : undefined,
2458
+ username: user.getUsername(),
2459
+ attributes: userAttrs,
2460
+ };
2461
+ return info;
2462
+ } catch (err) {
2463
+ logger.error('currentUserInfo error', err);
2464
+ return {};
2465
+ }
2466
+ }
2467
+
2468
+ if (source === 'federated') {
2469
+ const user = this.user;
2470
+ return user ? user : {};
2471
+ }
2472
+ }
2473
+
2474
+ public async federatedSignIn(
2475
+ providerOrOptions:
2476
+ | LegacyProvider
2477
+ | FederatedSignInOptions
2478
+ | FederatedSignInOptionsCustom,
2479
+ response?: FederatedResponse,
2480
+ user?: FederatedUser,
2481
+ customUserAgentDetails?: CustomUserAgentDetails
2482
+ ): Promise<ICredentials> {
2483
+ if (!this._config.identityPoolId && !this._config.userPoolId) {
2484
+ throw new Error(
2485
+ `Federation requires either a User Pool or Identity Pool in config`
2486
+ );
2487
+ }
2488
+
2489
+ // Ensure backwards compatability
2490
+ if (typeof providerOrOptions === 'undefined') {
2491
+ if (this._config.identityPoolId && !this._config.userPoolId) {
2492
+ throw new Error(
2493
+ `Federation with Identity Pools requires tokens passed as arguments`
2494
+ );
2495
+ }
2496
+ }
2497
+
2498
+ if (
2499
+ isFederatedSignInOptions(providerOrOptions) ||
2500
+ isFederatedSignInOptionsCustom(providerOrOptions) ||
2501
+ hasCustomState(providerOrOptions) ||
2502
+ typeof providerOrOptions === 'undefined'
2503
+ ) {
2504
+ const options = providerOrOptions || {
2505
+ provider: CognitoHostedUIIdentityProvider.Cognito,
2506
+ };
2507
+ const provider = isFederatedSignInOptions(options)
2508
+ ? options.provider
2509
+ : (options as FederatedSignInOptionsCustom).customProvider;
2510
+
2511
+ const customState = isFederatedSignInOptions(options)
2512
+ ? options.customState
2513
+ : (options as FederatedSignInOptionsCustom).customState;
2514
+
2515
+ if (this._config.userPoolId) {
2516
+ const client_id = isCognitoHostedOpts(this._config.oauth)
2517
+ ? this._config.userPoolWebClientId
2518
+ : this._config.oauth.clientID;
2519
+ /*Note: Invenstigate automatically adding trailing slash */
2520
+ const redirect_uri = isCognitoHostedOpts(this._config.oauth)
2521
+ ? this._config.oauth.redirectSignIn
2522
+ : this._config.oauth.redirectUri;
2523
+
2524
+ this._oAuthHandler.oauthSignIn(
2525
+ this._config.oauth.responseType,
2526
+ this._config.oauth.domain,
2527
+ redirect_uri,
2528
+ client_id,
2529
+ provider,
2530
+ customState
2531
+ );
2532
+ }
2533
+ } else {
2534
+ const provider = providerOrOptions;
2535
+ // To check if the user is already logged in
2536
+ try {
2537
+ const loggedInUser = JSON.stringify(
2538
+ JSON.parse(this._storage.getItem('aws-amplify-federatedInfo')).user
2539
+ );
2540
+ if (loggedInUser) {
2541
+ logger.warn(`There is already a signed in user: ${loggedInUser} in your app.
2542
+ You should not call Auth.federatedSignIn method again as it may cause unexpected behavior.`);
2543
+ }
2544
+ } catch (e) {}
2545
+
2546
+ const { token, identity_id, expires_at } = response;
2547
+ // Because this.Credentials.set would update the user info with identity id
2548
+ // So we need to retrieve the user again.
2549
+ const credentials = await this.Credentials.set(
2550
+ { provider, token, identity_id, user, expires_at },
2551
+ 'federation'
2552
+ );
2553
+ const currentUser = await this.currentAuthenticatedUser();
2554
+ dispatchAuthEvent(
2555
+ 'signIn',
2556
+ currentUser,
2557
+ `A user ${currentUser.username} has been signed in`
2558
+ );
2559
+ logger.debug('federated sign in credentials', credentials);
2560
+ return credentials;
2561
+ }
2562
+ }
2563
+
2564
+ /**
2565
+ * Used to complete the OAuth flow with or without the Cognito Hosted UI
2566
+ * @param {String} URL - optional parameter for customers to pass in the response URL
2567
+ */
2568
+ private async _handleAuthResponse(URL?: string) {
2569
+ if (this.oAuthFlowInProgress) {
2570
+ logger.debug(`Skipping URL ${URL} current flow in progress`);
2571
+ return;
2572
+ }
2573
+
2574
+ try {
2575
+ this.oAuthFlowInProgress = true;
2576
+ if (!this._config.userPoolId) {
2577
+ throw new Error(
2578
+ `OAuth responses require a User Pool defined in config`
2579
+ );
2580
+ }
2581
+
2582
+ dispatchAuthEvent(
2583
+ 'parsingCallbackUrl',
2584
+ { url: URL },
2585
+ `The callback url is being parsed`
2586
+ );
2587
+
2588
+ const currentUrl =
2589
+ URL || (browserOrNode().isBrowser ? window.location.href : '');
2590
+
2591
+ const hasCodeOrError = !!(parse(currentUrl).query || '')
2592
+ .split('&')
2593
+ .map(entry => entry.split('='))
2594
+ .find(([k]) => k === 'code' || k === 'error');
2595
+
2596
+ const hasTokenOrError = !!(parse(currentUrl).hash || '#')
2597
+ .substr(1)
2598
+ .split('&')
2599
+ .map(entry => entry.split('='))
2600
+ .find(([k]) => k === 'access_token' || k === 'error');
2601
+
2602
+ if (hasCodeOrError || hasTokenOrError) {
2603
+ this._storage.setItem('amplify-redirected-from-hosted-ui', 'true');
2604
+ try {
2605
+ const { accessToken, idToken, refreshToken, state } =
2606
+ await this._oAuthHandler.handleAuthResponse(currentUrl);
2607
+ const session = new CognitoUserSession({
2608
+ IdToken: new CognitoIdToken({ IdToken: idToken }),
2609
+ RefreshToken: new CognitoRefreshToken({
2610
+ RefreshToken: refreshToken,
2611
+ }),
2612
+ AccessToken: new CognitoAccessToken({
2613
+ AccessToken: accessToken,
2614
+ }),
2615
+ });
2616
+
2617
+ let credentials;
2618
+ // Get AWS Credentials & store if Identity Pool is defined
2619
+ if (this._config.identityPoolId) {
2620
+ credentials = await this.Credentials.set(session, 'session');
2621
+ logger.debug('AWS credentials', credentials);
2622
+ }
2623
+
2624
+ /*
2625
+ Prior to the request we do sign the custom state along with the state we set. This check will verify
2626
+ if there is a dash indicated when setting custom state from the request. If a dash is contained
2627
+ then there is custom state present on the state string.
2628
+ */
2629
+ const isCustomStateIncluded = /-/.test(state);
2630
+
2631
+ /*
2632
+ The following is to create a user for the Cognito Identity SDK to store the tokens
2633
+ When we remove this SDK later that logic will have to be centralized in our new version
2634
+ */
2635
+ //#region
2636
+ const currentUser = this.createCognitoUser(
2637
+ session.getIdToken().decodePayload()['cognito:username']
2638
+ );
2639
+
2640
+ // This calls cacheTokens() in Cognito SDK
2641
+ currentUser.setSignInUserSession(session);
2642
+
2643
+ if (window && typeof window.history !== 'undefined') {
2644
+ window.history.replaceState(
2645
+ {},
2646
+ null,
2647
+ (this._config.oauth as AwsCognitoOAuthOpts).redirectSignIn
2648
+ );
2649
+ }
2650
+
2651
+ dispatchAuthEvent(
2652
+ 'signIn',
2653
+ currentUser,
2654
+ `A user ${currentUser.getUsername()} has been signed in`
2655
+ );
2656
+ dispatchAuthEvent(
2657
+ 'cognitoHostedUI',
2658
+ currentUser,
2659
+ `A user ${currentUser.getUsername()} has been signed in via Cognito Hosted UI`
2660
+ );
2661
+
2662
+ if (isCustomStateIncluded) {
2663
+ const customState = state.split('-').splice(1).join('-');
2664
+
2665
+ dispatchAuthEvent(
2666
+ 'customOAuthState',
2667
+ urlSafeDecode(customState),
2668
+ `State for user ${currentUser.getUsername()}`
2669
+ );
2670
+ }
2671
+ //#endregion
2672
+
2673
+ return credentials;
2674
+ } catch (err) {
2675
+ logger.debug('Error in cognito hosted auth response', err);
2676
+
2677
+ // Just like a successful handling of `?code`, replace the window history to "dispose" of the `code`.
2678
+ // Otherwise, reloading the page will throw errors as the `code` has already been spent.
2679
+ if (window && typeof window.history !== 'undefined') {
2680
+ window.history.replaceState(
2681
+ {},
2682
+ null,
2683
+ (this._config.oauth as AwsCognitoOAuthOpts).redirectSignIn
2684
+ );
2685
+ }
2686
+
2687
+ dispatchAuthEvent(
2688
+ 'signIn_failure',
2689
+ err,
2690
+ `The OAuth response flow failed`
2691
+ );
2692
+ dispatchAuthEvent(
2693
+ 'cognitoHostedUI_failure',
2694
+ err,
2695
+ `A failure occurred when returning to the Cognito Hosted UI`
2696
+ );
2697
+ dispatchAuthEvent(
2698
+ 'customState_failure',
2699
+ err,
2700
+ `A failure occurred when returning state`
2701
+ );
2702
+ }
2703
+ }
2704
+ } finally {
2705
+ this.oAuthFlowInProgress = false;
2706
+ }
2707
+ }
2708
+
2709
+ /**
2710
+ * Compact version of credentials
2711
+ * @param {Object} credentials
2712
+ * @return {Object} - Credentials
2713
+ */
2714
+ public essentialCredentials(credentials): ICredentials {
2715
+ return {
2716
+ accessKeyId: credentials.accessKeyId,
2717
+ sessionToken: credentials.sessionToken,
2718
+ secretAccessKey: credentials.secretAccessKey,
2719
+ identityId: credentials.identityId,
2720
+ authenticated: credentials.authenticated,
2721
+ };
2722
+ }
2723
+
2724
+ private attributesToObject(attributes) {
2725
+ const obj = {};
2726
+ if (attributes) {
2727
+ attributes.map(attribute => {
2728
+ if (
2729
+ attribute.Name === 'email_verified' ||
2730
+ attribute.Name === 'phone_number_verified'
2731
+ ) {
2732
+ obj[attribute.Name] =
2733
+ this.isTruthyString(attribute.Value) || attribute.Value === true;
2734
+ } else {
2735
+ obj[attribute.Name] = attribute.Value;
2736
+ }
2737
+ });
2738
+ }
2739
+ return obj;
2740
+ }
2741
+
2742
+ private isTruthyString(value: any): boolean {
2743
+ return (
2744
+ typeof value.toLowerCase === 'function' && value.toLowerCase() === 'true'
2745
+ );
2746
+ }
2747
+
2748
+ private createCognitoUser(username: string): CognitoUser {
2749
+ const userData: ICognitoUserData = {
2750
+ Username: username,
2751
+ Pool: this.userPool,
2752
+ };
2753
+ userData.Storage = this._storage;
2754
+
2755
+ const { authenticationFlowType } = this._config;
2756
+
2757
+ const user = new CognitoUser(userData);
2758
+ if (authenticationFlowType) {
2759
+ user.setAuthenticationFlowType(authenticationFlowType);
2760
+ }
2761
+ return user;
2762
+ }
2763
+
2764
+ private _isValidAuthStorage(obj) {
2765
+ // We need to check if the obj has the functions of Storage
2766
+ return (
2767
+ !!obj &&
2768
+ typeof obj.getItem === 'function' &&
2769
+ typeof obj.setItem === 'function' &&
2770
+ typeof obj.removeItem === 'function' &&
2771
+ typeof obj.clear === 'function'
2772
+ );
2773
+ }
2774
+
2775
+ private noUserPoolErrorHandler(config: AuthOptions): AuthErrorTypes {
2776
+ if (config) {
2777
+ if (!config.userPoolId || !config.identityPoolId) {
2778
+ return AuthErrorTypes.MissingAuthConfig;
2779
+ }
2780
+ }
2781
+ return AuthErrorTypes.NoConfig;
2782
+ }
2783
+
2784
+ private rejectAuthError(type: AuthErrorTypes): Promise<never> {
2785
+ return Promise.reject(new AuthError(type));
2786
+ }
2787
+
2788
+ private rejectNoUserPool(): Promise<never> {
2789
+ const type = this.noUserPoolErrorHandler(this._config);
2790
+ return Promise.reject(new NoUserPoolError(type));
2791
+ }
2792
+
2793
+ public async rememberDevice(
2794
+ customUserAgentDetails?: CustomUserAgentDetails
2795
+ ): Promise<string | AuthError> {
2796
+ let currUser;
2797
+
2798
+ try {
2799
+ currUser = await this.currentUserPoolUser();
2800
+ } catch (error) {
2801
+ logger.debug('The user is not authenticated by the error', error);
2802
+ return Promise.reject('The user is not authenticated');
2803
+ }
2804
+
2805
+ currUser.getCachedDeviceKeyAndPassword();
2806
+ return new Promise((res, rej) => {
2807
+ currUser.setDeviceStatusRemembered({
2808
+ onSuccess: data => {
2809
+ res(data);
2810
+ },
2811
+ onFailure: err => {
2812
+ if (err.code === 'InvalidParameterException') {
2813
+ rej(new AuthError(AuthErrorTypes.DeviceConfig));
2814
+ } else if (err.code === 'NetworkError') {
2815
+ rej(new AuthError(AuthErrorTypes.NetworkError));
2816
+ } else {
2817
+ rej(err);
2818
+ }
2819
+ },
2820
+ });
2821
+ });
2822
+ }
2823
+
2824
+ public async forgetDevice(
2825
+ customUserAgentDetails?: CustomUserAgentDetails
2826
+ ): Promise<void> {
2827
+ let currUser;
2828
+
2829
+ try {
2830
+ currUser = await this.currentUserPoolUser();
2831
+ } catch (error) {
2832
+ logger.debug('The user is not authenticated by the error', error);
2833
+ return Promise.reject('The user is not authenticated');
2834
+ }
2835
+
2836
+ currUser.getCachedDeviceKeyAndPassword();
2837
+ return new Promise((res, rej) => {
2838
+ currUser.forgetDevice({
2839
+ onSuccess: data => {
2840
+ res(data);
2841
+ },
2842
+ onFailure: err => {
2843
+ if (err.code === 'InvalidParameterException') {
2844
+ rej(new AuthError(AuthErrorTypes.DeviceConfig));
2845
+ } else if (err.code === 'NetworkError') {
2846
+ rej(new AuthError(AuthErrorTypes.NetworkError));
2847
+ } else {
2848
+ rej(err);
2849
+ }
2850
+ },
2851
+ });
2852
+ });
2853
+ }
2854
+
2855
+ public async fetchDevices(
2856
+ customUserAgentDetails?: CustomUserAgentDetails
2857
+ ): Promise<IAuthDevice[]> {
2858
+ let currUser;
2859
+
2860
+ try {
2861
+ currUser = await this.currentUserPoolUser();
2862
+ } catch (error) {
2863
+ logger.debug('The user is not authenticated by the error', error);
2864
+ throw new Error('The user is not authenticated');
2865
+ }
2866
+
2867
+ currUser.getCachedDeviceKeyAndPassword();
2868
+ return new Promise((res, rej) => {
2869
+ const cb = {
2870
+ onSuccess(data) {
2871
+ const deviceList: IAuthDevice[] = data.Devices.map(device => {
2872
+ const deviceName =
2873
+ device.DeviceAttributes.find(
2874
+ ({ Name }) => Name === 'device_name'
2875
+ ) || {};
2876
+
2877
+ const deviceInfo: IAuthDevice = {
2878
+ id: device.DeviceKey,
2879
+ name: deviceName.Value,
2880
+ };
2881
+ return deviceInfo;
2882
+ });
2883
+ res(deviceList);
2884
+ },
2885
+ onFailure: err => {
2886
+ if (err.code === 'InvalidParameterException') {
2887
+ rej(new AuthError(AuthErrorTypes.DeviceConfig));
2888
+ } else if (err.code === 'NetworkError') {
2889
+ rej(new AuthError(AuthErrorTypes.NetworkError));
2890
+ } else {
2891
+ rej(err);
2892
+ }
2893
+ },
2894
+ };
2895
+ currUser.listDevices(MAX_DEVICES, null, cb);
2896
+ });
2897
+ }
2898
+ }
2899
+
2900
+ export const InternalAuth = new InternalAuthClass(null);
2901
+ Amplify.register(InternalAuth);