mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 09:13:33 +00:00
Ps/pm 5533/migrate decrypted user key (#7970)
* Move user key memory state to state providers
Note: state service observable change is because these updates are no longer internal to the class, but reporter directly to account service through crypto service on update of a user key
* remove decrypted user key state
Note, we're going to move the encrypted cryptoSymmetric key (and associated master key encrypted user keys) as part of the master key service creation. Crypto service will no longer be responsible for the encrypted forms of user key.
* Deprecate notices belong on abstraction
* Allow for single-direction status updates
This is necessary since we don't want to have to guarantee that the update to logged out occurs after the update to locked.
* Remove deprecated subject
It turns out the set for cryptoMasterKey was also unused 🎉
This commit is contained in:
@@ -47,6 +47,17 @@ export abstract class AccountService {
|
||||
* @param status
|
||||
*/
|
||||
abstract setAccountStatus(userId: UserId, status: AuthenticationStatus): Promise<void>;
|
||||
/**
|
||||
* Updates the `accounts$` observable with the new account status if the current status is higher than the `maxStatus`.
|
||||
*
|
||||
* This method only downgrades status to the maximum value sent in, it will not increase authentication status.
|
||||
*
|
||||
* @example An account is transitioning from unlocked to logged out. If callbacks that set the status to locked occur
|
||||
* after it is updated to logged out, the account will be in the incorrect state.
|
||||
* @param userId The user id of the account to be updated.
|
||||
* @param maxStatus The new status of the account.
|
||||
*/
|
||||
abstract setMaxAccountStatus(userId: UserId, maxStatus: AuthenticationStatus): Promise<void>;
|
||||
/**
|
||||
* Updates the `activeAccount$` observable with the new active account.
|
||||
* @param userId
|
||||
|
||||
@@ -207,4 +207,26 @@ describe("accountService", () => {
|
||||
expect(sut.switchAccount("unknown" as UserId)).rejects.toThrowError("Account does not exist");
|
||||
});
|
||||
});
|
||||
|
||||
describe("setMaxAccountStatus", () => {
|
||||
it("should update the account", async () => {
|
||||
accountsState.stateSubject.next({ [userId]: userInfo(AuthenticationStatus.Unlocked) });
|
||||
await sut.setMaxAccountStatus(userId, AuthenticationStatus.Locked);
|
||||
const currentState = await firstValueFrom(accountsState.state$);
|
||||
|
||||
expect(currentState).toEqual({
|
||||
[userId]: userInfo(AuthenticationStatus.Locked),
|
||||
});
|
||||
});
|
||||
|
||||
it("should not update if the new max status is higher than the current", async () => {
|
||||
accountsState.stateSubject.next({ [userId]: userInfo(AuthenticationStatus.LoggedOut) });
|
||||
await sut.setMaxAccountStatus(userId, AuthenticationStatus.Locked);
|
||||
const currentState = await firstValueFrom(accountsState.state$);
|
||||
|
||||
expect(currentState).toEqual({
|
||||
[userId]: userInfo(AuthenticationStatus.LoggedOut),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -84,6 +84,24 @@ export class AccountServiceImplementation implements InternalAccountService {
|
||||
}
|
||||
}
|
||||
|
||||
async setMaxAccountStatus(userId: UserId, maxStatus: AuthenticationStatus): Promise<void> {
|
||||
await this.accountsState.update(
|
||||
(accounts) => {
|
||||
accounts[userId].status = maxStatus;
|
||||
return accounts;
|
||||
},
|
||||
{
|
||||
shouldUpdate: (accounts) => {
|
||||
if (accounts?.[userId] == null) {
|
||||
throw new Error("Account does not exist");
|
||||
}
|
||||
|
||||
return accounts[userId].status > maxStatus;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async switchAccount(userId: UserId): Promise<void> {
|
||||
await this.activeAccountIdState.update(
|
||||
(_, accounts) => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AppIdService } from "../../platform/abstractions/app-id.service";
|
||||
import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service";
|
||||
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
||||
@@ -108,7 +110,7 @@ export class DeviceTrustCryptoService implements DeviceTrustCryptoServiceAbstrac
|
||||
}
|
||||
|
||||
// At this point of rotating their keys, they should still have their old user key in state
|
||||
const oldUserKey = await this.stateService.getUserKey();
|
||||
const oldUserKey = await firstValueFrom(this.cryptoService.activeUserKey$);
|
||||
|
||||
const deviceIdentifier = await this.appIdService.getAppId();
|
||||
const secretVerificationRequest = new SecretVerificationRequest();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { matches, mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { DeviceType } from "../../enums";
|
||||
import { AppIdService } from "../../platform/abstractions/app-id.service";
|
||||
@@ -19,6 +20,7 @@ import { UpdateDevicesTrustRequest } from "../models/request/update-devices-trus
|
||||
import { ProtectedDeviceResponse } from "../models/response/protected-device.response";
|
||||
|
||||
import { DeviceTrustCryptoService } from "./device-trust-crypto.service.implementation";
|
||||
|
||||
describe("deviceTrustCryptoService", () => {
|
||||
let deviceTrustCryptoService: DeviceTrustCryptoService;
|
||||
|
||||
@@ -495,6 +497,7 @@ describe("deviceTrustCryptoService", () => {
|
||||
const fakeNewUserKeyData = new Uint8Array(64);
|
||||
fakeNewUserKeyData.fill(FakeNewUserKeyMarker, 0, 1);
|
||||
fakeNewUserKey = new SymmetricCryptoKey(fakeNewUserKeyData) as UserKey;
|
||||
cryptoService.activeUserKey$ = of(fakeNewUserKey);
|
||||
});
|
||||
|
||||
it("does an early exit when the current device is not a trusted device", async () => {
|
||||
@@ -521,9 +524,7 @@ describe("deviceTrustCryptoService", () => {
|
||||
fakeOldUserKeyData.fill(FakeOldUserKeyMarker, 0, 1);
|
||||
|
||||
// Mock the retrieval of a user key that differs from the new one passed into the method
|
||||
stateService.getUserKey.mockResolvedValue(
|
||||
new SymmetricCryptoKey(fakeOldUserKeyData) as UserKey,
|
||||
);
|
||||
cryptoService.activeUserKey$ = of(new SymmetricCryptoKey(fakeOldUserKeyData) as UserKey);
|
||||
|
||||
appIdService.getAppId.mockResolvedValue("test_device_identifier");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user