refactor: application dependency management (#2363)

This commit is contained in:
Mo
2023-07-23 15:54:31 -05:00
committed by GitHub
parent e698b1c990
commit a77535456c
299 changed files with 7415 additions and 4890 deletions

View File

@@ -1,81 +1,62 @@
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
import { ApplicationStage } from './../Application/ApplicationStage'
import { SingletonManagerInterface } from './../Singleton/SingletonManagerInterface'
import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData'
import { SessionEvent } from './../Session/SessionEvent'
import { InternalEventInterface } from './../Internal/InternalEventInterface'
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { SharedVaultInviteServerHash, SharedVaultUserServerHash } from '@standardnotes/responses'
import {
TrustedContactContent,
TrustedContactContentSpecialized,
TrustedContactInterface,
FillItemContent,
TrustedContactMutator,
DecryptedItemInterface,
} from '@standardnotes/models'
import { TrustedContactInterface, TrustedContactMutator, DecryptedItemInterface } from '@standardnotes/models'
import { AbstractService } from '../Service/AbstractService'
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
import { ContactServiceEvent, ContactServiceInterface } from '../Contacts/ContactServiceInterface'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { UserClientInterface } from '../User/UserClientInterface'
import { CollaborationIDData, Version1CollaborationId } from './CollaborationID'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { ValidateItemSignerUseCase } from './UseCase/ValidateItemSigner'
import { ValidateItemSignerResult } from './UseCase/ValidateItemSignerResult'
import { FindTrustedContactUseCase } from './UseCase/FindTrustedContact'
import { SelfContactManager } from './Managers/SelfContactManager'
import { CreateOrEditTrustedContactUseCase } from './UseCase/CreateOrEditTrustedContact'
import { UpdateTrustedContactUseCase } from './UseCase/UpdateTrustedContact'
import { ContentType } from '@standardnotes/domain-core'
import { ValidateItemSigner } from './UseCase/ValidateItemSigner'
import { ItemSignatureValidationResult } from './UseCase/Types/ItemSignatureValidationResult'
import { FindContact } from './UseCase/FindContact'
import { SelfContactManager } from './SelfContactManager'
import { CreateOrEditContact } from './UseCase/CreateOrEditContact'
import { EditContact } from './UseCase/EditContact'
import { GetAllContacts } from './UseCase/GetAllContacts'
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
export class ContactService
extends AbstractService<ContactServiceEvent>
implements ContactServiceInterface, InternalEventHandlerInterface
{
private selfContactManager: SelfContactManager
constructor(
private sync: SyncServiceInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private session: SessionsClientInterface,
private crypto: PureCryptoInterface,
private user: UserClientInterface,
private selfContactManager: SelfContactManager,
private encryption: EncryptionProviderInterface,
singletons: SingletonManagerInterface,
private _findContact: FindContact,
private _getAllContacts: GetAllContacts,
private _createOrEditContact: CreateOrEditContact,
private _editContact: EditContact,
private _validateItemSigner: ValidateItemSigner,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
this.selfContactManager = new SelfContactManager(sync, items, mutator, session, singletons)
eventBus.addEventHandler(this, SessionEvent.UserKeyPairChanged)
}
public override async handleApplicationStage(stage: ApplicationStage): Promise<void> {
await super.handleApplicationStage(stage)
await this.selfContactManager.handleApplicationStage(stage)
}
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === SessionEvent.UserKeyPairChanged) {
const data = event.payload as UserKeyPairChangedEventData
await this.selfContactManager.updateWithNewPublicKeySet({
encryption: data.newKeyPair.publicKey,
signing: data.newSigningKeyPair.publicKey,
encryption: data.current.encryption.publicKey,
signing: data.current.signing.publicKey,
})
}
}
private get userUuid(): string {
return this.session.getSureUser().uuid
}
getSelfContact(): TrustedContactInterface | undefined {
return this.selfContactManager.selfContact
}
@@ -170,38 +151,11 @@ export class ContactService
contact: TrustedContactInterface,
params: { name: string; publicKey: string; signingPublicKey: string },
): Promise<TrustedContactInterface> {
const usecase = new UpdateTrustedContactUseCase(this.mutator, this.sync)
const updatedContact = await usecase.execute(contact, params)
const updatedContact = await this._editContact.execute(contact, params)
return updatedContact
}
async createOrUpdateTrustedContactFromContactShare(
data: TrustedContactContentSpecialized,
): Promise<TrustedContactInterface> {
if (data.contactUuid === this.userUuid) {
throw new Error('Cannot receive self from contact share')
}
let contact = this.findTrustedContact(data.contactUuid)
if (contact) {
contact = await this.mutator.changeItem<TrustedContactMutator, TrustedContactInterface>(contact, (mutator) => {
mutator.name = data.name
mutator.replacePublicKeySet(data.publicKeySet)
})
} else {
contact = await this.mutator.createItem<TrustedContactInterface>(
ContentType.TYPES.TrustedContact,
FillItemContent<TrustedContactContent>(data),
true,
)
}
await this.sync.sync()
return contact
}
async createOrEditTrustedContact(params: {
name?: string
contactUuid: string
@@ -209,8 +163,7 @@ export class ContactService
signingPublicKey: string
isMe?: boolean
}): Promise<TrustedContactInterface | undefined> {
const usecase = new CreateOrEditTrustedContactUseCase(this.items, this.mutator, this.sync)
const contact = await usecase.execute(params)
const contact = await this._createOrEditContact.execute(params)
return contact
}
@@ -224,12 +177,15 @@ export class ContactService
}
getAllContacts(): TrustedContactInterface[] {
return this.items.getItems(ContentType.TYPES.TrustedContact)
return this._getAllContacts.execute().getValue()
}
findTrustedContact(userUuid: string): TrustedContactInterface | undefined {
const usecase = new FindTrustedContactUseCase(this.items)
return usecase.execute({ userUuid })
const result = this._findContact.execute({ userUuid })
if (result.isFailed()) {
return undefined
}
return result.getValue()
}
findTrustedContactForServerUser(user: SharedVaultUserServerHash): TrustedContactInterface | undefined {
@@ -249,16 +205,23 @@ export class ContactService
})
}
isItemAuthenticallySigned(item: DecryptedItemInterface): ValidateItemSignerResult {
const usecase = new ValidateItemSignerUseCase(this.items)
return usecase.execute(item)
isItemAuthenticallySigned(item: DecryptedItemInterface): ItemSignatureValidationResult {
return this._validateItemSigner.execute(item)
}
override deinit(): void {
super.deinit()
this.selfContactManager.deinit()
;(this.sync as unknown) = undefined
;(this.items as unknown) = undefined
;(this.mutator as unknown) = undefined
;(this.session as unknown) = undefined
;(this.crypto as unknown) = undefined
;(this.user as unknown) = undefined
;(this.selfContactManager as unknown) = undefined
;(this.encryption as unknown) = undefined
;(this._findContact as unknown) = undefined
;(this._getAllContacts as unknown) = undefined
;(this._createOrEditContact as unknown) = undefined
;(this._editContact as unknown) = undefined
;(this._validateItemSigner as unknown) = undefined
}
}

View File

@@ -1,11 +1,7 @@
import {
DecryptedItemInterface,
TrustedContactContentSpecialized,
TrustedContactInterface,
} from '@standardnotes/models'
import { DecryptedItemInterface, TrustedContactInterface } from '@standardnotes/models'
import { AbstractService } from '../Service/AbstractService'
import { SharedVaultInviteServerHash, SharedVaultUserServerHash } from '@standardnotes/responses'
import { ValidateItemSignerResult } from './UseCase/ValidateItemSignerResult'
import { ItemSignatureValidationResult } from './UseCase/Types/ItemSignatureValidationResult'
export enum ContactServiceEvent {}
@@ -26,7 +22,6 @@ export interface ContactServiceInterface extends AbstractService<ContactServiceE
publicKey: string
signingPublicKey: string
}): Promise<TrustedContactInterface | undefined>
createOrUpdateTrustedContactFromContactShare(data: TrustedContactContentSpecialized): Promise<TrustedContactInterface>
editTrustedContactFromCollaborationID(
contact: TrustedContactInterface,
params: { name: string; collaborationID: string },
@@ -39,5 +34,5 @@ export interface ContactServiceInterface extends AbstractService<ContactServiceE
findTrustedContactForServerUser(user: SharedVaultUserServerHash): TrustedContactInterface | undefined
findTrustedContactForInvite(invite: SharedVaultInviteServerHash): TrustedContactInterface | undefined
isItemAuthenticallySigned(item: DecryptedItemInterface): ValidateItemSignerResult
isItemAuthenticallySigned(item: DecryptedItemInterface): ItemSignatureValidationResult
}

View File

@@ -1,12 +1,15 @@
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
import { InternalFeature } from './../../InternalFeatures/InternalFeature'
import { InternalFeatureService } from '../../InternalFeatures/InternalFeatureService'
import { ApplicationStage } from './../../Application/ApplicationStage'
import { SingletonManagerInterface } from './../../Singleton/SingletonManagerInterface'
import { SyncEvent } from './../../Event/SyncEvent'
import { SessionsClientInterface } from '../../Session/SessionsClientInterface'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import { ApplicationStageChangedEventPayload } from './../Event/ApplicationStageChangedEventPayload'
import { ApplicationEvent } from './../Event/ApplicationEvent'
import { InternalEventInterface } from './../Internal/InternalEventInterface'
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
import { InternalFeature } from '../InternalFeatures/InternalFeature'
import { InternalFeatureService } from '../InternalFeatures/InternalFeatureService'
import { ApplicationStage } from '../Application/ApplicationStage'
import { SingletonManagerInterface } from '../Singleton/SingletonManagerInterface'
import { SyncEvent } from '../Event/SyncEvent'
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import {
ContactPublicKeySet,
FillItemContent,
@@ -14,23 +17,25 @@ import {
TrustedContactContent,
TrustedContactContentSpecialized,
TrustedContactInterface,
PortablePublicKeySet,
} from '@standardnotes/models'
import { CreateOrEditTrustedContactUseCase } from '../UseCase/CreateOrEditTrustedContact'
import { PublicKeySet } from '@standardnotes/encryption'
import { CreateOrEditContact } from './UseCase/CreateOrEditContact'
import { ContentType } from '@standardnotes/domain-core'
export class SelfContactManager {
const SelfContactName = 'Me'
export class SelfContactManager implements InternalEventHandlerInterface {
public selfContact?: TrustedContactInterface
private isReloadingSelfContact = false
private eventDisposers: (() => void)[] = []
constructor(
private sync: SyncServiceInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
sync: SyncServiceInterface,
items: ItemManagerInterface,
private session: SessionsClientInterface,
private singletons: SingletonManagerInterface,
private createOrEditContact: CreateOrEditContact,
) {
this.eventDisposers.push(
sync.addEventObserver((event) => {
@@ -43,11 +48,26 @@ export class SelfContactManager {
}
}),
)
this.eventDisposers.push(
items.addObserver(ContentType.TYPES.TrustedContact, () => {
const updatedReference = this.singletons.findSingleton<TrustedContact>(
ContentType.TYPES.TrustedContact,
TrustedContact.singletonPredicate,
)
if (updatedReference) {
this.selfContact = updatedReference
}
}),
)
}
public async handleApplicationStage(stage: ApplicationStage): Promise<void> {
if (stage === ApplicationStage.LoadedDatabase_12) {
this.loadSelfContactFromDatabase()
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === ApplicationEvent.ApplicationStageChanged) {
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
if (stage === ApplicationStage.LoadedDatabase_12) {
this.loadSelfContactFromDatabase()
}
}
}
@@ -62,7 +82,7 @@ export class SelfContactManager {
)
}
public async updateWithNewPublicKeySet(publicKeySet: PublicKeySet) {
public async updateWithNewPublicKeySet(publicKeySet: PortablePublicKeySet) {
if (!InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) {
return
}
@@ -71,9 +91,8 @@ export class SelfContactManager {
return
}
const usecase = new CreateOrEditTrustedContactUseCase(this.items, this.mutator, this.sync)
await usecase.execute({
name: 'Me',
await this.createOrEditContact.execute({
name: SelfContactName,
contactUuid: this.selfContact.contactUuid,
publicKey: publicKeySet.encryption,
signingPublicKey: publicKeySet.signing,
@@ -104,13 +123,12 @@ export class SelfContactManager {
this.isReloadingSelfContact = true
const content: TrustedContactContentSpecialized = {
name: 'Me',
name: SelfContactName,
isMe: true,
contactUuid: this.session.getSureUser().uuid,
publicKeySet: ContactPublicKeySet.FromJson({
encryption: this.session.getPublicKey(),
signing: this.session.getSigningPublicKey(),
isRevoked: false,
timestamp: new Date(),
}),
}
@@ -126,10 +144,8 @@ export class SelfContactManager {
deinit() {
this.eventDisposers.forEach((disposer) => disposer())
;(this.sync as unknown) = undefined
;(this.items as unknown) = undefined
;(this.mutator as unknown) = undefined
;(this.session as unknown) = undefined
;(this.singletons as unknown) = undefined
;(this.createOrEditContact as unknown) = undefined
}
}

View File

@@ -1,6 +1,5 @@
import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
import { ItemManagerInterface } from './../../Item/ItemManagerInterface'
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
import {
ContactPublicKeySet,
FillItemContent,
@@ -8,16 +7,17 @@ import {
TrustedContactContentSpecialized,
TrustedContactInterface,
} from '@standardnotes/models'
import { FindTrustedContactUseCase } from './FindTrustedContact'
import { FindContact } from './FindContact'
import { UnknownContactName } from '../UnknownContactName'
import { UpdateTrustedContactUseCase } from './UpdateTrustedContact'
import { EditContact } from './EditContact'
import { ContentType } from '@standardnotes/domain-core'
export class CreateOrEditTrustedContactUseCase {
export class CreateOrEditContact {
constructor(
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private findContact: FindContact,
private editContact: EditContact,
) {}
async execute(params: {
@@ -27,13 +27,14 @@ export class CreateOrEditTrustedContactUseCase {
signingPublicKey: string
isMe?: boolean
}): Promise<TrustedContactInterface | undefined> {
const findUsecase = new FindTrustedContactUseCase(this.items)
const existingContact = findUsecase.execute({ userUuid: params.contactUuid })
const existingContact = this.findContact.execute({ userUuid: params.contactUuid })
if (existingContact) {
const updateUsecase = new UpdateTrustedContactUseCase(this.mutator, this.sync)
await updateUsecase.execute(existingContact, { ...params, name: params.name ?? existingContact.name })
return existingContact
if (!existingContact.isFailed()) {
await this.editContact.execute(existingContact.getValue(), {
...params,
name: params.name ?? existingContact.getValue().name,
})
return existingContact.getValue()
}
const content: TrustedContactContentSpecialized = {
@@ -41,7 +42,6 @@ export class CreateOrEditTrustedContactUseCase {
publicKeySet: ContactPublicKeySet.FromJson({
encryption: params.publicKey,
signing: params.signingPublicKey,
isRevoked: false,
timestamp: new Date(),
}),
contactUuid: params.contactUuid,

View File

@@ -1,8 +1,8 @@
import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
import { TrustedContactInterface, TrustedContactMutator } from '@standardnotes/models'
export class UpdateTrustedContactUseCase {
export class EditContact {
constructor(private mutator: MutatorClientInterface, private sync: SyncServiceInterface) {}
async execute(

View File

@@ -0,0 +1,38 @@
import { Predicate, TrustedContactInterface } from '@standardnotes/models'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { FindContactQuery } from './Types/FindContactQuery'
import { ContentType, Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
export class FindContact implements SyncUseCaseInterface<TrustedContactInterface> {
constructor(private items: ItemManagerInterface) {}
execute(query: FindContactQuery): Result<TrustedContactInterface> {
if ('userUuid' in query && query.userUuid) {
const contact = this.items.itemsMatchingPredicate<TrustedContactInterface>(
ContentType.TYPES.TrustedContact,
new Predicate<TrustedContactInterface>('contactUuid', '=', query.userUuid),
)[0]
if (contact) {
return Result.ok(contact)
} else {
return Result.fail('Contact not found')
}
}
if ('signingPublicKey' in query && query.signingPublicKey) {
const allContacts = this.items.getItems<TrustedContactInterface>(ContentType.TYPES.TrustedContact)
const contact = allContacts.find((contact) =>
contact.hasCurrentOrPreviousSigningPublicKey(query.signingPublicKey),
)
if (contact) {
return Result.ok(contact)
} else {
return Result.fail('Contact not found')
}
}
throw new Error('Invalid query')
}
}

View File

@@ -1,29 +0,0 @@
import { Predicate, TrustedContactInterface } from '@standardnotes/models'
import { ItemManagerInterface } from './../../Item/ItemManagerInterface'
import { FindContactQuery } from './FindContactQuery'
import { ContentType } from '@standardnotes/domain-core'
export class FindTrustedContactUseCase {
constructor(private items: ItemManagerInterface) {}
execute(query: FindContactQuery): TrustedContactInterface | undefined {
if ('userUuid' in query && query.userUuid) {
return this.items.itemsMatchingPredicate<TrustedContactInterface>(
ContentType.TYPES.TrustedContact,
new Predicate<TrustedContactInterface>('contactUuid', '=', query.userUuid),
)[0]
}
if ('signingPublicKey' in query && query.signingPublicKey) {
const allContacts = this.items.getItems<TrustedContactInterface>(ContentType.TYPES.TrustedContact)
return allContacts.find((contact) => contact.isSigningKeyTrusted(query.signingPublicKey))
}
if ('publicKey' in query && query.publicKey) {
const allContacts = this.items.getItems<TrustedContactInterface>(ContentType.TYPES.TrustedContact)
return allContacts.find((contact) => contact.isPublicKeyTrusted(query.publicKey))
}
throw new Error('Invalid query')
}
}

View File

@@ -0,0 +1,11 @@
import { TrustedContactInterface } from '@standardnotes/models'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { ContentType, Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
export class GetAllContacts implements SyncUseCaseInterface<TrustedContactInterface[]> {
constructor(private items: ItemManagerInterface) {}
execute(): Result<TrustedContactInterface[]> {
return Result.ok(this.items.getItems(ContentType.TYPES.TrustedContact))
}
}

View File

@@ -0,0 +1,31 @@
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { ReuploadAllInvites } from '../../SharedVaults/UseCase/ReuploadAllInvites'
import { ResendAllMessages } from '../../AsymmetricMessage/UseCase/ResendAllMessages'
export class HandleKeyPairChange implements UseCaseInterface<void> {
constructor(private reuploadAllInvites: ReuploadAllInvites, private resendAllMessages: ResendAllMessages) {}
async execute(dto: {
newKeys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
previousKeys?: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
}): Promise<Result<void>> {
await this.reuploadAllInvites.execute({
keys: dto.newKeys,
previousKeys: dto.previousKeys,
})
await this.resendAllMessages.execute({
keys: dto.newKeys,
previousKeys: dto.previousKeys,
})
return Result.ok()
}
}

View File

@@ -0,0 +1,55 @@
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
import {
FillItemContent,
MutationType,
PayloadEmitSource,
TrustedContactContent,
TrustedContactContentSpecialized,
TrustedContactInterface,
TrustedContactMutator,
} from '@standardnotes/models'
import { FindContact } from './FindContact'
import { ContentType, Result, UseCaseInterface } from '@standardnotes/domain-core'
export class ReplaceContactData implements UseCaseInterface<TrustedContactInterface> {
constructor(
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private findContact: FindContact,
) {}
async execute(data: TrustedContactContentSpecialized): Promise<Result<TrustedContactInterface>> {
const contactResult = this.findContact.execute({ userUuid: data.contactUuid })
if (contactResult.isFailed()) {
const newContact = await this.mutator.createItem<TrustedContactInterface>(
ContentType.TYPES.TrustedContact,
FillItemContent<TrustedContactContent>(data),
true,
)
await this.sync.sync()
return Result.ok(newContact)
}
const existingContact = contactResult.getValue()
if (existingContact.isMe) {
return Result.fail('Cannot replace data for me contact')
}
const updatedContact = await this.mutator.changeItem<TrustedContactMutator, TrustedContactInterface>(
existingContact,
(mutator) => {
mutator.name = data.name
mutator.replacePublicKeySet(data.publicKeySet)
},
MutationType.UpdateUserTimestamps,
PayloadEmitSource.RemoteRetrieved,
)
await this.sync.sync()
return Result.ok(updatedContact)
}
}

View File

@@ -1 +1 @@
export type FindContactQuery = { userUuid: string } | { signingPublicKey: string } | { publicKey: string }
export type FindContactQuery = { userUuid: string } | { signingPublicKey: string }

View File

@@ -0,0 +1,6 @@
export enum ItemSignatureValidationResult {
NotApplicable = 'NotApplicable',
Trusted = 'Trusted',
SignedWithNonCurrentKey = 'SignedWithNonCurrentKey',
NotTrusted = 'NotTrusted',
}

View File

@@ -2,21 +2,26 @@ import {
DecryptedItemInterface,
PayloadSource,
PersistentSignatureData,
PublicKeyTrustStatus,
TrustedContactInterface,
} from '@standardnotes/models'
import { ItemManagerInterface } from './../../Item/ItemManagerInterface'
import { ValidateItemSignerUseCase } from './ValidateItemSigner'
import { ValidateItemSigner } from './ValidateItemSigner'
import { FindContact } from './FindContact'
import { ItemSignatureValidationResult } from './Types/ItemSignatureValidationResult'
import { Result } from '@standardnotes/domain-core'
describe('validate item signer use case', () => {
let usecase: ValidateItemSignerUseCase
let items: ItemManagerInterface
let usecase: ValidateItemSigner
let findContact: FindContact
const trustedContact = {} as jest.Mocked<TrustedContactInterface>
trustedContact.isSigningKeyTrusted = jest.fn().mockReturnValue(true)
trustedContact.getTrustStatusForSigningPublicKey = jest.fn().mockReturnValue(PublicKeyTrustStatus.Trusted)
beforeEach(() => {
items = {} as jest.Mocked<ItemManagerInterface>
usecase = new ValidateItemSignerUseCase(items)
findContact = {} as jest.Mocked<FindContact>
findContact.execute = jest.fn().mockReturnValue(Result.ok(trustedContact))
usecase = new ValidateItemSigner(findContact)
})
const createItem = (params: {
@@ -42,7 +47,7 @@ describe('validate item signer use case', () => {
describe('has last edited by uuid', () => {
describe('trusted contact not found', () => {
beforeEach(() => {
items.itemsMatchingPredicate = jest.fn().mockReturnValue([])
findContact.execute = jest.fn().mockReturnValue(Result.fail('Not found'))
})
it('should return invalid signing is required', () => {
@@ -53,7 +58,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('no')
expect(result).toEqual(ItemSignatureValidationResult.NotTrusted)
})
it('should return not applicable signing is not required', () => {
@@ -64,15 +69,11 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('not-applicable')
expect(result).toEqual(ItemSignatureValidationResult.NotApplicable)
})
})
describe('trusted contact found for last editor', () => {
beforeEach(() => {
items.itemsMatchingPredicate = jest.fn().mockReturnValue([trustedContact])
})
describe('does not have signature data', () => {
it('should return not applicable if the item was just recently created', () => {
const item = createItem({
@@ -83,7 +84,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('not-applicable')
expect(result).toEqual(ItemSignatureValidationResult.NotApplicable)
})
it('should return not applicable if the item was just recently saved', () => {
@@ -95,7 +96,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('not-applicable')
expect(result).toEqual(ItemSignatureValidationResult.NotApplicable)
})
it('should return invalid if signing is required', () => {
@@ -106,7 +107,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('no')
expect(result).toEqual(ItemSignatureValidationResult.NotTrusted)
})
it('should return not applicable if signing is not required', () => {
@@ -117,7 +118,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('not-applicable')
expect(result).toEqual(ItemSignatureValidationResult.NotApplicable)
})
})
@@ -133,7 +134,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('no')
expect(result).toEqual(ItemSignatureValidationResult.NotTrusted)
})
it('should return not applicable if signing is not required', () => {
@@ -146,7 +147,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('not-applicable')
expect(result).toEqual(ItemSignatureValidationResult.NotApplicable)
})
})
@@ -164,7 +165,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('no')
expect(result).toEqual(ItemSignatureValidationResult.NotTrusted)
})
it('should return invalid if signature result passes and a trusted contact is NOT found for signature public key', () => {
@@ -180,10 +181,10 @@ describe('validate item signer use case', () => {
} as jest.Mocked<PersistentSignatureData>,
})
items.itemsMatchingPredicate = jest.fn().mockReturnValue([])
findContact.execute = jest.fn().mockReturnValue(Result.fail('Not found'))
const result = usecase.execute(item)
expect(result).toEqual('no')
expect(result).toEqual(ItemSignatureValidationResult.NotTrusted)
})
it('should return valid if signature result passes and a trusted contact is found for signature public key', () => {
@@ -199,10 +200,10 @@ describe('validate item signer use case', () => {
} as jest.Mocked<PersistentSignatureData>,
})
items.itemsMatchingPredicate = jest.fn().mockReturnValue([trustedContact])
findContact.execute = jest.fn().mockReturnValue(Result.ok(trustedContact))
const result = usecase.execute(item)
expect(result).toEqual('yes')
expect(result).toEqual(ItemSignatureValidationResult.Trusted)
})
})
})
@@ -220,7 +221,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('not-applicable')
expect(result).toEqual(ItemSignatureValidationResult.NotApplicable)
})
it('should return not applicable if the item was just recently saved', () => {
@@ -232,7 +233,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('not-applicable')
expect(result).toEqual(ItemSignatureValidationResult.NotApplicable)
})
it('should return invalid if signing is required', () => {
@@ -243,7 +244,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('no')
expect(result).toEqual(ItemSignatureValidationResult.NotTrusted)
})
it('should return not applicable if signing is not required', () => {
@@ -254,7 +255,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('not-applicable')
expect(result).toEqual(ItemSignatureValidationResult.NotApplicable)
})
})
@@ -270,7 +271,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('no')
expect(result).toEqual(ItemSignatureValidationResult.NotTrusted)
})
it('should return not applicable if signing is not required', () => {
@@ -283,7 +284,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('not-applicable')
expect(result).toEqual(ItemSignatureValidationResult.NotApplicable)
})
})
@@ -301,7 +302,7 @@ describe('validate item signer use case', () => {
})
const result = usecase.execute(item)
expect(result).toEqual('no')
expect(result).toEqual(ItemSignatureValidationResult.NotTrusted)
})
it('should return invalid if signature result passes and a trusted contact is NOT found for signature public key', () => {
@@ -317,10 +318,10 @@ describe('validate item signer use case', () => {
} as jest.Mocked<PersistentSignatureData>,
})
items.getItems = jest.fn().mockReturnValue([])
findContact.execute = jest.fn().mockReturnValue(Result.fail('Not found'))
const result = usecase.execute(item)
expect(result).toEqual('no')
expect(result).toEqual(ItemSignatureValidationResult.NotTrusted)
})
it('should return valid if signature result passes and a trusted contact is found for signature public key', () => {
@@ -336,10 +337,8 @@ describe('validate item signer use case', () => {
} as jest.Mocked<PersistentSignatureData>,
})
items.getItems = jest.fn().mockReturnValue([trustedContact])
const result = usecase.execute(item)
expect(result).toEqual('yes')
expect(result).toEqual(ItemSignatureValidationResult.Trusted)
})
})
})

View File

@@ -1,15 +1,12 @@
import { ItemManagerInterface } from './../../Item/ItemManagerInterface'
import { doesPayloadRequireSigning } from '@standardnotes/encryption/src/Domain/Operator/004/V004AlgorithmHelpers'
import { DecryptedItemInterface, PayloadSource } from '@standardnotes/models'
import { ValidateItemSignerResult } from './ValidateItemSignerResult'
import { FindTrustedContactUseCase } from './FindTrustedContact'
import { DecryptedItemInterface, PayloadSource, PublicKeyTrustStatus } from '@standardnotes/models'
import { ItemSignatureValidationResult } from './Types/ItemSignatureValidationResult'
import { FindContact } from './FindContact'
export class ValidateItemSignerUseCase {
private findContactUseCase = new FindTrustedContactUseCase(this.items)
export class ValidateItemSigner {
constructor(private findContact: FindContact) {}
constructor(private items: ItemManagerInterface) {}
execute(item: DecryptedItemInterface): ValidateItemSignerResult {
execute(item: DecryptedItemInterface): ItemSignatureValidationResult {
const uuidOfLastEditor = item.last_edited_by_uuid
if (uuidOfLastEditor) {
return this.validateSignatureWithLastEditedByUuid(item, uuidOfLastEditor)
@@ -29,15 +26,15 @@ export class ValidateItemSignerUseCase {
private validateSignatureWithLastEditedByUuid(
item: DecryptedItemInterface,
uuidOfLastEditor: string,
): ValidateItemSignerResult {
): ItemSignatureValidationResult {
const requiresSignature = doesPayloadRequireSigning(item)
const trustedContact = this.findContactUseCase.execute({ userUuid: uuidOfLastEditor })
if (!trustedContact) {
const trustedContact = this.findContact.execute({ userUuid: uuidOfLastEditor })
if (trustedContact.isFailed()) {
if (requiresSignature) {
return 'no'
return ItemSignatureValidationResult.NotTrusted
} else {
return 'not-applicable'
return ItemSignatureValidationResult.NotApplicable
}
}
@@ -46,38 +43,41 @@ export class ValidateItemSignerUseCase {
this.isItemLocallyCreatedAndDoesNotRequireSignature(item) ||
this.isItemResutOfRemoteSaveAndDoesNotRequireSignature(item)
) {
return 'not-applicable'
return ItemSignatureValidationResult.NotApplicable
}
if (requiresSignature) {
return 'no'
return ItemSignatureValidationResult.NotTrusted
}
return 'not-applicable'
return ItemSignatureValidationResult.NotApplicable
}
const signatureData = item.signatureData
if (!signatureData.result) {
if (signatureData.required) {
return 'no'
return ItemSignatureValidationResult.NotTrusted
}
return 'not-applicable'
return ItemSignatureValidationResult.NotApplicable
}
const signatureResult = signatureData.result
if (!signatureResult.passes) {
return 'no'
return ItemSignatureValidationResult.NotTrusted
}
const signerPublicKey = signatureResult.publicKey
if (trustedContact.isSigningKeyTrusted(signerPublicKey)) {
return 'yes'
const trustStatus = trustedContact.getValue().getTrustStatusForSigningPublicKey(signerPublicKey)
if (trustStatus === PublicKeyTrustStatus.Trusted) {
return ItemSignatureValidationResult.Trusted
} else if (trustStatus === PublicKeyTrustStatus.Previous) {
return ItemSignatureValidationResult.SignedWithNonCurrentKey
}
return 'no'
return ItemSignatureValidationResult.NotTrusted
}
private validateSignatureWithNoLastEditedByUuid(item: DecryptedItemInterface): ValidateItemSignerResult {
private validateSignatureWithNoLastEditedByUuid(item: DecryptedItemInterface): ItemSignatureValidationResult {
const requiresSignature = doesPayloadRequireSigning(item)
if (!item.signatureData) {
@@ -85,38 +85,45 @@ export class ValidateItemSignerUseCase {
this.isItemLocallyCreatedAndDoesNotRequireSignature(item) ||
this.isItemResutOfRemoteSaveAndDoesNotRequireSignature(item)
) {
return 'not-applicable'
return ItemSignatureValidationResult.NotApplicable
}
if (requiresSignature) {
return 'no'
return ItemSignatureValidationResult.NotTrusted
}
return 'not-applicable'
return ItemSignatureValidationResult.NotApplicable
}
const signatureData = item.signatureData
if (!signatureData.result) {
if (signatureData.required) {
return 'no'
return ItemSignatureValidationResult.NotTrusted
}
return 'not-applicable'
return ItemSignatureValidationResult.NotApplicable
}
const signatureResult = signatureData.result
if (!signatureResult.passes) {
return 'no'
return ItemSignatureValidationResult.NotTrusted
}
const signerPublicKey = signatureResult.publicKey
const trustedContact = this.findContactUseCase.execute({ signingPublicKey: signerPublicKey })
const trustedContact = this.findContact.execute({ signingPublicKey: signerPublicKey })
if (trustedContact) {
return 'yes'
if (trustedContact.isFailed()) {
return ItemSignatureValidationResult.NotTrusted
}
return 'no'
const trustStatus = trustedContact.getValue().getTrustStatusForSigningPublicKey(signerPublicKey)
if (trustStatus === PublicKeyTrustStatus.Trusted) {
return ItemSignatureValidationResult.Trusted
} else if (trustStatus === PublicKeyTrustStatus.Previous) {
return ItemSignatureValidationResult.SignedWithNonCurrentKey
}
return ItemSignatureValidationResult.NotTrusted
}
}

View File

@@ -1 +0,0 @@
export type ValidateItemSignerResult = 'not-applicable' | 'yes' | 'no'