mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 23:03:32 +00:00
[SM-89] Updates to encrypted export (#2963)
* Rough draft of Export/Import changes w/ password encryption * fix for encrypted export changes * Create launch.json * Updates to export logic modal user secret prompt * Updates to error handling * renaming the component for checking the user secret to a name that is more clear about what it accomplishes * Fixing lint errors * Adding a comment * Suggested changes from CR * Suggested changes from CR * Making suggested changes * removing unnecessary properties * changes suggested * Fix * Updating error messages * Removing unecessary launch.json file commit * running lint, removing commented code * removing launch.json * Updates to remove the userVerificationPromptService * updates * Removing unused import, running npm prettier/lint * Changes to use Form Fields * Updates * updates requested by Matt * Update apps/web/src/app/tools/import-export/export.component.ts Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * Suggested Changes from PR * Fix after merge from Master * changes to styling * Removing unused code and cleanup * Update libs/angular/src/components/user-verification-prompt.component.ts Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * Update apps/web/src/locales/en/messages.json Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * Changes suggested by Thomas R * Merging master into branch * Revert "Merging master into branch" This reverts commiteb2cdffe49. * Requested changes and improvements * merging master into feature branch * Revert "merging master into feature branch" This reverts commite287715251. * Suggested Changes * changes * requested changes * Requested changes * removing comments, fixing code * reducing copied code * fixing bug * fixing bug * changes * WIP * Thomas's requested changes * adding back missing spaces * change needed after the merge from master into feature branch * prettier + lint * Updating the EncryptedExportType Import * Fixing build errors Co-authored-by: Thomas Rittson <eliykat@users.noreply.github.com> * Move FilePasswordPrompt to ImportExportModule Also remove base class Also remove duplicate service providers * Run prettier * Suggested Changes from Thomas * only require filePassword and confirmFilePassword if it's type is FileEncrypted * Update to only enable the field when submitting a file password encrypted file * Requested changes, moving logic to web * undoing change to bit button * Refactor to process file-encrypted imports in main import.component * Refactor confirm file password check * Remove UserVerificationPromptService * Address CodeScene feedback * Updates to disable the required file password field when needed * Subscribe to reactive form changes to adjust validators * style changes requested by suhkleen * Delete duplicate classes Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com> Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Thomas Rittson <trittson@bitwarden.com> Co-authored-by: Thomas Rittson <eliykat@users.noreply.github.com>
This commit is contained in:
@@ -15,7 +15,10 @@
|
|||||||
"message": "No Folder"
|
"message": "No Folder"
|
||||||
},
|
},
|
||||||
"importEncKeyError": {
|
"importEncKeyError": {
|
||||||
"message": "Invalid file password."
|
"message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data."
|
||||||
|
},
|
||||||
|
"invalidFilePassword": {
|
||||||
|
"message": "Invalid file password, please use the password you entered when you created the export file."
|
||||||
},
|
},
|
||||||
"importPasswordRequired": {
|
"importPasswordRequired": {
|
||||||
"message": "File is password protected, please provide a decryption password."
|
"message": "File is password protected, please provide a decryption password."
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
|
||||||
|
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||||
|
<form class="modal-content" #form (ngSubmit)="submit()">
|
||||||
|
<h2 class="tw-mt-6 tw-mb-6 tw-pl-3.5 tw-pr-3.5 tw-font-semibold" id="modalTitle | i18n ">
|
||||||
|
{{ modalTitle | i18n | uppercase }}
|
||||||
|
</h2>
|
||||||
|
<div class="tw-border-0 tw-border-t tw-border-solid tw-border-secondary-300 tw-p-3.5">
|
||||||
|
{{ confirmDescription | i18n }}
|
||||||
|
</div>
|
||||||
|
<div class="tw-p-3.5">
|
||||||
|
<app-user-verification ngDefaultControl [formControl]="secret" name="secret">
|
||||||
|
</app-user-verification>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="tw-border-0 tw-border-t tw-border-solid tw-border-secondary-300 tw-bg-background-alt tw-p-3.5"
|
||||||
|
>
|
||||||
|
<button bitButton buttonType="primary" type="submit" appBlurClick>
|
||||||
|
<span>{{ confirmButtonText | i18n }}</span>
|
||||||
|
</button>
|
||||||
|
<button bitButton buttonType="secondary" data-dismiss="modal">
|
||||||
|
{{ "cancel" | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
|
import { UserVerificationPromptComponent as BaseUserVerificationPrompt } from "@bitwarden/angular/components/user-verification-prompt.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "user-verification-prompt.component.html",
|
||||||
|
})
|
||||||
|
export class UserVerificationPromptComponent extends BaseUserVerificationPrompt {}
|
||||||
@@ -2,6 +2,7 @@ import { Component } from "@angular/core";
|
|||||||
import { UntypedFormBuilder } from "@angular/forms";
|
import { UntypedFormBuilder } from "@angular/forms";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
|
||||||
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||||
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||||
import { ExportService } from "@bitwarden/common/abstractions/export.service";
|
import { ExportService } from "@bitwarden/common/abstractions/export.service";
|
||||||
@@ -32,7 +33,8 @@ export class OrganizationExportComponent extends ExportComponent {
|
|||||||
logService: LogService,
|
logService: LogService,
|
||||||
userVerificationService: UserVerificationService,
|
userVerificationService: UserVerificationService,
|
||||||
formBuilder: UntypedFormBuilder,
|
formBuilder: UntypedFormBuilder,
|
||||||
fileDownloadService: FileDownloadService
|
fileDownloadService: FileDownloadService,
|
||||||
|
modalService: ModalService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
cryptoService,
|
cryptoService,
|
||||||
@@ -44,7 +46,8 @@ export class OrganizationExportComponent extends ExportComponent {
|
|||||||
logService,
|
logService,
|
||||||
userVerificationService,
|
userVerificationService,
|
||||||
formBuilder,
|
formBuilder,
|
||||||
fileDownloadService
|
fileDownloadService,
|
||||||
|
modalService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
|
|
||||||
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||||
import { ImportService } from "@bitwarden/common/abstractions/import.service";
|
import { ImportService } from "@bitwarden/common/abstractions/import.service";
|
||||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||||
@@ -26,9 +27,18 @@ export class OrganizationImportComponent extends ImportComponent {
|
|||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
policyService: PolicyService,
|
policyService: PolicyService,
|
||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
logService: LogService
|
logService: LogService,
|
||||||
|
modalService: ModalService
|
||||||
) {
|
) {
|
||||||
super(i18nService, importService, router, platformUtilsService, policyService, logService);
|
super(
|
||||||
|
i18nService,
|
||||||
|
importService,
|
||||||
|
router,
|
||||||
|
platformUtilsService,
|
||||||
|
policyService,
|
||||||
|
logService,
|
||||||
|
modalService
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { NestedCheckboxComponent } from "../components/nested-checkbox.component
|
|||||||
import { OrganizationSwitcherComponent } from "../components/organization-switcher.component";
|
import { OrganizationSwitcherComponent } from "../components/organization-switcher.component";
|
||||||
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
|
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
|
||||||
import { PremiumBadgeComponent } from "../components/premium-badge.component";
|
import { PremiumBadgeComponent } from "../components/premium-badge.component";
|
||||||
|
import { UserVerificationPromptComponent } from "../components/user-verification-prompt.component";
|
||||||
import { FooterComponent } from "../layouts/footer.component";
|
import { FooterComponent } from "../layouts/footer.component";
|
||||||
import { FrontendLayoutComponent } from "../layouts/frontend-layout.component";
|
import { FrontendLayoutComponent } from "../layouts/frontend-layout.component";
|
||||||
import { NavbarComponent } from "../layouts/navbar.component";
|
import { NavbarComponent } from "../layouts/navbar.component";
|
||||||
@@ -253,6 +254,7 @@ import { SharedModule } from ".";
|
|||||||
PasswordGeneratorHistoryComponent,
|
PasswordGeneratorHistoryComponent,
|
||||||
PasswordGeneratorPolicyComponent,
|
PasswordGeneratorPolicyComponent,
|
||||||
PasswordRepromptComponent,
|
PasswordRepromptComponent,
|
||||||
|
UserVerificationPromptComponent,
|
||||||
PaymentComponent,
|
PaymentComponent,
|
||||||
PaymentMethodComponent,
|
PaymentMethodComponent,
|
||||||
PersonalOwnershipPolicyComponent,
|
PersonalOwnershipPolicyComponent,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<form
|
<form
|
||||||
#form
|
#form
|
||||||
(ngSubmit)="submit()"
|
(ngSubmit)="submit()"
|
||||||
ngNativeValidate
|
|
||||||
[appApiAction]="formPromise"
|
[appApiAction]="formPromise"
|
||||||
[formGroup]="exportForm"
|
[formGroup]="exportForm"
|
||||||
|
*ngIf="exportForm"
|
||||||
>
|
>
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1>{{ "exportVault" | i18n }}</h1>
|
<h1>{{ "exportVault" | i18n }}</h1>
|
||||||
@@ -18,25 +18,150 @@
|
|||||||
></app-export-scope-callout>
|
></app-export-scope-callout>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-6">
|
<div class="col-6">
|
||||||
<label for="format">{{ "fileFormat" | i18n }}</label>
|
<bit-form-field>
|
||||||
<select class="form-control" id="format" name="Format" formControlName="format">
|
<bit-label class="tw-text-lg" for="format">{{ "fileFormat" | i18n }}</bit-label>
|
||||||
<option *ngFor="let f of formatOptions" [value]="f.value">{{ f.name }}</option>
|
<select bitInput name="format" formControlName="format">
|
||||||
</select>
|
<option *ngFor="let f of formatOptions" [value]="f.value">{{ f.name }}</option>
|
||||||
|
</select>
|
||||||
|
</bit-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-6">
|
<div class="form-group col-6">
|
||||||
<app-user-verification ngDefaultControl formControlName="secret" name="secret">
|
<ng-container *ngIf="format === 'encrypted_json'">
|
||||||
</app-user-verification>
|
<div role="radiogroup" aria-labelledby="fileTypeHeading">
|
||||||
|
<label id="fileTypeHeading" class="tw-semi-bold tw-text-lg">
|
||||||
|
{{ "fileTypeHeading" | i18n }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div appBoxRow name="FileTypeOptions" class="tw-flex tw-items-center">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="radio"
|
||||||
|
name="fileEncryptionType"
|
||||||
|
id="AccountEncrypted"
|
||||||
|
[value]="encryptedExportType.AccountEncrypted"
|
||||||
|
formControlName="fileEncryptionType"
|
||||||
|
[checked]="fileEncryptionType === encryptedExportType.AccountEncrypted"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="tw-semi-bold tw-text-md tw-ml-1 tw-mt-1 tw-mb-1" for="AccountEncrypted">
|
||||||
|
{{ "accountBackup" | i18n }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-regular ml-3 pb-2 tw-text-sm">
|
||||||
|
{{ "accountBackupOptionDescription" | i18n }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-flex tw-items-center">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="radio"
|
||||||
|
name="fileEncryptionType"
|
||||||
|
id="FileEncrypted"
|
||||||
|
[value]="encryptedExportType.FileEncrypted"
|
||||||
|
formControlName="fileEncryptionType"
|
||||||
|
[checked]="fileEncryptionType === encryptedExportType.FileEncrypted"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="tw-semi-bold tw-text-md tw-ml-1 tw-mt-1 tw-mb-1" for="FileEncrypted">{{
|
||||||
|
"passwordProtected" | i18n
|
||||||
|
}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-regular ml-3 tw-text-sm">
|
||||||
|
{{ "passwordProtectedOptionDescription" | i18n }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<ng-container *ngIf="fileEncryptionType == encryptedExportType.FileEncrypted">
|
||||||
|
<div class="input-group">
|
||||||
|
<bit-form-field class="tw-w-full">
|
||||||
|
<bit-label>{{ "filePassword" | i18n }}</bit-label>
|
||||||
|
<input
|
||||||
|
bitInput
|
||||||
|
type="{{ showFilePassword ? 'text' : 'password' }}"
|
||||||
|
id="filePassword"
|
||||||
|
formControlName="filePassword"
|
||||||
|
name="password"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button
|
||||||
|
bitSuffix
|
||||||
|
bitButton
|
||||||
|
buttonType="secondary"
|
||||||
|
appStopClick
|
||||||
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
|
[attr.aria-pressed]="showFilePassword"
|
||||||
|
(click)="toggleFilePassword()"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-lg"
|
||||||
|
aria-hidden="true"
|
||||||
|
[ngClass]="{ 'bwi-eye': !showFilePassword, 'bwi-eye-slash': showFilePassword }"
|
||||||
|
></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</bit-form-field>
|
||||||
|
<div class="small text-muted">
|
||||||
|
{{ "exportPasswordDescription" | i18n }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group tw-mt-4">
|
||||||
|
<bit-form-field class="tw-w-full">
|
||||||
|
<bit-label>{{ "confirmFilePassword" | i18n }}</bit-label>
|
||||||
|
<input
|
||||||
|
bitInput
|
||||||
|
type="{{ showConfirmFilePassword ? 'text' : 'password' }}"
|
||||||
|
id="confirmFilePassword"
|
||||||
|
formControlName="confirmFilePassword"
|
||||||
|
name="confirmFilePassword"
|
||||||
|
/>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button
|
||||||
|
bitSuffix
|
||||||
|
bitButton
|
||||||
|
buttonType="secondary"
|
||||||
|
appStopClick
|
||||||
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
|
[attr.aria-pressed]="showConfirmFilePassword"
|
||||||
|
(click)="toggleConfirmFilePassword()"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-lg"
|
||||||
|
aria-hidden="true"
|
||||||
|
[ngClass]="{
|
||||||
|
'bwi-eye': !showConfirmFilePassword,
|
||||||
|
'bwi-eye-slash': showConfirmFilePassword
|
||||||
|
}"
|
||||||
|
></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</bit-form-field>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary btn-submit"
|
||||||
|
[disabled]="form.loading || disabled"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||||
|
<span>{{ "confirmFormat" | i18n }}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="btn btn-primary btn-submit"
|
|
||||||
[disabled]="form.loading || exportForm.disabled"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
|
||||||
<span>{{ "exportVault" | i18n }}</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Component } from "@angular/core";
|
|||||||
import { UntypedFormBuilder } from "@angular/forms";
|
import { UntypedFormBuilder } from "@angular/forms";
|
||||||
|
|
||||||
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/components/export.component";
|
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/components/export.component";
|
||||||
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||||
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||||
import { ExportService } from "@bitwarden/common/abstractions/export.service";
|
import { ExportService } from "@bitwarden/common/abstractions/export.service";
|
||||||
@@ -11,6 +12,9 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||||
|
import { EncryptedExportType } from "@bitwarden/common/enums/encryptedExportType";
|
||||||
|
|
||||||
|
import { UserVerificationPromptComponent } from "src/app/components/user-verification-prompt.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-export",
|
selector: "app-export",
|
||||||
@@ -18,6 +22,7 @@ import { UserVerificationService } from "@bitwarden/common/abstractions/userVeri
|
|||||||
})
|
})
|
||||||
export class ExportComponent extends BaseExportComponent {
|
export class ExportComponent extends BaseExportComponent {
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
|
encryptedExportType = EncryptedExportType;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
cryptoService: CryptoService,
|
cryptoService: CryptoService,
|
||||||
@@ -29,7 +34,8 @@ export class ExportComponent extends BaseExportComponent {
|
|||||||
logService: LogService,
|
logService: LogService,
|
||||||
userVerificationService: UserVerificationService,
|
userVerificationService: UserVerificationService,
|
||||||
formBuilder: UntypedFormBuilder,
|
formBuilder: UntypedFormBuilder,
|
||||||
fileDownloadService: FileDownloadService
|
fileDownloadService: FileDownloadService,
|
||||||
|
private modalService: ModalService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
cryptoService,
|
cryptoService,
|
||||||
@@ -46,8 +52,78 @@ export class ExportComponent extends BaseExportComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
if (this.isFileEncryptedExport && this.filePassword != this.confirmFilePassword) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
|
this.i18nService.t("errorOccurred"),
|
||||||
|
this.i18nService.t("filePasswordAndConfirmFilePasswordDoNotMatch")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.exportForm.markAllAsTouched();
|
||||||
|
if (!this.exportForm.valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.disabledByPolicy) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("personalVaultExportPolicyInEffect")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userVerified = await this.verifyUser();
|
||||||
|
if (!userVerified) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.doExport();
|
||||||
|
}
|
||||||
|
|
||||||
protected saved() {
|
protected saved() {
|
||||||
super.saved();
|
super.saved();
|
||||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("exportSuccess"));
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("exportSuccess"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private verifyUser() {
|
||||||
|
let confirmDescription = "exportWarningDesc";
|
||||||
|
if (this.isFileEncryptedExport) {
|
||||||
|
confirmDescription = "fileEncryptedExportWarningDesc";
|
||||||
|
} else if (this.isAccountEncryptedExport) {
|
||||||
|
confirmDescription = "encExportKeyWarningDesc";
|
||||||
|
}
|
||||||
|
|
||||||
|
const ref = this.modalService.open(UserVerificationPromptComponent, {
|
||||||
|
allowMultipleModals: true,
|
||||||
|
data: {
|
||||||
|
confirmDescription: confirmDescription,
|
||||||
|
confirmButtonText: "exportVault",
|
||||||
|
modalTitle: "confirmVaultExport",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ref == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref.onClosedPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
get isFileEncryptedExport() {
|
||||||
|
return (
|
||||||
|
this.format === "encrypted_json" &&
|
||||||
|
this.fileEncryptionType === EncryptedExportType.FileEncrypted
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAccountEncryptedExport() {
|
||||||
|
return (
|
||||||
|
this.format === "encrypted_json" &&
|
||||||
|
this.fileEncryptionType === EncryptedExportType.AccountEncrypted
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<div
|
||||||
|
class="modal fade"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
[attr.aria-labelledby]="'confirmVaultImport' | i18n"
|
||||||
|
>
|
||||||
|
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||||
|
<form #form (ngSubmit)="submit()">
|
||||||
|
<div class="form-group modal-content">
|
||||||
|
<h2 class="tw-mt-6 tw-mb-6 tw-ml-3.5 tw-font-semibold" id="confirmVaultImport">
|
||||||
|
{{ "confirmVaultImport" | i18n | uppercase }}
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
class="tw-border-0 tw-border-t tw-border-solid tw-border-secondary-300 tw-pr-3.5 tw-pt-3.5 tw-pl-3.5"
|
||||||
|
>
|
||||||
|
{{ "confirmVaultImportDesc" | i18n }}
|
||||||
|
<bit-form-field class="tw-w-full tw-pt-3.5">
|
||||||
|
<bit-label>{{ "confirmFilePassword" | i18n }}</bit-label>
|
||||||
|
<input
|
||||||
|
bitInput
|
||||||
|
required
|
||||||
|
type="{{ showFilePassword ? 'text' : 'password' }}"
|
||||||
|
name="filePassword"
|
||||||
|
[formControl]="filePassword"
|
||||||
|
appAutofocus
|
||||||
|
appInputVerbatim
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
bitSuffix
|
||||||
|
bitButton
|
||||||
|
appStopClick
|
||||||
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
|
[attr.aria-pressed]="showFilePassword"
|
||||||
|
(click)="toggleFilePassword()"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="bwi bwi-lg"
|
||||||
|
aria-hidden="true"
|
||||||
|
[ngClass]="{ 'bwi-eye': !showFilePassword, 'bwi-eye-slash': showFilePassword }"
|
||||||
|
></i>
|
||||||
|
</button>
|
||||||
|
</bit-form-field>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="tw-flex tw-w-full tw-flex-wrap tw-items-center tw-border-0 tw-border-t tw-border-solid tw-border-secondary-300 tw-bg-background-alt tw-pl-3.5 tw-pr-3.5 tw-pb-3.5 tw-pt-4 tw-pl-4 tw-pr-4"
|
||||||
|
>
|
||||||
|
<button bitButton buttonType="primary" class="tw-mr-2" type="submit" appBlurClick>
|
||||||
|
<span>{{ "importData" | i18n }}</span>
|
||||||
|
</button>
|
||||||
|
<button bitButton buttonType="secondary" type="button" (click)="cancel()">
|
||||||
|
<span>{{ "cancel" | i18n }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { FormControl, Validators } from "@angular/forms";
|
||||||
|
|
||||||
|
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "file-password-prompt.component.html",
|
||||||
|
})
|
||||||
|
export class FilePasswordPromptComponent {
|
||||||
|
showFilePassword: boolean;
|
||||||
|
filePassword = new FormControl("", Validators.required);
|
||||||
|
|
||||||
|
constructor(private modalRef: ModalRef) {}
|
||||||
|
|
||||||
|
toggleFilePassword() {
|
||||||
|
this.showFilePassword = !this.showFilePassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
this.filePassword.markAsTouched();
|
||||||
|
if (!this.filePassword.valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.modalRef.close(this.filePassword.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.modalRef.close(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,12 +12,13 @@ import { ImportService } from "@bitwarden/common/services/import.service";
|
|||||||
import { LooseComponentsModule, SharedModule } from "../../shared";
|
import { LooseComponentsModule, SharedModule } from "../../shared";
|
||||||
|
|
||||||
import { ExportComponent } from "./export.component";
|
import { ExportComponent } from "./export.component";
|
||||||
|
import { FilePasswordPromptComponent } from "./file-password-prompt.component";
|
||||||
import { ImportExportRoutingModule } from "./import-export-routing.module";
|
import { ImportExportRoutingModule } from "./import-export-routing.module";
|
||||||
import { ImportComponent } from "./import.component";
|
import { ImportComponent } from "./import.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [SharedModule, LooseComponentsModule, ImportExportRoutingModule],
|
imports: [SharedModule, LooseComponentsModule, ImportExportRoutingModule],
|
||||||
declarations: [ImportComponent, ExportComponent],
|
declarations: [ImportComponent, ExportComponent, FilePasswordPromptComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: ImportServiceAbstraction,
|
provide: ImportServiceAbstraction,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Router } from "@angular/router";
|
|||||||
import * as JSZip from "jszip";
|
import * as JSZip from "jszip";
|
||||||
import Swal, { SweetAlertIcon } from "sweetalert2";
|
import Swal, { SweetAlertIcon } from "sweetalert2";
|
||||||
|
|
||||||
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||||
import { ImportService } from "@bitwarden/common/abstractions/import.service";
|
import { ImportService } from "@bitwarden/common/abstractions/import.service";
|
||||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||||
@@ -10,6 +11,9 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
|
|||||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||||
import { ImportOption, ImportType } from "@bitwarden/common/enums/importOptions";
|
import { ImportOption, ImportType } from "@bitwarden/common/enums/importOptions";
|
||||||
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||||
|
import { ImportError } from "@bitwarden/common/importers/importError";
|
||||||
|
|
||||||
|
import { FilePasswordPromptComponent } from "./file-password-prompt.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-import",
|
selector: "app-import",
|
||||||
@@ -20,7 +24,7 @@ export class ImportComponent implements OnInit {
|
|||||||
importOptions: ImportOption[];
|
importOptions: ImportOption[];
|
||||||
format: ImportType = null;
|
format: ImportType = null;
|
||||||
fileContents: string;
|
fileContents: string;
|
||||||
formPromise: Promise<Error>;
|
formPromise: Promise<ImportError>;
|
||||||
loading = false;
|
loading = false;
|
||||||
importBlockedByPolicy = false;
|
importBlockedByPolicy = false;
|
||||||
|
|
||||||
@@ -33,7 +37,8 @@ export class ImportComponent implements OnInit {
|
|||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
protected policyService: PolicyService,
|
protected policyService: PolicyService,
|
||||||
private logService: LogService
|
private logService: LogService,
|
||||||
|
protected modalService: ModalService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@@ -106,12 +111,25 @@ export class ImportComponent implements OnInit {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.formPromise = this.importService.import(importer, fileContents, this.organizationId);
|
this.formPromise = this.importService.import(importer, fileContents, this.organizationId);
|
||||||
const error = await this.formPromise;
|
let error = await this.formPromise;
|
||||||
|
|
||||||
|
if (error?.passwordRequired) {
|
||||||
|
const filePassword = await this.getFilePassword();
|
||||||
|
if (filePassword == null) {
|
||||||
|
this.loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = await this.doPasswordProtectedImport(filePassword, fileContents);
|
||||||
|
}
|
||||||
|
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
this.error(error);
|
this.error(error);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//No errors, display success message
|
||||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("importSuccess"));
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("importSuccess"));
|
||||||
this.router.navigate(this.successNavigate);
|
this.router.navigate(this.successNavigate);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -225,4 +243,29 @@ export class ImportComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getFilePassword(): Promise<string> {
|
||||||
|
const ref = this.modalService.open(FilePasswordPromptComponent, {
|
||||||
|
allowMultipleModals: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ref == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await ref.onClosedPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
async doPasswordProtectedImport(
|
||||||
|
filePassword: string,
|
||||||
|
fileContents: string
|
||||||
|
): Promise<ImportError> {
|
||||||
|
const passwordProtectedImporter = this.importService.getImporter(
|
||||||
|
"bitwardenpasswordprotected",
|
||||||
|
this.organizationId,
|
||||||
|
filePassword
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.importService.import(passwordProtectedImporter, fileContents, this.organizationId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -678,6 +678,9 @@
|
|||||||
"invalidMasterPassword": {
|
"invalidMasterPassword": {
|
||||||
"message": "Invalid master password"
|
"message": "Invalid master password"
|
||||||
},
|
},
|
||||||
|
"invalidFilePassword": {
|
||||||
|
"message": "Invalid file password, please use the password you entered when you created the export file."
|
||||||
|
},
|
||||||
"lockNow": {
|
"lockNow": {
|
||||||
"message": "Lock Now"
|
"message": "Lock Now"
|
||||||
},
|
},
|
||||||
@@ -890,6 +893,48 @@
|
|||||||
"fileFormat": {
|
"fileFormat": {
|
||||||
"message": "File Format"
|
"message": "File Format"
|
||||||
},
|
},
|
||||||
|
"fileEncryptedExportWarningDesc": {
|
||||||
|
"message": "This file export will be password protected and require the file password to decrypt."
|
||||||
|
},
|
||||||
|
"exportPasswordDescription": {
|
||||||
|
"message": "This password will be used to export and import this file"
|
||||||
|
},
|
||||||
|
"confirmMasterPassword": {
|
||||||
|
"message": "Confirm Master Password"
|
||||||
|
},
|
||||||
|
"confirmFormat": {
|
||||||
|
"message": "Confirm Format"
|
||||||
|
},
|
||||||
|
"filePassword": {
|
||||||
|
"message": "File Password"
|
||||||
|
},
|
||||||
|
"confirmFilePassword": {
|
||||||
|
"message": "Confirm File Password"
|
||||||
|
},
|
||||||
|
"accountBackupOptionDescription": {
|
||||||
|
"message": "Leverages your Bitwarden account encryption, not master password, to protect the export. This export can only be imported into the current account. Use this to create a backup that cannot be used elsewhere."
|
||||||
|
},
|
||||||
|
"passwordProtectedOptionDescription": {
|
||||||
|
"message": "Create a user-generated password to protect the export. Use this to create an export that can be used in other accounts."
|
||||||
|
},
|
||||||
|
"fileTypeHeading": {
|
||||||
|
"message": "File Type"
|
||||||
|
},
|
||||||
|
"accountBackup": {
|
||||||
|
"message": "Account Backup"
|
||||||
|
},
|
||||||
|
"passwordProtected": {
|
||||||
|
"message": "Password Protected"
|
||||||
|
},
|
||||||
|
"filePasswordAndConfirmFilePasswordDoNotMatch": {
|
||||||
|
"message": "“File password” and “Confirm File Password“ do not match."
|
||||||
|
},
|
||||||
|
"confirmVaultImport": {
|
||||||
|
"message": "Confirm Vault Import"
|
||||||
|
},
|
||||||
|
"confirmVaultImportDesc": {
|
||||||
|
"message": "This file is password-protected. Please enter the file password to import data."
|
||||||
|
},
|
||||||
"exportSuccess": {
|
"exportSuccess": {
|
||||||
"message": "Your vault data has been exported."
|
"message": "Your vault data has been exported."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-footer-content {
|
||||||
|
border: none;
|
||||||
|
border-radius: none;
|
||||||
|
@include themify($themes) {
|
||||||
|
background-color: themed("footerBackgroundColor");
|
||||||
|
}
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
pointer-events: auto;
|
||||||
|
background-clip: padding-box;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-dialog {
|
.modal-dialog {
|
||||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
border-radius: 0.3rem;
|
border-radius: 0.3rem;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Directive, EventEmitter, OnInit, Output } from "@angular/core";
|
import { Directive, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core";
|
||||||
import { UntypedFormBuilder } from "@angular/forms";
|
import { UntypedFormBuilder, Validators } from "@angular/forms";
|
||||||
|
import { merge, takeUntil, Subject, startWith } from "rxjs";
|
||||||
|
|
||||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||||
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||||
@@ -10,19 +11,25 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||||
|
import { EncryptedExportType } from "@bitwarden/common/enums/encryptedExportType";
|
||||||
import { EventType } from "@bitwarden/common/enums/eventType";
|
import { EventType } from "@bitwarden/common/enums/eventType";
|
||||||
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class ExportComponent implements OnInit {
|
export class ExportComponent implements OnInit, OnDestroy {
|
||||||
@Output() onSaved = new EventEmitter();
|
@Output() onSaved = new EventEmitter();
|
||||||
|
|
||||||
formPromise: Promise<string>;
|
formPromise: Promise<string>;
|
||||||
disabledByPolicy = false;
|
disabledByPolicy = false;
|
||||||
|
showFilePassword: boolean;
|
||||||
|
showConfirmFilePassword: boolean;
|
||||||
|
|
||||||
exportForm = this.formBuilder.group({
|
exportForm = this.formBuilder.group({
|
||||||
format: ["json"],
|
format: ["json"],
|
||||||
secret: [""],
|
secret: [""],
|
||||||
|
filePassword: ["", Validators.required],
|
||||||
|
confirmFilePassword: ["", Validators.required],
|
||||||
|
fileEncryptionType: [EncryptedExportType.AccountEncrypted],
|
||||||
});
|
});
|
||||||
|
|
||||||
formatOptions = [
|
formatOptions = [
|
||||||
@@ -31,6 +38,8 @@ export class ExportComponent implements OnInit {
|
|||||||
{ name: ".json (Encrypted)", value: "encrypted_json" },
|
{ name: ".json (Encrypted)", value: "encrypted_json" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected cryptoService: CryptoService,
|
protected cryptoService: CryptoService,
|
||||||
protected i18nService: I18nService,
|
protected i18nService: I18nService,
|
||||||
@@ -47,6 +56,18 @@ export class ExportComponent implements OnInit {
|
|||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await this.checkExportDisabled();
|
await this.checkExportDisabled();
|
||||||
|
|
||||||
|
merge(
|
||||||
|
this.exportForm.get("format").valueChanges,
|
||||||
|
this.exportForm.get("fileEncryptionType").valueChanges
|
||||||
|
)
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.pipe(startWith(0))
|
||||||
|
.subscribe(() => this.adjustValidators());
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.destroy$.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkExportDisabled() {
|
async checkExportDisabled() {
|
||||||
@@ -62,6 +83,20 @@ export class ExportComponent implements OnInit {
|
|||||||
return this.format === "encrypted_json";
|
return this.format === "encrypted_json";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async doExport() {
|
||||||
|
try {
|
||||||
|
this.formPromise = this.getExportData();
|
||||||
|
const data = await this.formPromise;
|
||||||
|
this.downloadFile(data);
|
||||||
|
this.saved();
|
||||||
|
await this.collectEvent();
|
||||||
|
this.exportForm.get("secret").setValue("");
|
||||||
|
this.exportForm.clearValidators();
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
if (this.disabledByPolicy) {
|
if (this.disabledByPolicy) {
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
@@ -76,25 +111,15 @@ export class ExportComponent implements OnInit {
|
|||||||
if (!acceptedWarning) {
|
if (!acceptedWarning) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const secret = this.exportForm.get("secret").value;
|
const secret = this.exportForm.get("secret").value;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.userVerificationService.verifyUser(secret);
|
await this.userVerificationService.verifyUser(secret);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
|
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
this.doExport();
|
||||||
this.formPromise = this.getExportData();
|
|
||||||
const data = await this.formPromise;
|
|
||||||
this.downloadFile(data);
|
|
||||||
this.saved();
|
|
||||||
await this.collectEvent();
|
|
||||||
this.exportForm.get("secret").setValue("");
|
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async warningDialog() {
|
async warningDialog() {
|
||||||
@@ -126,7 +151,14 @@ export class ExportComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected getExportData() {
|
protected getExportData() {
|
||||||
return this.exportService.getExport(this.format);
|
if (
|
||||||
|
this.format === "encrypted_json" &&
|
||||||
|
this.fileEncryptionType === EncryptedExportType.FileEncrypted
|
||||||
|
) {
|
||||||
|
return this.exportService.getPasswordProtectedExport(this.filePassword);
|
||||||
|
} else {
|
||||||
|
return this.exportService.getExport(this.format, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getFileName(prefix?: string) {
|
protected getFileName(prefix?: string) {
|
||||||
@@ -150,6 +182,41 @@ export class ExportComponent implements OnInit {
|
|||||||
return this.exportForm.get("format").value;
|
return this.exportForm.get("format").value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get filePassword() {
|
||||||
|
return this.exportForm.get("filePassword").value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get confirmFilePassword() {
|
||||||
|
return this.exportForm.get("confirmFilePassword").value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get fileEncryptionType() {
|
||||||
|
return this.exportForm.get("fileEncryptionType").value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFilePassword() {
|
||||||
|
this.showFilePassword = !this.showFilePassword;
|
||||||
|
document.getElementById("filePassword").focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleConfirmFilePassword() {
|
||||||
|
this.showConfirmFilePassword = !this.showConfirmFilePassword;
|
||||||
|
document.getElementById("confirmFilePassword").focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustValidators() {
|
||||||
|
this.exportForm.get("confirmFilePassword").reset();
|
||||||
|
this.exportForm.get("filePassword").reset();
|
||||||
|
|
||||||
|
if (this.encryptedFormat && this.fileEncryptionType == EncryptedExportType.FileEncrypted) {
|
||||||
|
this.exportForm.controls.filePassword.enable();
|
||||||
|
this.exportForm.controls.confirmFilePassword.enable();
|
||||||
|
} else {
|
||||||
|
this.exportForm.controls.filePassword.disable();
|
||||||
|
this.exportForm.controls.confirmFilePassword.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private downloadFile(csv: string): void {
|
private downloadFile(csv: string): void {
|
||||||
const fileName = this.getFileName();
|
const fileName = this.getFileName();
|
||||||
this.fileDownloadService.download({
|
this.fileDownloadService.download({
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Directive } from "@angular/core";
|
||||||
|
import { FormBuilder, FormControl } from "@angular/forms";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||||
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||||
|
|
||||||
|
import { ModalConfig } from "../services/modal.service";
|
||||||
|
|
||||||
|
import { ModalRef } from "./modal/modal.ref";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to verify the user's identity (using their master password or email-based OTP for Key Connector users). You can customize all of the text in the modal.
|
||||||
|
*/
|
||||||
|
@Directive()
|
||||||
|
export class UserVerificationPromptComponent {
|
||||||
|
confirmDescription = this.config.data.confirmDescription;
|
||||||
|
confirmButtonText = this.config.data.confirmButtonText;
|
||||||
|
modalTitle = this.config.data.modalTitle;
|
||||||
|
secret = new FormControl();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private modalRef: ModalRef,
|
||||||
|
protected config: ModalConfig,
|
||||||
|
protected userVerificationService: UserVerificationService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private i18nService: I18nService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
try {
|
||||||
|
//Incorrect secret will throw an invalid password error.
|
||||||
|
await this.userVerificationService.verifyUser(this.secret.value);
|
||||||
|
} catch (e) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
|
this.i18nService.t("error"),
|
||||||
|
this.i18nService.t("invalidMasterPassword")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.modalRef.close(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -390,6 +390,7 @@ export const LOG_MAC_FAILURES = new InjectionToken<string>("LOG_MAC_FAILURES");
|
|||||||
CipherServiceAbstraction,
|
CipherServiceAbstraction,
|
||||||
ApiServiceAbstraction,
|
ApiServiceAbstraction,
|
||||||
CryptoServiceAbstraction,
|
CryptoServiceAbstraction,
|
||||||
|
CryptoFunctionServiceAbstraction,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
4
libs/common/src/enums/encryptedExportType.ts
Normal file
4
libs/common/src/enums/encryptedExportType.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export enum EncryptedExportType {
|
||||||
|
AccountEncrypted = 0,
|
||||||
|
FileEncrypted = 1,
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im
|
|||||||
|
|
||||||
if (!(await this.checkPassword(parsedData))) {
|
if (!(await this.checkPassword(parsedData))) {
|
||||||
result.success = false;
|
result.success = false;
|
||||||
result.errorMessage = this.i18nService.t("importEncKeyError");
|
result.errorMessage = this.i18nService.t("invalidFilePassword");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user