From 875e8c3ee4021e6373a0df9417aef43e3295ef9a Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Wed, 9 Aug 2023 22:17:47 +0530 Subject: [PATCH] chore: should validate password before deleting vault (#2400) --- .../services/src/Domain/Strings/Messages.ts | 1 + .../Vault/UseCase/AuthorizeVaultDeletion.ts | 71 +++++++++++++++++++ .../services/src/Domain/Vault/VaultService.ts | 6 ++ .../src/Domain/Vault/VaultServiceInterface.ts | 1 + .../Application/Dependencies/Dependencies.ts | 11 +++ .../lib/Application/Dependencies/Types.ts | 1 + .../Panes/Vaults/Vaults/VaultItem.tsx | 7 ++ 7 files changed, 98 insertions(+) create mode 100644 packages/services/src/Domain/Vault/UseCase/AuthorizeVaultDeletion.ts diff --git a/packages/services/src/Domain/Strings/Messages.ts b/packages/services/src/Domain/Strings/Messages.ts index 0bf160cbb5c..0814b9535d1 100644 --- a/packages/services/src/Domain/Strings/Messages.ts +++ b/packages/services/src/Domain/Strings/Messages.ts @@ -168,6 +168,7 @@ export const ChallengeStrings = { DeleteAccount: 'Authentication is required to delete your account', ListedAuthorization: 'Authentication is required to approve this note for Listed', UnlockVault: (vaultName: string) => `Unlock ${vaultName}`, + DeleteVault: (vaultName: string) => `Delete ${vaultName}`, EnterVaultPassword: 'Enter the password for this vault', } diff --git a/packages/services/src/Domain/Vault/UseCase/AuthorizeVaultDeletion.ts b/packages/services/src/Domain/Vault/UseCase/AuthorizeVaultDeletion.ts new file mode 100644 index 00000000000..345ad8dd941 --- /dev/null +++ b/packages/services/src/Domain/Vault/UseCase/AuthorizeVaultDeletion.ts @@ -0,0 +1,71 @@ +import { VaultListingInterface } from '@standardnotes/models' +import { ProtectionsClientInterface } from '../../Protection/ProtectionClientInterface' +import { VaultLockServiceInterface } from '../../VaultLock/VaultLockServiceInterface' +import { + ChallengeReason, + Challenge, + ChallengePrompt, + ChallengeServiceInterface, + ChallengeValidation, +} from '../../Challenge' +import { ChallengeStrings } from '../../Strings/Messages' +import { ValidateVaultPassword } from '../../VaultLock/UseCase/ValidateVaultPassword' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' + +export class AuthorizeVaultDeletion implements UseCaseInterface { + constructor( + private vaultLocks: VaultLockServiceInterface, + private protection: ProtectionsClientInterface, + private challenges: ChallengeServiceInterface, + private _validateVaultPassword: ValidateVaultPassword, + ) {} + + async execute(vault: VaultListingInterface): Promise> { + if (!this.vaultLocks.isVaultLockable(vault)) { + const authorized = await this.protection.authorizeAction(ChallengeReason.Custom, { + fallBackToAccountPassword: true, + requireAccountPassword: false, + forcePrompt: true, + }) + return Result.ok(authorized) + } + + const challenge = new Challenge( + [new ChallengePrompt(ChallengeValidation.None, undefined, 'Password')], + ChallengeReason.Custom, + true, + ChallengeStrings.DeleteVault(vault.name), + ChallengeStrings.EnterVaultPassword, + ) + + return new Promise((resolve) => { + this.challenges.addChallengeObserver(challenge, { + onCancel() { + resolve(Result.ok(false)) + }, + onNonvalidatedSubmit: async (challengeResponse) => { + const value = challengeResponse.getDefaultValue() + if (!value) { + this.challenges.completeChallenge(challenge) + resolve(Result.ok(false)) + return + } + + const password = value.value as string + + const validPassword = this._validateVaultPassword.execute(vault, password).getValue() + if (!validPassword) { + this.challenges.setValidationStatusForChallenge(challenge, value, false) + resolve(Result.ok(false)) + return + } + + this.challenges.completeChallenge(challenge) + resolve(Result.ok(true)) + }, + }) + + void this.challenges.promptForChallengeResponse(challenge) + }) + } +} diff --git a/packages/services/src/Domain/Vault/VaultService.ts b/packages/services/src/Domain/Vault/VaultService.ts index 14e17843577..0e580bddc6b 100644 --- a/packages/services/src/Domain/Vault/VaultService.ts +++ b/packages/services/src/Domain/Vault/VaultService.ts @@ -33,6 +33,7 @@ import { AlertService } from '../Alert/AlertService' import { GetVaults } from './UseCase/GetVaults' import { VaultLockServiceInterface } from '../VaultLock/VaultLockServiceInterface' import { Result } from '@standardnotes/domain-core' +import { AuthorizeVaultDeletion } from './UseCase/AuthorizeVaultDeletion' export class VaultService extends AbstractService @@ -55,6 +56,7 @@ export class VaultService private _sendVaultDataChangeMessage: SendVaultDataChangedMessage, private _isVaultOwner: IsVaultOwner, private _validateVaultPassword: ValidateVaultPassword, + private _authorizeVaultDeletion: AuthorizeVaultDeletion, eventBus: InternalEventBusInterface, ) { super(eventBus) @@ -183,6 +185,10 @@ export class VaultService return this.items.findSureItem(item.uuid) } + authorizeVaultDeletion(vault: VaultListingInterface): Promise> { + return this._authorizeVaultDeletion.execute(vault) + } + async deleteVault(vault: VaultListingInterface): Promise { if (vault.isSharedVaultListing()) { throw new Error('Shared vault must be deleted through SharedVaultService') diff --git a/packages/services/src/Domain/Vault/VaultServiceInterface.ts b/packages/services/src/Domain/Vault/VaultServiceInterface.ts index 1ad608e29ef..fcf7d60b880 100644 --- a/packages/services/src/Domain/Vault/VaultServiceInterface.ts +++ b/packages/services/src/Domain/Vault/VaultServiceInterface.ts @@ -29,6 +29,7 @@ export interface VaultServiceInterface getVaults(): VaultListingInterface[] getVault(dto: { keySystemIdentifier: KeySystemIdentifier }): VaultListingInterface | undefined + authorizeVaultDeletion(vault: VaultListingInterface): Promise> deleteVault(vault: VaultListingInterface): Promise moveItemToVault( diff --git a/packages/snjs/lib/Application/Dependencies/Dependencies.ts b/packages/snjs/lib/Application/Dependencies/Dependencies.ts index 9cb6cacb39b..730f2fe5444 100644 --- a/packages/snjs/lib/Application/Dependencies/Dependencies.ts +++ b/packages/snjs/lib/Application/Dependencies/Dependencies.ts @@ -164,6 +164,7 @@ import { Logger, isNotUndefined, isDeinitable } from '@standardnotes/utils' import { EncryptionOperators } from '@standardnotes/encryption' import { AsymmetricMessagePayload, AsymmetricMessageSharedVaultInvite } from '@standardnotes/models' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' +import { AuthorizeVaultDeletion } from '@standardnotes/services/src/Domain/Vault/UseCase/AuthorizeVaultDeletion' export class Dependencies { private factory = new Map unknown>() @@ -225,6 +226,15 @@ export class Dependencies { ) }) + this.factory.set(TYPES.AuthorizeVaultDeletion, () => { + return new AuthorizeVaultDeletion( + this.get(TYPES.VaultLockService), + this.get(TYPES.ProtectionService), + this.get(TYPES.ChallengeService), + this.get(TYPES.ValidateVaultPassword), + ) + }) + this.factory.set(TYPES.GenerateUuid, () => { return new GenerateUuid(this.get(TYPES.Crypto)) }) @@ -879,6 +889,7 @@ export class Dependencies { this.get(TYPES.SendVaultDataChangedMessage), this.get(TYPES.IsVaultOwner), this.get(TYPES.ValidateVaultPassword), + this.get(TYPES.AuthorizeVaultDeletion), this.get(TYPES.InternalEventBus), ) }) diff --git a/packages/snjs/lib/Application/Dependencies/Types.ts b/packages/snjs/lib/Application/Dependencies/Types.ts index eb2231f33fd..60e4c7a1c37 100644 --- a/packages/snjs/lib/Application/Dependencies/Types.ts +++ b/packages/snjs/lib/Application/Dependencies/Types.ts @@ -163,6 +163,7 @@ export const TYPES = { GenerateUuid: Symbol.for('GenerateUuid'), GetVaultItems: Symbol.for('GetVaultItems'), ValidateVaultPassword: Symbol.for('ValidateVaultPassword'), + AuthorizeVaultDeletion: Symbol.for('AuthorizeVaultDeletion'), // Mappers SessionStorageMapper: Symbol.for('SessionStorageMapper'), diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultItem.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultItem.tsx index 964dc5cc613..6fee6bd50e0 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultItem.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultItem.tsx @@ -29,10 +29,17 @@ const VaultItem = ({ vault }: Props) => { undefined, ButtonType.Danger, ) + if (!confirm) { return } + const authorized = await application.vaults.authorizeVaultDeletion(vault) + + if (!authorized.getValue()) { + return + } + if (vault.isSharedVaultListing()) { const result = await application.sharedVaults.deleteSharedVault(vault) if (isClientDisplayableError(result)) {