mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 00:33:44 +00:00
[PM-23243] In sync response and identity success response add MasterPasswordUnlockDataResponse in decryption options response model. (#15916)
* added master password unlock and decryption option fields into identity token connect response * incorrect master password unlock response parsing * use sdk * use sdk * better type checking on response parsing * not using sdk * revert of bad merge conflicts * revert of bad merge conflicts * master password unlock setter in state * unit test coverage for responses processing * master password unlock in identity user decryption options * unit test coverage * unit test coverage * unit test coverage * unit test coverage * lint error * set master password unlock data in state on identity response and sync response * revert change in auth's user decryption options * remove unnecessary cast * better docs * change to relative imports * MasterPasswordUnlockData serialization issue * explicit undefined type for `syncUserDecryption` * incorrect identity token response tests
This commit is contained in:
@@ -13,8 +13,9 @@ import {
|
||||
} from "@bitwarden/auth/common";
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
import { KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management";
|
||||
|
||||
import { makeEncString } from "../../../spec";
|
||||
import { Matrix } from "../../../spec/matrix";
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { InternalOrganizationServiceAbstraction } from "../../admin-console/abstractions/organization/organization.service.abstraction";
|
||||
@@ -29,6 +30,11 @@ import { DomainSettingsService } from "../../autofill/services/domain-settings.s
|
||||
import { BillingAccountProfileStateService } from "../../billing/abstractions";
|
||||
import { KeyConnectorService } from "../../key-management/key-connector/abstractions/key-connector.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "../../key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import {
|
||||
MasterKeyWrappedUserKey,
|
||||
MasterPasswordSalt,
|
||||
MasterPasswordUnlockData,
|
||||
} from "../../key-management/master-password/types/master-password.types";
|
||||
import { SendApiService } from "../../tools/send/services/send-api.service.abstraction";
|
||||
import { InternalSendService } from "../../tools/send/services/send.service.abstraction";
|
||||
import { UserId } from "../../types/guid";
|
||||
@@ -84,6 +90,7 @@ describe("DefaultSyncService", () => {
|
||||
sendService = mock();
|
||||
logService = mock();
|
||||
keyConnectorService = mock();
|
||||
keyConnectorService.convertAccountRequired$ = of(false);
|
||||
providerService = mock();
|
||||
folderApiService = mock();
|
||||
organizationService = mock();
|
||||
@@ -236,5 +243,55 @@ describe("DefaultSyncService", () => {
|
||||
expect(sut["inFlightApiCalls"].sync).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("syncUserDecryption", () => {
|
||||
const salt = "test@example.com";
|
||||
const kdf = new PBKDF2KdfConfig(600_000);
|
||||
const encryptedUserKey = makeEncString("testUserKey");
|
||||
|
||||
it("should set master password unlock when present in user decryption", async () => {
|
||||
const syncResponse = new SyncResponse({
|
||||
Profile: {
|
||||
Id: user1,
|
||||
},
|
||||
UserDecryption: {
|
||||
MasterPasswordUnlock: {
|
||||
Salt: salt,
|
||||
Kdf: {
|
||||
KdfType: kdf.kdfType,
|
||||
Iterations: kdf.iterations,
|
||||
},
|
||||
MasterKeyEncryptedUserKey: encryptedUserKey.encryptedString,
|
||||
},
|
||||
},
|
||||
});
|
||||
apiService.getSync.mockResolvedValue(syncResponse);
|
||||
|
||||
await sut.fullSync(true, true);
|
||||
|
||||
expect(masterPasswordAbstraction.setMasterPasswordUnlockData).toHaveBeenCalledWith(
|
||||
new MasterPasswordUnlockData(
|
||||
salt as MasterPasswordSalt,
|
||||
kdf,
|
||||
encryptedUserKey as MasterKeyWrappedUserKey,
|
||||
),
|
||||
user1,
|
||||
);
|
||||
});
|
||||
|
||||
it("should not set master password unlock when not present in user decryption", async () => {
|
||||
const syncResponse = new SyncResponse({
|
||||
Profile: {
|
||||
Id: user1,
|
||||
},
|
||||
UserDecryption: {},
|
||||
});
|
||||
apiService.getSync.mockResolvedValue(syncResponse);
|
||||
|
||||
await sut.fullSync(true, true);
|
||||
|
||||
expect(masterPasswordAbstraction.setMasterPasswordUnlockData).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,6 +38,7 @@ import { DomainSettingsService } from "../../autofill/services/domain-settings.s
|
||||
import { BillingAccountProfileStateService } from "../../billing/abstractions";
|
||||
import { KeyConnectorService } from "../../key-management/key-connector/abstractions/key-connector.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "../../key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { UserDecryptionResponse } from "../../key-management/models/response/user-decryption.response";
|
||||
import { DomainsResponse } from "../../models/response/domains.response";
|
||||
import { ProfileResponse } from "../../models/response/profile.response";
|
||||
import { SendData } from "../../tools/send/models/data/send.data";
|
||||
@@ -168,6 +169,7 @@ export class DefaultSyncService extends CoreSyncService {
|
||||
|
||||
const response = await this.inFlightApiCalls.sync;
|
||||
|
||||
await this.syncUserDecryption(response.profile.id, response.userDecryption);
|
||||
await this.syncProfile(response.profile);
|
||||
await this.syncFolders(response.folders, response.profile.id);
|
||||
await this.syncCollections(response.collections, response.profile.id);
|
||||
@@ -390,4 +392,21 @@ export class DefaultSyncService extends CoreSyncService {
|
||||
}
|
||||
return await this.policyService.replace(policies, userId);
|
||||
}
|
||||
|
||||
private async syncUserDecryption(
|
||||
userId: UserId,
|
||||
userDecryption: UserDecryptionResponse | undefined,
|
||||
) {
|
||||
if (userDecryption == null) {
|
||||
return;
|
||||
}
|
||||
if (userDecryption.masterPasswordUnlock != null) {
|
||||
const masterPasswordUnlockData =
|
||||
userDecryption.masterPasswordUnlock.toMasterPasswordUnlockData();
|
||||
await this.masterPasswordService.setMasterPasswordUnlockData(
|
||||
masterPasswordUnlockData,
|
||||
userId,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
libs/common/src/platform/sync/sync.response.spec.ts
Normal file
18
libs/common/src/platform/sync/sync.response.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { SyncResponse } from "./sync.response";
|
||||
|
||||
describe("SyncResponse", () => {
|
||||
it("should create response when user decryption is not provided", () => {
|
||||
const response = new SyncResponse({
|
||||
UserDecryption: undefined,
|
||||
});
|
||||
expect(response.userDecryption).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should create response when user decryption is provided", () => {
|
||||
const response = new SyncResponse({
|
||||
UserDecryption: {},
|
||||
});
|
||||
expect(response.userDecryption).toBeDefined();
|
||||
expect(response.userDecryption!.masterPasswordUnlock).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@
|
||||
import { CollectionDetailsResponse } from "@bitwarden/admin-console/common";
|
||||
|
||||
import { PolicyResponse } from "../../admin-console/models/response/policy.response";
|
||||
import { UserDecryptionResponse } from "../../key-management/models/response/user-decryption.response";
|
||||
import { BaseResponse } from "../../models/response/base.response";
|
||||
import { DomainsResponse } from "../../models/response/domains.response";
|
||||
import { ProfileResponse } from "../../models/response/profile.response";
|
||||
@@ -18,6 +19,7 @@ export class SyncResponse extends BaseResponse {
|
||||
domains?: DomainsResponse;
|
||||
policies?: PolicyResponse[] = [];
|
||||
sends: SendResponse[] = [];
|
||||
userDecryption?: UserDecryptionResponse;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
@@ -56,5 +58,10 @@ export class SyncResponse extends BaseResponse {
|
||||
if (sends != null) {
|
||||
this.sends = sends.map((s: any) => new SendResponse(s));
|
||||
}
|
||||
|
||||
const userDecryption = this.getResponseProperty("UserDecryption");
|
||||
if (userDecryption != null && typeof userDecryption === "object") {
|
||||
this.userDecryption = new UserDecryptionResponse(userDecryption);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user