diff --git a/apps/web/src/app/components/export-file-password-prompt.component.html b/apps/web/src/app/components/export-file-password-prompt.component.html new file mode 100644 index 00000000000..0f31451e01e --- /dev/null +++ b/apps/web/src/app/components/export-file-password-prompt.component.html @@ -0,0 +1,36 @@ + diff --git a/apps/web/src/app/components/export-file-password-prompt.component.ts b/apps/web/src/app/components/export-file-password-prompt.component.ts new file mode 100644 index 00000000000..504c606c4bb --- /dev/null +++ b/apps/web/src/app/components/export-file-password-prompt.component.ts @@ -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 {} diff --git a/apps/web/src/app/modules/loose-components.module.ts b/apps/web/src/app/modules/loose-components.module.ts index 382c7fdd193..66de29888aa 100644 --- a/apps/web/src/app/modules/loose-components.module.ts +++ b/apps/web/src/app/modules/loose-components.module.ts @@ -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, diff --git a/apps/web/src/app/organizations/tools/export.component.ts b/apps/web/src/app/organizations/tools/export.component.ts index e4b89654e9f..d56fa061119 100644 --- a/apps/web/src/app/organizations/tools/export.component.ts +++ b/apps/web/src/app/organizations/tools/export.component.ts @@ -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 { - // TODO - // await this.eventService.collect(EventType.Organization_ClientExportedVault); - } } diff --git a/apps/web/src/app/services/services.module.ts b/apps/web/src/app/services/services.module.ts index f616d683fdd..f055bb66b87 100644 --- a/apps/web/src/app/services/services.module.ts +++ b/apps/web/src/app/services/services.module.ts @@ -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, ], }) diff --git a/apps/web/src/app/tools/export.component.html b/apps/web/src/app/tools/export.component.html index a22ec2dd797..5c94f5dc37b 100644 --- a/apps/web/src/app/tools/export.component.html +++ b/apps/web/src/app/tools/export.component.html @@ -1,6 +1,6 @@
{ - // 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") + ); } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 60aa6e4e275..ff3777ed6f1 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -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." }, diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 555a229131c..c9a76572299 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -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." }, diff --git a/apps/web/src/scss/modals.scss b/apps/web/src/scss/modals.scss index 337bc3169c1..6b141d8677f 100644 --- a/apps/web/src/scss/modals.scss +++ b/apps/web/src/scss/modals.scss @@ -75,6 +75,10 @@ } } +.modal-top-padding-sm { + padding-top: 1.5em; +} + .close { @include themify($themes) { color: themed("textColor"); diff --git a/apps/web/src/services/exportFilePasswordPrompt.service.ts b/apps/web/src/services/exportFilePasswordPrompt.service.ts new file mode 100644 index 00000000000..c18f20f0e9e --- /dev/null +++ b/apps/web/src/services/exportFilePasswordPrompt.service.ts @@ -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; +} diff --git a/libs/angular/src/components/export-file-password-prompt.component.ts b/libs/angular/src/components/export-file-password-prompt.component.ts new file mode 100644 index 00000000000..4f7ae35caef --- /dev/null +++ b/libs/angular/src/components/export-file-password-prompt.component.ts @@ -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); + } +} diff --git a/libs/angular/src/components/export.component.ts b/libs/angular/src/components/export.component.ts index f139aabdb1a..ce5916e13cd 100644 --- a/libs/angular/src/components/export.component.ts +++ b/libs/angular/src/components/export.component.ts @@ -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() { diff --git a/libs/angular/src/services/exportFilePasswordPrompt.service.ts b/libs/angular/src/services/exportFilePasswordPrompt.service.ts new file mode 100644 index 00000000000..6645c12b47e --- /dev/null +++ b/libs/angular/src/services/exportFilePasswordPrompt.service.ts @@ -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()); + } +} diff --git a/libs/common/src/abstractions/exportFilePasswordPrompt.service.ts b/libs/common/src/abstractions/exportFilePasswordPrompt.service.ts new file mode 100644 index 00000000000..ca17d4191e8 --- /dev/null +++ b/libs/common/src/abstractions/exportFilePasswordPrompt.service.ts @@ -0,0 +1,9 @@ +export abstract class ExportFilePasswordPromptService { + protectedFields: () => string[]; + showPasswordPrompt: ( + confirmDescription: string, + confirmButtonText: string, + modalTitle: string + ) => Promise; + enabled: () => Promise; +}