mirror of
https://github.com/bitwarden/browser
synced 2025-12-21 18:53:29 +00:00
[PM-3726] Force migration of legacy user's encryption key (#6195)
* [PM-3726] migrate legacy user's encryption key * [PM-3726] add 2fa support and pr feedback * [PM-3726] revert launch.json & webpack.config changes * [PM-3726] remove update key component - also remove card in vault since legacy users can't login * [PM-3726] Fix i18n & PR feedback * [PM-3726] make standalone component * [PM-3726] linter * [PM-3726] missing await * [PM-3726] logout legacy users with vault timeout to never * [PM-3726] add await * [PM-3726] skip auto key migration for legacy users * [PM-3726] pr feedback * [PM-3726] move check for web into migrate method --------- Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
|
||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
@@ -210,4 +211,12 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest
|
||||
}
|
||||
await super.submit(false);
|
||||
}
|
||||
|
||||
protected override handleMigrateEncryptionKey(result: AuthResult): boolean {
|
||||
if (!result.requiresEncryptionKeyMigration) {
|
||||
return false;
|
||||
}
|
||||
this.router.navigate(["migrate-legacy-encryption"]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<div class="tw-mt-12 tw-flex tw-justify-center">
|
||||
<div class="tw-max-w-xl">
|
||||
<h1 bitTypography="h1" class="tw-mb-4 tw-text-center">{{ "updateEncryptionKey" | i18n }}</h1>
|
||||
<div
|
||||
class="tw-block tw-rounded tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-8"
|
||||
>
|
||||
<p>
|
||||
{{ "updateEncryptionSchemeDesc" | i18n }}
|
||||
<a
|
||||
href="https://bitwarden.com/help/account-encryption-key/#rotate-your-encryption-key"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>{{ "learnMore" | i18n }}</a
|
||||
>
|
||||
</p>
|
||||
<bit-callout type="warning">{{ "updateEncryptionKeyWarning" | i18n }}</bit-callout>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
id="masterPassword"
|
||||
bitInput
|
||||
type="password"
|
||||
formControlName="masterPassword"
|
||||
appAutofocus
|
||||
/>
|
||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
</bit-form-field>
|
||||
<button type="submit" bitButton bitFormButton buttonType="primary" block>
|
||||
{{ "updateEncryptionKey" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
import { SharedModule } from "../../shared";
|
||||
|
||||
import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
|
||||
|
||||
// The master key was originally used to encrypt user data, before the user key was introduced.
|
||||
// This component is used to migrate from the old encryption scheme to the new one.
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [SharedModule],
|
||||
providers: [MigrateFromLegacyEncryptionService],
|
||||
templateUrl: "migrate-legacy-encryption.component.html",
|
||||
})
|
||||
export class MigrateFromLegacyEncryptionComponent {
|
||||
protected formGroup = new FormGroup({
|
||||
masterPassword: new FormControl("", [Validators.required]),
|
||||
});
|
||||
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private migrationService: MigrateFromLegacyEncryptionService,
|
||||
private cryptoService: CryptoService,
|
||||
private messagingService: MessagingService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
submit = async () => {
|
||||
this.formGroup.markAsTouched();
|
||||
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasUserKey = await this.cryptoService.hasUserKey();
|
||||
if (hasUserKey) {
|
||||
this.messagingService.send("logout");
|
||||
throw new Error("User key already exists, cannot migrate legacy encryption.");
|
||||
}
|
||||
|
||||
const masterPassword = this.formGroup.value.masterPassword;
|
||||
|
||||
try {
|
||||
// Create new user key
|
||||
const [newUserKey, masterKeyEncUserKey] = await this.migrationService.createNewUserKey(
|
||||
masterPassword
|
||||
);
|
||||
|
||||
// Update admin recover keys
|
||||
await this.migrationService.updateAllAdminRecoveryKeys(masterPassword, newUserKey);
|
||||
|
||||
// Update emergency access
|
||||
await this.migrationService.updateEmergencyAccesses(newUserKey);
|
||||
|
||||
// Update keys, folders, ciphers, and sends
|
||||
await this.migrationService.updateKeysAndEncryptedData(
|
||||
masterPassword,
|
||||
newUserKey,
|
||||
masterKeyEncUserKey
|
||||
);
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
this.i18nService.t("keyUpdated"),
|
||||
this.i18nService.t("logBackInOthersToo"),
|
||||
{ timeout: 15000 }
|
||||
);
|
||||
this.messagingService.send("logout");
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/abstractions/organization-user/requests";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response";
|
||||
import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service";
|
||||
import { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
|
||||
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
|
||||
import { EmergencyAccessGranteeDetailsResponse } from "@bitwarden/common/auth/models/response/emergency-access.response";
|
||||
import { EncryptionType, KdfType } from "@bitwarden/common/enums";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { UserKeyResponse } from "@bitwarden/common/models/response/user-key.response";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import {
|
||||
MasterKey,
|
||||
SymmetricCryptoKey,
|
||||
UserKey,
|
||||
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { Send } from "@bitwarden/common/tools/send/models/domain/send";
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { Folder } from "@bitwarden/common/vault/models/domain/folder";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
|
||||
import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
|
||||
|
||||
describe("migrateFromLegacyEncryptionService", () => {
|
||||
let migrateFromLegacyEncryptionService: MigrateFromLegacyEncryptionService;
|
||||
|
||||
const organizationService = mock<OrganizationService>();
|
||||
const organizationApiService = mock<OrganizationApiService>();
|
||||
const organizationUserService = mock<OrganizationUserService>();
|
||||
const apiService = mock<ApiService>();
|
||||
const encryptService = mock<EncryptService>();
|
||||
const cryptoService = mock<CryptoService>();
|
||||
const syncService = mock<SyncService>();
|
||||
const cipherService = mock<CipherService>();
|
||||
const folderService = mock<FolderService>();
|
||||
const sendService = mock<SendService>();
|
||||
const stateService = mock<StateService>();
|
||||
let folderViews: BehaviorSubject<FolderView[]>;
|
||||
let sends: BehaviorSubject<Send[]>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
migrateFromLegacyEncryptionService = new MigrateFromLegacyEncryptionService(
|
||||
organizationService,
|
||||
organizationApiService,
|
||||
organizationUserService,
|
||||
apiService,
|
||||
cryptoService,
|
||||
encryptService,
|
||||
syncService,
|
||||
cipherService,
|
||||
folderService,
|
||||
sendService,
|
||||
stateService
|
||||
);
|
||||
});
|
||||
|
||||
it("instantiates", () => {
|
||||
expect(migrateFromLegacyEncryptionService).not.toBeFalsy();
|
||||
});
|
||||
|
||||
describe("createNewUserKey", () => {
|
||||
it("validates master password and legacy user", async () => {
|
||||
const mockMasterPassword = "mockMasterPassword";
|
||||
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||
const mockMasterKey = new SymmetricCryptoKey(mockRandomBytes) as MasterKey;
|
||||
stateService.getEmail.mockResolvedValue("mockEmail");
|
||||
stateService.getKdfType.mockResolvedValue(KdfType.PBKDF2_SHA256);
|
||||
stateService.getKdfConfig.mockResolvedValue({ iterations: 100000 });
|
||||
cryptoService.makeMasterKey.mockResolvedValue(mockMasterKey);
|
||||
cryptoService.isLegacyUser.mockResolvedValue(false);
|
||||
|
||||
await expect(
|
||||
migrateFromLegacyEncryptionService.createNewUserKey(mockMasterPassword)
|
||||
).rejects.toThrowError("Invalid master password or user may not be legacy");
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateKeysAndEncryptedData", () => {
|
||||
let mockMasterPassword: string;
|
||||
let mockUserKey: UserKey;
|
||||
let mockEncUserKey: EncString;
|
||||
|
||||
beforeEach(() => {
|
||||
mockMasterPassword = "mockMasterPassword";
|
||||
|
||||
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||
mockEncUserKey = new EncString("mockEncUserKey");
|
||||
|
||||
const mockFolders = [createMockFolder("1", "Folder 1"), createMockFolder("2", "Folder 2")];
|
||||
const mockCiphers = [createMockCipher("1", "Cipher 1"), createMockCipher("2", "Cipher 2")];
|
||||
const mockSends = [createMockSend("1", "Send 1"), createMockSend("2", "Send 2")];
|
||||
|
||||
cryptoService.getPrivateKey.mockResolvedValue(new Uint8Array(64) as CsprngArray);
|
||||
|
||||
folderViews = new BehaviorSubject<FolderView[]>(mockFolders);
|
||||
folderService.folderViews$ = folderViews;
|
||||
|
||||
cipherService.getAllDecrypted.mockResolvedValue(mockCiphers);
|
||||
|
||||
sends = new BehaviorSubject<Send[]>(mockSends);
|
||||
sendService.sends$ = sends;
|
||||
|
||||
encryptService.encrypt.mockImplementation((plainValue, userKey) => {
|
||||
return Promise.resolve(
|
||||
new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "Encrypted: " + plainValue)
|
||||
);
|
||||
});
|
||||
|
||||
folderService.encrypt.mockImplementation((folder, userKey) => {
|
||||
const encryptedFolder = new Folder();
|
||||
encryptedFolder.id = folder.id;
|
||||
encryptedFolder.name = new EncString(
|
||||
EncryptionType.AesCbc256_HmacSha256_B64,
|
||||
"Encrypted: " + folder.name
|
||||
);
|
||||
return Promise.resolve(encryptedFolder);
|
||||
});
|
||||
|
||||
cipherService.encrypt.mockImplementation((cipher, userKey) => {
|
||||
const encryptedCipher = new Cipher();
|
||||
encryptedCipher.id = cipher.id;
|
||||
encryptedCipher.name = new EncString(
|
||||
EncryptionType.AesCbc256_HmacSha256_B64,
|
||||
"Encrypted: " + cipher.name
|
||||
);
|
||||
return Promise.resolve(encryptedCipher);
|
||||
});
|
||||
});
|
||||
|
||||
it("derives the master key in case it hasn't been set", async () => {
|
||||
await migrateFromLegacyEncryptionService.updateKeysAndEncryptedData(
|
||||
mockMasterPassword,
|
||||
mockUserKey,
|
||||
mockEncUserKey
|
||||
);
|
||||
|
||||
expect(cryptoService.getOrDeriveMasterKey).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("syncs latest data", async () => {
|
||||
await migrateFromLegacyEncryptionService.updateKeysAndEncryptedData(
|
||||
mockMasterPassword,
|
||||
mockUserKey,
|
||||
mockEncUserKey
|
||||
);
|
||||
expect(syncService.fullSync).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it("does not post new account data if sync fails", async () => {
|
||||
syncService.fullSync.mockRejectedValueOnce(new Error("sync failed"));
|
||||
|
||||
await expect(
|
||||
migrateFromLegacyEncryptionService.updateKeysAndEncryptedData(
|
||||
mockMasterPassword,
|
||||
mockUserKey,
|
||||
mockEncUserKey
|
||||
)
|
||||
).rejects.toThrowError("sync failed");
|
||||
|
||||
expect(apiService.postAccountKey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not post new account data if data retrieval fails", async () => {
|
||||
(migrateFromLegacyEncryptionService as any).encryptCiphers = async () => {
|
||||
throw new Error("Ciphers failed to be retrieved");
|
||||
};
|
||||
|
||||
await expect(
|
||||
migrateFromLegacyEncryptionService.updateKeysAndEncryptedData(
|
||||
mockMasterPassword,
|
||||
mockUserKey,
|
||||
mockEncUserKey
|
||||
)
|
||||
).rejects.toThrowError("Ciphers failed to be retrieved");
|
||||
|
||||
expect(apiService.postAccountKey).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateEmergencyAccesses", () => {
|
||||
let mockUserKey: UserKey;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||
|
||||
const mockEmergencyAccess = {
|
||||
data: [
|
||||
createMockEmergencyAccess("0", "EA 0", EmergencyAccessStatusType.Invited),
|
||||
createMockEmergencyAccess("1", "EA 1", EmergencyAccessStatusType.Accepted),
|
||||
createMockEmergencyAccess("2", "EA 2", EmergencyAccessStatusType.Confirmed),
|
||||
createMockEmergencyAccess("3", "EA 3", EmergencyAccessStatusType.RecoveryInitiated),
|
||||
createMockEmergencyAccess("4", "EA 4", EmergencyAccessStatusType.RecoveryApproved),
|
||||
],
|
||||
} as ListResponse<EmergencyAccessGranteeDetailsResponse>;
|
||||
apiService.getEmergencyAccessTrusted.mockResolvedValue(mockEmergencyAccess);
|
||||
apiService.getUserPublicKey.mockResolvedValue({
|
||||
userId: "mockUserId",
|
||||
publicKey: "mockPublicKey",
|
||||
} as UserKeyResponse);
|
||||
|
||||
cryptoService.rsaEncrypt.mockImplementation((plainValue, publicKey) => {
|
||||
return Promise.resolve(
|
||||
new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "Encrypted: " + plainValue)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("Only updates emergency accesses with allowed statuses", async () => {
|
||||
await migrateFromLegacyEncryptionService.updateEmergencyAccesses(mockUserKey);
|
||||
|
||||
expect(apiService.putEmergencyAccess).not.toHaveBeenCalledWith(
|
||||
"0",
|
||||
expect.any(EmergencyAccessUpdateRequest)
|
||||
);
|
||||
expect(apiService.putEmergencyAccess).not.toHaveBeenCalledWith(
|
||||
"1",
|
||||
expect.any(EmergencyAccessUpdateRequest)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateAllAdminRecoveryKeys", () => {
|
||||
let mockMasterPassword: string;
|
||||
let mockUserKey: UserKey;
|
||||
|
||||
beforeEach(() => {
|
||||
mockMasterPassword = "mockMasterPassword";
|
||||
|
||||
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||
|
||||
organizationService.getAll.mockResolvedValue([
|
||||
createOrganization("1", "Org 1", true),
|
||||
createOrganization("2", "Org 2", true),
|
||||
createOrganization("3", "Org 3", false),
|
||||
createOrganization("4", "Org 4", false),
|
||||
]);
|
||||
|
||||
organizationApiService.getKeys.mockImplementation((orgId) => {
|
||||
return Promise.resolve({
|
||||
publicKey: orgId + "mockPublicKey",
|
||||
privateKey: orgId + "mockPrivateKey",
|
||||
} as OrganizationKeysResponse);
|
||||
});
|
||||
});
|
||||
|
||||
it("Only updates organizations that are enrolled in admin recovery", async () => {
|
||||
await migrateFromLegacyEncryptionService.updateAllAdminRecoveryKeys(
|
||||
mockMasterPassword,
|
||||
mockUserKey
|
||||
);
|
||||
|
||||
expect(
|
||||
organizationUserService.putOrganizationUserResetPasswordEnrollment
|
||||
).toHaveBeenCalledWith(
|
||||
"1",
|
||||
expect.any(String),
|
||||
expect.any(OrganizationUserResetPasswordEnrollmentRequest)
|
||||
);
|
||||
expect(
|
||||
organizationUserService.putOrganizationUserResetPasswordEnrollment
|
||||
).toHaveBeenCalledWith(
|
||||
"2",
|
||||
expect.any(String),
|
||||
expect.any(OrganizationUserResetPasswordEnrollmentRequest)
|
||||
);
|
||||
expect(
|
||||
organizationUserService.putOrganizationUserResetPasswordEnrollment
|
||||
).not.toHaveBeenCalledWith(
|
||||
"3",
|
||||
expect.any(String),
|
||||
expect.any(OrganizationUserResetPasswordEnrollmentRequest)
|
||||
);
|
||||
expect(
|
||||
organizationUserService.putOrganizationUserResetPasswordEnrollment
|
||||
).not.toHaveBeenCalledWith(
|
||||
"4",
|
||||
expect.any(String),
|
||||
expect.any(OrganizationUserResetPasswordEnrollmentRequest)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createMockFolder(id: string, name: string): FolderView {
|
||||
const folder = new FolderView();
|
||||
folder.id = id;
|
||||
folder.name = name;
|
||||
return folder;
|
||||
}
|
||||
|
||||
function createMockCipher(id: string, name: string): CipherView {
|
||||
const cipher = new CipherView();
|
||||
cipher.id = id;
|
||||
cipher.name = name;
|
||||
return cipher;
|
||||
}
|
||||
|
||||
function createMockSend(id: string, name: string): Send {
|
||||
const send = new Send();
|
||||
send.id = id;
|
||||
send.name = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, name);
|
||||
return send;
|
||||
}
|
||||
|
||||
function createMockEmergencyAccess(
|
||||
id: string,
|
||||
name: string,
|
||||
status: EmergencyAccessStatusType
|
||||
): EmergencyAccessGranteeDetailsResponse {
|
||||
const emergencyAccess = new EmergencyAccessGranteeDetailsResponse({});
|
||||
emergencyAccess.id = id;
|
||||
emergencyAccess.name = name;
|
||||
emergencyAccess.type = 0;
|
||||
emergencyAccess.status = status;
|
||||
return emergencyAccess;
|
||||
}
|
||||
|
||||
function createOrganization(id: string, name: string, resetPasswordEnrolled: boolean) {
|
||||
const org = new Organization();
|
||||
org.id = id;
|
||||
org.name = name;
|
||||
org.resetPasswordEnrolled = resetPasswordEnrolled;
|
||||
org.userId = "mockUserID";
|
||||
return org;
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
|
||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/abstractions/organization-user/requests";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
|
||||
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
|
||||
import { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request";
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request";
|
||||
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
|
||||
|
||||
// TODO: PM-3797 - This service should be expanded and used for user key rotations in change-password.component.ts
|
||||
@Injectable()
|
||||
export class MigrateFromLegacyEncryptionService {
|
||||
constructor(
|
||||
private organizationService: OrganizationService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private apiService: ApiService,
|
||||
private cryptoService: CryptoService,
|
||||
private encryptService: EncryptService,
|
||||
private syncService: SyncService,
|
||||
private cipherService: CipherService,
|
||||
private folderService: FolderService,
|
||||
private sendService: SendService,
|
||||
private stateService: StateService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Validates the master password and creates a new user key.
|
||||
* @returns A new user key along with the encrypted version
|
||||
*/
|
||||
async createNewUserKey(masterPassword: string): Promise<[UserKey, EncString]> {
|
||||
// Create master key to validate the master password
|
||||
const masterKey = await this.cryptoService.makeMasterKey(
|
||||
masterPassword,
|
||||
await this.stateService.getEmail(),
|
||||
await this.stateService.getKdfType(),
|
||||
await this.stateService.getKdfConfig()
|
||||
);
|
||||
|
||||
if (!masterKey) {
|
||||
throw new Error("Invalid master password");
|
||||
}
|
||||
|
||||
if (!(await this.cryptoService.isLegacyUser(masterKey))) {
|
||||
throw new Error("Invalid master password or user may not be legacy");
|
||||
}
|
||||
|
||||
// Set master key again in case it was lost (could be lost on refresh)
|
||||
await this.cryptoService.setMasterKey(masterKey);
|
||||
return await this.cryptoService.makeUserKey(masterKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user key, master key hash, private key, folders, ciphers, and sends
|
||||
* on the server.
|
||||
* @param masterPassword The master password
|
||||
* @param newUserKey The new user key
|
||||
* @param newEncUserKey The new encrypted user key
|
||||
*/
|
||||
async updateKeysAndEncryptedData(
|
||||
masterPassword: string,
|
||||
newUserKey: UserKey,
|
||||
newEncUserKey: EncString
|
||||
): Promise<void> {
|
||||
// Create new request and add master key and hash
|
||||
const request = new UpdateKeyRequest();
|
||||
request.key = newEncUserKey.encryptedString;
|
||||
request.masterPasswordHash = await this.cryptoService.hashMasterKey(
|
||||
masterPassword,
|
||||
await this.cryptoService.getOrDeriveMasterKey(masterPassword)
|
||||
);
|
||||
|
||||
// Sync before encrypting to make sure we have latest data
|
||||
await this.syncService.fullSync(true);
|
||||
|
||||
request.privateKey = await this.encryptPrivateKey(newUserKey);
|
||||
request.folders = await this.encryptFolders(newUserKey);
|
||||
request.ciphers = await this.encryptCiphers(newUserKey);
|
||||
request.sends = await this.encryptSends(newUserKey);
|
||||
|
||||
return this.apiService.postAccountKey(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets user's emergency access details from server and encrypts with new user key
|
||||
* on the server.
|
||||
* @param newUserKey The new user key
|
||||
*/
|
||||
async updateEmergencyAccesses(newUserKey: UserKey) {
|
||||
const emergencyAccess = await this.apiService.getEmergencyAccessTrusted();
|
||||
// Any Invited or Accepted requests won't have the key yet, so we don't need to update them
|
||||
const allowedStatuses = new Set([
|
||||
EmergencyAccessStatusType.Confirmed,
|
||||
EmergencyAccessStatusType.RecoveryInitiated,
|
||||
EmergencyAccessStatusType.RecoveryApproved,
|
||||
]);
|
||||
const filteredAccesses = emergencyAccess.data.filter((d) => allowedStatuses.has(d.status));
|
||||
|
||||
for (const details of filteredAccesses) {
|
||||
// Get public key of grantee
|
||||
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
|
||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||
|
||||
// Encrypt new user key with public key
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(newUserKey.key, publicKey);
|
||||
|
||||
const updateRequest = new EmergencyAccessUpdateRequest();
|
||||
updateRequest.type = details.type;
|
||||
updateRequest.waitTimeDays = details.waitTimeDays;
|
||||
updateRequest.keyEncrypted = encryptedKey.encryptedString;
|
||||
|
||||
await this.apiService.putEmergencyAccess(details.id, updateRequest);
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates all admin recovery keys on the server with the new user key
|
||||
* @param masterPassword The user's master password
|
||||
* @param newUserKey The new user key
|
||||
*/
|
||||
async updateAllAdminRecoveryKeys(masterPassword: string, newUserKey: UserKey) {
|
||||
const allOrgs = await this.organizationService.getAll();
|
||||
|
||||
for (const org of allOrgs) {
|
||||
// If not already enrolled, skip
|
||||
if (!org.resetPasswordEnrolled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Retrieve public key
|
||||
const response = await this.organizationApiService.getKeys(org.id);
|
||||
const publicKey = Utils.fromB64ToArray(response?.publicKey);
|
||||
|
||||
// Re-enroll - encrypt user key with organization public key
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(newUserKey.key, publicKey);
|
||||
|
||||
// Create/Execute request
|
||||
const request = new OrganizationUserResetPasswordEnrollmentRequest();
|
||||
request.resetPasswordKey = encryptedKey.encryptedString;
|
||||
request.masterPasswordHash = await this.cryptoService.hashMasterKey(
|
||||
masterPassword,
|
||||
await this.cryptoService.getOrDeriveMasterKey(masterPassword)
|
||||
);
|
||||
|
||||
await this.organizationUserService.putOrganizationUserResetPasswordEnrollment(
|
||||
org.id,
|
||||
org.userId,
|
||||
request
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async encryptPrivateKey(newUserKey: UserKey): Promise<EncryptedString | null> {
|
||||
const privateKey = await this.cryptoService.getPrivateKey();
|
||||
if (!privateKey) {
|
||||
return;
|
||||
}
|
||||
return (await this.encryptService.encrypt(privateKey, newUserKey)).encryptedString;
|
||||
}
|
||||
|
||||
private async encryptFolders(newUserKey: UserKey): Promise<FolderWithIdRequest[] | null> {
|
||||
const folders = await firstValueFrom(this.folderService.folderViews$);
|
||||
if (!folders) {
|
||||
return;
|
||||
}
|
||||
return await Promise.all(
|
||||
folders.map(async (folder) => {
|
||||
const encryptedFolder = await this.folderService.encrypt(folder, newUserKey);
|
||||
return new FolderWithIdRequest(encryptedFolder);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private async encryptCiphers(newUserKey: UserKey): Promise<CipherWithIdRequest[] | null> {
|
||||
const ciphers = await this.cipherService.getAllDecrypted();
|
||||
if (!ciphers) {
|
||||
return;
|
||||
}
|
||||
return await Promise.all(
|
||||
ciphers.map(async (cipher) => {
|
||||
const encryptedCipher = await this.cipherService.encrypt(cipher, newUserKey);
|
||||
return new CipherWithIdRequest(encryptedCipher);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private async encryptSends(newUserKey: UserKey): Promise<SendWithIdRequest[] | null> {
|
||||
const sends = await firstValueFrom(this.sendService.sends$);
|
||||
if (!sends) {
|
||||
return;
|
||||
}
|
||||
return await Promise.all(
|
||||
sends.map(async (send) => {
|
||||
const sendKey = await this.encryptService.decryptToBytes(send.key, null);
|
||||
send.key = (await this.encryptService.encrypt(sendKey, newUserKey)) ?? send.key;
|
||||
return new SendWithIdRequest(send);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -145,12 +145,6 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const hasUserKey = await this.cryptoService.hasUserKey();
|
||||
if (!hasUserKey) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("updateKey"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.masterPasswordHint != null && this.masterPasswordHint == this.masterPassword) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
@@ -86,6 +87,14 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||
);
|
||||
}
|
||||
|
||||
protected override handleMigrateEncryptionKey(result: AuthResult): boolean {
|
||||
if (!result.requiresEncryptionKeyMigration) {
|
||||
return false;
|
||||
}
|
||||
this.router.navigate(["migrate-legacy-encryption"]);
|
||||
return true;
|
||||
}
|
||||
|
||||
goAfterLogIn = async () => {
|
||||
this.loginService.clearValues();
|
||||
const previousUrl = this.routerService.getPreviousUrl();
|
||||
|
||||
@@ -175,6 +175,13 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
data: { titleId: "removeMasterPassword" },
|
||||
},
|
||||
{
|
||||
path: "migrate-legacy-encryption",
|
||||
loadComponent: () =>
|
||||
import("./auth/migrate-encryption/migrate-legacy-encryption.component").then(
|
||||
(mod) => mod.MigrateFromLegacyEncryptionComponent
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -42,12 +42,6 @@ export class ChangeEmailComponent implements OnInit {
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const hasUserKey = await this.cryptoService.hasUserKey();
|
||||
if (!hasUserKey) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("updateKey"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.newEmail = this.newEmail.trim().toLowerCase();
|
||||
if (!this.tokenSent) {
|
||||
const request = new EmailTokenRequest();
|
||||
|
||||
@@ -46,11 +46,6 @@ export class ChangeKdfConfirmationComponent {
|
||||
|
||||
async submit() {
|
||||
this.loading = true;
|
||||
const hasUserKey = await this.cryptoService.hasUserKey();
|
||||
if (!hasUserKey) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("updateKey"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.formPromise = this.makeKeyAndSaveAsync();
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="updateUserKeyTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<form
|
||||
class="modal-content"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title" id="updateUserKeyTitle">{{ "updateEncryptionKey" | i18n }}</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
{{ "updateEncryptionKeyShortDesc" | i18n }} {{ "updateEncryptionKeyDesc" | i18n }}
|
||||
<a
|
||||
href="https://bitwarden.com/help/account-encryption-key/#rotate-your-encryption-key"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>{{ "learnMore" | i18n }}</a
|
||||
>
|
||||
</p>
|
||||
<app-callout type="warning">{{ "updateEncryptionKeyWarning" | i18n }}</app-callout>
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="password"
|
||||
name="MasterPasswordHash"
|
||||
class="form-control"
|
||||
[(ngModel)]="masterPassword"
|
||||
required
|
||||
appAutofocus
|
||||
appInputVerbatim
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "updateEncryptionKey" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "close" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,108 +0,0 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { CipherWithIdRequest } from "@bitwarden/common/vault//models/request/cipher-with-id.request";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
|
||||
|
||||
@Component({
|
||||
selector: "app-update-key",
|
||||
templateUrl: "update-key.component.html",
|
||||
})
|
||||
export class UpdateKeyComponent {
|
||||
masterPassword: string;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private cryptoService: CryptoService,
|
||||
private messagingService: MessagingService,
|
||||
private syncService: SyncService,
|
||||
private folderService: FolderService,
|
||||
private cipherService: CipherService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
async submit() {
|
||||
const hasUserKey = await this.cryptoService.hasUserKey();
|
||||
if (hasUserKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.masterPassword == null || this.masterPassword === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("masterPasswordRequired")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.formPromise = this.makeRequest().then((request) => {
|
||||
return this.apiService.postAccountKey(request);
|
||||
});
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
this.i18nService.t("keyUpdated"),
|
||||
this.i18nService.t("logBackInOthersToo"),
|
||||
{ timeout: 15000 }
|
||||
);
|
||||
this.messagingService.send("logout");
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async makeRequest(): Promise<UpdateKeyRequest> {
|
||||
const masterKey = await this.cryptoService.getMasterKey();
|
||||
const newUserKey = await this.cryptoService.makeUserKey(masterKey);
|
||||
const privateKey = await this.cryptoService.getPrivateKey();
|
||||
let encPrivateKey: EncString = null;
|
||||
if (privateKey != null) {
|
||||
encPrivateKey = await this.cryptoService.encrypt(privateKey, newUserKey[0]);
|
||||
}
|
||||
const request = new UpdateKeyRequest();
|
||||
request.privateKey = encPrivateKey != null ? encPrivateKey.encryptedString : null;
|
||||
request.key = newUserKey[1].encryptedString;
|
||||
request.masterPasswordHash = await this.cryptoService.hashMasterKey(
|
||||
this.masterPassword,
|
||||
await this.cryptoService.getOrDeriveMasterKey(this.masterPassword)
|
||||
);
|
||||
|
||||
await this.syncService.fullSync(true);
|
||||
|
||||
const folders = await firstValueFrom(this.folderService.folderViews$);
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
if (folders[i].id == null) {
|
||||
continue;
|
||||
}
|
||||
const folder = await this.folderService.encrypt(folders[i], newUserKey[0]);
|
||||
request.folders.push(new FolderWithIdRequest(folder));
|
||||
}
|
||||
|
||||
const ciphers = await this.cipherService.getAllDecrypted();
|
||||
for (let i = 0; i < ciphers.length; i++) {
|
||||
if (ciphers[i].organizationId != null) {
|
||||
continue;
|
||||
}
|
||||
const cipher = await this.cipherService.encrypt(ciphers[i], newUserKey[0]);
|
||||
request.ciphers.push(new CipherWithIdRequest(cipher));
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,6 @@ import { PurgeVaultComponent } from "../settings/purge-vault.component";
|
||||
import { SecurityKeysComponent } from "../settings/security-keys.component";
|
||||
import { SecurityComponent } from "../settings/security.component";
|
||||
import { SettingsComponent } from "../settings/settings.component";
|
||||
import { UpdateKeyComponent } from "../settings/update-key.component";
|
||||
import { UpdateLicenseComponent } from "../settings/update-license.component";
|
||||
import { VaultTimeoutInputComponent } from "../settings/vault-timeout-input.component";
|
||||
import { GeneratorComponent } from "../tools/generator.component";
|
||||
@@ -216,7 +215,6 @@ import { SharedModule } from "./shared.module";
|
||||
TwoFactorVerifyComponent,
|
||||
TwoFactorWebAuthnComponent,
|
||||
TwoFactorYubiKeyComponent,
|
||||
UpdateKeyComponent,
|
||||
UpdateLicenseComponent,
|
||||
UpdatePasswordComponent,
|
||||
UpdateTempPasswordComponent,
|
||||
@@ -319,7 +317,6 @@ import { SharedModule } from "./shared.module";
|
||||
TwoFactorVerifyComponent,
|
||||
TwoFactorWebAuthnComponent,
|
||||
TwoFactorYubiKeyComponent,
|
||||
UpdateKeyComponent,
|
||||
UpdateLicenseComponent,
|
||||
UpdatePasswordComponent,
|
||||
UpdateTempPasswordComponent,
|
||||
|
||||
@@ -77,19 +77,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div class="card border-warning mb-4" *ngIf="showUpdateKey">
|
||||
<div class="card-header bg-warning text-white">
|
||||
<i class="bwi bwi-exclamation-triangle bwi-fw" aria-hidden="true"></i>
|
||||
{{ "updateKeyTitle" | i18n }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{ "updateEncryptionKeyShortDesc" | i18n }}</p>
|
||||
<button class="btn btn-block btn-outline-secondary" type="button" (click)="updateKey()">
|
||||
{{ "updateEncryptionKey" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-low-kdf class="d-block mb-4" *ngIf="showLowKdf"> </app-low-kdf>
|
||||
|
||||
<app-verify-email
|
||||
|
||||
@@ -60,7 +60,6 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||
import { DialogService, Icons } from "@bitwarden/components";
|
||||
|
||||
import { UpdateKeyComponent } from "../../settings/update-key.component";
|
||||
import { CollectionDialogAction, openCollectionDialog } from "../components/collection-dialog";
|
||||
import { VaultItemEvent } from "../components/vault-items/vault-item-event";
|
||||
import { getNestedCollectionTree } from "../utils/collection-utils";
|
||||
@@ -114,12 +113,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
@ViewChild("share", { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef;
|
||||
@ViewChild("collectionsModal", { read: ViewContainerRef, static: true })
|
||||
collectionsModalRef: ViewContainerRef;
|
||||
@ViewChild("updateKeyTemplate", { read: ViewContainerRef, static: true })
|
||||
updateKeyModalRef: ViewContainerRef;
|
||||
|
||||
showVerifyEmail = false;
|
||||
showBrowserOutdated = false;
|
||||
showUpdateKey = false;
|
||||
showPremiumCallout = false;
|
||||
showLowKdf = false;
|
||||
trashCleanupWarning: string = null;
|
||||
@@ -197,7 +193,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
this.showPremiumCallout =
|
||||
!this.showVerifyEmail && !canAccessPremium && !this.platformUtilsService.isSelfHost();
|
||||
this.showUpdateKey = !(await this.cryptoService.hasUserKey());
|
||||
|
||||
const cipherId = getCipherIdFromParams(params);
|
||||
if (!cipherId) {
|
||||
@@ -405,11 +400,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
get isShowingCards() {
|
||||
return (
|
||||
this.showBrowserOutdated ||
|
||||
this.showPremiumCallout ||
|
||||
this.showUpdateKey ||
|
||||
this.showVerifyEmail ||
|
||||
this.showLowKdf
|
||||
this.showBrowserOutdated || this.showPremiumCallout || this.showVerifyEmail || this.showLowKdf
|
||||
);
|
||||
}
|
||||
|
||||
@@ -865,10 +856,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
: this.cipherService.softDeleteWithServer(id);
|
||||
}
|
||||
|
||||
async updateKey() {
|
||||
await this.modalService.openViewRef(UpdateKeyComponent, this.updateKeyModalRef);
|
||||
}
|
||||
|
||||
async isLowKdfIteration() {
|
||||
const kdfType = await this.stateService.getKdfType();
|
||||
const kdfOptions = await this.stateService.getKdfConfig();
|
||||
|
||||
Reference in New Issue
Block a user