Skip to content

Commit

Permalink
chore: should validate password before deleting vault (#2400)
Browse files Browse the repository at this point in the history
  • Loading branch information
amanharwara committed Aug 9, 2023
1 parent 43e4e0e commit 875e8c3
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/services/src/Domain/Strings/Messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<boolean> {
constructor(
private vaultLocks: VaultLockServiceInterface,
private protection: ProtectionsClientInterface,
private challenges: ChallengeServiceInterface,
private _validateVaultPassword: ValidateVaultPassword,
) {}

async execute(vault: VaultListingInterface): Promise<Result<boolean>> {
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)
})
}
}
6 changes: 6 additions & 0 deletions packages/services/src/Domain/Vault/VaultService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<VaultServiceEvent, VaultServiceEventPayload[VaultServiceEvent]>
Expand All @@ -55,6 +56,7 @@ export class VaultService
private _sendVaultDataChangeMessage: SendVaultDataChangedMessage,
private _isVaultOwner: IsVaultOwner,
private _validateVaultPassword: ValidateVaultPassword,
private _authorizeVaultDeletion: AuthorizeVaultDeletion,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
Expand Down Expand Up @@ -183,6 +185,10 @@ export class VaultService
return this.items.findSureItem(item.uuid)
}

authorizeVaultDeletion(vault: VaultListingInterface): Promise<Result<boolean>> {
return this._authorizeVaultDeletion.execute(vault)
}

async deleteVault(vault: VaultListingInterface): Promise<boolean> {
if (vault.isSharedVaultListing()) {
throw new Error('Shared vault must be deleted through SharedVaultService')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface VaultServiceInterface

getVaults(): VaultListingInterface[]
getVault(dto: { keySystemIdentifier: KeySystemIdentifier }): VaultListingInterface | undefined
authorizeVaultDeletion(vault: VaultListingInterface): Promise<Result<boolean>>
deleteVault(vault: VaultListingInterface): Promise<boolean>

moveItemToVault(
Expand Down
11 changes: 11 additions & 0 deletions packages/snjs/lib/Application/Dependencies/Dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<symbol, () => unknown>()
Expand Down Expand Up @@ -225,6 +226,15 @@ export class Dependencies {
)
})

this.factory.set(TYPES.AuthorizeVaultDeletion, () => {
return new AuthorizeVaultDeletion(
this.get<VaultLockService>(TYPES.VaultLockService),
this.get<ProtectionService>(TYPES.ProtectionService),
this.get<ChallengeService>(TYPES.ChallengeService),
this.get<ValidateVaultPassword>(TYPES.ValidateVaultPassword),
)
})

this.factory.set(TYPES.GenerateUuid, () => {
return new GenerateUuid(this.get<PureCryptoInterface>(TYPES.Crypto))
})
Expand Down Expand Up @@ -879,6 +889,7 @@ export class Dependencies {
this.get<SendVaultDataChangedMessage>(TYPES.SendVaultDataChangedMessage),
this.get<IsVaultOwner>(TYPES.IsVaultOwner),
this.get<ValidateVaultPassword>(TYPES.ValidateVaultPassword),
this.get<AuthorizeVaultDeletion>(TYPES.AuthorizeVaultDeletion),
this.get<InternalEventBus>(TYPES.InternalEventBus),
)
})
Expand Down
1 change: 1 addition & 0 deletions packages/snjs/lib/Application/Dependencies/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down

0 comments on commit 875e8c3

Please sign in to comment.