mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-06 23:51:28 +01:00
PM-16220: Account does not exist during login race condition (#12488)
Wait for an account to become available from separate observable, instead of blindly accepting that the value is there using `firstValueFrom`, while it's sometimes not there immediately.
This commit is contained in:
parent
0e3e3c16c4
commit
9d987a2513
@ -15,6 +15,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf dist",
|
"clean": "rimraf dist",
|
||||||
"build": "npm run clean && tsc",
|
"build": "npm run clean && tsc",
|
||||||
"build:watch": "npm run clean && tsc -watch"
|
"build:watch": "npm run clean && tsc -watch",
|
||||||
|
"test": "jest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,7 @@ describe("accountService", () => {
|
|||||||
let sut: AccountServiceImplementation;
|
let sut: AccountServiceImplementation;
|
||||||
let accountsState: FakeGlobalState<Record<UserId, AccountInfo>>;
|
let accountsState: FakeGlobalState<Record<UserId, AccountInfo>>;
|
||||||
let activeAccountIdState: FakeGlobalState<UserId>;
|
let activeAccountIdState: FakeGlobalState<UserId>;
|
||||||
|
let accountActivityState: FakeGlobalState<Record<UserId, Date>>;
|
||||||
const userId = Utils.newGuid() as UserId;
|
const userId = Utils.newGuid() as UserId;
|
||||||
const userInfo = { email: "email", name: "name", emailVerified: true };
|
const userInfo = { email: "email", name: "name", emailVerified: true };
|
||||||
|
|
||||||
@ -81,6 +82,7 @@ describe("accountService", () => {
|
|||||||
|
|
||||||
accountsState = globalStateProvider.getFake(ACCOUNT_ACCOUNTS);
|
accountsState = globalStateProvider.getFake(ACCOUNT_ACCOUNTS);
|
||||||
activeAccountIdState = globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID);
|
activeAccountIdState = globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID);
|
||||||
|
accountActivityState = globalStateProvider.getFake(ACCOUNT_ACTIVITY);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -256,6 +258,7 @@ describe("accountService", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
accountsState.stateSubject.next({ [userId]: userInfo });
|
accountsState.stateSubject.next({ [userId]: userInfo });
|
||||||
activeAccountIdState.stateSubject.next(userId);
|
activeAccountIdState.stateSubject.next(userId);
|
||||||
|
accountActivityState.stateSubject.next({ [userId]: new Date(1) });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should emit null if no account is provided", async () => {
|
it("should emit null if no account is provided", async () => {
|
||||||
@ -269,6 +272,34 @@ describe("accountService", () => {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
expect(sut.switchAccount("unknown" as UserId)).rejects.toThrowError("Account does not exist");
|
expect(sut.switchAccount("unknown" as UserId)).rejects.toThrowError("Account does not exist");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should change active account when switched to the new account", async () => {
|
||||||
|
const newUserId = Utils.newGuid() as UserId;
|
||||||
|
accountsState.stateSubject.next({ [newUserId]: userInfo });
|
||||||
|
|
||||||
|
await sut.switchAccount(newUserId);
|
||||||
|
|
||||||
|
await expect(firstValueFrom(sut.activeAccount$)).resolves.toEqual({
|
||||||
|
id: newUserId,
|
||||||
|
...userInfo,
|
||||||
|
});
|
||||||
|
await expect(firstValueFrom(sut.accountActivity$)).resolves.toEqual({
|
||||||
|
[userId]: new Date(1),
|
||||||
|
[newUserId]: expect.toAlmostEqual(new Date(), 1000),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not change active account when already switched to the same account", async () => {
|
||||||
|
await sut.switchAccount(userId);
|
||||||
|
|
||||||
|
await expect(firstValueFrom(sut.activeAccount$)).resolves.toEqual({
|
||||||
|
id: userId,
|
||||||
|
...userInfo,
|
||||||
|
});
|
||||||
|
await expect(firstValueFrom(sut.accountActivity$)).resolves.toEqual({
|
||||||
|
[userId]: new Date(1),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("account activity", () => {
|
describe("account activity", () => {
|
||||||
|
@ -7,6 +7,9 @@ import {
|
|||||||
shareReplay,
|
shareReplay,
|
||||||
combineLatest,
|
combineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
|
filter,
|
||||||
|
timeout,
|
||||||
|
of,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -149,21 +152,28 @@ export class AccountServiceImplementation implements InternalAccountService {
|
|||||||
async switchAccount(userId: UserId | null): Promise<void> {
|
async switchAccount(userId: UserId | null): Promise<void> {
|
||||||
let updateActivity = false;
|
let updateActivity = false;
|
||||||
await this.activeAccountIdState.update(
|
await this.activeAccountIdState.update(
|
||||||
(_, accounts) => {
|
(_, __) => {
|
||||||
if (userId == null) {
|
|
||||||
// indicates no account is active
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accounts?.[userId] == null) {
|
|
||||||
throw new Error("Account does not exist");
|
|
||||||
}
|
|
||||||
updateActivity = true;
|
updateActivity = true;
|
||||||
return userId;
|
return userId;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
combineLatestWith: this.accounts$,
|
combineLatestWith: this.accountsState.state$.pipe(
|
||||||
shouldUpdate: (id) => {
|
filter((accounts) => {
|
||||||
|
if (userId == null) {
|
||||||
|
// Don't worry about accounts when we are about to set active user to null
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts?.[userId] != null;
|
||||||
|
}),
|
||||||
|
// If we don't get the desired account with enough time, just return empty as that will result in the same error
|
||||||
|
timeout({ first: 1000, with: () => of({} as Record<UserId, AccountInfo>) }),
|
||||||
|
),
|
||||||
|
shouldUpdate: (id, accounts) => {
|
||||||
|
if (userId != null && accounts?.[userId] == null) {
|
||||||
|
throw new Error("Account does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
// update only if userId changes
|
// update only if userId changes
|
||||||
return id !== userId;
|
return id !== userId;
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user