1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-12 14:34:02 +00:00

Updates to export logic modal user secret prompt

This commit is contained in:
CarleyDiaz-Bitwarden
2022-06-20 12:50:56 -04:00
parent 8f06a8cf35
commit f5c78590db
15 changed files with 264 additions and 65 deletions

View File

@@ -0,0 +1,36 @@
<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()">
<div class="tw-text-center bwi-3x">
<i class="bwi bwi-exclamation-triangle text-warning"></i>
</div>
<h2 class="modal-title tw-text-center" id="confirmUserTitle">
{{ modalTitle | i18n }}
</h2>
<div class="modal-body">
<div>
{{ confirmDescription | i18n }}
</div>
</div>
<div class="modal-footer">
<div class="modal-content">
<div [formGroup]="myGroup">
<div class="form-group">
<app-user-verification ngDefaultControl formControlName="secret" name="secret">
</app-user-verification>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{ confirmButtonText | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,8 @@
import { Component } from "@angular/core";
import { ExportFilePasswordPromptComponent as BaseExportFilePasswordPrompt } from "@bitwarden/angular/components/export-file-password-prompt.component";
@Component({
templateUrl: "export-file-password-prompt.component.html",
})
export class ExportFilePasswordPromptComponent extends BaseExportFilePasswordPrompt {}

View File

@@ -19,6 +19,7 @@ import { UpdatePasswordComponent } from "../accounts/update-password.component";
import { UpdateTempPasswordComponent } from "../accounts/update-temp-password.component";
import { VerifyEmailTokenComponent } from "../accounts/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "../accounts/verify-recover-delete.component";
import { ExportFilePasswordPromptComponent } from "../components/export-file-password-prompt.component";
import { FilePasswordPromptComponent } from "../components/file-password-prompt.component";
import { NestedCheckboxComponent } from "../components/nested-checkbox.component";
import { OrganizationSwitcherComponent } from "../components/organization-switcher.component";
@@ -271,6 +272,7 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga
PasswordGeneratorPolicyComponent,
PasswordRepromptComponent,
FilePasswordPromptComponent,
ExportFilePasswordPromptComponent,
PasswordStrengthComponent,
PaymentComponent,
PaymentMethodComponent,

View File

@@ -2,7 +2,8 @@ import { Component } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { ActivatedRoute } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ExportFilePasswordPromptService } from "@bitwarden/angular/services/exportFilePasswordPrompt.service";
import { ModalConfig, ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
@@ -34,7 +35,9 @@ export class ExportComponent extends BaseExportComponent {
formBuilder: FormBuilder,
modalService: ModalService,
apiService: ApiService,
stateService: StateService
stateService: StateService,
exportFilePasswordPromptService: ExportFilePasswordPromptService,
modalConfig: ModalConfig
) {
super(
cryptoService,
@@ -48,8 +51,13 @@ export class ExportComponent extends BaseExportComponent {
formBuilder,
modalService,
apiService,
stateService
stateService,
exportFilePasswordPromptService,
modalConfig
);
this.confirmDescription = modalConfig.data.confirmDescription;
this.confirmButtonText = modalConfig.data.confirmButtonText;
this.modalTitle = modalConfig.data.modalTitle;
}
async ngOnInit() {
@@ -70,9 +78,4 @@ export class ExportComponent extends BaseExportComponent {
getFileName() {
return super.getFileName("org");
}
async collectEvent(): Promise<any> {
// TODO
// await this.eventService.collect(EventType.Organization_ClientExportedVault);
}
}

View File

@@ -9,13 +9,14 @@ import {
LOCALES_DIRECTORY,
SYSTEM_LANGUAGE,
} from "@bitwarden/angular/services/jslib-services.module";
import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service";
import { ModalService as ModalServiceAbstraction , ModalConfig as ModalConfigAbstraction , ModalConfig } from "@bitwarden/angular/services/modal.service";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/common/abstractions/collection.service";
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { ExportService as ExportServiceAbstraction } from "@bitwarden/common/abstractions/export.service";
import { ExportFilePasswordPromptService as ExportFilePasswordPromptServiceAbstraction } from "@bitwarden/common/abstractions/exportFilePasswordPrompt.service";
import { FilePasswordPromptService as FilePasswordPromptServiceAbstraction } from "@bitwarden/common/abstractions/filePasswordPrompt.service";
import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/abstractions/folder.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service";
@@ -35,6 +36,7 @@ import { StateService as StateServiceAbstraction } from "../../abstractions/stat
import { Account } from "../../models/account";
import { GlobalState } from "../../models/globalState";
import { BroadcasterMessagingService } from "../../services/broadcasterMessaging.service";
import { ExportFilePasswordPromptService } from "../../services/exportFilePasswordPrompt.service";
import { FilePasswordPromptService } from "../../services/filePasswordPrompt.service";
import { HtmlStorageService } from "../../services/htmlStorage.service";
import { I18nService } from "../../services/i18n.service";
@@ -95,6 +97,7 @@ import { RouterService } from "./router.service";
},
{ provide: MessagingServiceAbstraction, useClass: BroadcasterMessagingService },
{ provide: ModalServiceAbstraction, useClass: ModalService },
{ provide: ModalConfigAbstraction, useClass: ModalConfig },
{
provide: ImportServiceAbstraction,
useClass: ImportService,
@@ -148,6 +151,10 @@ import { RouterService } from "./router.service";
provide: FilePasswordPromptServiceAbstraction,
useClass: FilePasswordPromptService,
},
{
provide: ExportFilePasswordPromptServiceAbstraction,
useClass: ExportFilePasswordPromptService,
},
HomeGuard,
],
})

View File

@@ -1,6 +1,6 @@
<form
#form
(ngSubmit)="promptUserForSecret()"
(ngSubmit)="promptUserForSecret(encryptionType)"
ngNativeValidate
[appApiAction]="formPromise"
[formGroup]="exportForm"

View File

@@ -2,11 +2,12 @@ import { Component, EventEmitter, Output, ViewChild, ViewContainerRef } from "@a
import { FormBuilder, FormControl } from "@angular/forms";
import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/components/export.component";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ModalConfig, ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { ExportService } from "@bitwarden/common/abstractions/export.service";
import { ExportFilePasswordPromptService } from "@bitwarden/common/abstractions/exportFilePasswordPrompt.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
@@ -14,8 +15,6 @@ import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { ApiKeyComponent } from "../settings/api-key.component";
@Component({
selector: "app-export",
templateUrl: "export.component.html",
@@ -28,6 +27,9 @@ export class ExportComponent extends BaseExportComponent {
showConfirmPassword: boolean;
secretValue: string;
secret: FormControl;
confirmDescription: string;
confirmButtonText: string;
modalTitle: string;
@ViewChild("viewUserApiKeyModalRef", { read: ViewContainerRef, static: true })
viewUserApiKeyModalRef: ViewContainerRef;
@@ -44,7 +46,9 @@ export class ExportComponent extends BaseExportComponent {
formBuilder: FormBuilder,
modalService: ModalService,
apiService: ApiService,
stateService: StateService
stateService: StateService,
exportFilePasswordPromptService: ExportFilePasswordPromptService,
modalConfig: ModalConfig
) {
super(
cryptoService,
@@ -59,31 +63,52 @@ export class ExportComponent extends BaseExportComponent {
formBuilder,
modalService,
apiService,
stateService
stateService,
exportFilePasswordPromptService,
modalConfig
);
}
async promptUserForSecret() {
async promptUserForSecret(encryptionType: string) {
//Default text values
let confirmDescription = "encExportKeyWarningDesc";
let confirmButtonText = "exportVault";
let modalTitle = "confirmVaultExport";
//Password encrypted export
if (encryptionType == "2") {
confirmDescription = "confirmVaultExportDesc";
confirmButtonText = "exportVault";
modalTitle = "confirmVaultExport";
}
const entityId = await this.stateService.getUserId();
try {
// //TODO get help from Thomas on this/ other options I have to get a Secret :
// await this.modalService.openViewRef(ApiKeyComponent, this.viewUserApiKeyModalRef, (comp) => {
// comp.keyType = "user";
// comp.entityId = entityId;
// comp.postKey = this.apiService.postUserApiKey.bind(this.apiService);
// comp.scope = "api";
// comp.grantType = "client_credentials";
// comp.apiKeyTitle = "apiKey";
// comp.apiKeyWarning = "userApiKeyWarning";
// comp.apiKeyDescription = "userApiKeyDesc";
// });
if (
await this.exportFilePasswordPromptService.showPasswordPrompt(
confirmDescription,
confirmButtonText,
modalTitle
)
) {
//successful
} else {
//failed
this.platformUtilsService.showToast(
"error",
this.i18nService.t("error"),
this.i18nService.t("invalidMasterPassword")
);
return;
}
// this.platformUtilsService.showToast("error", "This line doesn't get called because of an error ", this.i18nService.t("exportFail"));
//If verification is successful:
this.submitWithSecretAlreadyVerified();
} catch {
this.platformUtilsService.showToast("error", "FAIL", this.i18nService.t("exportFail"));
this.platformUtilsService.showToast(
"error",
this.i18nService.t("error"),
this.i18nService.t("invalidMasterPassword")
);
}
}

View File

@@ -878,6 +878,15 @@
"fileFormat": {
"message": "File Format"
},
"confirmVaultExportDesc" : {
"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"
},
@@ -900,7 +909,7 @@
"message": "Confirm Vault Import"
},
"confirmVaultImportDesc": {
"message": "This file is password protected. Please enter the file password to import data."
"message": "This file is password-protected. Please enter the file password to import data."
},
"exportSuccess": {
"message": "Your vault data has been exported."
@@ -4725,9 +4734,6 @@
"confirmIdentity": {
"message": "Confirm your identity to continue."
},
"exportPasswordDescription" : {
"message": "This password will be used to export and import this file"
},
"verificationCodeRequired": {
"message": "Verification code is required."
},

View File

@@ -878,30 +878,6 @@
"fileFormat": {
"message": "File format"
},
"confirmFormat": {
"message": "Confirm Format"
},
"filePassword": {
"message": "File Password"
},
"confirmFilePassword": {
"message": "Confirm File Password"
},
"passwordProtectedOptionDescription": {
"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."
},
"accountBackupOptionDescription": {
"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"
},
"confirmVaultImport": {
"message": "Confirm Vault Import"
},
"confirmVaultImportDesc": {
"message": "This file is password protected. Please enter the file password to import data."
},
"exportSuccess": {
"message": "Your vault data has been exported."
},
@@ -4725,9 +4701,6 @@
"confirmIdentity": {
"message": "Confirm your identity to continue."
},
"exportPasswordDescription" : {
"message": "This password will be used to export and import this file"
},
"verificationCodeRequired": {
"message": "Verification code is required."
},

View File

@@ -75,6 +75,10 @@
}
}
.modal-top-padding-sm {
padding-top: 1.5em;
}
.close {
@include themify($themes) {
color: themed("textColor");

View File

@@ -0,0 +1,10 @@
import { Injectable } from "@angular/core";
import { ExportFilePasswordPromptService as BaseExportFilePasswordPrompt } from "@bitwarden/angular/services/exportFilePasswordPrompt.service";
import { ExportFilePasswordPromptComponent } from "../app/components/export-file-password-prompt.component";
@Injectable()
export class ExportFilePasswordPromptService extends BaseExportFilePasswordPrompt {
component = ExportFilePasswordPromptComponent;
}

View File

@@ -0,0 +1,60 @@
import { Directive } from "@angular/core";
import { FormBuilder, FormControl, FormGroup } from "@angular/forms";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { ImportService } from "@bitwarden/common/abstractions/import.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { ModalConfig } from "../services/modal.service";
import { ModalRef } from "./modal/modal.ref";
/**
* Used to verify the user's secret, you can customize all of the text in the modal.
*/
@Directive()
export class ExportFilePasswordPromptComponent {
showPassword = false;
organizationId = "";
confirmDescription = "";
confirmButtonText = "";
modalTitle = "";
myGroup = this.formBuilder.group({
secret: new FormControl(),
});
constructor(
private modalRef: ModalRef,
private cryptoService: CryptoService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private importService: ImportService,
protected config: ModalConfig,
protected userVerificationService: UserVerificationService,
private formBuilder: FormBuilder
) {
this.confirmDescription = config.data.confirmDescription;
this.confirmButtonText = config.data.confirmButtonText;
this.modalTitle = config.data.modalTitle;
}
togglePassword() {
this.showPassword = !this.showPassword;
}
async submit() {
const secret = this.myGroup.get("secret").value;
try {
await this.userVerificationService.verifyUser(secret);
} catch (e) {
this.modalRef.close(false);
return;
}
this.modalRef.close(true);
}
}

View File

@@ -8,10 +8,12 @@ import {
} from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { ModalConfig, ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { ExportService } from "@bitwarden/common/abstractions/export.service";
import { ExportFilePasswordPromptService } from "@bitwarden/common/abstractions/exportFilePasswordPrompt.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
@@ -21,8 +23,6 @@ import { UserVerificationService } from "@bitwarden/common/abstractions/userVeri
import { EventType } from "@bitwarden/common/enums/eventType";
import { PolicyType } from "@bitwarden/common/enums/policyType";
import { ModalService } from "../services/modal.service";
@Directive()
export class ExportComponent implements OnInit {
@Output() onSaved = new EventEmitter();
@@ -63,7 +63,9 @@ export class ExportComponent implements OnInit {
private formBuilder: FormBuilder,
protected modalService: ModalService,
protected apiService: ApiService,
protected stateService: StateService
protected stateService: StateService,
protected exportFilePasswordPromptService: ExportFilePasswordPromptService,
protected modalConfig: ModalConfig
) {}
async ngOnInit() {

View File

@@ -0,0 +1,54 @@
import { Injectable } from "@angular/core";
import { ExportFilePasswordPromptService as ExportFilePasswordPromptServiceAbstraction } from "@bitwarden/common/abstractions/exportFilePasswordPrompt.service";
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
import { ExportFilePasswordPromptComponent } from "../components/export-file-password-prompt.component";
import { ModalService } from "./modal.service";
/**
* Used to verify the user's File Password for the "Import passwords using File Password" feature only.
*/
@Injectable()
export class ExportFilePasswordPromptService implements ExportFilePasswordPromptServiceAbstraction {
protected component = ExportFilePasswordPromptComponent;
constructor(
private modalService: ModalService,
private keyConnectorService: KeyConnectorService
) {}
protectedFields() {
return ["TOTP", "Password", "H_Field", "Card Number", "Security Code"];
}
async showPasswordPrompt(
confirmDescription: string,
confirmButtonText: string,
modalTitle: string
) {
if (!(await this.enabled())) {
return true;
}
const ref = await this.modalService.open(this.component, {
allowMultipleModals: true,
data: {
confirmDescription: confirmDescription,
confirmButtonText: confirmButtonText,
modalTitle: modalTitle,
},
});
if (ref == null) {
return false;
}
return (await ref.onClosedPromise()) === true;
}
async enabled() {
return !(await this.keyConnectorService.getUsesKeyConnector());
}
}

View File

@@ -0,0 +1,9 @@
export abstract class ExportFilePasswordPromptService {
protectedFields: () => string[];
showPasswordPrompt: (
confirmDescription: string,
confirmButtonText: string,
modalTitle: string
) => Promise<boolean>;
enabled: () => Promise<boolean>;
}