tests: vaults-2 (#2368)

This commit is contained in:
Mo
2023-07-26 04:55:58 -05:00
committed by GitHub
parent 6ad5d028af
commit 7222ca7fd0
47 changed files with 900 additions and 310 deletions

View File

@@ -3,7 +3,7 @@ import { V004Algorithm } from '../../../../Algorithm'
import { import {
KeySystemRootKeyInterface, KeySystemRootKeyInterface,
KeySystemRootKeyParamsInterface, KeySystemRootKeyParamsInterface,
KeySystemRootKeyPasswordType, KeySystemPasswordType,
} from '@standardnotes/models' } from '@standardnotes/models'
import { ProtocolVersion } from '@standardnotes/common' import { ProtocolVersion } from '@standardnotes/common'
import { DeriveKeySystemRootKeyUseCase } from './DeriveKeySystemRootKey' import { DeriveKeySystemRootKeyUseCase } from './DeriveKeySystemRootKey'
@@ -20,7 +20,7 @@ export class CreateRandomKeySystemRootKey {
const keyParams: KeySystemRootKeyParamsInterface = { const keyParams: KeySystemRootKeyParamsInterface = {
systemIdentifier: dto.systemIdentifier, systemIdentifier: dto.systemIdentifier,
passwordType: KeySystemRootKeyPasswordType.Randomized, passwordType: KeySystemPasswordType.Randomized,
creationTimestamp: new Date().getTime(), creationTimestamp: new Date().getTime(),
seed, seed,
version, version,

View File

@@ -4,7 +4,7 @@ import {
KeySystemIdentifier, KeySystemIdentifier,
KeySystemRootKeyInterface, KeySystemRootKeyInterface,
KeySystemRootKeyParamsInterface, KeySystemRootKeyParamsInterface,
KeySystemRootKeyPasswordType, KeySystemPasswordType,
} from '@standardnotes/models' } from '@standardnotes/models'
import { ProtocolVersion } from '@standardnotes/common' import { ProtocolVersion } from '@standardnotes/common'
import { DeriveKeySystemRootKeyUseCase } from './DeriveKeySystemRootKey' import { DeriveKeySystemRootKeyUseCase } from './DeriveKeySystemRootKey'
@@ -19,7 +19,7 @@ export class CreateUserInputKeySystemRootKey {
const keyParams: KeySystemRootKeyParamsInterface = { const keyParams: KeySystemRootKeyParamsInterface = {
systemIdentifier: dto.systemIdentifier, systemIdentifier: dto.systemIdentifier,
passwordType: KeySystemRootKeyPasswordType.UserInputted, passwordType: KeySystemPasswordType.UserInputted,
creationTimestamp: new Date().getTime(), creationTimestamp: new Date().getTime(),
seed, seed,
version, version,

View File

@@ -1,4 +1,4 @@
export enum KeySystemRootKeyPasswordType { export enum KeySystemPasswordType {
UserInputted = 'user_inputted', UserInputted = 'user_inputted',
Randomized = 'randomized', Randomized = 'randomized',
} }

View File

@@ -1,6 +1,6 @@
import { ProtocolVersion } from '@standardnotes/common' import { ProtocolVersion } from '@standardnotes/common'
import { KeySystemIdentifier } from '../../Syncable/KeySystemRootKey/KeySystemIdentifier' import { KeySystemIdentifier } from '../../Syncable/KeySystemRootKey/KeySystemIdentifier'
import { KeySystemRootKeyPasswordType } from './KeySystemRootKeyPasswordType' import { KeySystemPasswordType } from './KeySystemPasswordType'
/** /**
* Key params are public data that contain information about how a root key was created. * Key params are public data that contain information about how a root key was created.
@@ -11,6 +11,6 @@ export interface KeySystemRootKeyParamsInterface {
systemIdentifier: KeySystemIdentifier systemIdentifier: KeySystemIdentifier
seed: string seed: string
version: ProtocolVersion version: ProtocolVersion
passwordType: KeySystemRootKeyPasswordType passwordType: KeySystemPasswordType
creationTimestamp: number creationTimestamp: number
} }

View File

@@ -0,0 +1,127 @@
import {
Collection,
DecryptedCollectionElement,
DeletedCollectionElement,
EncryptedCollectionElement,
} from './Collection'
import { FullyFormedPayloadInterface } from '../../Abstract/Payload'
class TestCollection<P extends FullyFormedPayloadInterface = FullyFormedPayloadInterface> extends Collection<
P,
DecryptedCollectionElement,
EncryptedCollectionElement,
DeletedCollectionElement
> {}
describe('Collection', () => {
let collection: TestCollection
beforeEach(() => {
collection = new TestCollection()
})
it('should initialize correctly', () => {
expect(collection.map).toEqual({})
expect(collection.typedMap).toEqual({})
expect(collection.referenceMap).toBeDefined()
expect(collection.conflictMap).toBeDefined()
})
it('should set and get element correctly', () => {
const testElement = {
uuid: 'test-uuid',
content_type: 'test-type',
content: {},
references: [],
} as unknown as FullyFormedPayloadInterface
collection.set(testElement)
const element = collection.find('test-uuid')
expect(element).toBe(testElement)
})
it('should check existence of an element correctly', () => {
const testElement = {
uuid: 'test-uuid',
content_type: 'test-type',
content: {},
references: [],
} as unknown as FullyFormedPayloadInterface
collection.set(testElement)
const hasElement = collection.has('test-uuid')
expect(hasElement).toBe(true)
})
it('should return all elements', () => {
const testElement1 = {
uuid: 'test-uuid-1',
content_type: 'test-type',
content: {},
references: [],
} as unknown as FullyFormedPayloadInterface
const testElement2 = {
uuid: 'test-uuid-2',
content_type: 'test-type',
content: {},
references: [],
} as unknown as FullyFormedPayloadInterface
collection.set(testElement1)
collection.set(testElement2)
const allElements = collection.all()
expect(allElements).toEqual([testElement1, testElement2])
})
it('should add uuid to invalidsIndex if element is error decrypting', () => {
const testElement = {
uuid: 'test-uuid',
content_type: 'test-type',
content: 'encrypted content',
errorDecrypting: true,
} as unknown as FullyFormedPayloadInterface
collection.set(testElement)
expect(collection.invalidsIndex.has(testElement.uuid)).toBe(true)
})
it('should add uuid to invalidsIndex if element is encrypted', () => {
const testElement = {
uuid: 'test-uuid',
content_type: 'test-type',
content: 'encrypted content',
} as unknown as FullyFormedPayloadInterface
collection.set(testElement)
expect(collection.invalidsIndex.has(testElement.uuid)).toBe(true)
})
it('should remove uuid from invalidsIndex if element is not encrypted', () => {
const testElement1 = {
uuid: 'test-uuid-1',
content_type: 'test-type',
content: 'encrypted content',
errorDecrypting: true,
} as unknown as FullyFormedPayloadInterface
const testElement2 = {
uuid: 'test-uuid-1',
content_type: 'test-type',
content: {},
references: [],
} as unknown as FullyFormedPayloadInterface
collection.set(testElement1)
expect(collection.invalidsIndex.has(testElement1.uuid)).toBe(true)
collection.set(testElement2)
expect(collection.invalidsIndex.has(testElement2.uuid)).toBe(false)
})
})

View File

@@ -59,7 +59,7 @@ export abstract class Collection<
} }
isErrorDecryptingElement = (e: Decrypted | Encrypted | Deleted): e is Encrypted => { isErrorDecryptingElement = (e: Decrypted | Encrypted | Deleted): e is Encrypted => {
return this.isEncryptedElement(e) && e.errorDecrypting === true return this.isEncryptedElement(e)
} }
isDeletedElement = (e: Decrypted | Encrypted | Deleted): e is Deleted => { isDeletedElement = (e: Decrypted | Encrypted | Deleted): e is Deleted => {
@@ -78,10 +78,10 @@ export abstract class Collection<
conflictMapCopy?: UuidMap, conflictMapCopy?: UuidMap,
) { ) {
if (copy) { if (copy) {
this.map = mapCopy! this.map = mapCopy as Record<string, Element>
this.typedMap = typedMapCopy! this.typedMap = typedMapCopy as Record<string, Element[]>
this.referenceMap = referenceMapCopy! this.referenceMap = referenceMapCopy as UuidMap
this.conflictMap = conflictMapCopy! this.conflictMap = conflictMapCopy as UuidMap
} else { } else {
this.referenceMap = new UuidMap() this.referenceMap = new UuidMap()
this.conflictMap = new UuidMap() this.conflictMap = new UuidMap()

View File

@@ -2,7 +2,7 @@ import { ConflictStrategy, DecryptedItem } from '../../Abstract/Item'
import { DecryptedPayloadInterface } from '../../Abstract/Payload' import { DecryptedPayloadInterface } from '../../Abstract/Payload'
import { HistoryEntryInterface } from '../../Runtime/History' import { HistoryEntryInterface } from '../../Runtime/History'
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface' import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
import { KeySystemRootKeyPasswordType } from '../../Local/KeyParams/KeySystemRootKeyPasswordType' import { KeySystemPasswordType } from '../../Local/KeyParams/KeySystemPasswordType'
import { SharedVaultListingInterface, VaultListingInterface } from './VaultListingInterface' import { SharedVaultListingInterface, VaultListingInterface } from './VaultListingInterface'
import { VaultListingContent } from './VaultListingContent' import { VaultListingContent } from './VaultListingContent'
import { KeySystemRootKeyStorageMode } from '../KeySystemRootKey/KeySystemRootKeyStorageMode' import { KeySystemRootKeyStorageMode } from '../KeySystemRootKey/KeySystemRootKeyStorageMode'
@@ -44,7 +44,7 @@ export class VaultListing extends DecryptedItem<VaultListingContent> implements
return incomingKeyTimestamp > baseKeyTimestamp ? ConflictStrategy.KeepApply : ConflictStrategy.KeepBase return incomingKeyTimestamp > baseKeyTimestamp ? ConflictStrategy.KeepApply : ConflictStrategy.KeepBase
} }
get keyPasswordType(): KeySystemRootKeyPasswordType { get keyPasswordType(): KeySystemPasswordType {
return this.rootKeyParams.passwordType return this.rootKeyParams.passwordType
} }

View File

@@ -1,6 +1,6 @@
import { KeySystemIdentifier } from '../KeySystemRootKey/KeySystemIdentifier' import { KeySystemIdentifier } from '../KeySystemRootKey/KeySystemIdentifier'
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface' import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
import { KeySystemRootKeyPasswordType } from '../../Local/KeyParams/KeySystemRootKeyPasswordType' import { KeySystemPasswordType } from '../../Local/KeyParams/KeySystemPasswordType'
import { KeySystemRootKeyStorageMode } from '../KeySystemRootKey/KeySystemRootKeyStorageMode' import { KeySystemRootKeyStorageMode } from '../KeySystemRootKey/KeySystemRootKeyStorageMode'
import { VaultListingSharingInfo } from './VaultListingSharingInfo' import { VaultListingSharingInfo } from './VaultListingSharingInfo'
import { VaultListingContent } from './VaultListingContent' import { VaultListingContent } from './VaultListingContent'
@@ -17,7 +17,7 @@ export interface VaultListingInterface extends DecryptedItemInterface<VaultListi
sharing?: VaultListingSharingInfo sharing?: VaultListingSharingInfo
get keyPasswordType(): KeySystemRootKeyPasswordType get keyPasswordType(): KeySystemPasswordType
isSharedVaultListing(): this is SharedVaultListingInterface isSharedVaultListing(): this is SharedVaultListingInterface
get key_system_identifier(): undefined get key_system_identifier(): undefined

View File

@@ -37,7 +37,7 @@ export * from './Device/Platform'
export * from './Local/KeyParams/RootKeyParamsInterface' export * from './Local/KeyParams/RootKeyParamsInterface'
export * from './Local/KeyParams/KeySystemRootKeyParamsInterface' export * from './Local/KeyParams/KeySystemRootKeyParamsInterface'
export * from './Local/KeyParams/KeySystemRootKeyPasswordType' export * from './Local/KeyParams/KeySystemPasswordType'
export * from './Local/RootKey/KeychainTypes' export * from './Local/RootKey/KeychainTypes'
export * from './Local/RootKey/RootKeyContent' export * from './Local/RootKey/RootKeyContent'
export * from './Local/RootKey/RootKeyInterface' export * from './Local/RootKey/RootKeyInterface'

View File

@@ -2,24 +2,24 @@ import { ItemsKeyMutator } from '@standardnotes/encryption'
import { MutatorClientInterface } from '../../../Mutator/MutatorClientInterface' import { MutatorClientInterface } from '../../../Mutator/MutatorClientInterface'
import { ItemManagerInterface } from '../../../Item/ItemManagerInterface' import { ItemManagerInterface } from '../../../Item/ItemManagerInterface'
import { CreateNewDefaultItemsKey } from './CreateNewDefaultItemsKey' import { CreateNewDefaultItemsKey } from './CreateNewDefaultItemsKey'
import { RemoveItemsLocally } from '../../../UseCase/RemoveItemsLocally' import { DiscardItemsLocally } from '../../../UseCase/DiscardItemsLocally'
import { FindDefaultItemsKey } from './FindDefaultItemsKey' import { FindDefaultItemsKey } from './FindDefaultItemsKey'
export class CreateNewItemsKeyWithRollback { export class CreateNewItemsKeyWithRollback {
constructor( constructor(
private mutator: MutatorClientInterface, private mutator: MutatorClientInterface,
private items: ItemManagerInterface, private items: ItemManagerInterface,
private createDefaultItemsKey: CreateNewDefaultItemsKey, private _createDefaultItemsKey: CreateNewDefaultItemsKey,
private removeItemsLocally: RemoveItemsLocally, private _discardItemsLocally: DiscardItemsLocally,
private findDefaultItemsKey: FindDefaultItemsKey, private _findDefaultItemsKey: FindDefaultItemsKey,
) {} ) {}
async execute(): Promise<() => Promise<void>> { async execute(): Promise<() => Promise<void>> {
const currentDefaultItemsKey = this.findDefaultItemsKey.execute(this.items.getDisplayableItemsKeys()).getValue() const currentDefaultItemsKey = this._findDefaultItemsKey.execute(this.items.getDisplayableItemsKeys()).getValue()
const newDefaultItemsKey = await this.createDefaultItemsKey.execute() const newDefaultItemsKey = await this._createDefaultItemsKey.execute()
const rollback = async () => { const rollback = async () => {
await this.removeItemsLocally.execute([newDefaultItemsKey]) await this._discardItemsLocally.execute([newDefaultItemsKey])
if (currentDefaultItemsKey) { if (currentDefaultItemsKey) {
await this.mutator.changeItem<ItemsKeyMutator>(currentDefaultItemsKey, (mutator) => { await this.mutator.changeItem<ItemsKeyMutator>(currentDefaultItemsKey, (mutator) => {

View File

@@ -80,7 +80,7 @@ export interface ItemManagerInterface extends AbstractService {
): T[] ): T[]
subItemsMatchingPredicates<T extends DecryptedItemInterface>(items: T[], predicates: PredicateInterface<T>[]): T[] subItemsMatchingPredicates<T extends DecryptedItemInterface>(items: T[], predicates: PredicateInterface<T>[]): T[]
removeAllItemsFromMemory(): Promise<void> removeAllItemsFromMemory(): Promise<void>
removeItemsLocally(items: AnyItemInterface[]): void removeItemsFromMemory(items: AnyItemInterface[]): void
getDirtyItems(): (DecryptedItemInterface | DeletedItemInterface)[] getDirtyItems(): (DecryptedItemInterface | DeletedItemInterface)[]
getTagLongTitle(tag: SNTag): string getTagLongTitle(tag: SNTag): string
getSortedTagsForItem(item: DecryptedItemInterface<ItemContent>): SNTag[] getSortedTagsForItem(item: DecryptedItemInterface<ItemContent>): SNTag[]

View File

@@ -1,3 +1,4 @@
import { RemoveItemsFromMemory } from './../Storage/UseCase/RemoveItemsFromMemory'
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface' import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface' import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
import { ApplicationStage } from './../Application/ApplicationStage' import { ApplicationStage } from './../Application/ApplicationStage'
@@ -36,11 +37,20 @@ export class KeySystemKeyManager
private readonly items: ItemManagerInterface, private readonly items: ItemManagerInterface,
private readonly mutator: MutatorClientInterface, private readonly mutator: MutatorClientInterface,
private readonly storage: StorageServiceInterface, private readonly storage: StorageServiceInterface,
private readonly _removeItemsFromMemory: RemoveItemsFromMemory,
eventBus: InternalEventBusInterface, eventBus: InternalEventBusInterface,
) { ) {
super(eventBus) super(eventBus)
} }
public override deinit(): void {
;(this.items as unknown) = undefined
;(this.mutator as unknown) = undefined
;(this.storage as unknown) = undefined
;(this._removeItemsFromMemory as unknown) = undefined
super.deinit()
}
async handleEvent(event: InternalEventInterface): Promise<void> { async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === ApplicationEvent.ApplicationStageChanged) { if (event.type === ApplicationEvent.ApplicationStageChanged) {
const stage = (event.payload as ApplicationStageChangedEventPayload).stage const stage = (event.payload as ApplicationStageChangedEventPayload).stage
@@ -60,9 +70,28 @@ export class KeySystemKeyManager
const keyPayloads = keyRawPayloads.map((rawPayload) => new DecryptedPayload<KeySystemRootKeyContent>(rawPayload)) const keyPayloads = keyRawPayloads.map((rawPayload) => new DecryptedPayload<KeySystemRootKeyContent>(rawPayload))
const keys = keyPayloads.map((payload) => new KeySystemRootKey(payload)) const keys = keyPayloads.map((payload) => new KeySystemRootKey(payload))
keys.forEach((key) => {
for (const key of keys) {
this.rootKeyMemoryCache[key.systemIdentifier] = key this.rootKeyMemoryCache[key.systemIdentifier] = key
}) }
}
public getRootKeyFromStorageForVault(
keySystemIdentifier: KeySystemIdentifier,
): KeySystemRootKeyInterface | undefined {
const payload = this.storage.getValue<DecryptedTransferPayload<KeySystemRootKeyContent>>(
this.storageKeyForRootKey(keySystemIdentifier),
)
if (!payload) {
return undefined
}
const keyPayload = new DecryptedPayload<KeySystemRootKeyContent>(payload)
const key = new KeySystemRootKey(keyPayload)
return key
} }
private storageKeyForRootKey(systemIdentifier: KeySystemIdentifier): string { private storageKeyForRootKey(systemIdentifier: KeySystemIdentifier): string {
@@ -73,17 +102,14 @@ export class KeySystemKeyManager
* When the key system root key changes, we must re-encrypt all vault items keys * When the key system root key changes, we must re-encrypt all vault items keys
* with this new key system root key (by simply re-syncing). * with this new key system root key (by simply re-syncing).
*/ */
public async reencryptKeySystemItemsKeysForVault(keySystemIdentifier: KeySystemIdentifier): Promise<void> { public async queueVaultItemsKeysForReencryption(keySystemIdentifier: KeySystemIdentifier): Promise<void> {
const keySystemItemsKeys = this.getKeySystemItemsKeys(keySystemIdentifier) const keySystemItemsKeys = this.getKeySystemItemsKeys(keySystemIdentifier)
if (keySystemItemsKeys.length > 0) { if (keySystemItemsKeys.length > 0) {
await this.mutator.setItemsDirty(keySystemItemsKeys) await this.mutator.setItemsDirty(keySystemItemsKeys)
} }
} }
public intakeNonPersistentKeySystemRootKey( public cacheKey(key: KeySystemRootKeyInterface, storage: KeySystemRootKeyStorageMode): void {
key: KeySystemRootKeyInterface,
storage: KeySystemRootKeyStorageMode,
): void {
this.rootKeyMemoryCache[key.systemIdentifier] = key this.rootKeyMemoryCache[key.systemIdentifier] = key
if (storage === KeySystemRootKeyStorageMode.Local) { if (storage === KeySystemRootKeyStorageMode.Local) {
@@ -91,7 +117,7 @@ export class KeySystemKeyManager
} }
} }
public undoIntakeNonPersistentKeySystemRootKey(systemIdentifier: KeySystemIdentifier): void { public removeKeyFromCache(systemIdentifier: KeySystemIdentifier): void {
delete this.rootKeyMemoryCache[systemIdentifier] delete this.rootKeyMemoryCache[systemIdentifier]
void this.storage.removeValue(this.storageKeyForRootKey(systemIdentifier)) void this.storage.removeValue(this.storageKeyForRootKey(systemIdentifier))
} }
@@ -100,11 +126,11 @@ export class KeySystemKeyManager
return this.items.getItems(ContentType.TYPES.KeySystemRootKey) return this.items.getItems(ContentType.TYPES.KeySystemRootKey)
} }
public clearMemoryOfKeysRelatedToVault(vault: VaultListingInterface): void { public async wipeVaultKeysFromMemory(vault: VaultListingInterface): Promise<void> {
delete this.rootKeyMemoryCache[vault.systemIdentifier] delete this.rootKeyMemoryCache[vault.systemIdentifier]
const itemsKeys = this.getKeySystemItemsKeys(vault.systemIdentifier) const itemsKeys = this.getKeySystemItemsKeys(vault.systemIdentifier)
this.items.removeItemsLocally(itemsKeys) await this._removeItemsFromMemory.execute(itemsKeys)
} }
public getSyncedKeySystemRootKeysForVault(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface[] { public getSyncedKeySystemRootKeysForVault(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface[] {
@@ -131,19 +157,6 @@ export class KeySystemKeyManager
await this.mutator.setItemsToBeDeleted(keys) await this.mutator.setItemsToBeDeleted(keys)
} }
public getKeySystemRootKeyWithToken(
systemIdentifier: KeySystemIdentifier,
rootKeyToken: string,
): KeySystemRootKeyInterface | undefined {
const keys = this.getAllKeySystemRootKeysForVault(systemIdentifier).filter((key) => key.token === rootKeyToken)
if (keys.length > 1) {
throw new Error('Multiple synced key system root keys found for token')
}
return keys[0]
}
public getPrimaryKeySystemRootKey(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface | undefined { public getPrimaryKeySystemRootKey(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface | undefined {
const keys = this.getAllKeySystemRootKeysForVault(systemIdentifier) const keys = this.getAllKeySystemRootKeysForVault(systemIdentifier)

View File

@@ -16,17 +16,13 @@ export interface KeySystemKeyManagerInterface {
getAllKeySystemRootKeysForVault(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface[] getAllKeySystemRootKeysForVault(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface[]
getSyncedKeySystemRootKeysForVault(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface[] getSyncedKeySystemRootKeysForVault(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface[]
getAllSyncedKeySystemRootKeys(): KeySystemRootKeyInterface[] getAllSyncedKeySystemRootKeys(): KeySystemRootKeyInterface[]
getKeySystemRootKeyWithToken(
systemIdentifier: KeySystemIdentifier,
keyIdentifier: string,
): KeySystemRootKeyInterface | undefined
getPrimaryKeySystemRootKey(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface | undefined getPrimaryKeySystemRootKey(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface | undefined
reencryptKeySystemItemsKeysForVault(keySystemIdentifier: KeySystemIdentifier): Promise<void> queueVaultItemsKeysForReencryption(keySystemIdentifier: KeySystemIdentifier): Promise<void>
intakeNonPersistentKeySystemRootKey(key: KeySystemRootKeyInterface, storage: KeySystemRootKeyStorageMode): void cacheKey(key: KeySystemRootKeyInterface, storage: KeySystemRootKeyStorageMode): void
undoIntakeNonPersistentKeySystemRootKey(systemIdentifier: KeySystemIdentifier): void removeKeyFromCache(systemIdentifier: KeySystemIdentifier): void
clearMemoryOfKeysRelatedToVault(vault: VaultListingInterface): void wipeVaultKeysFromMemory(vault: VaultListingInterface): Promise<void>
deleteNonPersistentSystemRootKeysForVault(systemIdentifier: KeySystemIdentifier): Promise<void> deleteNonPersistentSystemRootKeysForVault(systemIdentifier: KeySystemIdentifier): Promise<void>
deleteAllSyncedKeySystemRootKeys(systemIdentifier: KeySystemIdentifier): Promise<void> deleteAllSyncedKeySystemRootKeys(systemIdentifier: KeySystemIdentifier): Promise<void>
} }

View File

@@ -1,3 +1,4 @@
import { DiscardItemsLocally } from './../UseCase/DiscardItemsLocally'
import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface' import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface'
import { GetOwnedSharedVaults } from './UseCase/GetOwnedSharedVaults' import { GetOwnedSharedVaults } from './UseCase/GetOwnedSharedVaults'
import { IsVaultOwner } from './../VaultUser/UseCase/IsVaultOwner' import { IsVaultOwner } from './../VaultUser/UseCase/IsVaultOwner'
@@ -42,6 +43,7 @@ describe('SharedVaultService', () => {
const convertToSharedVault = {} as jest.Mocked<ConvertToSharedVault> const convertToSharedVault = {} as jest.Mocked<ConvertToSharedVault>
const deleteSharedVaultUseCase = {} as jest.Mocked<DeleteSharedVault> const deleteSharedVaultUseCase = {} as jest.Mocked<DeleteSharedVault>
const isVaultAdmin = {} as jest.Mocked<IsVaultOwner> const isVaultAdmin = {} as jest.Mocked<IsVaultOwner>
const discardItemsLocally = {} as jest.Mocked<DiscardItemsLocally>
const eventBus = {} as jest.Mocked<InternalEventBusInterface> const eventBus = {} as jest.Mocked<InternalEventBusInterface>
eventBus.addEventHandler = jest.fn() eventBus.addEventHandler = jest.fn()
@@ -62,6 +64,7 @@ describe('SharedVaultService', () => {
convertToSharedVault, convertToSharedVault,
deleteSharedVaultUseCase, deleteSharedVaultUseCase,
isVaultAdmin, isVaultAdmin,
discardItemsLocally,
eventBus, eventBus,
) )
}) })

View File

@@ -1,3 +1,4 @@
import { DiscardItemsLocally } from './../UseCase/DiscardItemsLocally'
import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData' import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData'
import { ClientDisplayableError, UserEventType } from '@standardnotes/responses' import { ClientDisplayableError, UserEventType } from '@standardnotes/responses'
import { import {
@@ -55,6 +56,7 @@ export class SharedVaultService
private _convertToSharedVault: ConvertToSharedVault, private _convertToSharedVault: ConvertToSharedVault,
private _deleteSharedVault: DeleteSharedVault, private _deleteSharedVault: DeleteSharedVault,
private _isVaultAdmin: IsVaultOwner, private _isVaultAdmin: IsVaultOwner,
private _discardItemsLocally: DiscardItemsLocally,
eventBus: InternalEventBusInterface, eventBus: InternalEventBusInterface,
) { ) {
super(eventBus) super(eventBus)
@@ -132,7 +134,7 @@ export class SharedVaultService
case UserEventType.SharedVaultItemRemoved: { case UserEventType.SharedVaultItemRemoved: {
const item = this.items.findItem(event.eventPayload.itemUuid) const item = this.items.findItem(event.eventPayload.itemUuid)
if (item) { if (item) {
this.items.removeItemsLocally([item]) void this._discardItemsLocally.execute([item])
} }
break break
} }

View File

@@ -2,7 +2,7 @@ import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface' import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { AnyItemInterface, VaultListingInterface } from '@standardnotes/models' import { AnyItemInterface, VaultListingInterface } from '@standardnotes/models'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
import { RemoveItemsLocally } from '../../UseCase/RemoveItemsLocally' import { DiscardItemsLocally } from '../../UseCase/DiscardItemsLocally'
import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface' import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface'
export class DeleteThirdPartyVault { export class DeleteThirdPartyVault {
@@ -11,7 +11,7 @@ export class DeleteThirdPartyVault {
private mutator: MutatorClientInterface, private mutator: MutatorClientInterface,
private keys: KeySystemKeyManagerInterface, private keys: KeySystemKeyManagerInterface,
private sync: SyncServiceInterface, private sync: SyncServiceInterface,
private removeItemsLocally: RemoveItemsLocally, private _discardItemsLocally: DiscardItemsLocally,
) {} ) {}
async execute(vault: VaultListingInterface): Promise<void> { async execute(vault: VaultListingInterface): Promise<void> {
@@ -33,7 +33,7 @@ export class DeleteThirdPartyVault {
const itemsKeys = this.keys.getKeySystemItemsKeys(vault.systemIdentifier) const itemsKeys = this.keys.getKeySystemItemsKeys(vault.systemIdentifier)
await this.removeItemsLocally.execute([...vaultItems, ...itemsKeys]) await this._discardItemsLocally.execute([...vaultItems, ...itemsKeys])
} }
private async deleteDataOwnedByThisUser(vault: VaultListingInterface): Promise<void> { private async deleteDataOwnedByThisUser(vault: VaultListingInterface): Promise<void> {

View File

@@ -14,14 +14,17 @@ export interface StorageServiceInterface {
getAllKeys(mode?: StorageValueModes): string[] getAllKeys(mode?: StorageValueModes): string[]
getValue<T>(key: string, mode?: StorageValueModes, defaultValue?: T): T getValue<T>(key: string, mode?: StorageValueModes, defaultValue?: T): T
canDecryptWithKey(key: RootKeyInterface): Promise<boolean> canDecryptWithKey(key: RootKeyInterface): Promise<boolean>
savePayload(payload: PayloadInterface): Promise<void>
savePayloads(decryptedPayloads: PayloadInterface[]): Promise<void>
setValue<T>(key: string, value: T, mode?: StorageValueModes): void setValue<T>(key: string, value: T, mode?: StorageValueModes): void
removeValue(key: string, mode?: StorageValueModes): Promise<void> removeValue(key: string, mode?: StorageValueModes): Promise<void>
setPersistencePolicy(persistencePolicy: StoragePersistencePolicies): Promise<void> setPersistencePolicy(persistencePolicy: StoragePersistencePolicies): Promise<void>
clearAllData(): Promise<void> clearAllData(): Promise<void>
getRawPayloads(uuids: string[]): Promise<FullyFormedTransferPayload[]>
savePayload(payload: PayloadInterface): Promise<void>
savePayloads(decryptedPayloads: PayloadInterface[]): Promise<void>
deletePayloads(payloads: FullyFormedPayloadInterface[]): Promise<void> deletePayloads(payloads: FullyFormedPayloadInterface[]): Promise<void>
deletePayloadsWithUuids(uuids: string[]): Promise<void> deletePayloadsWithUuids(uuids: string[]): Promise<void>
clearAllPayloads(): Promise<void> clearAllPayloads(): Promise<void>
isEphemeralSession(): boolean isEphemeralSession(): boolean
} }

View File

@@ -0,0 +1,48 @@
import { RemoveItemsFromMemory } from './RemoveItemsFromMemory'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { StorageServiceInterface } from '../StorageServiceInterface'
import { PayloadManagerInterface } from '../../Payloads/PayloadManagerInterface'
import { PayloadEmitSource, DecryptedItemInterface } from '@standardnotes/models'
import { Uuids } from '@standardnotes/utils'
describe('RemoveItemsFromMemory', () => {
let storage: StorageServiceInterface
let items: ItemManagerInterface
let payloads: PayloadManagerInterface
let removeItemsFromMemory: RemoveItemsFromMemory
beforeEach(() => {
storage = {
getRawPayloads: jest.fn().mockImplementation(() => Promise.resolve([])),
} as unknown as StorageServiceInterface
items = {
removeItemsFromMemory: jest.fn(),
} as unknown as ItemManagerInterface
payloads = {
emitPayloads: jest.fn().mockImplementation(() => Promise.resolve()),
} as unknown as PayloadManagerInterface
removeItemsFromMemory = new RemoveItemsFromMemory(storage, items, payloads)
})
it('should execute removeItemsFromMemory use case correctly', async () => {
const testItems: DecryptedItemInterface[] = [
<DecryptedItemInterface>{
uuid: 'uuid1',
content_type: 'type1',
},
<DecryptedItemInterface>{
uuid: 'uuid2',
content_type: 'type2',
},
]
await removeItemsFromMemory.execute(testItems)
expect(items.removeItemsFromMemory).toHaveBeenCalledWith(testItems)
expect(storage.getRawPayloads).toHaveBeenCalledWith(Uuids(testItems))
expect(payloads.emitPayloads).toHaveBeenCalledWith([], PayloadEmitSource.LocalDatabaseLoaded)
})
})

View File

@@ -0,0 +1,26 @@
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { StorageServiceInterface } from '../StorageServiceInterface'
import { CreatePayload, DecryptedItemInterface, PayloadEmitSource, PayloadSource } from '@standardnotes/models'
import { Uuids } from '@standardnotes/utils'
import { PayloadManagerInterface } from '../../Payloads/PayloadManagerInterface'
export class RemoveItemsFromMemory implements UseCaseInterface<void> {
constructor(
private storage: StorageServiceInterface,
private items: ItemManagerInterface,
private payloads: PayloadManagerInterface,
) {}
async execute(items: DecryptedItemInterface[]): Promise<Result<void>> {
this.items.removeItemsFromMemory(items)
const rawPayloads = await this.storage.getRawPayloads(Uuids(items))
const encryptedPayloads = rawPayloads.map((payload) => CreatePayload(payload, PayloadSource.LocalDatabaseLoaded))
await this.payloads.emitPayloads(encryptedPayloads, PayloadEmitSource.LocalDatabaseLoaded)
return Result.ok()
}
}

View File

@@ -3,11 +3,11 @@ import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { AnyItemInterface } from '@standardnotes/models' import { AnyItemInterface } from '@standardnotes/models'
import { Uuids } from '@standardnotes/utils' import { Uuids } from '@standardnotes/utils'
export class RemoveItemsLocally { export class DiscardItemsLocally {
constructor(private readonly items: ItemManagerInterface, private readonly storage: StorageServiceInterface) {} constructor(private readonly items: ItemManagerInterface, private readonly storage: StorageServiceInterface) {}
async execute(items: AnyItemInterface[]): Promise<void> { async execute(items: AnyItemInterface[]): Promise<void> {
this.items.removeItemsLocally(items) this.items.removeItemsFromMemory(items)
await this.storage.deletePayloadsWithUuids(Uuids(items)) await this.storage.deletePayloadsWithUuids(Uuids(items))
} }

View File

@@ -1,6 +1,6 @@
import { MutatorClientInterface, SyncServiceInterface } from '@standardnotes/services' import { MutatorClientInterface, SyncServiceInterface } from '@standardnotes/services'
import { import {
KeySystemRootKeyPasswordType, KeySystemPasswordType,
KeySystemRootKeyStorageMode, KeySystemRootKeyStorageMode,
VaultListingInterface, VaultListingInterface,
VaultListingMutator, VaultListingMutator,
@@ -9,8 +9,9 @@ import { ChangeVaultKeyOptionsDTO } from './ChangeVaultKeyOptionsDTO'
import { GetVault } from './GetVault' import { GetVault } from './GetVault'
import { EncryptionProviderInterface } from '../../Encryption/EncryptionProviderInterface' import { EncryptionProviderInterface } from '../../Encryption/EncryptionProviderInterface'
import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface' import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
export class ChangeVaultKeyOptions { export class ChangeVaultKeyOptions implements UseCaseInterface<void> {
constructor( constructor(
private mutator: MutatorClientInterface, private mutator: MutatorClientInterface,
private sync: SyncServiceInterface, private sync: SyncServiceInterface,
@@ -19,59 +20,101 @@ export class ChangeVaultKeyOptions {
private getVault: GetVault, private getVault: GetVault,
) {} ) {}
async execute(dto: ChangeVaultKeyOptionsDTO): Promise<void> { async execute(dto: ChangeVaultKeyOptionsDTO): Promise<Result<void>> {
const useStorageMode = dto.newKeyStorageMode ?? dto.vault.keyStorageMode
if (dto.newPasswordType) { if (dto.newPasswordType) {
if (dto.vault.keyPasswordType === dto.newPasswordType.passwordType) { const result = await this.handleNewPasswordType(dto)
throw new Error('Vault password type is already set to this type') if (result.isFailed()) {
} return result
if (dto.newPasswordType.passwordType === KeySystemRootKeyPasswordType.UserInputted) {
if (!dto.newPasswordType.userInputtedPassword) {
throw new Error('User inputted password is required')
}
await this.changePasswordTypeToUserInputted(dto.vault, dto.newPasswordType.userInputtedPassword, useStorageMode)
} else if (dto.newPasswordType.passwordType === KeySystemRootKeyPasswordType.Randomized) {
await this.changePasswordTypeToRandomized(dto.vault, useStorageMode)
} }
} }
if (dto.newKeyStorageMode) { if (dto.newStorageMode) {
const result = this.getVault.execute({ keySystemIdentifier: dto.vault.systemIdentifier }) const result = await this.handleNewStorageMode(dto)
if (result.isFailed()) { if (result.isFailed()) {
throw new Error('Vault not found') return result
}
const latestVault = result.getValue()
if (latestVault.rootKeyParams.passwordType !== KeySystemRootKeyPasswordType.UserInputted) {
throw new Error('Vault uses randomized password and cannot change its storage preference')
}
if (dto.newKeyStorageMode === latestVault.keyStorageMode) {
throw new Error('Vault already uses this storage preference')
}
if (
dto.newKeyStorageMode === KeySystemRootKeyStorageMode.Local ||
dto.newKeyStorageMode === KeySystemRootKeyStorageMode.Ephemeral
) {
await this.changeStorageModeToLocalOrEphemeral(latestVault, dto.newKeyStorageMode)
} else if (dto.newKeyStorageMode === KeySystemRootKeyStorageMode.Synced) {
await this.changeStorageModeToSynced(latestVault)
} }
} }
await this.sync.sync() await this.sync.sync()
return Result.ok()
}
private async handleNewPasswordType(dto: ChangeVaultKeyOptionsDTO): Promise<Result<void>> {
if (!dto.newPasswordType) {
return Result.ok()
}
if (dto.vault.keyPasswordType === dto.newPasswordType.passwordType) {
return Result.fail('Vault password type is already set to this type')
}
if (dto.newPasswordType.passwordType === KeySystemPasswordType.UserInputted) {
if (!dto.newPasswordType.userInputtedPassword) {
return Result.fail('User inputted password is required')
}
const useStorageMode = dto.newStorageMode ?? dto.vault.keyStorageMode
const result = await this.changePasswordTypeToUserInputted(
dto.vault,
dto.newPasswordType.userInputtedPassword,
useStorageMode,
)
if (result.isFailed()) {
return result
}
} else if (dto.newPasswordType.passwordType === KeySystemPasswordType.Randomized) {
const result = await this.changePasswordTypeToRandomized(dto.vault)
if (result.isFailed()) {
return result
}
}
return Result.ok()
}
private async handleNewStorageMode(dto: ChangeVaultKeyOptionsDTO): Promise<Result<void>> {
if (!dto.newStorageMode) {
return Result.ok()
}
const result = this.getVault.execute({ keySystemIdentifier: dto.vault.systemIdentifier })
if (result.isFailed()) {
return Result.fail('Vault not found')
}
const latestVault = result.getValue()
if (latestVault.rootKeyParams.passwordType !== KeySystemPasswordType.UserInputted) {
return Result.fail('Vault uses randomized password and cannot change its storage preference')
}
if (dto.newStorageMode === latestVault.keyStorageMode) {
return Result.fail('Vault already uses this storage preference')
}
if (
dto.newStorageMode === KeySystemRootKeyStorageMode.Local ||
dto.newStorageMode === KeySystemRootKeyStorageMode.Ephemeral
) {
const result = await this.changeStorageModeToLocalOrEphemeral(latestVault, dto.newStorageMode)
if (result.isFailed()) {
return result
}
} else if (dto.newStorageMode === KeySystemRootKeyStorageMode.Synced) {
const result = await this.changeStorageModeToSynced(latestVault)
if (result.isFailed()) {
return result
}
}
return Result.ok()
} }
private async changePasswordTypeToUserInputted( private async changePasswordTypeToUserInputted(
vault: VaultListingInterface, vault: VaultListingInterface,
userInputtedPassword: string, userInputtedPassword: string,
storageMode: KeySystemRootKeyStorageMode, storageMode: KeySystemRootKeyStorageMode,
): Promise<void> { ): Promise<Result<void>> {
const newRootKey = this.encryption.createUserInputtedKeySystemRootKey({ const newRootKey = this.encryption.createUserInputtedKeySystemRootKey({
systemIdentifier: vault.systemIdentifier, systemIdentifier: vault.systemIdentifier,
userInputtedPassword: userInputtedPassword, userInputtedPassword: userInputtedPassword,
@@ -80,60 +123,73 @@ export class ChangeVaultKeyOptions {
if (storageMode === KeySystemRootKeyStorageMode.Synced) { if (storageMode === KeySystemRootKeyStorageMode.Synced) {
await this.mutator.insertItem(newRootKey, true) await this.mutator.insertItem(newRootKey, true)
} else { } else {
this.keys.intakeNonPersistentKeySystemRootKey(newRootKey, storageMode) this.keys.cacheKey(newRootKey, storageMode)
} }
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => { await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
mutator.rootKeyParams = newRootKey.keyParams mutator.rootKeyParams = newRootKey.keyParams
}) })
await this.keys.reencryptKeySystemItemsKeysForVault(vault.systemIdentifier) await this.keys.queueVaultItemsKeysForReencryption(vault.systemIdentifier)
return Result.ok()
} }
private async changePasswordTypeToRandomized( private async changePasswordTypeToRandomized(vault: VaultListingInterface): Promise<Result<void>> {
vault: VaultListingInterface, if (vault.keyStorageMode !== KeySystemRootKeyStorageMode.Synced) {
storageMode: KeySystemRootKeyStorageMode, this.keys.removeKeyFromCache(vault.systemIdentifier)
): Promise<void> {
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
mutator.keyStorageMode = KeySystemRootKeyStorageMode.Synced
})
}
const newRootKey = this.encryption.createRandomizedKeySystemRootKey({ const newRootKey = this.encryption.createRandomizedKeySystemRootKey({
systemIdentifier: vault.systemIdentifier, systemIdentifier: vault.systemIdentifier,
}) })
if (storageMode !== KeySystemRootKeyStorageMode.Synced) {
throw new Error('Cannot change to randomized password if root key storage is not synced')
}
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => { await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
mutator.rootKeyParams = newRootKey.keyParams mutator.rootKeyParams = newRootKey.keyParams
}) })
await this.mutator.insertItem(newRootKey, true) await this.mutator.insertItem(newRootKey, true)
await this.keys.reencryptKeySystemItemsKeysForVault(vault.systemIdentifier) await this.keys.queueVaultItemsKeysForReencryption(vault.systemIdentifier)
return Result.ok()
} }
private async changeStorageModeToLocalOrEphemeral( private async changeStorageModeToLocalOrEphemeral(
vault: VaultListingInterface, vault: VaultListingInterface,
newKeyStorageMode: KeySystemRootKeyStorageMode, newStorageMode: KeySystemRootKeyStorageMode,
): Promise<void> { ): Promise<Result<void>> {
const primaryKey = this.keys.getPrimaryKeySystemRootKey(vault.systemIdentifier) const primaryKey = this.keys.getPrimaryKeySystemRootKey(vault.systemIdentifier)
if (!primaryKey) { if (!primaryKey) {
throw new Error('No primary key found') return Result.fail('No primary key found')
} }
this.keys.intakeNonPersistentKeySystemRootKey(primaryKey, newKeyStorageMode) if (newStorageMode === KeySystemRootKeyStorageMode.Ephemeral) {
this.keys.removeKeyFromCache(vault.systemIdentifier)
}
this.keys.cacheKey(primaryKey, newStorageMode)
await this.keys.deleteAllSyncedKeySystemRootKeys(vault.systemIdentifier) await this.keys.deleteAllSyncedKeySystemRootKeys(vault.systemIdentifier)
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => { await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
mutator.keyStorageMode = newKeyStorageMode mutator.keyStorageMode = newStorageMode
}) })
await this.sync.sync() await this.sync.sync()
return Result.ok()
} }
private async changeStorageModeToSynced(vault: VaultListingInterface): Promise<void> { private async changeStorageModeToSynced(vault: VaultListingInterface): Promise<Result<void>> {
const allRootKeys = this.keys.getAllKeySystemRootKeysForVault(vault.systemIdentifier) const allRootKeys = this.keys.getAllKeySystemRootKeysForVault(vault.systemIdentifier)
const syncedRootKeys = this.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier) const syncedRootKeys = this.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
this.keys.removeKeyFromCache(vault.systemIdentifier)
for (const key of allRootKeys) { for (const key of allRootKeys) {
const existingSyncedKey = syncedRootKeys.find((syncedKey) => syncedKey.token === key.token) const existingSyncedKey = syncedRootKeys.find((syncedKey) => syncedKey.token === key.token)
if (existingSyncedKey) { if (existingSyncedKey) {
@@ -146,5 +202,7 @@ export class ChangeVaultKeyOptions {
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => { await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
mutator.keyStorageMode = KeySystemRootKeyStorageMode.Synced mutator.keyStorageMode = KeySystemRootKeyStorageMode.Synced
}) })
return Result.ok()
} }
} }

View File

@@ -1,10 +1,10 @@
import { KeySystemRootKeyPasswordType, KeySystemRootKeyStorageMode, VaultListingInterface } from '@standardnotes/models' import { KeySystemPasswordType, KeySystemRootKeyStorageMode, VaultListingInterface } from '@standardnotes/models'
export type ChangeVaultKeyOptionsDTO = { export type ChangeVaultKeyOptionsDTO = {
vault: VaultListingInterface vault: VaultListingInterface
newPasswordType: newPasswordType:
| { passwordType: KeySystemRootKeyPasswordType.Randomized } | { passwordType: KeySystemPasswordType.Randomized }
| { passwordType: KeySystemRootKeyPasswordType.UserInputted; userInputtedPassword: string } | { passwordType: KeySystemPasswordType.UserInputted; userInputtedPassword: string }
| undefined | undefined
newKeyStorageMode: KeySystemRootKeyStorageMode | undefined newStorageMode: KeySystemRootKeyStorageMode | undefined
} }

View File

@@ -2,7 +2,7 @@ import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import { UuidGenerator } from '@standardnotes/utils' import { UuidGenerator } from '@standardnotes/utils'
import { import {
KeySystemRootKeyParamsInterface, KeySystemRootKeyParamsInterface,
KeySystemRootKeyPasswordType, KeySystemPasswordType,
VaultListingContentSpecialized, VaultListingContentSpecialized,
VaultListingInterface, VaultListingInterface,
KeySystemRootKeyStorageMode, KeySystemRootKeyStorageMode,
@@ -44,9 +44,7 @@ export class CreateVault {
keySystemIdentifier, keySystemIdentifier,
vaultName: dto.vaultName, vaultName: dto.vaultName,
vaultDescription: dto.vaultDescription, vaultDescription: dto.vaultDescription,
passwordType: dto.userInputtedPassword passwordType: dto.userInputtedPassword ? KeySystemPasswordType.UserInputted : KeySystemPasswordType.Randomized,
? KeySystemRootKeyPasswordType.UserInputted
: KeySystemRootKeyPasswordType.Randomized,
rootKeyParams: rootKey.keyParams, rootKeyParams: rootKey.keyParams,
storage: dto.storagePreference, storage: dto.storagePreference,
}) })
@@ -60,7 +58,7 @@ export class CreateVault {
keySystemIdentifier: string keySystemIdentifier: string
vaultName: string vaultName: string
vaultDescription?: string vaultDescription?: string
passwordType: KeySystemRootKeyPasswordType passwordType: KeySystemPasswordType
rootKeyParams: KeySystemRootKeyParamsInterface rootKeyParams: KeySystemRootKeyParamsInterface
storage: KeySystemRootKeyStorageMode storage: KeySystemRootKeyStorageMode
}): Promise<VaultListingInterface> { }): Promise<VaultListingInterface> {
@@ -109,7 +107,7 @@ export class CreateVault {
if (dto.storagePreference === KeySystemRootKeyStorageMode.Synced) { if (dto.storagePreference === KeySystemRootKeyStorageMode.Synced) {
await this.mutator.insertItem(newRootKey, true) await this.mutator.insertItem(newRootKey, true)
} else { } else {
this.keys.intakeNonPersistentKeySystemRootKey(newRootKey, dto.storagePreference) this.keys.cacheKey(newRootKey, dto.storagePreference)
} }
return newRootKey return newRootKey

View File

@@ -3,7 +3,7 @@ import { ClientDisplayableError, isClientDisplayableError } from '@standardnotes
import { import {
KeySystemIdentifier, KeySystemIdentifier,
KeySystemRootKeyInterface, KeySystemRootKeyInterface,
KeySystemRootKeyPasswordType, KeySystemPasswordType,
KeySystemRootKeyStorageMode, KeySystemRootKeyStorageMode,
VaultListingInterface, VaultListingInterface,
VaultListingMutator, VaultListingMutator,
@@ -31,7 +31,7 @@ export class RotateVaultKey {
let newRootKey: KeySystemRootKeyInterface | undefined let newRootKey: KeySystemRootKeyInterface | undefined
if (currentRootKey.keyParams.passwordType === KeySystemRootKeyPasswordType.UserInputted) { if (currentRootKey.keyParams.passwordType === KeySystemPasswordType.UserInputted) {
if (!params.userInputtedPassword) { if (!params.userInputtedPassword) {
throw new Error('Cannot rotate key system root key; user inputted password required') throw new Error('Cannot rotate key system root key; user inputted password required')
} }
@@ -40,7 +40,7 @@ export class RotateVaultKey {
systemIdentifier: params.vault.systemIdentifier, systemIdentifier: params.vault.systemIdentifier,
userInputtedPassword: params.userInputtedPassword, userInputtedPassword: params.userInputtedPassword,
}) })
} else if (currentRootKey.keyParams.passwordType === KeySystemRootKeyPasswordType.Randomized) { } else if (currentRootKey.keyParams.passwordType === KeySystemPasswordType.Randomized) {
newRootKey = this.encryption.createRandomizedKeySystemRootKey({ newRootKey = this.encryption.createRandomizedKeySystemRootKey({
systemIdentifier: params.vault.systemIdentifier, systemIdentifier: params.vault.systemIdentifier,
}) })
@@ -53,7 +53,7 @@ export class RotateVaultKey {
if (params.vault.keyStorageMode === KeySystemRootKeyStorageMode.Synced) { if (params.vault.keyStorageMode === KeySystemRootKeyStorageMode.Synced) {
await this.mutator.insertItem(newRootKey, true) await this.mutator.insertItem(newRootKey, true)
} else { } else {
this.keys.intakeNonPersistentKeySystemRootKey(newRootKey, params.vault.keyStorageMode) this.keys.cacheKey(newRootKey, params.vault.keyStorageMode)
} }
await this.mutator.changeItem<VaultListingMutator>(params.vault, (mutator) => { await this.mutator.changeItem<VaultListingMutator>(params.vault, (mutator) => {
@@ -73,7 +73,7 @@ export class RotateVaultKey {
errors.push(updateKeySystemItemsKeyResult) errors.push(updateKeySystemItemsKeyResult)
} }
await this.keys.reencryptKeySystemItemsKeysForVault(params.vault.systemIdentifier) await this.keys.queueVaultItemsKeysForReencryption(params.vault.systemIdentifier)
return errors return errors
} }

View File

@@ -26,6 +26,7 @@ import { MutatorClientInterface } from '../Mutator/MutatorClientInterface'
import { AlertService } from '../Alert/AlertService' import { AlertService } from '../Alert/AlertService'
import { GetVaults } from './UseCase/GetVaults' import { GetVaults } from './UseCase/GetVaults'
import { VaultLockServiceInterface } from '../VaultLock/VaultLockServiceInterface' import { VaultLockServiceInterface } from '../VaultLock/VaultLockServiceInterface'
import { Result } from '@standardnotes/domain-core'
export class VaultService export class VaultService
extends AbstractService<VaultServiceEvent, VaultServiceEventPayload[VaultServiceEvent]> extends AbstractService<VaultServiceEvent, VaultServiceEventPayload[VaultServiceEvent]>
@@ -194,7 +195,7 @@ export class VaultService
return updatedVault return updatedVault
} }
async rotateVaultRootKey(vault: VaultListingInterface): Promise<void> { async rotateVaultRootKey(vault: VaultListingInterface, vaultPassword?: string): Promise<void> {
if (this.vaultLocks.isVaultLocked(vault)) { if (this.vaultLocks.isVaultLocked(vault)) {
throw new Error('Cannot rotate root key of locked vault') throw new Error('Cannot rotate root key of locked vault')
} }
@@ -202,7 +203,7 @@ export class VaultService
await this._rotateVaultKey.execute({ await this._rotateVaultKey.execute({
vault, vault,
sharedVaultUuid: vault.isSharedVaultListing() ? vault.sharing.sharedVaultUuid : undefined, sharedVaultUuid: vault.isSharedVaultListing() ? vault.sharing.sharedVaultUuid : undefined,
userInputtedPassword: undefined, userInputtedPassword: vaultPassword,
}) })
await this.notifyEventSync(VaultServiceEvent.VaultRootKeyRotated, { vault }) await this.notifyEventSync(VaultServiceEvent.VaultRootKeyRotated, { vault })
@@ -227,15 +228,17 @@ export class VaultService
return this.getVault({ keySystemIdentifier: latestItem.key_system_identifier }) return this.getVault({ keySystemIdentifier: latestItem.key_system_identifier })
} }
async changeVaultOptions(dto: ChangeVaultKeyOptionsDTO): Promise<void> { async changeVaultOptions(dto: ChangeVaultKeyOptionsDTO): Promise<Result<void>> {
if (this.vaultLocks.isVaultLocked(dto.vault)) { if (this.vaultLocks.isVaultLocked(dto.vault)) {
throw new Error('Attempting to change vault options on a locked vault') throw new Error('Attempting to change vault options on a locked vault')
} }
await this._changeVaultKeyOptions.execute(dto) const result = await this._changeVaultKeyOptions.execute(dto)
if (dto.newPasswordType) { if (dto.newPasswordType) {
await this.notifyEventSync(VaultServiceEvent.VaultRootKeyRotated, { vault: dto.vault }) await this.notifyEventSync(VaultServiceEvent.VaultRootKeyRotated, { vault: dto.vault })
} }
return result
} }
} }

View File

@@ -7,6 +7,7 @@ import {
import { AbstractService } from '../Service/AbstractService' import { AbstractService } from '../Service/AbstractService'
import { VaultServiceEvent, VaultServiceEventPayload } from './VaultServiceEvent' import { VaultServiceEvent, VaultServiceEventPayload } from './VaultServiceEvent'
import { ChangeVaultKeyOptionsDTO } from './UseCase/ChangeVaultKeyOptionsDTO' import { ChangeVaultKeyOptionsDTO } from './UseCase/ChangeVaultKeyOptionsDTO'
import { Result } from '@standardnotes/domain-core'
export interface VaultServiceInterface export interface VaultServiceInterface
extends AbstractService<VaultServiceEvent, VaultServiceEventPayload[VaultServiceEvent]> { extends AbstractService<VaultServiceEvent, VaultServiceEventPayload[VaultServiceEvent]> {
@@ -34,6 +35,6 @@ export interface VaultServiceInterface
vault: VaultListingInterface, vault: VaultListingInterface,
params: { name: string; description: string }, params: { name: string; description: string },
): Promise<VaultListingInterface> ): Promise<VaultListingInterface>
rotateVaultRootKey(vault: VaultListingInterface): Promise<void> rotateVaultRootKey(vault: VaultListingInterface, vaultPassword?: string): Promise<void>
changeVaultOptions(dto: ChangeVaultKeyOptionsDTO): Promise<void> changeVaultOptions(dto: ChangeVaultKeyOptionsDTO): Promise<Result<void>>
} }

View File

@@ -1,5 +1,5 @@
import { GetVaults } from '../Vault/UseCase/GetVaults' import { GetVaults } from '../Vault/UseCase/GetVaults'
import { KeySystemRootKeyPasswordType, KeySystemRootKeyStorageMode, VaultListingInterface } from '@standardnotes/models' import { KeySystemPasswordType, KeySystemRootKeyStorageMode, VaultListingInterface } from '@standardnotes/models'
import { VaultLockServiceInterface } from './VaultLockServiceInterface' import { VaultLockServiceInterface } from './VaultLockServiceInterface'
import { VaultLockServiceEvent, VaultLockServiceEventPayload } from './VaultLockServiceEvent' import { VaultLockServiceEvent, VaultLockServiceEventPayload } from './VaultLockServiceEvent'
import { AbstractService } from '../Service/AbstractService' import { AbstractService } from '../Service/AbstractService'
@@ -51,19 +51,19 @@ export class VaultLockService
} }
public isVaultLockable(vault: VaultListingInterface): boolean { public isVaultLockable(vault: VaultListingInterface): boolean {
return vault.keyPasswordType === KeySystemRootKeyPasswordType.UserInputted return vault.keyPasswordType === KeySystemPasswordType.UserInputted
} }
public lockNonPersistentVault(vault: VaultListingInterface): void { public async lockNonPersistentVault(vault: VaultListingInterface): Promise<void> {
if (vault.keyStorageMode === KeySystemRootKeyStorageMode.Synced) { if (vault.keyStorageMode === KeySystemRootKeyStorageMode.Synced) {
throw new Error('Vault uses synced key storage and cannot be locked') throw new Error('Vault uses synced key storage and cannot be locked')
} }
if (vault.keyPasswordType !== KeySystemRootKeyPasswordType.UserInputted) { if (vault.keyPasswordType !== KeySystemPasswordType.UserInputted) {
throw new Error('Vault uses randomized password and cannot be locked') throw new Error('Vault uses randomized password and cannot be locked')
} }
this.keys.clearMemoryOfKeysRelatedToVault(vault) await this.keys.wipeVaultKeysFromMemory(vault)
this.lockMap.set(vault.uuid, true) this.lockMap.set(vault.uuid, true)
@@ -71,7 +71,7 @@ export class VaultLockService
} }
public async unlockNonPersistentVault(vault: VaultListingInterface, password: string): Promise<boolean> { public async unlockNonPersistentVault(vault: VaultListingInterface, password: string): Promise<boolean> {
if (vault.keyPasswordType !== KeySystemRootKeyPasswordType.UserInputted) { if (vault.keyPasswordType !== KeySystemPasswordType.UserInputted) {
throw new Error('Vault uses randomized password and cannot be unlocked with user inputted password') throw new Error('Vault uses randomized password and cannot be unlocked with user inputted password')
} }
@@ -84,12 +84,12 @@ export class VaultLockService
userInputtedPassword: password, userInputtedPassword: password,
}) })
this.keys.intakeNonPersistentKeySystemRootKey(derivedRootKey, vault.keyStorageMode) this.keys.cacheKey(derivedRootKey, vault.keyStorageMode)
await this.encryption.decryptErroredPayloads() await this.encryption.decryptErroredPayloads()
if (this.computeVaultLockState(vault) === 'locked') { if (this.computeVaultLockState(vault) === 'locked') {
this.keys.undoIntakeNonPersistentKeySystemRootKey(vault.systemIdentifier) this.keys.removeKeyFromCache(vault.systemIdentifier)
return false return false
} }

View File

@@ -7,6 +7,6 @@ export interface VaultLockServiceInterface
getLockedvaults(): VaultListingInterface[] getLockedvaults(): VaultListingInterface[]
isVaultLocked(vault: VaultListingInterface): boolean isVaultLocked(vault: VaultListingInterface): boolean
isVaultLockable(vault: VaultListingInterface): boolean isVaultLockable(vault: VaultListingInterface): boolean
lockNonPersistentVault(vault: VaultListingInterface): void lockNonPersistentVault(vault: VaultListingInterface): Promise<void>
unlockNonPersistentVault(vault: VaultListingInterface, password: string): Promise<boolean> unlockNonPersistentVault(vault: VaultListingInterface, password: string): Promise<boolean>
} }

View File

@@ -153,6 +153,7 @@ export * from './Storage/KeyValueStoreInterface'
export * from './Storage/StorageKeys' export * from './Storage/StorageKeys'
export * from './Storage/StorageServiceInterface' export * from './Storage/StorageServiceInterface'
export * from './Storage/StorageTypes' export * from './Storage/StorageTypes'
export * from './Storage/UseCase/RemoveItemsFromMemory'
export * from './Strings/InfoStrings' export * from './Strings/InfoStrings'
export * from './Strings/Messages' export * from './Strings/Messages'
export * from './Subscription/AppleIAPProductId' export * from './Subscription/AppleIAPProductId'
@@ -166,7 +167,7 @@ export * from './Sync/SyncOptions'
export * from './Sync/SyncQueueStrategy' export * from './Sync/SyncQueueStrategy'
export * from './Sync/SyncServiceInterface' export * from './Sync/SyncServiceInterface'
export * from './Sync/SyncSource' export * from './Sync/SyncSource'
export * from './UseCase/RemoveItemsLocally' export * from './UseCase/DiscardItemsLocally'
export * from './User/AccountEvent' export * from './User/AccountEvent'
export * from './User/AccountEventData' export * from './User/AccountEventData'
export * from './User/CredentialsChangeFunctionResponse' export * from './User/CredentialsChangeFunctionResponse'

View File

@@ -47,7 +47,7 @@ import {
IntegrityService, IntegrityService,
InternalEventBus, InternalEventBus,
KeySystemKeyManager, KeySystemKeyManager,
RemoveItemsLocally, DiscardItemsLocally,
RevisionManager, RevisionManager,
SelfContactManager, SelfContactManager,
StatusService, StatusService,
@@ -118,6 +118,7 @@ import {
ContactBelongsToVault, ContactBelongsToVault,
DeleteContact, DeleteContact,
VaultLockService, VaultLockService,
RemoveItemsFromMemory,
} from '@standardnotes/services' } from '@standardnotes/services'
import { ItemManager } from '../../Services/Items/ItemManager' import { ItemManager } from '../../Services/Items/ItemManager'
import { PayloadManager } from '../../Services/Payloads/PayloadManager' import { PayloadManager } from '../../Services/Payloads/PayloadManager'
@@ -222,8 +223,16 @@ export class Dependencies {
return new DecryptBackupFile(this.get(TYPES.EncryptionService)) return new DecryptBackupFile(this.get(TYPES.EncryptionService))
}) })
this.factory.set(TYPES.RemoveItemsLocally, () => { this.factory.set(TYPES.DiscardItemsLocally, () => {
return new RemoveItemsLocally(this.get(TYPES.ItemManager), this.get(TYPES.DiskStorageService)) return new DiscardItemsLocally(this.get(TYPES.ItemManager), this.get(TYPES.DiskStorageService))
})
this.factory.set(TYPES.RemoveItemsFromMemory, () => {
return new RemoveItemsFromMemory(
this.get(TYPES.DiskStorageService),
this.get(TYPES.ItemManager),
this.get(TYPES.PayloadManager),
)
}) })
this.factory.set(TYPES.FindContact, () => { this.factory.set(TYPES.FindContact, () => {
@@ -442,7 +451,7 @@ export class Dependencies {
this.get(TYPES.MutatorService), this.get(TYPES.MutatorService),
this.get(TYPES.KeySystemKeyManager), this.get(TYPES.KeySystemKeyManager),
this.get(TYPES.SyncService), this.get(TYPES.SyncService),
this.get(TYPES.RemoveItemsLocally), this.get(TYPES.DiscardItemsLocally),
) )
}) })
@@ -555,7 +564,7 @@ export class Dependencies {
this.get(TYPES.MutatorService), this.get(TYPES.MutatorService),
this.get(TYPES.ItemManager), this.get(TYPES.ItemManager),
this.get(TYPES.CreateNewDefaultItemsKey), this.get(TYPES.CreateNewDefaultItemsKey),
this.get(TYPES.RemoveItemsLocally), this.get(TYPES.DiscardItemsLocally),
this.get(TYPES.FindDefaultItemsKey), this.get(TYPES.FindDefaultItemsKey),
) )
}) })
@@ -723,6 +732,7 @@ export class Dependencies {
this.get(TYPES.ConvertToSharedVault), this.get(TYPES.ConvertToSharedVault),
this.get(TYPES.DeleteSharedVault), this.get(TYPES.DeleteSharedVault),
this.get(TYPES.IsVaultOwner), this.get(TYPES.IsVaultOwner),
this.get(TYPES.DiscardItemsLocally),
this.get(TYPES.InternalEventBus), this.get(TYPES.InternalEventBus),
) )
}) })
@@ -1221,6 +1231,7 @@ export class Dependencies {
this.get(TYPES.ItemManager), this.get(TYPES.ItemManager),
this.get(TYPES.MutatorService), this.get(TYPES.MutatorService),
this.get(TYPES.DiskStorageService), this.get(TYPES.DiskStorageService),
this.get(TYPES.RemoveItemsFromMemory),
this.get(TYPES.InternalEventBus), this.get(TYPES.InternalEventBus),
) )
}) })

View File

@@ -89,7 +89,7 @@ export const TYPES = {
GetRevision: Symbol.for('GetRevision'), GetRevision: Symbol.for('GetRevision'),
DeleteRevision: Symbol.for('DeleteRevision'), DeleteRevision: Symbol.for('DeleteRevision'),
ImportDataUseCase: Symbol.for('ImportDataUseCase'), ImportDataUseCase: Symbol.for('ImportDataUseCase'),
RemoveItemsLocally: Symbol.for('RemoveItemsLocally'), DiscardItemsLocally: Symbol.for('DiscardItemsLocally'),
FindContact: Symbol.for('FindContact'), FindContact: Symbol.for('FindContact'),
GetAllContacts: Symbol.for('GetAllContacts'), GetAllContacts: Symbol.for('GetAllContacts'),
CreateOrEditContact: Symbol.for('CreateOrEditContact'), CreateOrEditContact: Symbol.for('CreateOrEditContact'),
@@ -150,6 +150,7 @@ export const TYPES = {
EncryptTypeAPayloadWithKeyLookup: Symbol.for('EncryptTypeAPayloadWithKeyLookup'), EncryptTypeAPayloadWithKeyLookup: Symbol.for('EncryptTypeAPayloadWithKeyLookup'),
DecryptBackupFile: Symbol.for('DecryptBackupFile'), DecryptBackupFile: Symbol.for('DecryptBackupFile'),
IsVaultOwner: Symbol.for('IsVaultOwner'), IsVaultOwner: Symbol.for('IsVaultOwner'),
RemoveItemsFromMemory: Symbol.for('RemoveItemsFromMemory'),
// Mappers // Mappers
SessionStorageMapper: Symbol.for('SessionStorageMapper'), SessionStorageMapper: Symbol.for('SessionStorageMapper'),

View File

@@ -18,7 +18,7 @@ export class Migration2_20_0 extends Migration {
const items = this.services.itemManager.getItems(contentType) const items = this.services.itemManager.getItems(contentType)
for (const item of items) { for (const item of items) {
this.services.itemManager.removeItemLocally(item) this.services.itemManager.removeItemFromMemory(item)
await this.services.storageService.deletePayloadWithUuid(item.uuid) await this.services.storageService.deletePayloadWithUuid(item.uuid)
} }
} }

View File

@@ -18,7 +18,7 @@ export class Migration2_36_0 extends Migration {
const items = this.services.itemManager.getItems(contentType) const items = this.services.itemManager.getItems(contentType)
for (const item of items) { for (const item of items) {
this.services.itemManager.removeItemLocally(item) this.services.itemManager.removeItemFromMemory(item)
await this.services.storageService.deletePayloadWithUuid(item.uuid) await this.services.storageService.deletePayloadWithUuid(item.uuid)
} }
} }

View File

@@ -816,14 +816,14 @@ export class ItemManager extends Services.AbstractService implements Services.It
/** /**
* Important: Caller must coordinate with storage service separately to delete item from persistent database. * Important: Caller must coordinate with storage service separately to delete item from persistent database.
*/ */
public removeItemLocally(item: Models.AnyItemInterface): void { public removeItemFromMemory(item: Models.AnyItemInterface): void {
this.removeItemsLocally([item]) this.removeItemsFromMemory([item])
} }
/** /**
* Important: Caller must coordinate with storage service separately to delete item from persistent database. * Important: Caller must coordinate with storage service separately to delete item from persistent database.
*/ */
public removeItemsLocally(items: Models.AnyItemInterface[]): void { public removeItemsFromMemory(items: Models.AnyItemInterface[]): void {
this.collection.discard(items) this.collection.discard(items)
this.payloadManager.removePayloadLocally(items.map((item) => item.payload)) this.payloadManager.removePayloadLocally(items.map((item) => item.payload))

View File

@@ -69,7 +69,7 @@ export class DiskStorageService
private values!: StorageValuesObject private values!: StorageValuesObject
constructor( constructor(
private deviceInterface: DeviceInterface, private device: DeviceInterface,
private identifier: string, private identifier: string,
protected override internalEventBus: InternalEventBusInterface, protected override internalEventBus: InternalEventBusInterface,
) { ) {
@@ -82,7 +82,7 @@ export class DiskStorageService
} }
public override deinit() { public override deinit() {
;(this.deviceInterface as unknown) = undefined ;(this.device as unknown) = undefined
;(this.encryptionProvider as unknown) = undefined ;(this.encryptionProvider as unknown) = undefined
this.storagePersistable = false this.storagePersistable = false
super.deinit() super.deinit()
@@ -104,9 +104,9 @@ export class DiskStorageService
this.persistencePolicy = persistencePolicy this.persistencePolicy = persistencePolicy
if (this.persistencePolicy === StoragePersistencePolicies.Ephemeral) { if (this.persistencePolicy === StoragePersistencePolicies.Ephemeral) {
await this.deviceInterface.clearNamespacedKeychainValue(this.identifier) await this.device.clearNamespacedKeychainValue(this.identifier)
await this.deviceInterface.removeAllDatabaseEntries(this.identifier) await this.device.removeAllDatabaseEntries(this.identifier)
await this.deviceInterface.removeRawStorageValuesForIdentifier(this.identifier) await this.device.removeRawStorageValuesForIdentifier(this.identifier)
await this.clearAllPayloads() await this.clearAllPayloads()
} }
} }
@@ -116,7 +116,7 @@ export class DiskStorageService
} }
public async initializeFromDisk(): Promise<void> { public async initializeFromDisk(): Promise<void> {
const value = await this.deviceInterface.getRawStorageValue(this.getPersistenceKey()) const value = await this.device.getRawStorageValue(this.getPersistenceKey())
const values = value ? JSON.parse(value as string) : undefined const values = value ? JSON.parse(value as string) : undefined
await this.setInitialValues(values) await this.setInitialValues(values)
@@ -240,7 +240,7 @@ export class DiskStorageService
return values return values
} }
await this.deviceInterface?.setRawStorageValue(this.getPersistenceKey(), JSON.stringify(values)) await this.device?.setRawStorageValue(this.getPersistenceKey(), JSON.stringify(values))
return values return values
}) })
@@ -385,7 +385,11 @@ export class DiskStorageService
} }
public async getAllRawPayloads(): Promise<FullyFormedTransferPayload[]> { public async getAllRawPayloads(): Promise<FullyFormedTransferPayload[]> {
return this.deviceInterface.getAllDatabaseEntries(this.identifier) return this.device.getAllDatabaseEntries(this.identifier)
}
public async getRawPayloads(uuids: string[]): Promise<FullyFormedTransferPayload[]> {
return this.device.getDatabaseEntries(this.identifier, uuids)
} }
public async savePayload(payload: FullyFormedPayloadInterface): Promise<void> { public async savePayload(payload: FullyFormedPayloadInterface): Promise<void> {
@@ -440,7 +444,7 @@ export class DiskStorageService
const exportedDeleted = deleted.map(CreateDeletedLocalStorageContextPayload) const exportedDeleted = deleted.map(CreateDeletedLocalStorageContextPayload)
return this.executeCriticalFunction(async () => { return this.executeCriticalFunction(async () => {
return this.deviceInterface?.saveDatabaseEntries( return this.device?.saveDatabaseEntries(
[...exportedEncrypted, ...exportedDecrypted, ...exportedDeleted], [...exportedEncrypted, ...exportedDecrypted, ...exportedDeleted],
this.identifier, this.identifier,
) )
@@ -453,19 +457,19 @@ export class DiskStorageService
public async deletePayloadsWithUuids(uuids: string[]): Promise<void> { public async deletePayloadsWithUuids(uuids: string[]): Promise<void> {
await this.executeCriticalFunction(async () => { await this.executeCriticalFunction(async () => {
await Promise.all(uuids.map((uuid) => this.deviceInterface.removeDatabaseEntry(uuid, this.identifier))) await Promise.all(uuids.map((uuid) => this.device.removeDatabaseEntry(uuid, this.identifier)))
}) })
} }
public async deletePayloadWithUuid(uuid: string) { public async deletePayloadWithUuid(uuid: string) {
return this.executeCriticalFunction(async () => { return this.executeCriticalFunction(async () => {
await this.deviceInterface.removeDatabaseEntry(uuid, this.identifier) await this.device.removeDatabaseEntry(uuid, this.identifier)
}) })
} }
public async clearAllPayloads() { public async clearAllPayloads() {
return this.executeCriticalFunction(async () => { return this.executeCriticalFunction(async () => {
return this.deviceInterface.removeAllDatabaseEntries(this.identifier) return this.device.removeAllDatabaseEntries(this.identifier)
}) })
} }
@@ -474,9 +478,9 @@ export class DiskStorageService
await this.clearValues() await this.clearValues()
await this.clearAllPayloads() await this.clearAllPayloads()
await this.deviceInterface.removeRawStorageValue(namespacedKey(this.identifier, RawStorageKey.SnjsVersion)) await this.device.removeRawStorageValue(namespacedKey(this.identifier, RawStorageKey.SnjsVersion))
await this.deviceInterface.removeRawStorageValue(this.getPersistenceKey()) await this.device.removeRawStorageValue(this.getPersistenceKey())
}) })
} }
} }

View File

@@ -11,7 +11,7 @@ export const VaultTests = {
'vaults/signatures.test.js', 'vaults/signatures.test.js',
'vaults/shared_vaults.test.js', 'vaults/shared_vaults.test.js',
'vaults/invites.test.js', 'vaults/invites.test.js',
'vaults/locking.test.js', 'vaults/key-management.test.js',
'vaults/items.test.js', 'vaults/items.test.js',
'vaults/conflicts.test.js', 'vaults/conflicts.test.js',
'vaults/deletion.test.js', 'vaults/deletion.test.js',

View File

@@ -449,7 +449,7 @@ describe('basic auth', function () {
} }
const mutatorSpy = sinon.spy(this.application.mutator, 'setItemToBeDeleted') const mutatorSpy = sinon.spy(this.application.mutator, 'setItemToBeDeleted')
const removeItemsSpy = sinon.spy(this.application.items, 'removeItemsLocally') const removeItemsSpy = sinon.spy(this.application.items, 'removeItemsFromMemory')
const deletePayloadsSpy = sinon.spy(this.application.storage, 'deletePayloadsWithUuids') const deletePayloadsSpy = sinon.spy(this.application.storage, 'deletePayloadsWithUuids')
await this.context.changePassword('new-password') await this.context.changePassword('new-password')

View File

@@ -201,7 +201,7 @@ describe('item manager', function () {
observed.push({ changed, inserted, removed, ignored }) observed.push({ changed, inserted, removed, ignored })
}) })
const note = await createNote() const note = await createNote()
await application.items.removeItemLocally(note) await application.items.removeItemFromMemory(note)
expect(observed.length).to.equal(1) expect(observed.length).to.equal(1)
expect(application.items.findItem(note.uuid)).to.not.be.ok expect(application.items.findItem(note.uuid)).to.not.be.ok

View File

@@ -190,7 +190,7 @@ describe('keys', function () {
const itemsKey = this.application.encryption.itemsKeyForEncryptedPayload(encryptedPayload) const itemsKey = this.application.encryption.itemsKeyForEncryptedPayload(encryptedPayload)
await this.application.items.removeItemLocally(itemsKey) await this.application.items.removeItemFromMemory(itemsKey)
const erroredPayload = await this.application.encryption.decryptSplitSingle({ const erroredPayload = await this.application.encryption.decryptSplitSingle({
usesItemsKeyWithKeyLookup: { usesItemsKeyWithKeyLookup: {

View File

@@ -63,6 +63,16 @@ export default class WebDeviceInterface {
return models return models
} }
async getDatabaseEntries(identifier, ids) {
const models = []
for (const id of ids) {
const key = this._keyForPayloadId(id, identifier)
const model = JSON.parse(localStorage[key])
models.push(model)
}
return models
}
async getDatabaseLoadChunks(options, identifier) { async getDatabaseLoadChunks(options, identifier) {
const entries = await this.getAllDatabaseEntries(identifier) const entries = await this.getAllDatabaseEntries(identifier)
const { const {

View File

@@ -53,7 +53,7 @@ describe('sync integrity', () => {
const didEnterOutOfSync = awaitSyncEventPromise(this.application, SyncEvent.EnterOutOfSync) const didEnterOutOfSync = awaitSyncEventPromise(this.application, SyncEvent.EnterOutOfSync)
await this.application.sync.sync({ checkIntegrity: true }) await this.application.sync.sync({ checkIntegrity: true })
await this.application.items.removeItemLocally(item) await this.application.items.removeItemFromMemory(item)
await this.application.sync.sync({ checkIntegrity: true, awaitAll: true }) await this.application.sync.sync({ checkIntegrity: true, awaitAll: true })
await didEnterOutOfSync await didEnterOutOfSync
@@ -70,7 +70,7 @@ describe('sync integrity', () => {
const didExitOutOfSync = awaitSyncEventPromise(this.application, SyncEvent.ExitOutOfSync) const didExitOutOfSync = awaitSyncEventPromise(this.application, SyncEvent.ExitOutOfSync)
await this.application.sync.sync({ checkIntegrity: true }) await this.application.sync.sync({ checkIntegrity: true })
await this.application.items.removeItemLocally(item) await this.application.items.removeItemFromMemory(item)
await this.application.sync.sync({ checkIntegrity: true, awaitAll: true }) await this.application.sync.sync({ checkIntegrity: true, awaitAll: true })
await Promise.all([didEnterOutOfSync, didExitOutOfSync]) await Promise.all([didEnterOutOfSync, didExitOutOfSync])

View File

@@ -0,0 +1,398 @@
import * as Factory from '../lib/factory.js'
chai.use(chaiAsPromised)
const expect = chai.expect
describe('vault key management', function () {
this.timeout(Factory.TwentySecondTimeout)
let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
await context.launch()
})
it('should lock non-persistent vault', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Ephemeral,
})
await context.vaultLocks.lockNonPersistentVault(vault)
expect(context.vaultLocks.isVaultLocked(vault)).to.be.true
})
it('should not be able to lock user-inputted vault with synced key', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Synced,
})
await Factory.expectThrowsAsync(
() => context.vaultLocks.lockNonPersistentVault(vault),
'Vault uses synced key storage and cannot be locked',
)
})
it('should not be able to lock randomized vault', async () => {
const vault = await context.vaults.createRandomizedVault({
name: 'test vault',
description: 'test vault description',
})
await Factory.expectThrowsAsync(
() => context.vaultLocks.lockNonPersistentVault(vault),
'Vault uses synced key storage and cannot be locked',
)
})
it('should throw if attempting to change password of locked vault', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Ephemeral,
})
await context.vaultLocks.lockNonPersistentVault(vault)
await Factory.expectThrowsAsync(
() => context.vaults.changeVaultOptions({ vault }),
'Attempting to change vault options on a locked vault',
)
})
describe('key rotation and persistence', () => {
it('rotating ephemeral vault should not persist keys', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Ephemeral,
})
await context.vaults.rotateVaultRootKey(vault, 'test password')
const syncedKeys = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
expect(syncedKeys.length).to.equal(0)
const storedKey = context.keys.getRootKeyFromStorageForVault(vault.systemIdentifier)
expect(storedKey).to.be.undefined
})
it('rotating local vault should not sync keys', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Local,
})
await context.vaults.rotateVaultRootKey(vault, 'test password')
const syncedKeys = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
expect(syncedKeys.length).to.equal(0)
const storedKey = context.keys.getRootKeyFromStorageForVault(vault.systemIdentifier)
expect(storedKey).to.not.be.undefined
})
it('rotating synced vault should sync new key', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Synced,
})
await context.vaults.rotateVaultRootKey(vault, 'test password')
const syncedKeys = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
expect(syncedKeys.length).to.equal(2)
const storedKey = context.keys.getRootKeyFromStorageForVault(vault.systemIdentifier)
expect(storedKey).to.be.undefined
})
})
describe('memory management', () => {
it('locking a vault should clear decrypted items keys from memory', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Ephemeral,
})
const itemsKeys = context.keys.getKeySystemItemsKeys(vault.systemIdentifier)
expect(itemsKeys.length).to.equal(1)
await context.vaultLocks.lockNonPersistentVault(vault)
const itemsKeysAfterLock = context.keys.getKeySystemItemsKeys(vault.systemIdentifier)
expect(itemsKeysAfterLock.length).to.equal(0)
})
it('locking then unlocking a vault should bring items keys back into memory', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Ephemeral,
})
await context.vaultLocks.lockNonPersistentVault(vault)
await context.vaultLocks.unlockNonPersistentVault(vault, 'test password')
const itemsKeys = context.keys.getKeySystemItemsKeys(vault.systemIdentifier)
expect(itemsKeys.length).to.equal(1)
const rootKeys = context.keys.getAllKeySystemRootKeysForVault(vault.systemIdentifier)
expect(rootKeys.length).to.equal(1)
})
})
describe('changeVaultOptions', () => {
describe('change storage type', () => {
it('should not be able to change randomized vault from synced to local', async () => {
const vault = await context.vaults.createRandomizedVault({
name: 'test vault',
description: 'test vault description',
})
const result = await context.vaults.changeVaultOptions({
vault,
newStorageMode: KeySystemRootKeyStorageMode.Local,
})
expect(result.isFailed()).to.be.true
expect(result.getError()).to.equal('Vault uses randomized password and cannot change its storage preference')
})
it('should not be able to change randomized vault from synced to ephemeral', async () => {
const vault = await context.vaults.createRandomizedVault({
name: 'test vault',
description: 'test vault description',
})
const result = await context.vaults.changeVaultOptions({
vault,
newStorageMode: KeySystemRootKeyStorageMode.Local,
})
expect(result.isFailed()).to.be.true
expect(result.getError()).to.equal('Vault uses randomized password and cannot change its storage preference')
})
it('should change user password vault from synced to local', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Synced,
})
let syncedKeys = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
const result = await context.vaults.changeVaultOptions({
vault,
newStorageMode: KeySystemRootKeyStorageMode.Local,
})
expect(result.isFailed()).to.be.false
syncedKeys = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
expect(syncedKeys.length).to.equal(0)
const storedKey = context.keys.getRootKeyFromStorageForVault(vault.systemIdentifier)
expect(storedKey).to.not.be.undefined
})
it('should change user password vault from synced to ephemeral', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Synced,
})
let syncedKeys = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
const result = await context.vaults.changeVaultOptions({
vault,
newStorageMode: KeySystemRootKeyStorageMode.Ephemeral,
})
expect(result.isFailed()).to.be.false
syncedKeys = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
expect(syncedKeys.length).to.equal(0)
const storedKey = context.keys.getRootKeyFromStorageForVault(vault.systemIdentifier)
expect(storedKey).to.be.undefined
const memKeys = context.keys.getAllKeySystemRootKeysForVault(vault.systemIdentifier)
expect(memKeys.length).to.equal(1)
})
it('should change user password vault from local to synced', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Local,
})
let syncedKeys = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
const result = await context.vaults.changeVaultOptions({
vault,
newStorageMode: KeySystemRootKeyStorageMode.Synced,
})
expect(result.isFailed()).to.be.false
syncedKeys = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
expect(syncedKeys.length).to.equal(1)
const storedKey = context.keys.getRootKeyFromStorageForVault(vault.systemIdentifier)
expect(storedKey).to.be.undefined
const memKeys = context.keys.getAllKeySystemRootKeysForVault(vault.systemIdentifier)
expect(memKeys.length).to.equal(1)
})
it('should change user password vault from local to ephemeral', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Local,
})
let syncedKeys = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
const result = await context.vaults.changeVaultOptions({
vault,
newStorageMode: KeySystemRootKeyStorageMode.Ephemeral,
})
expect(result.isFailed()).to.be.false
syncedKeys = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
expect(syncedKeys.length).to.equal(0)
const storedKey = context.keys.getRootKeyFromStorageForVault(vault.systemIdentifier)
expect(storedKey).to.be.undefined
const memKeys = context.keys.getAllKeySystemRootKeysForVault(vault.systemIdentifier)
expect(memKeys.length).to.equal(1)
})
})
describe('change password type', () => {
it('should fail to change password type from randomized to user inputted if password is not supplied', async () => {
const vault = await context.vaults.createRandomizedVault({
name: 'test vault',
description: 'test vault description',
})
const result = await context.vaults.changeVaultOptions({
vault,
newPasswordType: {
passwordType: KeySystemPasswordType.UserInputted,
},
})
expect(result.isFailed()).to.be.true
})
it('should change password type from randomized to user inputted', async () => {
const vault = await context.vaults.createRandomizedVault({
name: 'test vault',
description: 'test vault description',
})
const rootKeysBeforeChange = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
expect(rootKeysBeforeChange.length).to.equal(1)
const result = await context.vaults.changeVaultOptions({
vault,
newPasswordType: {
passwordType: KeySystemPasswordType.UserInputted,
userInputtedPassword: 'test password',
},
})
expect(result.isFailed()).to.be.false
const rootKeysAfterChange = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
expect(rootKeysAfterChange.length).to.equal(2)
expect(rootKeysAfterChange[0].itemsKey).to.not.equal(rootKeysAfterChange[1].itemsKey)
})
it('should change password type from user inputted to randomized', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Local,
})
const result = await context.vaults.changeVaultOptions({
vault,
newPasswordType: {
passwordType: KeySystemPasswordType.Randomized,
},
})
expect(result.isFailed()).to.be.false
const rootKeysAfterChange = context.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
expect(rootKeysAfterChange.length).to.equal(1)
const storedKey = context.keys.getRootKeyFromStorageForVault(vault.systemIdentifier)
expect(storedKey).to.be.undefined
const updatedVault = context.vaults.getVault({ keySystemIdentifier: vault.systemIdentifier })
expect(updatedVault.keyStorageMode).to.equal(KeySystemRootKeyStorageMode.Synced)
})
it('should fail to change password type from user inputted to randomized if storage mode is not synced', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Local,
})
const result = await context.vaults.changeVaultOptions({
vault,
newPasswordType: {
passwordType: KeySystemPasswordType.Randomized,
},
newStorageMode: KeySystemRootKeyStorageMode.Local,
})
expect(result.isFailed()).to.be.true
expect(result.getError()).to.equal('Vault uses randomized password and cannot change its storage preference')
})
})
})
})

View File

@@ -29,7 +29,7 @@ describe('shared vault key rotation', function () {
contactContext.lockSyncing() contactContext.lockSyncing()
const spy = sinon.spy(context.keys, 'reencryptKeySystemItemsKeysForVault') const spy = sinon.spy(context.keys, 'queueVaultItemsKeysForReencryption')
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault) const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
await context.vaults.rotateVaultRootKey(sharedVault) await context.vaults.rotateVaultRootKey(sharedVault)

View File

@@ -1,108 +0,0 @@
import * as Factory from '../lib/factory.js'
import * as Collaboration from '../lib/Collaboration.js'
chai.use(chaiAsPromised)
const expect = chai.expect
describe('vault locking', function () {
this.timeout(Factory.TwentySecondTimeout)
let context
afterEach(async function () {
await context.deinit()
localStorage.clear()
})
beforeEach(async function () {
localStorage.clear()
context = await Factory.createAppContextWithRealCrypto()
await context.launch()
await context.register()
})
it('should lock non-persistent vault', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Ephemeral,
})
context.vaultLocks.lockNonPersistentVault(vault)
expect(context.vaultLocks.isVaultLocked(vault)).to.be.true
})
it('should not be able to lock user-inputted vault with synced key', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Synced,
})
expect(() => context.vaultLocks.lockNonPersistentVault(vault)).to.throw(
Error,
'Vault uses synced key storage and cannot be locked',
)
})
it('should not be able to lock randomized vault', async () => {
const vault = await context.vaults.createRandomizedVault({
name: 'test vault',
description: 'test vault description',
})
expect(() => context.vaultLocks.lockNonPersistentVault(vault)).to.throw(
Error,
'Vault uses synced key storage and cannot be locked',
)
})
it('should throw if attempting to change password of locked vault', async () => {
const vault = await context.vaults.createUserInputtedPasswordVault({
name: 'test vault',
description: 'test vault description',
userInputtedPassword: 'test password',
storagePreference: KeySystemRootKeyStorageMode.Ephemeral,
})
context.vaultLocks.lockNonPersistentVault(vault)
await Factory.expectThrowsAsync(
() => context.vaults.changeVaultOptions({ vault }),
'Attempting to change vault options on a locked vault',
)
})
it('should respect storage preference when rotating key system root key', async () => {
console.error('TODO: implement')
})
it('should change storage preference from synced to local', async () => {
console.error('TODO: implement')
})
it('should change storage preference from local to synced', async () => {
console.error('TODO: implement')
})
it('should resync key system items key if it is encrypted with noncurrent key system root key', async () => {
console.error('TODO: implement')
})
it('should change password type from user inputted to randomized', async () => {
console.error('TODO: implement')
})
it('should change password type from randomized to user inputted', async () => {
console.error('TODO: implement')
})
it('should not be able to change storage mode of third party vault', async () => {
console.error('TODO: implement')
})
})

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { isString, AlertService, uniqueArray } from '@standardnotes/snjs' import { isString, AlertService, uniqueArray } from '@standardnotes/snjs'
const STORE_NAME = 'items' const STORE_NAME = 'items'
@@ -140,10 +141,6 @@ export class Database {
}) })
} }
/**
* This function is actually unused, but implemented to conform to protocol in case it is eventually needed.
* We could remove implementation and throw instead, but it might be better to offer a functional alternative instead.
*/
public async getPayloadsForKeys(keys: string[]): Promise<any[]> { public async getPayloadsForKeys(keys: string[]): Promise<any[]> {
const db = (await this.openDatabase()) as IDBDatabase const db = (await this.openDatabase()) as IDBDatabase
return new Promise((resolve) => { return new Promise((resolve) => {

View File

@@ -4,7 +4,7 @@ import DecoratedInput from '@/Components/Input/DecoratedInput'
import { useApplication } from '@/Components/ApplicationProvider' import { useApplication } from '@/Components/ApplicationProvider'
import { import {
ChangeVaultKeyOptionsDTO, ChangeVaultKeyOptionsDTO,
KeySystemRootKeyPasswordType, KeySystemPasswordType,
KeySystemRootKeyStorageMode, KeySystemRootKeyStorageMode,
SharedVaultInviteServerHash, SharedVaultInviteServerHash,
SharedVaultUserServerHash, SharedVaultUserServerHash,
@@ -32,9 +32,7 @@ const EditVaultModal: FunctionComponent<Props> = ({ onCloseDialog, existingVault
const [members, setMembers] = useState<SharedVaultUserServerHash[]>([]) const [members, setMembers] = useState<SharedVaultUserServerHash[]>([])
const [invites, setInvites] = useState<SharedVaultInviteServerHash[]>([]) const [invites, setInvites] = useState<SharedVaultInviteServerHash[]>([])
const [isAdmin, setIsAdmin] = useState<boolean>(true) const [isAdmin, setIsAdmin] = useState<boolean>(true)
const [passwordType, setPasswordType] = useState<KeySystemRootKeyPasswordType>( const [passwordType, setPasswordType] = useState<KeySystemPasswordType>(KeySystemPasswordType.Randomized)
KeySystemRootKeyPasswordType.Randomized,
)
const [keyStorageMode, setKeyStorageMode] = useState<KeySystemRootKeyStorageMode>(KeySystemRootKeyStorageMode.Synced) const [keyStorageMode, setKeyStorageMode] = useState<KeySystemRootKeyStorageMode>(KeySystemRootKeyStorageMode.Synced)
const [customPassword, setCustomPassword] = useState<string | undefined>(undefined) const [customPassword, setCustomPassword] = useState<string | undefined>(undefined)
@@ -94,7 +92,7 @@ const EditVaultModal: FunctionComponent<Props> = ({ onCloseDialog, existingVault
throw new Error('Password type is not changing') throw new Error('Password type is not changing')
} }
if (passwordType === KeySystemRootKeyPasswordType.UserInputted) { if (passwordType === KeySystemPasswordType.UserInputted) {
if (!customPassword) { if (!customPassword) {
throw new Error('Custom password is not set') throw new Error('Custom password is not set')
} }
@@ -113,7 +111,7 @@ const EditVaultModal: FunctionComponent<Props> = ({ onCloseDialog, existingVault
await application.vaults.changeVaultOptions({ await application.vaults.changeVaultOptions({
vault, vault,
newPasswordType: isChangingPasswordType ? getPasswordTypeParams() : undefined, newPasswordType: isChangingPasswordType ? getPasswordTypeParams() : undefined,
newKeyStorageMode: isChangingKeyStorageMode ? keyStorageMode : undefined, newStorageMode: isChangingKeyStorageMode ? keyStorageMode : undefined,
}) })
} }
}, },
@@ -121,7 +119,7 @@ const EditVaultModal: FunctionComponent<Props> = ({ onCloseDialog, existingVault
) )
const createNewVault = useCallback(async () => { const createNewVault = useCallback(async () => {
if (passwordType === KeySystemRootKeyPasswordType.UserInputted) { if (passwordType === KeySystemPasswordType.UserInputted) {
if (!customPassword) { if (!customPassword) {
throw new Error('Custom key is not set') throw new Error('Custom key is not set')
} }

View File

@@ -1,22 +1,22 @@
import { KeySystemRootKeyPasswordType } from '@standardnotes/snjs' import { KeySystemPasswordType } from '@standardnotes/snjs'
import StyledRadioInput from '@/Components/Radio/StyledRadioInput' import StyledRadioInput from '@/Components/Radio/StyledRadioInput'
import DecoratedPasswordInput from '@/Components/Input/DecoratedPasswordInput' import DecoratedPasswordInput from '@/Components/Input/DecoratedPasswordInput'
import { useState } from 'react' import { useState } from 'react'
type PasswordTypePreference = { type PasswordTypePreference = {
value: KeySystemRootKeyPasswordType value: KeySystemPasswordType
label: string label: string
description: string description: string
} }
const options: PasswordTypePreference[] = [ const options: PasswordTypePreference[] = [
{ {
value: KeySystemRootKeyPasswordType.Randomized, value: KeySystemPasswordType.Randomized,
label: 'Randomized (Recommended)', label: 'Randomized (Recommended)',
description: 'Your vault key will be randomly generated and synced to your account.', description: 'Your vault key will be randomly generated and synced to your account.',
}, },
{ {
value: KeySystemRootKeyPasswordType.UserInputted, value: KeySystemPasswordType.UserInputted,
label: 'Custom (Advanced)', label: 'Custom (Advanced)',
description: description:
'Choose your own key for your vault. This is an advanced option and is not recommended for most users.', 'Choose your own key for your vault. This is an advanced option and is not recommended for most users.',
@@ -28,8 +28,8 @@ export const PasswordTypePreference = ({
onChange, onChange,
onCustomKeyChange, onCustomKeyChange,
}: { }: {
value: KeySystemRootKeyPasswordType value: KeySystemPasswordType
onChange: (value: KeySystemRootKeyPasswordType) => void onChange: (value: KeySystemPasswordType) => void
onCustomKeyChange: (value: string) => void onCustomKeyChange: (value: string) => void
}) => { }) => {
const [customKey, setCustomKey] = useState('') const [customKey, setCustomKey] = useState('')
@@ -57,7 +57,7 @@ export const PasswordTypePreference = ({
) )
})} })}
{value === KeySystemRootKeyPasswordType.UserInputted && ( {value === KeySystemPasswordType.UserInputted && (
<div> <div>
<div className="text-gray-500 mt-3 text-sm">{options[1].description}</div> <div className="text-gray-500 mt-3 text-sm">{options[1].description}</div>