mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +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();
|
||||
|
||||
@@ -508,9 +508,6 @@
|
||||
"maxFileSize": {
|
||||
"message": "Maximum file size is 500 MB."
|
||||
},
|
||||
"updateKey": {
|
||||
"message": "You cannot use this feature until you update your encryption key."
|
||||
},
|
||||
"addedItem": {
|
||||
"message": "Item added"
|
||||
},
|
||||
@@ -3476,17 +3473,11 @@
|
||||
"keyUpdated": {
|
||||
"message": "Key updated"
|
||||
},
|
||||
"updateKeyTitle": {
|
||||
"message": "Update key"
|
||||
},
|
||||
"updateEncryptionKey": {
|
||||
"message": "Update encryption key"
|
||||
},
|
||||
"updateEncryptionKeyShortDesc": {
|
||||
"message": "You are currently using an outdated encryption scheme."
|
||||
},
|
||||
"updateEncryptionKeyDesc": {
|
||||
"message": "We've moved to larger encryption keys that provide better security and access to newer features. Updating your encryption key is quick and easy. Just type your master password below. This update will eventually become mandatory."
|
||||
"updateEncryptionSchemeDesc": {
|
||||
"message": "We've changed the encryption scheme to provide better security. Update your encryption key now by entering your master password below."
|
||||
},
|
||||
"updateEncryptionKeyWarning": {
|
||||
"message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed."
|
||||
|
||||
Reference in New Issue
Block a user