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

[Due for payment 2025-03-05] [$250] Copilot - User gets stucked in Copilot account when removed by the other user #56165

Open
3 of 8 tasks
vincdargento opened this issue Jan 31, 2025 · 28 comments
Assignees
Labels
Awaiting Payment Auto-added when associated PR is deployed to production Bug Something is broken. Auto assigns a BugZero manager. External Added to denote the issue can be worked on by a contributor Weekly KSv2

Comments

@vincdargento
Copy link

vincdargento commented Jan 31, 2025

If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!


Version Number: 9.0.93-0
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught on HybridApp, is this reproducible on New Expensify Standalone?: Yes, reproducible on both
If this was caught during regression testing, add the test name, ID and link from TestRail: https://expensify.testrail.io/index.php?/tests/view/5546706&group_by=cases:section_id&group_order=asc&group_id=229064
Email or phone of affected tester (no customers): [email protected]
Issue reported by: Applause Internal Team
Device used: Motorola MotoG60 - Android 12 - Chrome / Windows 10 - Chrome
App Component: User Settings

Action Performed:

  1. Open the Expensify app.
  2. Log in with an account that was assigned as Copilot.
  3. Tap on "Settings"
  4. Tap on the copilot switcher on top of the screen and switch to the copilot account.
  5. In another device or environment, open the main account and removed the first account as copilot.
  6. Switch to first account again and check if the copilot account was removed and the account can be normally used.
  7. If copilot wasn´t removed, try to switch accounts again to return to the main one.
  8. Check the app´s behavior with this action.

Expected Result:

When user is removed as copilot, the app should be automatically updated and returned to the main account.

Actual Result:

User can´t return to main account when removed as copilot after switching account. When selecting main account on switcher, a "Something went wrong" message is displayed and the user gets stuck in copilot account. Killing and relaunching the app, leads to infinite loading in settings section and the only way to be able to use the account normally again, is by logging out and re logging in.

Workaround:

Unknown

Platforms:

  • Android: Standalone
  • Android: HybridApp
  • Android: mWeb Chrome
  • iOS: Standalone
  • iOS: HybridApp
  • iOS: mWeb Safari
  • MacOS: Chrome / Safari
  • MacOS: Desktop

Screenshots/Videos

bug.mp4

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~021885343462254488429
  • Upwork Job ID: 1885343462254488429
  • Last Price Increase: 2025-02-07
  • Automatic offers:
    • ijmalik | Contributor | 106130462
Issue OwnerCurrent Issue Owner: @slafortune
@vincdargento vincdargento added Bug Something is broken. Auto assigns a BugZero manager. Daily KSv2 labels Jan 31, 2025
Copy link

melvin-bot bot commented Jan 31, 2025

Triggered auto assignment to @slafortune (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.

@slafortune slafortune added the External Added to denote the issue can be worked on by a contributor label Jan 31, 2025
Copy link

melvin-bot bot commented Jan 31, 2025

Job added to Upwork: https://www.upwork.com/jobs/~021885343462254488429

@melvin-bot melvin-bot bot changed the title Copilot - User gets stucked in Copilot account when removed by the other user [$250] Copilot - User gets stucked in Copilot account when removed by the other user Jan 31, 2025
@melvin-bot melvin-bot bot added the Help Wanted Apply this label when an issue is open to proposals by contributors label Jan 31, 2025
Copy link

melvin-bot bot commented Jan 31, 2025

Triggered auto assignment to Contributor-plus team member for initial proposal review - @c3024 (External)

@ijmalik
Copy link
Contributor

ijmalik commented Feb 1, 2025

🚨 Edited by proposal-police: This proposal was edited at 2025-02-02 05:41:28 UTC.

Proposal

Please re-state the problem that we are trying to solve in this issue.

Copilot - User remains stuck in the Copilot account after being removed by another user.

What is the root cause of that problem?

1.1- Onyx State Before Copilot removal: complete state

{
    "delegates": [
        {"email": "[email protected]", "role": "all"},
        {"email": "[email protected]", "role": "all"}
    ],
    "delegators": [],
    "delegate": "[email protected]"
}

The delegate email [email protected] is present in the delegatesarray.

1.2- When another user removes the copilot

Pusher event at client side:

{
    "updates": [
        {
            "data": [
                {
                    "key": "account",
                    "onyxMethod": "merge",
                    "value": {
                        "delegatedAccess": {
                            "delegates": [{"email": "[email protected]", "role": "all"}],
                            "delegators": []
                        }
                    }
                }
            ],
            "eventType": "onyxApiUpdate"
        }
    ],
    "lastUpdateID": 4106093418,
    "previousUpdateID": 4106079691
}

Onyx State After Copilot removal by pusher: complete state

{
    "delegates": [{"email": "[email protected]", "role": "all"}],
    "delegators": [],
    "delegate": "[email protected]"
}

delegate email [email protected] is not present in delegates array. it has been removed by the pusher.

So pusher is properly updating Onyx.

2. Why Users Get Stuck?

const restrictedToken = response.restrictedToken;
const policyID = activePolicyID;
return SequentialQueue.waitForIdle()
.then(() => Onyx.clear(KEYS_TO_PRESERVE_DELEGATE_ACCESS))
.then(() => {
// Update authToken in Onyx and in our local variables so that API requests will use the new authToken
updateSessionAuthTokens(response?.restrictedToken, response?.encryptedAuthToken);
NetworkStore.setAuthToken(response?.restrictedToken ?? null);
confirmReadyToOpenApp();
openApp().then(() => NativeModules.HybridAppModule?.switchAccount(email, restrictedToken, policyID, String(previousAccountID)));
});

When connected as copilot [email protected], all server communication is tied to this account.
After removal, the session remains active but API calls fail with the following response:

{
    "jsonCode": 408,
    "title": "Session expired",
    "message": "The account you are trying to use is deleted.",
    "requestID": "90af0866deabeaeb-KHI"
}

This results in the user being stuck.

What changes do you think we should make in order to solve the problem?

Remove following

Onyx.connect({
key: ONYXKEYS.CREDENTIALS,
callback: (value) => (credentials = value ?? {}),
});

And add following code at

Onyx.connect({
    key: ONYXKEYS.ACCOUNT,
    callback: (val) => {
        delegatedAccess = val?.delegatedAccess ?? {};

        if (!delegatedAccess || Object.keys(delegatedAccess).length === 0 || !stashedSession || Object.keys(stashedSession).length === 0) {
            return;
        }
        
        const {delegates = [], delegate} = delegatedAccess;
        
        if (delegates.length && !delegates.some(d => d.email === delegate)) {
            restoreDelegateSession(stashedSession);
        }
    },
});

Note: This is pseudo-code and will be refined in the PR.

What specific scenarios should we cover in automated tests to prevent reintroducing this issue in the future?

What alternative solutions did you explore? (Optional)

Handling invalid responses by restoring the delegate session when authentication tokens are missing.

if (!response?.authToken || !response?.encryptedAuthToken) {
Log.alert('[Delegate] No auth token returned while disconnecting as a delegate');
return;
}
if (!response?.requesterID || !response?.requesterEmail) {
Log.alert('[Delegate] No requester data returned while disconnecting as a delegate');
return;
}

with

if (!response?.authToken || !response?.encryptedAuthToken || !response?.requesterID || !response?.requesterEmail) {
    restoreDelegateSession(stashedSession);
    return;
}

Please refer to this detailed video for a clearer understanding
Video

@ijmalik
Copy link
Contributor

ijmalik commented Feb 2, 2025

Proposal

Updated

Copy link
Contributor

github-actions bot commented Feb 2, 2025

⚠️ @ijmalik Thanks for your proposal. Please update it to follow the proposal template, as proposals are only reviewed if they follow that format (note the mandatory sections).

@dangrous dangrous self-assigned this Feb 3, 2025
@dangrous
Copy link
Contributor

dangrous commented Feb 3, 2025

Happy to be the internal reviewer on this since I dealt with copilot stuff

@c3024
Copy link
Contributor

c3024 commented Feb 4, 2025

@ijmalik

Thanks for the proposal. That idea looks good.

However, Pusher sends updates correctly for removing the delegator when we remove the copilot from another device.

Image

But there are differences in update IDs received in the response, so this Pusher response should be queued for a later update, and the missing updates (updates on the server but not on the client) should be fetched with GetMissingOnyxMessages. After these missing updates are applied, the queued Pusher update should be applied, and the delegator account should be removed automatically.

There seems to be an issue with updating this Pusher update due to rejected responses from GetMissingOnyxMessages.

Ideally, we should find out why the deferred Pusher update is not applied and fix it.

@ijmalik
Copy link
Contributor

ijmalik commented Feb 4, 2025

Hi @c3024,

You're absolutely right.

Currently, we're early returning if the response lacks necessary data, which results in the error message:

"Oops, something went wrong. Please try again."

if (!response?.authToken || !response?.encryptedAuthToken) {
    Log.alert('[Delegate] No auth token returned while disconnecting as a delegate');
    return;
}

if (!response?.requesterID || !response?.requesterEmail) {
    Log.alert('[Delegate] No requester data returned while disconnecting as a delegate');
    return;
}

Instead of just returning an error, I propose implementing my solution to ensure that:

✅ Even after fixing the deferred Pusher update issue, we handle cases where the response lacks proper data.
✅ If the Copilot cannot be removed due to missing response data, we automatically switch back to the main account as a fallback.

This proactive approach will help prevent similar issues in the future while ensuring a seamless user experience.

Let me know what you think! 🚀

@c3024
Copy link
Contributor

c3024 commented Feb 6, 2025

We should fix the root cause first and then consider adding any proactive workarounds for this specific issue.

When Pusher sends a response removing the delegator, the account should be removed immediately from the switcher. This works correctly if the user is on their main account when the Pusher response for removing the delegator is received. However, if the user is on the delegator account, this fails. This failure still allows the user to go to FAB and begin requests (though the backend will reject them). So, let’s first fix why the Pusher response is not correctly merged into Onyx.

@ijmalik
Copy link
Contributor

ijmalik commented Feb 6, 2025

Proposal

Updated

Copy link
Contributor

github-actions bot commented Feb 6, 2025

⚠️ @ijmalik Thanks for your proposal. Please update it to follow the proposal template, as proposals are only reviewed if they follow that format (note the mandatory sections).

Copy link

melvin-bot bot commented Feb 7, 2025

📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

Copy link

melvin-bot bot commented Feb 11, 2025

@dangrous, @slafortune, @c3024 Eep! 4 days overdue now. Issues have feelings too...

@melvin-bot melvin-bot bot added the Overdue label Feb 11, 2025
@c3024
Copy link
Contributor

c3024 commented Feb 11, 2025

I am checking this. Will update soon!

@melvin-bot melvin-bot bot removed the Overdue label Feb 11, 2025
@c3024
Copy link
Contributor

c3024 commented Feb 12, 2025

@ijmalik

Thanks for the update!

@dangrous

Here's what I understood:
1. User B is a copilot for User A.
2. User B switches to User A using the account switcher.
3. User A removes User B as a copilot from another device.
4. The auth token for User B (acting as a copilot for User A’s account) is invalidated on the backend. As a result, any requests, including switching back to User B’s account, keep failing due to the invalid token.
5. Pusher updates the delegates and delegators fields in the account Onyx value for User B (switched and acting as copilot as User A).
6. At this point, User A’s account should automatically be removed from User B’s account switcher due to the Pusher update.
7. However, this does not happen because delegate still remains as User A in the account value, and there is no existing logic to update copilots based on Pusher updates.
8. We cannot easily push an update with the delegate value in the account object from the backend, as the frontend modifies it, and its value depends on whether the main or copilot account is selected.
9. So, we should:
• Restore the stashed credentials of the main account when the response for disconnect does not return a token, as suggested by @ijmalik in his alternative solution.
• Remove the removed copilot account automatically from the account switcher when a Pusher update removes a copilot, using a useEffect like this here

useEffect(() => {
    if (!account?.delegatedAccess?.delegate) {
        return;
    }
    if (account?.delegatedAccess?.delegates?.some(d => d.email === account?.delegatedAccess?.delegate)) {
        return;
    }
    disconnect();
}, [account?.delegatedAccess?.delegates, account?.delegatedAccess?.delegate]);

@ijmalik

Your primary solution did not work for me. It also does not appear to be better than your alternative solution. So, let us proceed with the alternative solution. Please correct me if I am missing anything.

Alternative solution of @ijmalik here looks good to me.

🎀 👀 🎀 C+ reviewed!

Copy link

melvin-bot bot commented Feb 12, 2025

Current assignee @dangrous is eligible for the choreEngineerContributorManagement assigner, not assigning anyone new.

@ijmalik
Copy link
Contributor

ijmalik commented Feb 12, 2025

@c3024

You're absolutely right. Let's move forward with the alternative solution.

Copy link

melvin-bot bot commented Feb 14, 2025

@dangrous @slafortune @c3024 this issue was created 2 weeks ago. Are we close to approving a proposal? If not, what's blocking us from getting this issue assigned? Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!

@melvin-bot melvin-bot bot added the Overdue label Feb 14, 2025
@dangrous
Copy link
Contributor

Thanks for your patience, I missed this one. Alternate solution looks good, though I think we should keep the log lines in.

@melvin-bot melvin-bot bot removed the Help Wanted Apply this label when an issue is open to proposals by contributors label Feb 14, 2025
Copy link

melvin-bot bot commented Feb 14, 2025

📣 @ijmalik 🎉 An offer has been automatically sent to your Upwork account for the Contributor role 🎉 Thanks for contributing to the Expensify app!

Offer link
Upwork job
Please accept the offer and leave a comment on the Github issue letting us know when we can expect a PR to be ready for review 🧑‍💻
Keep in mind: Code of Conduct | Contributing 📖

@melvin-bot melvin-bot bot removed the Overdue label Feb 14, 2025
@ijmalik
Copy link
Contributor

ijmalik commented Feb 15, 2025

@dangrous @c3024

Thanks! I’ve accepted the Upwork offer and will have the PR ready for review in the next couple of days. I’ll keep you updated on progress.

@melvin-bot melvin-bot bot added Reviewing Has a PR in review Weekly KSv2 and removed Daily KSv2 labels Feb 15, 2025
@melvin-bot melvin-bot bot added Weekly KSv2 Awaiting Payment Auto-added when associated PR is deployed to production and removed Weekly KSv2 labels Feb 26, 2025
@melvin-bot melvin-bot bot changed the title [$250] Copilot - User gets stucked in Copilot account when removed by the other user [Due for payment 2025-03-05] [$250] Copilot - User gets stucked in Copilot account when removed by the other user Feb 26, 2025
Copy link

melvin-bot bot commented Feb 26, 2025

Reviewing label has been removed, please complete the "BugZero Checklist".

@melvin-bot melvin-bot bot removed the Reviewing Has a PR in review label Feb 26, 2025
Copy link

melvin-bot bot commented Feb 26, 2025

The solution for this issue has been 🚀 deployed to production 🚀 in version 9.1.6-1 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:

If no regressions arise, payment will be issued on 2025-03-05. 🎊

For reference, here are some details about the assignees on this issue:

Copy link

melvin-bot bot commented Feb 26, 2025

@c3024 @slafortune @c3024 The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the BugZero Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]

@dangrous
Copy link
Contributor

This has been deployed but according to QA might not be fixed yet? I'm going to follow up with them, but just noting this here so I remember.

@ijmalik
Copy link
Contributor

ijmalik commented Feb 28, 2025 via email

@dangrous
Copy link
Contributor

dangrous commented Mar 3, 2025

Thanks for that detail! I agree that that is probably what QA encountered? I'll ping them again to re-test, and if it DOES keep showing up we might need to have them try explicitly with waiting between the removal of the copilot and the attempt to return to the original account. It probably won't happen that frequently in real life, but in testing it could.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting Payment Auto-added when associated PR is deployed to production Bug Something is broken. Auto assigns a BugZero manager. External Added to denote the issue can be worked on by a contributor Weekly KSv2
Projects
Status: LOW
Development

No branches or pull requests

5 participants