tests: vault tests (#2366)

This commit is contained in:
Mo
2023-07-25 07:40:28 -05:00
committed by GitHub
parent 80436cd0b9
commit 596e041c42
47 changed files with 479 additions and 289 deletions

View File

@@ -1,3 +1,5 @@
import { VaultUserServiceInterface, VaultInviteServiceInterface } from '@standardnotes/services'
import { VaultLockServiceInterface } from './../VaultLock/VaultLockServiceInterface'
import { HistoryServiceInterface } from './../History/HistoryServiceInterface'
import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface'
import { PreferenceServiceInterface } from './../Preferences/PreferenceServiceInterface'
@@ -5,7 +7,7 @@ import { AsymmetricMessageServiceInterface } from './../AsymmetricMessage/Asymme
import { SyncOptions } from './../Sync/SyncOptions'
import { ImportDataReturnType } from './../Mutator/ImportDataUseCase'
import { ChallengeServiceInterface } from './../Challenge/ChallengeServiceInterface'
import { VaultServiceInterface } from './../Vaults/VaultServiceInterface'
import { VaultServiceInterface } from '../Vault/VaultServiceInterface'
import { ApplicationIdentifier } from '@standardnotes/common'
import {
BackupFile,
@@ -95,24 +97,27 @@ export interface ApplicationInterface {
syncOptions?: SyncOptions,
): Promise<void>
get features(): FeaturesClientInterface
get componentManager(): ComponentManagerInterface
get items(): ItemManagerInterface
get mutator(): MutatorClientInterface
get user(): UserClientInterface
get files(): FilesClientInterface
get subscriptions(): SubscriptionManagerInterface
get fileBackups(): BackupServiceInterface | undefined
get sessions(): SessionsClientInterface
get homeServer(): HomeServerServiceInterface | undefined
get vaults(): VaultServiceInterface
get challenges(): ChallengeServiceInterface
get alerts(): AlertService
get asymmetric(): AsymmetricMessageServiceInterface
get preferences(): PreferenceServiceInterface
get events(): InternalEventBusInterface
get history(): HistoryServiceInterface
get challenges(): ChallengeServiceInterface
get componentManager(): ComponentManagerInterface
get encryption(): EncryptionProviderInterface
get events(): InternalEventBusInterface
get features(): FeaturesClientInterface
get fileBackups(): BackupServiceInterface | undefined
get files(): FilesClientInterface
get history(): HistoryServiceInterface
get homeServer(): HomeServerServiceInterface | undefined
get items(): ItemManagerInterface
get mutator(): MutatorClientInterface
get preferences(): PreferenceServiceInterface
get sessions(): SessionsClientInterface
get subscriptions(): SubscriptionManagerInterface
get user(): UserClientInterface
get vaults(): VaultServiceInterface
get vaultLocks(): VaultLockServiceInterface
get vaultUsers(): VaultUserServiceInterface
get vaultInvites(): VaultInviteServiceInterface
readonly identifier: ApplicationIdentifier
readonly platform: Platform

View File

@@ -1,3 +1,4 @@
import { GetVault } from './../Vault/UseCase/GetVault'
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface'
import { GetUntrustedPayload } from './UseCase/GetUntrustedPayload'
@@ -5,7 +6,6 @@ import { GetInboundMessages } from './UseCase/GetInboundMessages'
import { GetOutboundMessages } from './UseCase/GetOutboundMessages'
import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessage'
import { HandleRootKeyChangedMessage } from './UseCase/HandleRootKeyChangedMessage'
import { GetVault } from './../Vaults/UseCase/GetVault'
import { GetTrustedPayload } from './UseCase/GetTrustedPayload'
import { ReplaceContactData } from './../Contacts/UseCase/ReplaceContactData'
import { GetAllContacts } from './../Contacts/UseCase/GetAllContacts'

View File

@@ -26,7 +26,7 @@ import { UserKeyPairChangedEventData } from '../Session/UserKeyPairChangedEventD
import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessage'
import { GetOutboundMessages } from './UseCase/GetOutboundMessages'
import { GetInboundMessages } from './UseCase/GetInboundMessages'
import { GetVault } from '../Vaults/UseCase/GetVault'
import { GetVault } from '../Vault/UseCase/GetVault'
import { AsymmetricMessageServiceInterface } from './AsymmetricMessageServiceInterface'
import { GetUntrustedPayload } from './UseCase/GetUntrustedPayload'
import { FindContact } from '../Contacts/UseCase/FindContact'

View File

@@ -10,7 +10,7 @@ import {
} from '@standardnotes/models'
import { ContentType } from '@standardnotes/domain-core'
import { GetVault } from '../../Vaults/UseCase/GetVault'
import { GetVault } from '../../Vault/UseCase/GetVault'
import { EncryptionProviderInterface } from '../../Encryption/EncryptionProviderInterface'
export class HandleRootKeyChangedMessage {

View File

@@ -11,7 +11,7 @@ import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessa
import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation'
import { HandleKeyPairChange } from './../Contacts/UseCase/HandleKeyPairChange'
import { CreateSharedVault } from './UseCase/CreateSharedVault'
import { GetVault } from './../Vaults/UseCase/GetVault'
import { GetVault } from './../Vault/UseCase/GetVault'
import { SharedVaultService } from './SharedVaultService'
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'

View File

@@ -21,13 +21,13 @@ import { InternalEventInterface } from '../Internal/InternalEventInterface'
import { UserEventServiceEvent, UserEventServiceEventPayload } from '../UserEvent/UserEventServiceEvent'
import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault'
import { DeleteSharedVault } from './UseCase/DeleteSharedVault'
import { VaultServiceEvent, VaultServiceEventPayload } from '../Vaults/VaultServiceEvent'
import { VaultServiceEvent, VaultServiceEventPayload } from '../Vault/VaultServiceEvent'
import { ShareContactWithVault } from './UseCase/ShareContactWithVault'
import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation'
import { CreateSharedVault } from './UseCase/CreateSharedVault'
import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessage'
import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault'
import { GetVault } from '../Vaults/UseCase/GetVault'
import { GetVault } from '../Vault/UseCase/GetVault'
import { ContentType } from '@standardnotes/domain-core'
import { HandleKeyPairChange } from '../Contacts/UseCase/HandleKeyPairChange'
import { FindContact } from '../Contacts/UseCase/FindContact'

View File

@@ -2,7 +2,7 @@ import { SharedVaultListingInterface, VaultListingInterface, VaultListingMutator
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultServerInterface } from '@standardnotes/api'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { MoveItemsToVault } from '../../Vaults/UseCase/MoveItemsToVault'
import { MoveItemsToVault } from '../../Vault/UseCase/MoveItemsToVault'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
export class ConvertToSharedVault {

View File

@@ -7,8 +7,8 @@ import {
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultServerInterface } from '@standardnotes/api'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { CreateVault } from '../../Vaults/UseCase/CreateVault'
import { MoveItemsToVault } from '../../Vaults/UseCase/MoveItemsToVault'
import { CreateVault } from '../../Vault/UseCase/CreateVault'
import { MoveItemsToVault } from '../../Vault/UseCase/MoveItemsToVault'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
export class CreateSharedVault {

View File

@@ -2,7 +2,7 @@ import { ClientDisplayableError, isErrorResponse } from '@standardnotes/response
import { SharedVaultServerInterface } from '@standardnotes/api'
import { SharedVaultListingInterface } from '@standardnotes/models'
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import { DeleteVault } from '../../Vaults/UseCase/DeleteVault'
import { DeleteVault } from '../../Vault/UseCase/DeleteVault'
export class DeleteSharedVault {
constructor(

View File

@@ -1,4 +1,4 @@
import { GetVaults } from './../../Vaults/UseCase/GetVaults'
import { GetVaults } from '../../Vault/UseCase/GetVaults'
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
import { SharedVaultListingInterface } from '@standardnotes/models'

View File

@@ -5,7 +5,7 @@ import {
VaultListingInterface,
VaultListingMutator,
} from '@standardnotes/models'
import { ChangeVaultOptionsDTO } from '../ChangeVaultOptionsDTO'
import { ChangeVaultKeyOptionsDTO } from './ChangeVaultKeyOptionsDTO'
import { GetVault } from './GetVault'
import { EncryptionProviderInterface } from '../../Encryption/EncryptionProviderInterface'
import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface'
@@ -19,7 +19,7 @@ export class ChangeVaultKeyOptions {
private getVault: GetVault,
) {}
async execute(dto: ChangeVaultOptionsDTO): Promise<void> {
async execute(dto: ChangeVaultKeyOptionsDTO): Promise<void> {
const useStorageMode = dto.newKeyStorageMode ?? dto.vault.keyStorageMode
if (dto.newPasswordType) {

View File

@@ -1,6 +1,6 @@
import { KeySystemRootKeyPasswordType, KeySystemRootKeyStorageMode, VaultListingInterface } from '@standardnotes/models'
export type ChangeVaultOptionsDTO = {
export type ChangeVaultKeyOptionsDTO = {
vault: VaultListingInterface
newPasswordType:
| { passwordType: KeySystemRootKeyPasswordType.Randomized }

View File

@@ -1,4 +1,4 @@
import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import { UuidGenerator } from '@standardnotes/utils'
import {
KeySystemRootKeyParamsInterface,

View File

@@ -1,5 +1,5 @@
import { VaultListingInterface } from '@standardnotes/models'
import { ItemManagerInterface } from './../../Item/ItemManagerInterface'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { ContentType, Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
export class GetVault implements SyncUseCaseInterface<VaultListingInterface> {

View File

@@ -3,14 +3,13 @@ import {
DecryptedItemInterface,
FileItem,
KeySystemIdentifier,
KeySystemRootKeyPasswordType,
KeySystemRootKeyStorageMode,
VaultListingInterface,
VaultListingMutator,
isNote,
} from '@standardnotes/models'
import { VaultServiceInterface } from './VaultServiceInterface'
import { ChangeVaultOptionsDTO } from './ChangeVaultOptionsDTO'
import { ChangeVaultKeyOptionsDTO } from './UseCase/ChangeVaultKeyOptionsDTO'
import { VaultServiceEvent, VaultServiceEventPayload } from './VaultServiceEvent'
import { CreateVault } from './UseCase/CreateVault'
import { AbstractService } from '../Service/AbstractService'
@@ -25,23 +24,18 @@ import { GetVault } from './UseCase/GetVault'
import { ChangeVaultKeyOptions } from './UseCase/ChangeVaultKeyOptions'
import { MutatorClientInterface } from '../Mutator/MutatorClientInterface'
import { AlertService } from '../Alert/AlertService'
import { ContentType } from '@standardnotes/domain-core'
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
import { KeySystemKeyManagerInterface } from '../KeySystem/KeySystemKeyManagerInterface'
import { GetVaults } from './UseCase/GetVaults'
import { VaultLockServiceInterface } from '../VaultLock/VaultLockServiceInterface'
export class VaultService
extends AbstractService<VaultServiceEvent, VaultServiceEventPayload[VaultServiceEvent]>
implements VaultServiceInterface
{
private lockMap = new Map<VaultListingInterface['uuid'], boolean>()
constructor(
private sync: SyncServiceInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private encryption: EncryptionProviderInterface,
private keys: KeySystemKeyManagerInterface,
private vaultLocks: VaultLockServiceInterface,
private alerts: AlertService,
private _getVault: GetVault,
private _getVaults: GetVaults,
@@ -49,29 +43,34 @@ export class VaultService
private _moveItemsToVault: MoveItemsToVault,
private _createVault: CreateVault,
private _removeItemFromVault: RemoveItemFromVault,
private _deleteVaultUse: DeleteVault,
private _deleteVault: DeleteVault,
private _rotateVaultKey: RotateVaultKey,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
}
items.addObserver(
[ContentType.TYPES.KeySystemItemsKey, ContentType.TYPES.KeySystemRootKey, ContentType.TYPES.VaultListing],
() => {
void this.recomputeAllVaultsLockingState()
},
)
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()
}
getLockedvaults(): VaultListingInterface[] {
const vaults = this.getVaults()
return vaults.filter((vault) => this.isVaultLocked(vault))
}
public getVault(dto: { keySystemIdentifier: KeySystemIdentifier }): VaultListingInterface | undefined {
const result = this._getVault.execute(dto)
if (result.isFailed()) {
@@ -90,16 +89,12 @@ export class VaultService
return vault
}
async createRandomizedVault(dto: {
name: string
description?: string
storagePreference: KeySystemRootKeyStorageMode
}): Promise<VaultListingInterface> {
async createRandomizedVault(dto: { name: string; description?: string }): Promise<VaultListingInterface> {
return this.createVaultWithParameters({
name: dto.name,
description: dto.description,
userInputtedPassword: undefined,
storagePreference: dto.storagePreference,
storagePreference: KeySystemRootKeyStorageMode.Synced,
})
}
@@ -132,7 +127,7 @@ export class VaultService
vault: VaultListingInterface,
item: DecryptedItemInterface,
): Promise<DecryptedItemInterface | undefined> {
if (this.isVaultLocked(vault)) {
if (this.vaultLocks.isVaultLocked(vault)) {
throw new Error('Attempting to add item to locked vault')
}
@@ -162,7 +157,7 @@ export class VaultService
throw new Error('Cannot find vault to remove item from')
}
if (this.isVaultLocked(vault)) {
if (this.vaultLocks.isVaultLocked(vault)) {
throw new Error('Attempting to remove item from locked vault')
}
@@ -175,7 +170,7 @@ export class VaultService
throw new Error('Shared vault must be deleted through SharedVaultService')
}
const error = await this._deleteVaultUse.execute(vault)
const error = await this._deleteVault.execute(vault)
if (isClientDisplayableError(error)) {
return false
@@ -200,7 +195,7 @@ export class VaultService
}
async rotateVaultRootKey(vault: VaultListingInterface): Promise<void> {
if (this.computeVaultLockState(vault) === 'locked') {
if (this.vaultLocks.isVaultLocked(vault)) {
throw new Error('Cannot rotate root key of locked vault')
}
@@ -232,8 +227,8 @@ export class VaultService
return this.getVault({ keySystemIdentifier: latestItem.key_system_identifier })
}
async changeVaultOptions(dto: ChangeVaultOptionsDTO): Promise<void> {
if (this.isVaultLocked(dto.vault)) {
async changeVaultOptions(dto: ChangeVaultKeyOptionsDTO): Promise<void> {
if (this.vaultLocks.isVaultLocked(dto.vault)) {
throw new Error('Attempting to change vault options on a locked vault')
}
@@ -243,99 +238,4 @@ export class VaultService
await this.notifyEventSync(VaultServiceEvent.VaultRootKeyRotated, { vault: dto.vault })
}
}
public isVaultLocked(vault: VaultListingInterface): boolean {
return this.lockMap.get(vault.uuid) === true
}
public async lockNonPersistentVault(vault: VaultListingInterface): Promise<void> {
if (vault.keyStorageMode === KeySystemRootKeyStorageMode.Synced) {
throw new Error('Vault uses synced root key and cannot be locked')
}
this.keys.clearMemoryOfKeysRelatedToVault(vault)
this.lockMap.set(vault.uuid, true)
void this.notifyEventSync(VaultServiceEvent.VaultLocked, { vault })
}
public async unlockNonPersistentVault(vault: VaultListingInterface, password: string): Promise<boolean> {
if (vault.keyPasswordType !== KeySystemRootKeyPasswordType.UserInputted) {
throw new Error('Vault uses randomized password and cannot be unlocked with user inputted password')
}
if (vault.keyStorageMode === KeySystemRootKeyStorageMode.Synced) {
throw new Error('Vault uses synced root key and cannot be unlocked with user inputted password')
}
const derivedRootKey = this.encryption.deriveUserInputtedKeySystemRootKey({
keyParams: vault.rootKeyParams,
userInputtedPassword: password,
})
this.keys.intakeNonPersistentKeySystemRootKey(derivedRootKey, vault.keyStorageMode)
await this.encryption.decryptErroredPayloads()
if (this.computeVaultLockState(vault) === 'locked') {
this.keys.undoIntakeNonPersistentKeySystemRootKey(vault.systemIdentifier)
return false
}
this.lockMap.set(vault.uuid, false)
void this.notifyEventSync(VaultServiceEvent.VaultUnlocked, { vault })
return true
}
private recomputeAllVaultsLockingState = async (): Promise<void> => {
const vaults = this.getVaults()
for (const vault of vaults) {
const locked = this.computeVaultLockState(vault) === 'locked'
if (this.lockMap.get(vault.uuid) !== locked) {
this.lockMap.set(vault.uuid, locked)
if (locked) {
void this.notifyEvent(VaultServiceEvent.VaultLocked, { vault })
} else {
void this.notifyEvent(VaultServiceEvent.VaultUnlocked, { vault })
}
}
}
}
private computeVaultLockState(vault: VaultListingInterface): 'locked' | 'unlocked' {
const rootKey = this.keys.getPrimaryKeySystemRootKey(vault.systemIdentifier)
if (!rootKey) {
return 'locked'
}
const itemsKey = this.keys.getPrimaryKeySystemItemsKey(vault.systemIdentifier)
if (!itemsKey) {
return 'locked'
}
return 'unlocked'
}
override deinit(): void {
super.deinit()
;(this.sync as unknown) = undefined
;(this.items as unknown) = undefined
;(this.mutator as unknown) = undefined
;(this.encryption 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._deleteVaultUse as unknown) = undefined
;(this._rotateVaultKey as unknown) = undefined
this.lockMap.clear()
}
}

View File

@@ -2,18 +2,10 @@ import { VaultListingInterface } from '@standardnotes/models'
export enum VaultServiceEvent {
VaultRootKeyRotated = 'VaultRootKeyRotated',
VaultUnlocked = 'VaultUnlocked',
VaultLocked = 'VaultLocked',
}
export type VaultServiceEventPayload = {
[VaultServiceEvent.VaultRootKeyRotated]: {
vault: VaultListingInterface
}
[VaultServiceEvent.VaultUnlocked]: {
vault: VaultListingInterface
}
[VaultServiceEvent.VaultLocked]: {
vault: VaultListingInterface
}
}

View File

@@ -6,15 +6,11 @@ import {
} from '@standardnotes/models'
import { AbstractService } from '../Service/AbstractService'
import { VaultServiceEvent, VaultServiceEventPayload } from './VaultServiceEvent'
import { ChangeVaultOptionsDTO } from './ChangeVaultOptionsDTO'
import { ChangeVaultKeyOptionsDTO } from './UseCase/ChangeVaultKeyOptionsDTO'
export interface VaultServiceInterface
extends AbstractService<VaultServiceEvent, VaultServiceEventPayload[VaultServiceEvent]> {
createRandomizedVault(dto: {
name: string
description?: string
storagePreference: KeySystemRootKeyStorageMode
}): Promise<VaultListingInterface>
createRandomizedVault(dto: { name: string; description?: string }): Promise<VaultListingInterface>
createUserInputtedPasswordVault(dto: {
name: string
description?: string
@@ -24,7 +20,6 @@ export interface VaultServiceInterface
getVaults(): VaultListingInterface[]
getVault(dto: { keySystemIdentifier: KeySystemIdentifier }): VaultListingInterface | undefined
getLockedvaults(): VaultListingInterface[]
deleteVault(vault: VaultListingInterface): Promise<boolean>
moveItemToVault(
@@ -40,8 +35,5 @@ export interface VaultServiceInterface
params: { name: string; description: string },
): Promise<VaultListingInterface>
rotateVaultRootKey(vault: VaultListingInterface): Promise<void>
changeVaultOptions(dto: ChangeVaultOptionsDTO): Promise<void>
isVaultLocked(vault: VaultListingInterface): boolean
unlockNonPersistentVault(vault: VaultListingInterface, password: string): Promise<boolean>
changeVaultOptions(dto: ChangeVaultKeyOptionsDTO): Promise<void>
}

View File

@@ -9,7 +9,7 @@ import { GetUntrustedPayload } from './../AsymmetricMessage/UseCase/GetUntrusted
import { GetTrustedPayload } from './../AsymmetricMessage/UseCase/GetTrustedPayload'
import { InviteRecord } from './InviteRecord'
import { VaultUserServiceInterface } from './../VaultUser/VaultUserServiceInterface'
import { GetVault } from './../Vaults/UseCase/GetVault'
import { GetVault } from '../Vault/UseCase/GetVault'
import { InviteToVault } from './UseCase/InviteToVault'
import { GetVaultContacts } from '../VaultUser/UseCase/GetVaultContacts'
import { SyncServiceInterface } from './../Sync/SyncServiceInterface'

View File

@@ -0,0 +1,133 @@
import { GetVaults } from '../Vault/UseCase/GetVaults'
import { KeySystemRootKeyPasswordType, KeySystemRootKeyStorageMode, VaultListingInterface } from '@standardnotes/models'
import { VaultLockServiceInterface } from './VaultLockServiceInterface'
import { VaultLockServiceEvent, VaultLockServiceEventPayload } from './VaultLockServiceEvent'
import { AbstractService } from '../Service/AbstractService'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { ContentType } from '@standardnotes/domain-core'
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
import { KeySystemKeyManagerInterface } from '../KeySystem/KeySystemKeyManagerInterface'
export class VaultLockService
extends AbstractService<VaultLockServiceEvent, VaultLockServiceEventPayload[VaultLockServiceEvent]>
implements VaultLockServiceInterface
{
private lockMap = new Map<VaultListingInterface['uuid'], boolean>()
constructor(
items: ItemManagerInterface,
private encryption: EncryptionProviderInterface,
private keys: KeySystemKeyManagerInterface,
private _getVaults: GetVaults,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
items.addObserver(
[ContentType.TYPES.KeySystemItemsKey, ContentType.TYPES.KeySystemRootKey, ContentType.TYPES.VaultListing],
() => {
void this.recomputeAllVaultsLockingState()
},
)
}
override deinit(): void {
super.deinit()
;(this.encryption as unknown) = undefined
;(this.keys as unknown) = undefined
;(this._getVaults as unknown) = undefined
this.lockMap.clear()
}
public getLockedvaults(): VaultListingInterface[] {
const vaults = this._getVaults.execute().getValue()
return vaults.filter((vault) => this.isVaultLocked(vault))
}
public isVaultLocked(vault: VaultListingInterface): boolean {
return this.lockMap.get(vault.uuid) === true
}
public isVaultLockable(vault: VaultListingInterface): boolean {
return vault.keyPasswordType === KeySystemRootKeyPasswordType.UserInputted
}
public lockNonPersistentVault(vault: VaultListingInterface): void {
if (vault.keyStorageMode === KeySystemRootKeyStorageMode.Synced) {
throw new Error('Vault uses synced key storage and cannot be locked')
}
if (vault.keyPasswordType !== KeySystemRootKeyPasswordType.UserInputted) {
throw new Error('Vault uses randomized password and cannot be locked')
}
this.keys.clearMemoryOfKeysRelatedToVault(vault)
this.lockMap.set(vault.uuid, true)
void this.notifyEventSync(VaultLockServiceEvent.VaultLocked, { vault })
}
public async unlockNonPersistentVault(vault: VaultListingInterface, password: string): Promise<boolean> {
if (vault.keyPasswordType !== KeySystemRootKeyPasswordType.UserInputted) {
throw new Error('Vault uses randomized password and cannot be unlocked with user inputted password')
}
if (vault.keyStorageMode === KeySystemRootKeyStorageMode.Synced) {
throw new Error('Vault uses synced root key and cannot be unlocked with user inputted password')
}
const derivedRootKey = this.encryption.deriveUserInputtedKeySystemRootKey({
keyParams: vault.rootKeyParams,
userInputtedPassword: password,
})
this.keys.intakeNonPersistentKeySystemRootKey(derivedRootKey, vault.keyStorageMode)
await this.encryption.decryptErroredPayloads()
if (this.computeVaultLockState(vault) === 'locked') {
this.keys.undoIntakeNonPersistentKeySystemRootKey(vault.systemIdentifier)
return false
}
this.lockMap.set(vault.uuid, false)
void this.notifyEventSync(VaultLockServiceEvent.VaultUnlocked, { vault })
return true
}
private recomputeAllVaultsLockingState = async (): Promise<void> => {
const vaults = this._getVaults.execute().getValue()
for (const vault of vaults) {
const locked = this.computeVaultLockState(vault) === 'locked'
if (this.lockMap.get(vault.uuid) !== locked) {
this.lockMap.set(vault.uuid, locked)
if (locked) {
void this.notifyEvent(VaultLockServiceEvent.VaultLocked, { vault })
} else {
void this.notifyEvent(VaultLockServiceEvent.VaultUnlocked, { vault })
}
}
}
}
private computeVaultLockState(vault: VaultListingInterface): 'locked' | 'unlocked' {
const rootKey = this.keys.getPrimaryKeySystemRootKey(vault.systemIdentifier)
if (!rootKey) {
return 'locked'
}
const itemsKey = this.keys.getPrimaryKeySystemItemsKey(vault.systemIdentifier)
if (!itemsKey) {
return 'locked'
}
return 'unlocked'
}
}

View File

@@ -0,0 +1,15 @@
import { VaultListingInterface } from '@standardnotes/models'
export enum VaultLockServiceEvent {
VaultUnlocked = 'VaultUnlocked',
VaultLocked = 'VaultLocked',
}
export type VaultLockServiceEventPayload = {
[VaultLockServiceEvent.VaultUnlocked]: {
vault: VaultListingInterface
}
[VaultLockServiceEvent.VaultLocked]: {
vault: VaultListingInterface
}
}

View File

@@ -0,0 +1,12 @@
import { VaultListingInterface } from '@standardnotes/models'
import { AbstractService } from '../Service/AbstractService'
import { VaultLockServiceEvent, VaultLockServiceEventPayload } from './VaultLockServiceEvent'
export interface VaultLockServiceInterface
extends AbstractService<VaultLockServiceEvent, VaultLockServiceEventPayload[VaultLockServiceEvent]> {
getLockedvaults(): VaultListingInterface[]
isVaultLocked(vault: VaultListingInterface): boolean
isVaultLockable(vault: VaultListingInterface): boolean
lockNonPersistentVault(vault: VaultListingInterface): void
unlockNonPersistentVault(vault: VaultListingInterface, password: string): Promise<boolean>
}

View File

@@ -1,8 +1,9 @@
import { VaultLockServiceInterface } from './../VaultLock/VaultLockServiceInterface'
import { LeaveVault } from './UseCase/LeaveSharedVault'
import { GetVault } from './../Vaults/UseCase/GetVault'
import { GetVault } from '../Vault/UseCase/GetVault'
import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface'
import { RemoveVaultMember } from './UseCase/RemoveSharedVaultMember'
import { VaultServiceInterface } from './../Vaults/VaultServiceInterface'
import { VaultServiceInterface } from '../Vault/VaultServiceInterface'
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
import { GetVaultUsers } from './UseCase/GetVaultUsers'
import { SharedVaultListingInterface } from '@standardnotes/models'
@@ -17,6 +18,7 @@ export class VaultUserService extends AbstractService<VaultUserServiceEvent> imp
constructor(
private session: SessionsClientInterface,
private vaults: VaultServiceInterface,
private vaultLocks: VaultLockServiceInterface,
private _getVaultUsers: GetVaultUsers,
private _removeVaultMember: RemoveVaultMember,
private _isVaultOwner: IsVaultOwner,
@@ -66,7 +68,7 @@ export class VaultUserService extends AbstractService<VaultUserServiceEvent> imp
throw new Error('Only vault admins can remove users')
}
if (this.vaults.isVaultLocked(sharedVault)) {
if (this.vaultLocks.isVaultLocked(sharedVault)) {
throw new Error('Cannot remove user from locked vault')
}

View File

@@ -187,18 +187,21 @@ export * from './VaultInvite/UseCase/SendVaultInvite'
export * from './VaultInvite/VaultInviteService'
export * from './VaultInvite/VaultInviteServiceEvent'
export * from './VaultInvite/VaultInviteServiceInterface'
export * from './Vaults/ChangeVaultOptionsDTO'
export * from './Vaults/UseCase/ChangeVaultKeyOptions'
export * from './Vaults/UseCase/CreateVault'
export * from './Vaults/UseCase/DeleteVault'
export * from './Vaults/UseCase/GetVault'
export * from './Vaults/UseCase/GetVaults'
export * from './Vaults/UseCase/MoveItemsToVault'
export * from './Vaults/UseCase/RemoveItemFromVault'
export * from './Vaults/UseCase/RotateVaultKey'
export * from './Vaults/VaultService'
export * from './Vaults/VaultServiceEvent'
export * from './Vaults/VaultServiceInterface'
export * from './Vault/UseCase/ChangeVaultKeyOptionsDTO'
export * from './Vault/UseCase/ChangeVaultKeyOptions'
export * from './Vault/UseCase/CreateVault'
export * from './Vault/UseCase/DeleteVault'
export * from './Vault/UseCase/GetVault'
export * from './Vault/UseCase/GetVaults'
export * from './Vault/UseCase/MoveItemsToVault'
export * from './Vault/UseCase/RemoveItemFromVault'
export * from './Vault/UseCase/RotateVaultKey'
export * from './Vault/VaultService'
export * from './Vault/VaultServiceEvent'
export * from './Vault/VaultServiceInterface'
export * from './VaultLock/VaultLockService'
export * from './VaultLock/VaultLockServiceEvent'
export * from './VaultLock/VaultLockServiceInterface'
export * from './VaultUser/UseCase/GetVaultContacts'
export * from './VaultUser/UseCase/GetVaultContacts'
export * from './VaultUser/UseCase/GetVaultUsers'