Page MenuHomePhabricator

[lib] scaffolding for delete identity account
ClosedPublic

Authored by varun on Dec 5 2023, 10:18 PM.
Tags
None
Referenced Files
F2193604: D10202.id34890.diff
Thu, Jul 4, 10:36 PM
F2188989: D10202.id.diff
Thu, Jul 4, 9:54 AM
Unknown Object (File)
Wed, Jul 3, 9:53 PM
Unknown Object (File)
Wed, Jul 3, 4:45 AM
Unknown Object (File)
Tue, Jul 2, 2:31 PM
Unknown Object (File)
Tue, Jul 2, 11:46 AM
Unknown Object (File)
Tue, Jul 2, 11:12 AM
Unknown Object (File)
Tue, Jul 2, 8:39 AM
Subscribers

Details

Summary

introduce some scaffolding for deleteIdentityAccount

Test Plan

tested the following scenarios:

account deletion on native with valid CSAT -> account deleted on keyserver and identity service
account deletion on native with invalid CSAT -> account deleted on keyserver and saw DELETE_IDENTITY_ACCOUNT_FAILED in devtools
account deletion on native while disconnected from identity service -> account deleted on keyserver and saw DELETE_IDENTITY_ACCOUNT_FAILED in devtools

Diff Detail

Repository
rCOMM Comm
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

lib/actions/user-actions.js
166 ↗(On Diff #34325)

on native, the client that implements the IdentityServiceClient interface will use the commRustModule gRPC client. on web, the client that implements this interface will use the codegen'd grpc-web client

varun requested review of this revision.Dec 5 2023, 10:39 PM
ashoat requested changes to this revision.EditedDec 6 2023, 12:16 AM

I can’t review this without seeing an example usage of the action types or the actual action. The action likely needs a payload. Can you update this diff to be more substantial? Or re-request review after enough of the stack is ready that it can actually be reviewed?

This revision now requires changes to proceed.Dec 6 2023, 12:16 AM

On Linear, you estimated that account deletion would take 1 day. Given that this diff updates zero callsites and zero reducers, should we be worried about this estimate? How long do we expect ENG-4357 to actually take?

Last question – what’s the purpose of this new action type, and how does it relate to the existing one? Do we really need two types still? If your view is eg. that the existing action handles deletion from a single keyserver, is this something you’ve confirmed with @inka? Who is going to handle eg. updating the reducer to handle the existing action a different way when your new CSAT constant is set?

@varun and I talked about that delete account from keyserver and from identity will be different actions.

  1. Currently, delete account and logout action are reduced in the same way
  2. I will be refactoring logout logic in reducers, to be able to clear the state for a subset of keyservers (and to generally clear only the state that is supposed to be cleared on keyserver logout. For example it won't be clearing the CSAT). So reducers will be automatically refactored for delete account from keyserver action. This will allow us to use this action for removing a keyserver from the keyserver list. This is also needed for invalidSessionDowngrade.
  3. After recent discussions, we decided that it would be more efficient if I also took care of reducing logout action for identity (see this comment). This is one of the reasons for my rescoping my December goal. This means I will automatically take care of reducers logic for identity delete account, since, if I'm not missing something, it will have the same effect on the redux store as identity logout.

Thanks @inka – you've answered a lot of my questions. It sounds like you have all of the reducer logic covered, which is great to hear.

Two questions for you:

  1. In some cases, @varun's work will be blocked on reducer logic. As an example, in ENG-4332 we need the identity login handler component to attempt login only when eg. a commServicesAccessToken is missing. If the identity logout action is not reduced in services-access-token-reducer.js, then it will be hard to test this via logout / log back in.
    • In this case, does it make sense for @varun to implement some basic reducer logic in services-access-token-reducer.js as part of his logout diff? Otherwise it seems like the sequencing of work might get rather complicated, with lots of interdependencies. cc @tomek
    • Curious what other examples exist like this.
  2. I wonder if the DELETE_IDENTITY_ACCOUNT_SUCCESS action needs a payload.
    • Whose responsibility is it to determine action payloads? Do you and @varun have a process for determining what data will be in the payloads of these actions?

Currently, delete account and logout action are reduced in the same way

I assume you're aware of this, but there are some differences, such as how the logout start action is handled in some reducers, but the delete start action is not.

Meanwhile, I'm leaving some new inline comments for @varun that should be addressed before landing. Since my availability will be limited in the coming days, I'm going to resign from the diff, in the hopes that one of the other reviewers can confirm my comments have been addressed. I'm open to this diff being landed without any callsite or reducers if @inka feels like they are able to review the diff without needing it.

lib/actions/user-actions.js
168 ↗(On Diff #34325)

In hooks you should never ever return a new function like this. It will cause a bunch of unnecessary rerenders... every time you call this hook it returns a new function, so all of the hooks that depend on it will have to be rerendered as well.

The typical thing to do is to wrap all declarations with React.useCallback. But in this case, there appears to be a way better / more simple solution... can't you just return deleteIdentityAccount directly here?

lib/types/identity-service-types.js
22 ↗(On Diff #34325)

We should avoid interface declarations because inheritance makes typechecking harder. Always try to use structural type declarations:

export type IdentityServiceClient = {
  +deleteUser: () => Promise<mixed>,
  ...
};

Note that I use Promise<mixed> to make the API more flexible

This revision now requires review to proceed.Dec 6 2023, 9:19 AM
ashoat added a subscriber: ashoat.

Realized there are no other reviewers. Adding @inka and @tomek (I figure @tomek makes sense since he has been scoping with @inka, and will be looking at ENG-5997)

the deleteUser identity RPC doesn't return anything so I don't think we need an action payload for DELETE_IDENTITY_ACCOUNT_SUCCESS

lib/actions/user-actions.js
168 ↗(On Diff #34325)

I couldn't return deleteIdentityAccount directly in the useDeleteIdentityAccount hook because it expects a client parameter, whereas the hook needs to return a parameterless function.

I assume you're aware of this, but there are some differences, such as how the logout start action is handled in some reducers, but the delete start action is not.

Yes, I just mean that wherever deleteKeyserverAccountActionTypes.success is reduced, logOutActionTypes.success is reduced in the same way. So introducing changes to logOutActionTypes.success gives us changes for deleteKeyserverAccountActionTypes.success, so no additional work will have to be done. And other deleteKeyserverAccountActionTypes (failed, started) are never reduced.

In some cases, @varun's work will be blocked on reducer logic. (...) In this case, does it make sense for @varun to implement some basic reducer logic in services-access-token-reducer.js as part of his logout diff?

Yes, whenever @varun feels blocked we should try to resolve as fast as possible, by either of us implementing the required code. We are messaging and syncing regularly recently, so I think we can work together to keep both of us unblocked. I just need to know if anything I was planing on doing has already been done, so if @varun could subscribe me to those diffs, or add me as a reviewer, that would be great. I will also try to land my diffs regularly, to avoid us duplicating each others work.

Curious what other examples exist like this.

I'm not sure. Generally all of this is pretty intertwined. @varun, can you think of anything else that will soon get blocked on reducers?

I wonder if the DELETE_IDENTITY_ACCOUNT_SUCCESS action needs a payload.

Currently, deleteKeyserverAccountActionTypes's payload is LogOutResult:

export type LogOutResult = {
  +currentUserInfo: ?LoggedOutUserInfo,
  +preRequestUserState: PreRequestUserState,
};

These values are used to:

  1. Set currentUserInfo in the store
  2. Check invalidSessionDowngrade

According to the scenario described by @ashoat here, I think we still need to check invalidSessionDowngrade for identity logout. So we need both of those values. (currentUserInfo in this context is a LoggedOutUserInfo = { +anonymous: true }. We wanted to remove it, and replace with null/undefined, but I had problems doing so. There is a task tracking this: ENG-5625, and here is a brief explanation of the problems I was facing: comment)
I'm not sure if anything else will be needed in the payload, I can't see why it would be. But if either of us finds out that another value is needed, we will let each other know, and figure out what needs to be done about it.

@varun can you add this payload to this action? See deleteKeyserverAccount for how to do this. Basically preRequestUserState should be in the input of this action, and currentUserInfo should be loggedOutUserInfo, which is a constant defined in lib/actions/user-actions.js

lib/actions/user-actions.js
3 ↗(On Diff #34362)

We should probably import from react which should fix the build issues. Please make sure that the test plan is still valid after each revision.

lib/actions/user-actions.js
3 ↗(On Diff #34362)

+1 – the diff should not be updated unless you ran through the test plan again

This import should be import * as React from 'react';, as it is everywhere else in the codebase

address feedback and add a callsite

lib/types/identity-service-types.js
22 ↗(On Diff #34325)

Talked about this with @varun in our 1:1. It looks like it's not possible because Spec in native/schema/CommRustModuleSchema.js is defined as an interface, not a type

condition dispatching the new delete action on the usingCSAT bool

note that this diff does not change any redux state. that will come later.

tomek added 1 blocking reviewer(s): inka.
tomek added inline comments.
lib/actions/user-actions.js
176 ↗(On Diff #34696)

Wondering how the returned currentUserInfo will be used. Both deleteIdentityAccountActionTypes and deleteKeyserverAccountActionTypes return it - it might be a little complicated to decide what to put into store when one of them succeeds and one fails. We probably can set a new value after each success, but I'm not sure.

native/profile/delete-account.react.js
38–47 ↗(On Diff #34696)

Why do we have to keep the deviceID in a state? Can't we call await getContentSigningKey() inside useDeleteIdentityAccount? Or maybe, the deviceID should be stored in a more global place, e.g. somewhere in Redux?

74–87 ↗(On Diff #34696)

Maybe we should modify the alerts so that we know which action failed.

inka added inline comments.
lib/actions/user-actions.js
176 ↗(On Diff #34696)

Generally we want to stop sending CurrentUserInfo from keyservers. It doesn't make sense for the keyservers to be sending the user their id, avatar, username. All of this they should have already gotten from identity (or backup?). Settings I'm not sure about, but they would then be keyserver specific, and they aren't for now.

In case of delete account this is even more evident, as we can disconnect from one keyserver. This should have no effect on currentUserInfo.
It also is less problematic in case of delete account, as loggedOutUserInfo is just { anonymous: true}

This revision is now accepted and ready to land.Dec 15 2023, 8:31 AM
lib/actions/user-actions.js
176 ↗(On Diff #34696)

So it sounds like we don't need to return currentUserInfo from actions at all and instead set { anonymous: true} as a current user when reducing .success actions, when necessary, right?

In case of this diff, we should stop returning currentUserInfo.

ashoat requested changes to this revision.Dec 15 2023, 10:54 AM
ashoat added inline comments.
native/profile/delete-account.react.js
38–47 ↗(On Diff #34696)

This is concerning because we have no guarantee that deviceID will be set by the time callDeleteIdentityAccount is called

We should wrap callDeleteIdentityAccount in an async callback that first calls await getContentSigningKey()

Alternately, if @varun's reason for running this in an effect is to "precalculate" it for performance, then I think we could replace the deviceID state with a deviceIDPromise ref, and wrap callDeleteIdentityAccount in an async callback that first calls await contentSigningKeyPromiseRef.current

This revision now requires changes to proceed.Dec 15 2023, 10:54 AM
lib/actions/user-actions.js
176 ↗(On Diff #34696)

Looks like changes are effectively being requested here

lib/actions/user-actions.js
176 ↗(On Diff #34696)

Made a note in ENG-5766 to handle this in reducers

varun marked 2 inline comments as done.
  • make alerts distinguishable
  • stop returning currentUserInfo in hook
  • prevent calling callDeleteIdentityAccount before deviceID is set
native/profile/delete-account.react.js
38–47 ↗(On Diff #34696)

We can't call await getContentSigningKey() inside useDeleteIdentityAccount because it's a native-specific function

38–47 ↗(On Diff #34696)

We should wrap callDeleteIdentityAccount in an async callback that first calls await getContentSigningKey()

I'm not sure how to do this since useDeleteIdentityAccount is a hook and therefore can't be called inside a callback. I think we have to store deviceID in a state or ref.

I didn't want to ignore your feedback completely so I've added an ActivityIndicator to prevent callDeleteIdentityAccount from being called before deviceID is set. Will undo this if I can figure out how to implement your original suggestion.

tomek requested changes to this revision.Dec 21 2023, 1:14 AM
tomek added inline comments.
native/profile/delete-account.react.js
28–30 ↗(On Diff #34890)

We probably need another selector that checks deleteIdentityAccountActionTypes status

130–136 ↗(On Diff #34890)

We already have an indicator inside buttonContent - shouldn't we modify a condition to check if a deviceID is present?

131 ↗(On Diff #34890)

I guess we should make this button disabled when an action is pending or when a deviceID isn't set

38–47 ↗(On Diff #34696)

We can't call await getContentSigningKey() inside useDeleteIdentityAccount because it's a native-specific function

If we need a result of this function to delete an account, and this function is available only on native, will it be possible to delete an account on the web?

When a function is available only on one platform, we can e.g. provide it as a parameter from a platform-specific component and then call it in lib. But in this case, I suspect that we need a similar function on the web.

This revision now requires changes to proceed.Dec 21 2023, 1:14 AM
native/profile/delete-account.react.js
130–136 ↗(On Diff #34890)

Let's avoid ternary in JSX – see here

varun marked 3 inline comments as done.Jan 7 2024, 8:33 PM
varun added inline comments.
native/profile/delete-account.react.js
38–47 ↗(On Diff #34696)

on web we get the deviceID with:

const deviceID = useSelector(
  state => state.cryptoStore?.primaryIdentityKeys.ed25519,
);
varun marked an inline comment as done.Jan 7 2024, 9:29 PM

unclear what this android failure is...

tomek added inline comments.
native/profile/delete-account.react.js
63–64 ↗(On Diff #35336)

We can use an util combineLoadingStatuses

67 ↗(On Diff #35336)

Is it a good idea to display an activity indicator when deviceID is null?

116–119 ↗(On Diff #35336)

Is it a good idea to proceed with identity delete if a keyserver delete fails?

ashoat added inline comments.
native/profile/delete-account.react.js
67 ↗(On Diff #35336)

I don't see why not?

116–119 ↗(On Diff #35336)

I think so. If eg. a keyserver is offline, that should not prevent the account deletion

This revision is now accepted and ready to land.Jan 8 2024, 4:28 AM
native/profile/delete-account.react.js
38–47 ↗(On Diff #34696)

on web we get the deviceID with:

const deviceID = useSelector(
  state => state.cryptoStore?.primaryIdentityKeys.ed25519,
);

Why is it stored in redux on the web, but on native, we have to await it in an effect and set it to a state? Can't we store it in the same way on both platforms?

native/profile/delete-account.react.js
38–47 ↗(On Diff #34696)

i think we want to avoid storing it in two places on native, and we have to store it in crypto::CryptoModule.

refactored this so we don't have to await it in an effect and set it to a state, at least

This revision was landed with ongoing or failed builds.Jan 9 2024, 9:12 PM
This revision was automatically updated to reflect the committed changes.