Page MenuHomePhabricator

[keyserver] Fix sending a user cookie when olm session creation failed
Needs RevisionPublic

Authored by inka on Thu, Apr 25, 8:09 AM.
Tags
None
Referenced Files
Unknown Object (File)
Fri, May 3, 10:56 PM
Unknown Object (File)
Fri, May 3, 7:07 PM
Unknown Object (File)
Thu, May 2, 5:45 PM
Unknown Object (File)
Thu, May 2, 1:46 PM
Unknown Object (File)
Thu, May 2, 11:01 AM
Unknown Object (File)
Thu, May 2, 10:55 AM
Unknown Object (File)
Thu, May 2, 5:03 AM
Unknown Object (File)
Thu, May 2, 4:31 AM
Subscribers

Details

Reviewers
varun
tomek
ashoat
Summary

issue: ENG-7247
It used to be possible to have a user cookie even though the notif olm session creation failed. This was because the cookie was created before the session, so even though an error was thrown, the user cookie would be sent with it.

We want to create the user cookie once both olm sessions are created. The only thing I really did here is moving code around so that the cookie creation is called after both session are created

Test Plan

Tested that if an error if thrown from persistOlmNotifSessionPromise, the user doesn't get a user cookie and is no longer logged in after refreshing the app
Tested this for both users how were already registered with the keyserver, and users who weren't
Tested that it is possible to login if olm sessions are created correctly

Diff Detail

Repository
rCOMM Comm
Lint
No Lint Coverage
Unit
No Test Coverage

Event Timeline

keyserver/src/creators/account-creator.js
273 ↗(On Diff #39490)

This is only called in keyserverAuthResponder

295–303 ↗(On Diff #39490)

The cookie is created in processSuccessfulLogin called by keyserverAuthResponder

305 ↗(On Diff #39490)

This is handled later in keyserverAuthResponder by processSuccessfulLogin

307 ↗(On Diff #39490)

This needs the cookie. I moved this out to keyserverAuthResponder after processSuccessfulLogin, which creates the cookie.

keyserver/src/responders/user-responders.js
343–351 ↗(On Diff #39490)

We create the cookie here

827–830 ↗(On Diff #39490)

processAccountCreationCommon was previously called inside processOLMAccountCreation (line 806 above). processOLMAccountCreation was only called if !existingUsername!

inka requested review of this revision.Thu, Apr 25, 8:25 AM
ashoat requested changes to this revision.Thu, Apr 25, 11:16 AM
ashoat removed 1 blocking reviewer(s): varun.

I can review this one, as I'm most familiar with the code. General direction makes sense, but I have some concerns

keyserver/src/creators/account-creator.js
278 ↗(On Diff #39490)

This call appears to be simply removed, without being reintroduced anywhere. Can you please explain why it was removed?

keyserver/src/responders/user-responders.js
343–351 ↗(On Diff #39490)

Hmmm... it appears that we only create the cookie here if !cookieHasBeenSet. It looks like that is usually the case, but there is an exception if we are calling keyserverAuth for an account that previously hasn't been registered with this keyserver.

Looking more closely at this code, it seems like the only point of the cookieHasBeenSet param is to handle a case where the cookie is already set by processOLMAccountCreation. But in this diff, you change processOLMAccountCreation so that it no longer sets a cookie.

Based on that, it seems like this diff should be updated to deprecate the cookieHasBeenSet param, and processSuccessfulLogin should be updated to always create a cookie.

814 ↗(On Diff #39490)

There seems to be a comment for each step in this function. Do you think we should add a step 6 for processAccountCreationCommon?

826–831 ↗(On Diff #39490)

Why do we need the async IIFE?

This revision now requires changes to proceed.Thu, Apr 25, 11:16 AM

Address review

keyserver/src/creators/account-creator.js
278 ↗(On Diff #39490)

This was a mistake, I'll fix it

keyserver/src/responders/user-responders.js
374 ↗(On Diff #39530)

We didn't used to check this here, but we used to check that in all places where processSuccessfulLogin is used:

  • in logInResponder always
  • in keyserverAuth in case of registration
  • in siweAuthResponder in case of registration

But I think since we were checking it always for login, we don't want to allow the query to ever contain non-existant threads. So we can just check it here

ashoat added inline comments.
keyserver/src/creators/account-creator.js
262–270 ↗(On Diff #39533)

We no longer seem to need all of these params

273 ↗(On Diff #39533)

I think the reason this function was separated from keyserverAuthResponder in the first place was because it called processAccountCreationCommon. Now that it no longer calls processAccountCreationCommon, I think it can be inlined into keyserverAuth. What do you think? If you're opposed to inlining it I think it at least makes sense to move it to the same file

keyserver/src/responders/user-responders.js
321 ↗(On Diff #39533)

Could you add a comment above this explaining that we need to call createOlmSession before we start the setNewCookiePromise to avoid propagating a cookie if we fail to create the Olm session?

765–771 ↗(On Diff #39533)

Can you update this comment to say:

// 3. Create content olm session. (The notif session was introduced first and
//    as such is created in legacy auth responders as well. It's factored out
//    into in the shared utility `processSuccessfulLogin(...)`.)
This revision is now accepted and ready to land.Fri, Apr 26, 6:08 PM

Address review - update comments and inline processOLMAccountCreation

inka planned changes to this revision.Tue, Apr 30, 1:48 AM

This solution has a problem - the policies are not marked as accepted until processAccountCreationCommon. So for new users processSuccessfulLogin doesn't set a session to avoid sending user all the data before policies are accepted. But this results in errors when creating threads in processAccountCreationCommon -> createUpdates, because a session is missing.

Marking policies as accepted requires a cookie (we check if viewer.loggedIn).

Move marking policies accepted out of processAccountCreationCommon, before fetchNotAcknowledgedPolicies in processSuccessfulLogin

This revision is now accepted and ready to land.Tue, Apr 30, 2:27 AM
inka requested review of this revision.Tue, Apr 30, 2:27 AM

Hmm. Thanks for catching this error case and fixing up the diff, but I'm not sure I understand the explanation.

It sounds like you're suggesting that the following order is necessary:

  1. First, we have to call setNewCookie
  2. Then we can mark the policies as accepted, which requires viewer.loggedIn (changed as a result of setNewCookie)
  3. Then we can call setNewSession. This needs to come after marking the policies as accepted, in order to "avoid sending user all the data before policies are accepted" (not sure I understand this part very well)
  4. Finally, we can call processAccountCreationCommon, which requires a session since it calls createUpdates

However, in createAccount, we appear to call setNewSession right after setNewCookie, and before we call viewerAcknowledgmentUpdater. Based on your explanation, it seems like that logic is broken?

This diff is very confusing. It's doing way too many things at once, and the test plan is insufficient to cover all of the code that's being modified.

I would love if you could decompose this into several smaller diffs. If you feel strongly that you don't have time for that, then at the very least the test plan needs to be extended to cover all of the responders that you're modifying, including the legacy ones, and including testing siweAuthResponder and keyserverAuthResponder for both an account creation and a login

keyserver/src/responders/user-responders.js
298

Why do we have an any type here? This should be typed better. Can you try this?

{
  +deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest,
  +platformDetails: PlatformDetails,
  +watchedIDs: $ReadOnlyArray<string>,
  ...
}

It would probably be better to actually just insert these three params directly into the ProcessSuccessfulLoginParams list

305

We should be marking policies as accepted on registration, but not on login. I think you've made it too easy for somebody to make a mistake here. This parameter isn't named in a way where it's clear that it should be specified only for registration. See my shouldMarkPoliciesAsAcceptedAfterCookieCreation suggestion below

797–799

Indentation is broken here

802
  1. Why are we passing this in as a function? I think it would be more clear what's going on if we pass in shouldMarkPoliciesAsAcceptedAfterCookieCreation: boolean, and it also avoids some level of misdirection (it's confusing to see viewerAcknowledgmentUpdater called here when it's really being called in processSuccessfulLogin)
  2. It appears that you're always passing in markPoliciesAccepted here, but it should only be called on registration, not on login
ashoat requested changes to this revision.Tue, Apr 30, 6:51 AM
This revision now requires changes to proceed.Tue, Apr 30, 6:51 AM
keyserver/src/responders/user-responders.js
802

Oops, I was wrong about point 2 (got confused by the broken indentation)

I spoke to @inka in our 1:1 today and it appears that some information was lost in communication between @inka and @kamil. In consultation with @inka, we determined that the order should be the following:

  1. First, we have to call setNewCookie
  2. Then, in the case of a registration, we should mark the policies as accepted. This can happen before setNewSession
  3. Then, in all cases, we should check if the policies are accepted. For registration we can assume they are, but for login we need to query the database. This should happen before setNewSession
  4. Then we call setNewSession, but only if the policies are accepted
  5. Finally, we can call processAccountCreationCommon, which requires a session since it calls createUpdates

We then went one-by-one through every single responder that has been modified in this diff, and analyzed whether the order is correct:

  1. createAccount – no changes have currently been made here, but I suggest a change below. If we make that change, I think we should update the test plan to test this responder.
  2. logInResponder – two issues described inline. Given the amount of changes, I think we should update the test plan to test this responder.
  3. siweAuthResponder – several changes, and I suggest more below. Given the amount of changes, I think we should update the test plan to test this responder. We should test this responder for both login and registration.
  4. keyserverAuthResponder – this one is obviously the one with the most changes, and it's already covered in the test plan.

Finally, a note about splitting up this diff. At this point I've spent a couple hours reviewing it, so I feel like I understand it, and I'm not sure how much would be gained by splitting it up. I feel strongly that it would have been easier to review if it had been split up earlier, but I'll leave it up to @inka to decide whether to split it up at this point, as it might be more work than is worth it at this point.

keyserver/src/creators/account-creator.js
129

Don't we have the same problem here that you solved for processSuccessfulLogin? It seems like the notif session creation needs to be attempted before we do any writes to the database, so that if it fails we don't end up in an inconsistent state.

251

I think it's confusing that siweAuthResponder calls processSuccessfulLogin, which has been modified to support handling this viewerAcknowledgmentUpdater call... but instead we handle it in a custom way here.

I think if we go with my shouldMarkPoliciesAsAcceptedAfterCookieCreation suggestion below, it should be clear that this corresponds to registration. So it makes sense for siweAuthResponder to set that in the registration case.

In general, I think it's better to have a consistent mechanisms for these things. Unifying codepaths makes it easier for the reader to understand how things work, and that makes it easier to update the code without making mistakes.

keyserver/src/responders/user-responders.js
340

This async IIFE isn't doing anything anymore, and should have been removed. But this comment can be ignored because my next comment below suggests assigning the promise

340–358

Discussed this with @inka in our 1:1 and I suggested to replace these lines with the following for two reasons:

  1. Slightly improves performance in the case where markPoliciesAccepted does not need to be called, by avoiding blocking the fetchNotAcknowledgedPolicies call on the cookie creation
  2. Makes more explicit the requirement that the cookie is created before markPoliciesAccepted is called, and that fetchNotAcknowledgedPolicies should be called after markPoliciesAccepted
  3. Lets us skip fetchNotAcknowledgedPolicies if we just marked the policies as acknowledged
const setNewCookiePromise = (async () => {
  const [userViewerData] = await Promise.all([
    createNewUserCookie(userID, {
      platformDetails: request.platformDetails,
      deviceToken,
      socialProof,
      signedIdentityKeysBlob,
    }),
    deleteCookie(viewer.cookieID),
  ]);
  viewer.setNewCookie(userViewerData);
})();
const policiesCheckAndUpdate = (async () => {
  if (markPoliciesAccepted) {
    await setNewCookiePromise;
    await markPoliciesAccepted(viewer);
    return [];
  }
  return await fetchNotAcknowledgedPolicies(
    userID,
    baseLegalPolicies,
  );
})();
const [notAcknowledgedPolicies] = await Promise.all([
  policiesCheckAndUpdate,
  setNewCookiePromise,
]);

Two quick notes on this logic:

  1. Note that this logic will have to be updated to reflect my shouldMarkPoliciesAsAcceptedAfterCookieCreation suggestion below.
  2. It would be good to make the relationship between baseLegalPolicies (passed to fetchNotAcknowledgedPolicies) and policyTypes.tosAndPrivacyPolicy (passed to viewerAcknowledgmentUpdater) more explicit. If somebody were to add another policy to baseLegalPolicies, it seems like this logic would break: we would be assuming it's okay to proceed because tosAndPrivacyPolicy has been acknowledged, but we're not checking about other policies
381

This check used to happen earlier in some cases, such as logInResponder.

  1. Now the check doesn't even happen if the policies haven't been acknowledged
  2. If the check happens and fails, it can leave us in a weird state where a user has a cookie but no session

I initially thought we could just move this call to the start of processSuccessfulLogin, but then it presents a different risk: if called during registration, we might see a situation where a user is added to the users table, but then a crash occurs when verifyCalendarQueryThreadIDs is called. On subsequent calls we would think that the user is already created, and as a result the policies would never be set as acknowledged.

Instead, it seems like we will need to move this check to occur earlier, and probably it will need to be called separately for each responder before any writes occur.

688–696

processSIWEAccountCreation handles cookie creation when siweAuthResponder is called for registration. But in that case, we don't seem to be doing anything to prevent processSuccessfulLogin from creating the cookie as well.

As a result, it looks like registration via siweAuthResponder results in two cookies being created. This issue was originally tracked in ENG-2767, but it looks like it was never fully fixed up. (cc @varun)

Prior to your changes, we could have passed cookieHasBeenSet to processSuccessfulLogin here to disable cookie creation. But now it seems like the best approach would be to do the same thing we did for processOLMAccountCreation:

  1. Get rid of processSIWEAccountCreation
  2. Inline the parts of processSIWEAccountCreation that handle creating the row in the users table
  3. Rely on processSuccessfulLogin to handle the rest (cookie creation and policy stuff)
  4. Have siweAuthResponder call createAndSendReservedUsernameMessage directly (only for registration)