Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(aws-amplify/auth): debouncing get user session calls #10654

Merged
merged 10 commits into from Nov 16, 2022

Conversation

AllanZhengYP
Copy link
Contributor

@AllanZhengYP AllanZhengYP commented Nov 9, 2022

Description of changes

Short version

This change adds back changes made in #10553, which was reverted in #10630. It was reverted because of DataStore auth integration test failures.
The DataStore issue causes every authenticated query/mutation request to make extra Cognito requests.

The additional commit 1fe6949 resolves the DataStore issue.

Integration test passed here

Long version

In general this change is to reduce concurrent API requests to CognitoIdentityProvider.GetUser. Compared to previously reverted PR, the additional changes are to resolve the issue that DataStore makes more Cognito requests before each query. Here's 2 screenshots of the network tab of an App sign-in a user and querySubscribe to a DataStore model. The 2 screenshots shows the difference before and after the change:

Network tab before the change

Screen Shot 2022-11-08 at 12 17 33 PM

Network tab after the change

Screen Shot 2022-11-08 at 12 15 27 PM

From the screenshots above, you can see not only did the DataStore make more Cognito requests, but also the Authenticator UI is make more Cognito requests. This is likely due to unintended removal localstorage.

Here's an analogue of the workflow of the DataStore request authentication:

  1. For any query/mutation in DataStore module, and the model is marked as cognito user pool authentication, _headerBasedAuth() is called:
    case 'AMAZON_COGNITO_USER_POOLS':
    try {
    const session = await this.Auth.currentSession();
    headers = {
    Authorization: session.getAccessToken().getJwtToken(),
    };
    } catch (e) {
    throw new Error(GraphQLAuthError.NO_CURRENT_USER);
    }
  2. Fetch the current user pool user in Auth module:
    public currentUserPoolUser(
    params?: CurrentUserOpts
    ): Promise<CognitoUser | any> {
    1. First it loads the user session with user.getSession(). This function is debounced and only first of concurrent calls are made. The other concurrent invocations will resolve the promise of the 1st invocation:
      private async _userSession(user?: CognitoUser): Promise<CognitoUserSession> {
    2. With the user session, load the rest of the user attributes with user.getUserData(). This function is not debounced:
      user.getUserData(

What caused more Cognito calls?
For every DataStore query/mutation invocation, it will reach to the code where we set user session to CognitoUser instance:

user.setSignInUserSession(userSession!);
This is necessary because we debounce the concurrent call the fetch the user session. But we still need to set the fetched user session to each of the concurrent CognitoUser instances. If we look deeper to the user.setSignInUserSession implementation:
setSignInUserSession(signInUserSession) {
this.clearCachedUserData();
this.signInUserSession = signInUserSession;
this.cacheTokens();
}
. We will find it will remove the user data(including attributes) besides setting the private signInUserSession property. As a result, when we call user.getUserData(), we always find the user data is disappears from the local storage. Hense, extra Cognito requests need to be made to fetch the user data repeatedly.

What is the solution?
The solution is simple, instead of calling user.setSignInUserSession(), we can set the private member directly. So the userdata localstorage won't be removed. When user.getUserData() is called, it won't make API request, but load from localstorage.

What is really causing the integration test to fail?
It's caused by interference between the tests. The each integ test is ended by the ready event. The event change will trigger new graphql query. Because now each graphql query causes GetUser API call and hence refreshes localstorage. By the time previous test ended and 2nd test started, the localstorage is reloaded by the async requests from the 1st test.

Checklist

  • PR description included
  • yarn test passes
  • Tests are changed or added
  • Relevant documentation is changed or added (and PR referenced)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@AllanZhengYP AllanZhengYP marked this pull request as ready for review November 9, 2022 23:28
@AllanZhengYP AllanZhengYP requested review from a team as code owners November 9, 2022 23:28
@codecov-commenter
Copy link

Codecov Report

Merging #10654 (ac8f5e9) into main (623374d) will increase coverage by 0.00%.
The diff coverage is 96.42%.

@@           Coverage Diff           @@
##             main   #10654   +/-   ##
=======================================
  Coverage   85.59%   85.59%           
=======================================
  Files         196      196           
  Lines       18236    18237    +1     
  Branches     3887     3886    -1     
=======================================
+ Hits        15609    15610    +1     
  Misses       2549     2549           
  Partials       78       78           
Impacted Files Coverage Δ
packages/auth/src/Auth.ts 87.14% <96.42%> (+0.17%) ⬆️
...kages/amazon-cognito-identity-js/src/BigInteger.js 89.31% <0.00%> (-0.41%) ⬇️

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

Copy link
Contributor

@jimblanc jimblanc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, small question about de-bounce logic. Appreciate the detailed PR write-up!

);
const clientMetadata = this._config.clientMetadata;
// Debouncing the concurrent userSession calls by caching the promise.
// This solution asumes users will always call this function with the same CognitoUser instance.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize original PR had this also, but is this a safe assumption to make? E.g. if user.getSession takes longer than normal and in the mean time a different user logs in, would this method return the session from the original user?

Also, small typo asumes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed with @elorzafe this is low risk, but I'd like him to chime-in.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is a good concern @jimblanc this class could potenially have a RC on this, do you think we could add in a separate PR a mechanism to use this debouncer for other operations as well?

jimblanc
jimblanc previously approved these changes Nov 14, 2022
Copy link
Contributor

@elorzafe elorzafe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just minor comments on tests but overall looks good!

Thanks @AllanZhengYP 🌮

packages/auth/__tests__/auth-unit-test.ts Outdated Show resolved Hide resolved
packages/auth/__tests__/auth-unit-test.ts Outdated Show resolved Hide resolved
packages/auth/__tests__/auth-unit-test.ts Show resolved Hide resolved
packages/auth/__tests__/auth-unit-test.ts Show resolved Hide resolved
);
const clientMetadata = this._config.clientMetadata;
// Debouncing the concurrent userSession calls by caching the promise.
// This solution asumes users will always call this function with the same CognitoUser instance.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is a good concern @jimblanc this class could potenially have a RC on this, do you think we could add in a separate PR a mechanism to use this debouncer for other operations as well?

Copy link
Contributor

@elorzafe elorzafe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me

Thanks @AllanZhengYP 🚢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants