Files
standardnotes-app-web/packages/services/src/Domain/Vault/VaultService.ts
2023-07-26 04:55:58 -05:00

245 lines
8.0 KiB
TypeScript

import { isClientDisplayableError } from '@standardnotes/responses'
import {
DecryptedItemInterface,
FileItem,
KeySystemIdentifier,
KeySystemRootKeyStorageMode,
VaultListingInterface,
VaultListingMutator,
isNote,
} from '@standardnotes/models'
import { VaultServiceInterface } from './VaultServiceInterface'
import { ChangeVaultKeyOptionsDTO } from './UseCase/ChangeVaultKeyOptionsDTO'
import { VaultServiceEvent, VaultServiceEventPayload } from './VaultServiceEvent'
import { CreateVault } from './UseCase/CreateVault'
import { AbstractService } from '../Service/AbstractService'
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { RemoveItemFromVault } from './UseCase/RemoveItemFromVault'
import { DeleteVault } from './UseCase/DeleteVault'
import { MoveItemsToVault } from './UseCase/MoveItemsToVault'
import { RotateVaultKey } from './UseCase/RotateVaultKey'
import { GetVault } from './UseCase/GetVault'
import { ChangeVaultKeyOptions } from './UseCase/ChangeVaultKeyOptions'
import { MutatorClientInterface } from '../Mutator/MutatorClientInterface'
import { AlertService } from '../Alert/AlertService'
import { GetVaults } from './UseCase/GetVaults'
import { VaultLockServiceInterface } from '../VaultLock/VaultLockServiceInterface'
import { Result } from '@standardnotes/domain-core'
export class VaultService
extends AbstractService<VaultServiceEvent, VaultServiceEventPayload[VaultServiceEvent]>
implements VaultServiceInterface
{
constructor(
private sync: SyncServiceInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private vaultLocks: VaultLockServiceInterface,
private alerts: AlertService,
private _getVault: GetVault,
private _getVaults: GetVaults,
private _changeVaultKeyOptions: ChangeVaultKeyOptions,
private _moveItemsToVault: MoveItemsToVault,
private _createVault: CreateVault,
private _removeItemFromVault: RemoveItemFromVault,
private _deleteVault: DeleteVault,
private _rotateVaultKey: RotateVaultKey,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
}
override deinit(): void {
super.deinit()
;(this.sync as unknown) = undefined
;(this.items as unknown) = undefined
;(this.mutator as unknown) = undefined
;(this.vaultLocks as unknown) = undefined
;(this.alerts as unknown) = undefined
;(this._getVault as unknown) = undefined
;(this._getVaults as unknown) = undefined
;(this._changeVaultKeyOptions as unknown) = undefined
;(this._moveItemsToVault as unknown) = undefined
;(this._createVault as unknown) = undefined
;(this._removeItemFromVault as unknown) = undefined
;(this._deleteVault as unknown) = undefined
;(this._rotateVaultKey as unknown) = undefined
}
getVaults(): VaultListingInterface[] {
return this._getVaults.execute().getValue()
}
public getVault(dto: { keySystemIdentifier: KeySystemIdentifier }): VaultListingInterface | undefined {
const result = this._getVault.execute(dto)
if (result.isFailed()) {
return undefined
}
return result.getValue()
}
public getSureVault(dto: { keySystemIdentifier: KeySystemIdentifier }): VaultListingInterface {
const vault = this.getVault(dto)
if (!vault) {
throw new Error('Vault not found')
}
return vault
}
async createRandomizedVault(dto: { name: string; description?: string }): Promise<VaultListingInterface> {
return this.createVaultWithParameters({
name: dto.name,
description: dto.description,
userInputtedPassword: undefined,
storagePreference: KeySystemRootKeyStorageMode.Synced,
})
}
async createUserInputtedPasswordVault(dto: {
name: string
description?: string
userInputtedPassword: string
storagePreference: KeySystemRootKeyStorageMode
}): Promise<VaultListingInterface> {
return this.createVaultWithParameters(dto)
}
private async createVaultWithParameters(dto: {
name: string
description?: string
userInputtedPassword: string | undefined
storagePreference: KeySystemRootKeyStorageMode
}): Promise<VaultListingInterface> {
const result = await this._createVault.execute({
vaultName: dto.name,
vaultDescription: dto.description,
userInputtedPassword: dto.userInputtedPassword,
storagePreference: dto.storagePreference,
})
return result
}
async moveItemToVault(
vault: VaultListingInterface,
item: DecryptedItemInterface,
): Promise<DecryptedItemInterface | undefined> {
if (this.vaultLocks.isVaultLocked(vault)) {
throw new Error('Attempting to add item to locked vault')
}
let linkedFiles: FileItem[] = []
if (isNote(item)) {
linkedFiles = this.items.getNoteLinkedFiles(item)
if (linkedFiles.length > 0) {
const confirmed = await this.alerts.confirmV2({
title: 'Linked files will be moved to vault',
text: `This note has ${linkedFiles.length} linked files. They will also be moved to the vault. Do you want to continue?`,
})
if (!confirmed) {
return undefined
}
}
}
await this._moveItemsToVault.execute({ vault, items: [item, ...linkedFiles] })
return this.items.findSureItem(item.uuid)
}
async removeItemFromVault(item: DecryptedItemInterface): Promise<DecryptedItemInterface> {
const vault = this.getItemVault(item)
if (!vault) {
throw new Error('Cannot find vault to remove item from')
}
if (this.vaultLocks.isVaultLocked(vault)) {
throw new Error('Attempting to remove item from locked vault')
}
await this._removeItemFromVault.execute({ item })
return this.items.findSureItem(item.uuid)
}
async deleteVault(vault: VaultListingInterface): Promise<boolean> {
if (vault.isSharedVaultListing()) {
throw new Error('Shared vault must be deleted through SharedVaultService')
}
const error = await this._deleteVault.execute(vault)
if (isClientDisplayableError(error)) {
return false
}
await this.sync.sync()
return true
}
async changeVaultNameAndDescription(
vault: VaultListingInterface,
params: { name: string; description?: string },
): Promise<VaultListingInterface> {
const updatedVault = await this.mutator.changeItem<VaultListingMutator, VaultListingInterface>(vault, (mutator) => {
mutator.name = params.name
mutator.description = params.description
})
await this.sync.sync()
return updatedVault
}
async rotateVaultRootKey(vault: VaultListingInterface, vaultPassword?: string): Promise<void> {
if (this.vaultLocks.isVaultLocked(vault)) {
throw new Error('Cannot rotate root key of locked vault')
}
await this._rotateVaultKey.execute({
vault,
sharedVaultUuid: vault.isSharedVaultListing() ? vault.sharing.sharedVaultUuid : undefined,
userInputtedPassword: vaultPassword,
})
await this.notifyEventSync(VaultServiceEvent.VaultRootKeyRotated, { vault })
await this.sync.sync()
}
isItemInVault(item: DecryptedItemInterface): boolean {
return item.key_system_identifier !== undefined
}
getItemVault(item: DecryptedItemInterface): VaultListingInterface | undefined {
const latestItem = this.items.findItem(item.uuid)
if (!latestItem) {
throw new Error('Cannot find latest version of item to get vault for')
}
if (!latestItem.key_system_identifier) {
return undefined
}
return this.getVault({ keySystemIdentifier: latestItem.key_system_identifier })
}
async changeVaultOptions(dto: ChangeVaultKeyOptionsDTO): Promise<Result<void>> {
if (this.vaultLocks.isVaultLocked(dto.vault)) {
throw new Error('Attempting to change vault options on a locked vault')
}
const result = await this._changeVaultKeyOptions.execute(dto)
if (dto.newPasswordType) {
await this.notifyEventSync(VaultServiceEvent.VaultRootKeyRotated, { vault: dto.vault })
}
return result
}
}