refactor: application dependency management (#2363)
This commit is contained in:
@@ -1,31 +1,121 @@
|
||||
import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface'
|
||||
import { GetUntrustedPayload } from './UseCase/GetUntrustedPayload'
|
||||
import { GetInboundMessages } from './UseCase/GetInboundMessages'
|
||||
import { GetOutboundMessages } from './UseCase/GetOutboundMessages'
|
||||
import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessage'
|
||||
import { HandleRootKeyChangedMessage } from './UseCase/HandleRootKeyChangedMessage'
|
||||
import { GetVault } from './../Vaults/UseCase/GetVault'
|
||||
import { GetTrustedPayload } from './UseCase/GetTrustedPayload'
|
||||
import { ReplaceContactData } from './../Contacts/UseCase/ReplaceContactData'
|
||||
import { GetAllContacts } from './../Contacts/UseCase/GetAllContacts'
|
||||
import { FindContact } from './../Contacts/UseCase/FindContact'
|
||||
import { CreateOrEditContact } from './../Contacts/UseCase/CreateOrEditContact'
|
||||
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
|
||||
import { HttpServiceInterface } from '@standardnotes/api'
|
||||
import { AsymmetricMessageServer } from '@standardnotes/api'
|
||||
import { AsymmetricMessageService } from './AsymmetricMessageService'
|
||||
import { ContactServiceInterface } from './../Contacts/ContactServiceInterface'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { EncryptionProviderInterface } from '@standardnotes/encryption'
|
||||
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { AsymmetricMessagePayloadType } from '@standardnotes/models'
|
||||
import {
|
||||
AsymmetricMessagePayloadType,
|
||||
AsymmetricMessageSenderKeypairChanged,
|
||||
AsymmetricMessageSharedVaultInvite,
|
||||
AsymmetricMessageSharedVaultMetadataChanged,
|
||||
AsymmetricMessageSharedVaultRootKeyChanged,
|
||||
AsymmetricMessageTrustedContactShare,
|
||||
KeySystemRootKeyContentSpecialized,
|
||||
TrustedContactInterface,
|
||||
} from '@standardnotes/models'
|
||||
|
||||
describe('AsymmetricMessageService', () => {
|
||||
let sync: jest.Mocked<SyncServiceInterface>
|
||||
let mutator: jest.Mocked<MutatorClientInterface>
|
||||
let encryption: jest.Mocked<EncryptionProviderInterface>
|
||||
let service: AsymmetricMessageService
|
||||
|
||||
beforeEach(() => {
|
||||
const http = {} as jest.Mocked<HttpServiceInterface>
|
||||
http.delete = jest.fn()
|
||||
const messageServer = {} as jest.Mocked<AsymmetricMessageServer>
|
||||
messageServer.deleteMessage = jest.fn()
|
||||
|
||||
const encryption = {} as jest.Mocked<EncryptionProviderInterface>
|
||||
const contacts = {} as jest.Mocked<ContactServiceInterface>
|
||||
const items = {} as jest.Mocked<ItemManagerInterface>
|
||||
const sync = {} as jest.Mocked<SyncServiceInterface>
|
||||
const mutator = {} as jest.Mocked<MutatorClientInterface>
|
||||
encryption = {} as jest.Mocked<EncryptionProviderInterface>
|
||||
const createOrEditContact = {} as jest.Mocked<CreateOrEditContact>
|
||||
const findContact = {} as jest.Mocked<FindContact>
|
||||
const getAllContacts = {} as jest.Mocked<GetAllContacts>
|
||||
const replaceContactData = {} as jest.Mocked<ReplaceContactData>
|
||||
const getTrustedPayload = {} as jest.Mocked<GetTrustedPayload>
|
||||
const getVault = {} as jest.Mocked<GetVault>
|
||||
const handleRootKeyChangedMessage = {} as jest.Mocked<HandleRootKeyChangedMessage>
|
||||
const sendOwnContactChangedMessage = {} as jest.Mocked<SendOwnContactChangeMessage>
|
||||
const getOutboundMessagesUseCase = {} as jest.Mocked<GetOutboundMessages>
|
||||
const getInboundMessagesUseCase = {} as jest.Mocked<GetInboundMessages>
|
||||
const getUntrustedPayload = {} as jest.Mocked<GetUntrustedPayload>
|
||||
|
||||
sync = {} as jest.Mocked<SyncServiceInterface>
|
||||
sync.sync = jest.fn()
|
||||
|
||||
mutator = {} as jest.Mocked<MutatorClientInterface>
|
||||
mutator.changeItem = jest.fn()
|
||||
|
||||
const eventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||
eventBus.addEventHandler = jest.fn()
|
||||
|
||||
service = new AsymmetricMessageService(http, encryption, contacts, items, mutator, sync, eventBus)
|
||||
service = new AsymmetricMessageService(
|
||||
messageServer,
|
||||
encryption,
|
||||
mutator,
|
||||
createOrEditContact,
|
||||
findContact,
|
||||
getAllContacts,
|
||||
replaceContactData,
|
||||
getTrustedPayload,
|
||||
getVault,
|
||||
handleRootKeyChangedMessage,
|
||||
sendOwnContactChangedMessage,
|
||||
getOutboundMessagesUseCase,
|
||||
getInboundMessagesUseCase,
|
||||
getUntrustedPayload,
|
||||
eventBus,
|
||||
)
|
||||
})
|
||||
|
||||
describe('sortServerMessages', () => {
|
||||
it('should prioritize keypair changed messages over other messages', () => {
|
||||
const messages: AsymmetricMessageServerHash[] = [
|
||||
{
|
||||
uuid: 'keypair-changed-message',
|
||||
user_uuid: '1',
|
||||
sender_uuid: '2',
|
||||
encrypted_message: 'encrypted_message',
|
||||
created_at_timestamp: 2,
|
||||
updated_at_timestamp: 2,
|
||||
},
|
||||
{
|
||||
uuid: 'misc-message',
|
||||
user_uuid: '1',
|
||||
sender_uuid: '2',
|
||||
encrypted_message: 'encrypted_message',
|
||||
created_at_timestamp: 1,
|
||||
updated_at_timestamp: 1,
|
||||
},
|
||||
]
|
||||
|
||||
service.getUntrustedMessagePayload = jest.fn()
|
||||
service.getServerMessageType = jest.fn().mockImplementation((message) => {
|
||||
if (message.uuid === 'keypair-changed-message') {
|
||||
return AsymmetricMessagePayloadType.SenderKeypairChanged
|
||||
} else {
|
||||
return AsymmetricMessagePayloadType.ContactShare
|
||||
}
|
||||
})
|
||||
|
||||
const sorted = service.sortServerMessages(messages)
|
||||
expect(sorted[0].uuid).toEqual('keypair-changed-message')
|
||||
expect(sorted[1].uuid).toEqual('misc-message')
|
||||
|
||||
const reverseSorted = service.sortServerMessages(messages.reverse())
|
||||
expect(reverseSorted[0].uuid).toEqual('keypair-changed-message')
|
||||
expect(reverseSorted[1].uuid).toEqual('misc-message')
|
||||
})
|
||||
})
|
||||
|
||||
it('should process incoming messages oldest first', async () => {
|
||||
@@ -50,7 +140,9 @@ describe('AsymmetricMessageService', () => {
|
||||
|
||||
const trustedPayloadMock = { type: AsymmetricMessagePayloadType.ContactShare, data: { recipientUuid: '1' } }
|
||||
|
||||
service.getTrustedMessagePayload = jest.fn().mockReturnValue(trustedPayloadMock)
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(trustedPayloadMock)
|
||||
|
||||
const handleTrustedContactShareMessageMock = jest.fn()
|
||||
service.handleTrustedContactShareMessage = handleTrustedContactShareMessageMock
|
||||
@@ -60,4 +152,174 @@ describe('AsymmetricMessageService', () => {
|
||||
expect(handleTrustedContactShareMessageMock.mock.calls[0][0]).toEqual(messages[1])
|
||||
expect(handleTrustedContactShareMessageMock.mock.calls[1][0]).toEqual(messages[0])
|
||||
})
|
||||
|
||||
it('should handle ContactShare message', async () => {
|
||||
const message: AsymmetricMessageServerHash = {
|
||||
uuid: 'message',
|
||||
user_uuid: '1',
|
||||
sender_uuid: '2',
|
||||
encrypted_message: 'encrypted_message',
|
||||
created_at_timestamp: 2,
|
||||
updated_at_timestamp: 2,
|
||||
}
|
||||
|
||||
const decryptedMessagePayload: AsymmetricMessageTrustedContactShare = {
|
||||
type: AsymmetricMessagePayloadType.ContactShare,
|
||||
data: {
|
||||
recipientUuid: '1',
|
||||
trustedContact: {} as TrustedContactInterface,
|
||||
},
|
||||
}
|
||||
|
||||
service.handleTrustedContactShareMessage = jest.fn()
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(decryptedMessagePayload)
|
||||
|
||||
await service.handleRemoteReceivedAsymmetricMessages([message])
|
||||
|
||||
expect(service.handleTrustedContactShareMessage).toHaveBeenCalledWith(message, decryptedMessagePayload)
|
||||
})
|
||||
|
||||
it('should handle SenderKeypairChanged message', async () => {
|
||||
const message: AsymmetricMessageServerHash = {
|
||||
uuid: 'message',
|
||||
user_uuid: '1',
|
||||
sender_uuid: '2',
|
||||
encrypted_message: 'encrypted_message',
|
||||
created_at_timestamp: 2,
|
||||
updated_at_timestamp: 2,
|
||||
}
|
||||
|
||||
const decryptedMessagePayload: AsymmetricMessageSenderKeypairChanged = {
|
||||
type: AsymmetricMessagePayloadType.SenderKeypairChanged,
|
||||
data: {
|
||||
recipientUuid: '1',
|
||||
newEncryptionPublicKey: 'new-encryption-public-key',
|
||||
newSigningPublicKey: 'new-signing-public-key',
|
||||
},
|
||||
}
|
||||
|
||||
service.handleTrustedSenderKeypairChangedMessage = jest.fn()
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(decryptedMessagePayload)
|
||||
|
||||
await service.handleRemoteReceivedAsymmetricMessages([message])
|
||||
|
||||
expect(service.handleTrustedSenderKeypairChangedMessage).toHaveBeenCalledWith(message, decryptedMessagePayload)
|
||||
})
|
||||
|
||||
it('should handle SharedVaultRootKeyChanged message', async () => {
|
||||
const message: AsymmetricMessageServerHash = {
|
||||
uuid: 'message',
|
||||
user_uuid: '1',
|
||||
sender_uuid: '2',
|
||||
encrypted_message: 'encrypted_message',
|
||||
created_at_timestamp: 2,
|
||||
updated_at_timestamp: 2,
|
||||
}
|
||||
|
||||
const decryptedMessagePayload: AsymmetricMessageSharedVaultRootKeyChanged = {
|
||||
type: AsymmetricMessagePayloadType.SharedVaultRootKeyChanged,
|
||||
data: {
|
||||
recipientUuid: '1',
|
||||
rootKey: {} as KeySystemRootKeyContentSpecialized,
|
||||
},
|
||||
}
|
||||
|
||||
service.handleTrustedSharedVaultRootKeyChangedMessage = jest.fn()
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(decryptedMessagePayload)
|
||||
|
||||
await service.handleRemoteReceivedAsymmetricMessages([message])
|
||||
|
||||
expect(service.handleTrustedSharedVaultRootKeyChangedMessage).toHaveBeenCalledWith(message, decryptedMessagePayload)
|
||||
})
|
||||
|
||||
it('should handle SharedVaultMetadataChanged message', async () => {
|
||||
const message: AsymmetricMessageServerHash = {
|
||||
uuid: 'message',
|
||||
user_uuid: '1',
|
||||
sender_uuid: '2',
|
||||
encrypted_message: 'encrypted_message',
|
||||
created_at_timestamp: 2,
|
||||
updated_at_timestamp: 2,
|
||||
}
|
||||
|
||||
const decryptedMessagePayload: AsymmetricMessageSharedVaultMetadataChanged = {
|
||||
type: AsymmetricMessagePayloadType.SharedVaultMetadataChanged,
|
||||
data: {
|
||||
recipientUuid: '1',
|
||||
sharedVaultUuid: 'shared-vault-uuid',
|
||||
name: 'Vault name',
|
||||
description: 'Vault description',
|
||||
},
|
||||
}
|
||||
|
||||
service.handleTrustedVaultMetadataChangedMessage = jest.fn()
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(decryptedMessagePayload)
|
||||
|
||||
await service.handleRemoteReceivedAsymmetricMessages([message])
|
||||
|
||||
expect(service.handleTrustedVaultMetadataChangedMessage).toHaveBeenCalledWith(message, decryptedMessagePayload)
|
||||
})
|
||||
|
||||
it('should throw if message type is SharedVaultInvite', async () => {
|
||||
const message: AsymmetricMessageServerHash = {
|
||||
uuid: 'message',
|
||||
user_uuid: '1',
|
||||
sender_uuid: '2',
|
||||
encrypted_message: 'encrypted_message',
|
||||
created_at_timestamp: 2,
|
||||
updated_at_timestamp: 2,
|
||||
}
|
||||
|
||||
const decryptedMessagePayload: AsymmetricMessageSharedVaultInvite = {
|
||||
type: AsymmetricMessagePayloadType.SharedVaultInvite,
|
||||
data: {
|
||||
recipientUuid: '1',
|
||||
},
|
||||
} as AsymmetricMessageSharedVaultInvite
|
||||
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(decryptedMessagePayload)
|
||||
|
||||
await expect(service.handleRemoteReceivedAsymmetricMessages([message])).rejects.toThrow(
|
||||
'Shared vault invites payloads are not handled as part of asymmetric messages',
|
||||
)
|
||||
})
|
||||
|
||||
it('should delete message from server after processing', async () => {
|
||||
const message: AsymmetricMessageServerHash = {
|
||||
uuid: 'message',
|
||||
user_uuid: '1',
|
||||
sender_uuid: '2',
|
||||
encrypted_message: 'encrypted_message',
|
||||
created_at_timestamp: 2,
|
||||
updated_at_timestamp: 2,
|
||||
}
|
||||
|
||||
const decryptedMessagePayload: AsymmetricMessageTrustedContactShare = {
|
||||
type: AsymmetricMessagePayloadType.ContactShare,
|
||||
data: {
|
||||
recipientUuid: '1',
|
||||
trustedContact: {} as TrustedContactInterface,
|
||||
},
|
||||
}
|
||||
|
||||
service.deleteMessageAfterProcessing = jest.fn()
|
||||
service.handleTrustedContactShareMessage = jest.fn()
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(decryptedMessagePayload)
|
||||
|
||||
await service.handleRemoteReceivedAsymmetricMessages([message])
|
||||
|
||||
expect(service.deleteMessageAfterProcessing).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
|
||||
import { ContactServiceInterface } from './../Contacts/ContactServiceInterface'
|
||||
import { AsymmetricMessageServerHash, ClientDisplayableError, isClientDisplayableError } from '@standardnotes/responses'
|
||||
import { SyncEvent, SyncEventReceivedAsymmetricMessagesData } from '../Event/SyncEvent'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
|
||||
import { InternalEventInterface } from '../Internal/InternalEventInterface'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { GetAsymmetricMessageTrustedPayload } from './UseCase/GetAsymmetricMessageTrustedPayload'
|
||||
import { EncryptionProviderInterface } from '@standardnotes/encryption'
|
||||
import { GetTrustedPayload } from './UseCase/GetTrustedPayload'
|
||||
import {
|
||||
AsymmetricMessageSharedVaultRootKeyChanged,
|
||||
AsymmetricMessagePayloadType,
|
||||
@@ -16,61 +14,68 @@ import {
|
||||
AsymmetricMessagePayload,
|
||||
AsymmetricMessageSharedVaultMetadataChanged,
|
||||
VaultListingMutator,
|
||||
MutationType,
|
||||
PayloadEmitSource,
|
||||
VaultListingInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { HandleTrustedSharedVaultRootKeyChangedMessage } from './UseCase/HandleTrustedSharedVaultRootKeyChangedMessage'
|
||||
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
|
||||
import { HandleRootKeyChangedMessage } from './UseCase/HandleRootKeyChangedMessage'
|
||||
import { SessionEvent } from '../Session/SessionEvent'
|
||||
import { AsymmetricMessageServer, HttpServiceInterface } from '@standardnotes/api'
|
||||
import { AsymmetricMessageServer } from '@standardnotes/api'
|
||||
import { UserKeyPairChangedEventData } from '../Session/UserKeyPairChangedEventData'
|
||||
import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessage'
|
||||
import { GetOutboundAsymmetricMessages } from './UseCase/GetOutboundAsymmetricMessages'
|
||||
import { GetInboundAsymmetricMessages } from './UseCase/GetInboundAsymmetricMessages'
|
||||
import { GetVaultUseCase } from '../Vaults/UseCase/GetVault'
|
||||
import { GetOutboundMessages } from './UseCase/GetOutboundMessages'
|
||||
import { GetInboundMessages } from './UseCase/GetInboundMessages'
|
||||
import { GetVault } from '../Vaults/UseCase/GetVault'
|
||||
import { AsymmetricMessageServiceInterface } from './AsymmetricMessageServiceInterface'
|
||||
import { GetUntrustedPayload } from './UseCase/GetUntrustedPayload'
|
||||
import { FindContact } from '../Contacts/UseCase/FindContact'
|
||||
import { CreateOrEditContact } from '../Contacts/UseCase/CreateOrEditContact'
|
||||
import { ReplaceContactData } from '../Contacts/UseCase/ReplaceContactData'
|
||||
import { GetAllContacts } from '../Contacts/UseCase/GetAllContacts'
|
||||
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
|
||||
|
||||
export class AsymmetricMessageService
|
||||
extends AbstractService
|
||||
implements AsymmetricMessageServiceInterface, InternalEventHandlerInterface
|
||||
{
|
||||
private messageServer: AsymmetricMessageServer
|
||||
|
||||
constructor(
|
||||
http: HttpServiceInterface,
|
||||
private messageServer: AsymmetricMessageServer,
|
||||
private encryption: EncryptionProviderInterface,
|
||||
private contacts: ContactServiceInterface,
|
||||
private items: ItemManagerInterface,
|
||||
private mutator: MutatorClientInterface,
|
||||
private sync: SyncServiceInterface,
|
||||
private _createOrEditContact: CreateOrEditContact,
|
||||
private _findContact: FindContact,
|
||||
private _getAllContacts: GetAllContacts,
|
||||
private _replaceContactData: ReplaceContactData,
|
||||
private _getTrustedPayload: GetTrustedPayload,
|
||||
private _getVault: GetVault,
|
||||
private _handleRootKeyChangedMessage: HandleRootKeyChangedMessage,
|
||||
private _sendOwnContactChangedMessage: SendOwnContactChangeMessage,
|
||||
private _getOutboundMessagesUseCase: GetOutboundMessages,
|
||||
private _getInboundMessagesUseCase: GetInboundMessages,
|
||||
private _getUntrustedPayload: GetUntrustedPayload,
|
||||
eventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(eventBus)
|
||||
|
||||
this.messageServer = new AsymmetricMessageServer(http)
|
||||
|
||||
eventBus.addEventHandler(this, SyncEvent.ReceivedAsymmetricMessages)
|
||||
eventBus.addEventHandler(this, SessionEvent.UserKeyPairChanged)
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
if (event.type === SessionEvent.UserKeyPairChanged) {
|
||||
void this.messageServer.deleteAllInboundMessages()
|
||||
void this.sendOwnContactChangeEventToAllContacts(event.payload as UserKeyPairChangedEventData)
|
||||
}
|
||||
|
||||
if (event.type === SyncEvent.ReceivedAsymmetricMessages) {
|
||||
void this.handleRemoteReceivedAsymmetricMessages(event.payload as SyncEventReceivedAsymmetricMessagesData)
|
||||
switch (event.type) {
|
||||
case SessionEvent.UserKeyPairChanged:
|
||||
void this.messageServer.deleteAllInboundMessages()
|
||||
void this.sendOwnContactChangeEventToAllContacts(event.payload as UserKeyPairChangedEventData)
|
||||
break
|
||||
case SyncEvent.ReceivedAsymmetricMessages:
|
||||
void this.handleRemoteReceivedAsymmetricMessages(event.payload as SyncEventReceivedAsymmetricMessagesData)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public async getOutboundMessages(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError> {
|
||||
const usecase = new GetOutboundAsymmetricMessages(this.messageServer)
|
||||
return usecase.execute()
|
||||
return this._getOutboundMessagesUseCase.execute()
|
||||
}
|
||||
|
||||
public async getInboundMessages(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError> {
|
||||
const usecase = new GetInboundAsymmetricMessages(this.messageServer)
|
||||
return usecase.execute()
|
||||
return this._getInboundMessagesUseCase.execute()
|
||||
}
|
||||
|
||||
public async downloadAndProcessInboundMessages(): Promise<void> {
|
||||
@@ -83,118 +88,223 @@ export class AsymmetricMessageService
|
||||
}
|
||||
|
||||
private async sendOwnContactChangeEventToAllContacts(data: UserKeyPairChangedEventData): Promise<void> {
|
||||
if (!data.oldKeyPair || !data.oldSigningKeyPair) {
|
||||
if (!data.previous) {
|
||||
return
|
||||
}
|
||||
|
||||
const useCase = new SendOwnContactChangeMessage(this.encryption, this.messageServer)
|
||||
const contacts = this._getAllContacts.execute()
|
||||
if (contacts.isFailed()) {
|
||||
return
|
||||
}
|
||||
|
||||
const contacts = this.contacts.getAllContacts()
|
||||
|
||||
for (const contact of contacts) {
|
||||
for (const contact of contacts.getValue()) {
|
||||
if (contact.isMe) {
|
||||
continue
|
||||
}
|
||||
|
||||
await useCase.execute({
|
||||
senderOldKeyPair: data.oldKeyPair,
|
||||
senderOldSigningKeyPair: data.oldSigningKeyPair,
|
||||
senderNewKeyPair: data.newKeyPair,
|
||||
senderNewSigningKeyPair: data.newSigningKeyPair,
|
||||
await this._sendOwnContactChangedMessage.execute({
|
||||
senderOldKeyPair: data.previous.encryption,
|
||||
senderOldSigningKeyPair: data.previous.signing,
|
||||
senderNewKeyPair: data.current.encryption,
|
||||
senderNewSigningKeyPair: data.current.signing,
|
||||
contact,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sortServerMessages(messages: AsymmetricMessageServerHash[]): AsymmetricMessageServerHash[] {
|
||||
const SortedPriorityTypes = [AsymmetricMessagePayloadType.SenderKeypairChanged]
|
||||
|
||||
const priority: AsymmetricMessageServerHash[] = []
|
||||
const regular: AsymmetricMessageServerHash[] = []
|
||||
|
||||
const allMessagesOldestFirst = messages.slice().sort((a, b) => a.created_at_timestamp - b.created_at_timestamp)
|
||||
|
||||
const messageTypeMap: Record<string, AsymmetricMessagePayloadType> = {}
|
||||
|
||||
for (const message of allMessagesOldestFirst) {
|
||||
const messageType = this.getServerMessageType(message)
|
||||
if (!messageType) {
|
||||
continue
|
||||
}
|
||||
|
||||
messageTypeMap[message.uuid] = messageType
|
||||
|
||||
if (SortedPriorityTypes.includes(messageType)) {
|
||||
priority.push(message)
|
||||
} else {
|
||||
regular.push(message)
|
||||
}
|
||||
}
|
||||
|
||||
const sortedPriority = priority.sort((a, b) => {
|
||||
const typeA = messageTypeMap[a.uuid]
|
||||
const typeB = messageTypeMap[b.uuid]
|
||||
|
||||
if (typeA !== typeB) {
|
||||
return SortedPriorityTypes.indexOf(typeA) - SortedPriorityTypes.indexOf(typeB)
|
||||
}
|
||||
|
||||
return a.created_at_timestamp - b.created_at_timestamp
|
||||
})
|
||||
|
||||
const regularMessagesOldestFirst = regular.sort((a, b) => a.created_at_timestamp - b.created_at_timestamp)
|
||||
|
||||
return [...sortedPriority, ...regularMessagesOldestFirst]
|
||||
}
|
||||
|
||||
getServerMessageType(message: AsymmetricMessageServerHash): AsymmetricMessagePayloadType | undefined {
|
||||
const result = this.getUntrustedMessagePayload(message)
|
||||
|
||||
if (!result) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return result.type
|
||||
}
|
||||
|
||||
async handleRemoteReceivedAsymmetricMessages(messages: AsymmetricMessageServerHash[]): Promise<void> {
|
||||
if (messages.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const sortedMessages = messages.slice().sort((a, b) => a.created_at_timestamp - b.created_at_timestamp)
|
||||
const sortedMessages = this.sortServerMessages(messages)
|
||||
|
||||
for (const message of sortedMessages) {
|
||||
const trustedMessagePayload = this.getTrustedMessagePayload(message)
|
||||
if (!trustedMessagePayload) {
|
||||
const trustedPayload = this.getTrustedMessagePayload(message)
|
||||
if (!trustedPayload) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (trustedMessagePayload.data.recipientUuid !== message.user_uuid) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (trustedMessagePayload.type === AsymmetricMessagePayloadType.ContactShare) {
|
||||
await this.handleTrustedContactShareMessage(message, trustedMessagePayload)
|
||||
} else if (trustedMessagePayload.type === AsymmetricMessagePayloadType.SenderKeypairChanged) {
|
||||
await this.handleTrustedSenderKeypairChangedMessage(message, trustedMessagePayload)
|
||||
} else if (trustedMessagePayload.type === AsymmetricMessagePayloadType.SharedVaultRootKeyChanged) {
|
||||
await this.handleTrustedSharedVaultRootKeyChangedMessage(message, trustedMessagePayload)
|
||||
} else if (trustedMessagePayload.type === AsymmetricMessagePayloadType.SharedVaultMetadataChanged) {
|
||||
await this.handleVaultMetadataChangedMessage(message, trustedMessagePayload)
|
||||
} else if (trustedMessagePayload.type === AsymmetricMessagePayloadType.SharedVaultInvite) {
|
||||
throw new Error('Shared vault invites payloads are not handled as part of asymmetric messages')
|
||||
}
|
||||
|
||||
await this.deleteMessageAfterProcessing(message)
|
||||
await this.handleTrustedMessageResult(message, trustedPayload)
|
||||
}
|
||||
}
|
||||
|
||||
getTrustedMessagePayload(message: AsymmetricMessageServerHash): AsymmetricMessagePayload | undefined {
|
||||
const useCase = new GetAsymmetricMessageTrustedPayload(this.encryption, this.contacts)
|
||||
|
||||
return useCase.execute({
|
||||
privateKey: this.encryption.getKeyPair().privateKey,
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
private async deleteMessageAfterProcessing(message: AsymmetricMessageServerHash): Promise<void> {
|
||||
await this.messageServer.deleteMessage({ messageUuid: message.uuid })
|
||||
}
|
||||
|
||||
async handleVaultMetadataChangedMessage(
|
||||
_message: AsymmetricMessageServerHash,
|
||||
trustedPayload: AsymmetricMessageSharedVaultMetadataChanged,
|
||||
private async handleTrustedMessageResult(
|
||||
message: AsymmetricMessageServerHash,
|
||||
payload: AsymmetricMessagePayload,
|
||||
): Promise<void> {
|
||||
const vault = new GetVaultUseCase(this.items).execute({ sharedVaultUuid: trustedPayload.data.sharedVaultUuid })
|
||||
if (!vault) {
|
||||
if (payload.data.recipientUuid !== message.user_uuid) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
|
||||
mutator.name = trustedPayload.data.name
|
||||
mutator.description = trustedPayload.data.description
|
||||
if (payload.type === AsymmetricMessagePayloadType.ContactShare) {
|
||||
await this.handleTrustedContactShareMessage(message, payload)
|
||||
} else if (payload.type === AsymmetricMessagePayloadType.SenderKeypairChanged) {
|
||||
await this.handleTrustedSenderKeypairChangedMessage(message, payload)
|
||||
} else if (payload.type === AsymmetricMessagePayloadType.SharedVaultRootKeyChanged) {
|
||||
await this.handleTrustedSharedVaultRootKeyChangedMessage(message, payload)
|
||||
} else if (payload.type === AsymmetricMessagePayloadType.SharedVaultMetadataChanged) {
|
||||
await this.handleTrustedVaultMetadataChangedMessage(message, payload)
|
||||
} else if (payload.type === AsymmetricMessagePayloadType.SharedVaultInvite) {
|
||||
throw new Error('Shared vault invites payloads are not handled as part of asymmetric messages')
|
||||
}
|
||||
|
||||
await this.deleteMessageAfterProcessing(message)
|
||||
}
|
||||
|
||||
getUntrustedMessagePayload(message: AsymmetricMessageServerHash): AsymmetricMessagePayload | undefined {
|
||||
const result = this._getUntrustedPayload.execute({
|
||||
privateKey: this.encryption.getKeyPair().privateKey,
|
||||
message,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return result.getValue()
|
||||
}
|
||||
|
||||
getTrustedMessagePayload(message: AsymmetricMessageServerHash): AsymmetricMessagePayload | undefined {
|
||||
const contact = this._findContact.execute({ userUuid: message.sender_uuid })
|
||||
if (contact.isFailed()) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const result = this._getTrustedPayload.execute({
|
||||
privateKey: this.encryption.getKeyPair().privateKey,
|
||||
sender: contact.getValue(),
|
||||
message,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return result.getValue()
|
||||
}
|
||||
|
||||
async deleteMessageAfterProcessing(message: AsymmetricMessageServerHash): Promise<void> {
|
||||
await this.messageServer.deleteMessage({ messageUuid: message.uuid })
|
||||
}
|
||||
|
||||
async handleTrustedVaultMetadataChangedMessage(
|
||||
_message: AsymmetricMessageServerHash,
|
||||
trustedPayload: AsymmetricMessageSharedVaultMetadataChanged,
|
||||
): Promise<void> {
|
||||
const vault = this._getVault.execute<VaultListingInterface>({
|
||||
sharedVaultUuid: trustedPayload.data.sharedVaultUuid,
|
||||
})
|
||||
if (vault.isFailed()) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.mutator.changeItem<VaultListingMutator>(
|
||||
vault.getValue(),
|
||||
(mutator) => {
|
||||
mutator.name = trustedPayload.data.name
|
||||
mutator.description = trustedPayload.data.description
|
||||
},
|
||||
MutationType.UpdateUserTimestamps,
|
||||
PayloadEmitSource.RemoteRetrieved,
|
||||
)
|
||||
}
|
||||
|
||||
async handleTrustedContactShareMessage(
|
||||
_message: AsymmetricMessageServerHash,
|
||||
trustedPayload: AsymmetricMessageTrustedContactShare,
|
||||
): Promise<void> {
|
||||
await this.contacts.createOrUpdateTrustedContactFromContactShare(trustedPayload.data.trustedContact)
|
||||
if (trustedPayload.data.trustedContact.isMe) {
|
||||
return
|
||||
}
|
||||
|
||||
await this._replaceContactData.execute(trustedPayload.data.trustedContact)
|
||||
}
|
||||
|
||||
private async handleTrustedSenderKeypairChangedMessage(
|
||||
async handleTrustedSenderKeypairChangedMessage(
|
||||
message: AsymmetricMessageServerHash,
|
||||
trustedPayload: AsymmetricMessageSenderKeypairChanged,
|
||||
): Promise<void> {
|
||||
await this.contacts.createOrEditTrustedContact({
|
||||
await this._createOrEditContact.execute({
|
||||
contactUuid: message.sender_uuid,
|
||||
publicKey: trustedPayload.data.newEncryptionPublicKey,
|
||||
signingPublicKey: trustedPayload.data.newSigningPublicKey,
|
||||
})
|
||||
}
|
||||
|
||||
private async handleTrustedSharedVaultRootKeyChangedMessage(
|
||||
async handleTrustedSharedVaultRootKeyChangedMessage(
|
||||
_message: AsymmetricMessageServerHash,
|
||||
trustedPayload: AsymmetricMessageSharedVaultRootKeyChanged,
|
||||
): Promise<void> {
|
||||
const useCase = new HandleTrustedSharedVaultRootKeyChangedMessage(
|
||||
this.mutator,
|
||||
this.items,
|
||||
this.sync,
|
||||
this.encryption,
|
||||
)
|
||||
await useCase.execute(trustedPayload)
|
||||
await this._handleRootKeyChangedMessage.execute(trustedPayload)
|
||||
}
|
||||
|
||||
public override deinit(): void {
|
||||
super.deinit()
|
||||
;(this.messageServer as unknown) = undefined
|
||||
;(this.encryption as unknown) = undefined
|
||||
;(this.mutator as unknown) = undefined
|
||||
;(this._createOrEditContact as unknown) = undefined
|
||||
;(this._findContact as unknown) = undefined
|
||||
;(this._getAllContacts as unknown) = undefined
|
||||
;(this._replaceContactData as unknown) = undefined
|
||||
;(this._getTrustedPayload as unknown) = undefined
|
||||
;(this._getVault as unknown) = undefined
|
||||
;(this._handleRootKeyChangedMessage as unknown) = undefined
|
||||
;(this._sendOwnContactChangedMessage as unknown) = undefined
|
||||
;(this._getOutboundMessagesUseCase as unknown) = undefined
|
||||
;(this._getInboundMessagesUseCase as unknown) = undefined
|
||||
;(this._getUntrustedPayload as unknown) = undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { EncryptionProviderInterface } from '@standardnotes/encryption'
|
||||
import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface'
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { AsymmetricMessagePayload } from '@standardnotes/models'
|
||||
|
||||
export class GetAsymmetricMessageTrustedPayload<M extends AsymmetricMessagePayload> {
|
||||
constructor(private encryption: EncryptionProviderInterface, private contacts: ContactServiceInterface) {}
|
||||
|
||||
execute(dto: { privateKey: string; message: AsymmetricMessageServerHash }): M | undefined {
|
||||
const trustedContact = this.contacts.findTrustedContact(dto.message.sender_uuid)
|
||||
if (!trustedContact) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const decryptionResult = this.encryption.asymmetricallyDecryptMessage<M>({
|
||||
encryptedString: dto.message.encrypted_message,
|
||||
trustedSender: trustedContact,
|
||||
privateKey: dto.privateKey,
|
||||
})
|
||||
|
||||
return decryptionResult
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { EncryptionProviderInterface } from '@standardnotes/encryption'
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { AsymmetricMessagePayload } from '@standardnotes/models'
|
||||
|
||||
export class GetAsymmetricMessageUntrustedPayload<M extends AsymmetricMessagePayload> {
|
||||
constructor(private encryption: EncryptionProviderInterface) {}
|
||||
|
||||
execute(dto: { privateKey: string; message: AsymmetricMessageServerHash }): M | undefined {
|
||||
const decryptionResult = this.encryption.asymmetricallyDecryptMessage<M>({
|
||||
encryptedString: dto.message.encrypted_message,
|
||||
trustedSender: undefined,
|
||||
privateKey: dto.privateKey,
|
||||
})
|
||||
|
||||
return decryptionResult
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ClientDisplayableError, isErrorResponse, AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { AsymmetricMessageServerInterface } from '@standardnotes/api'
|
||||
|
||||
export class GetInboundAsymmetricMessages {
|
||||
export class GetInboundMessages {
|
||||
constructor(private messageServer: AsymmetricMessageServerInterface) {}
|
||||
|
||||
async execute(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError> {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ClientDisplayableError, isErrorResponse, AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { AsymmetricMessageServerInterface } from '@standardnotes/api'
|
||||
|
||||
export class GetOutboundAsymmetricMessages {
|
||||
export class GetOutboundMessages {
|
||||
constructor(private messageServer: AsymmetricMessageServerInterface) {}
|
||||
|
||||
async execute(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError> {
|
||||
@@ -0,0 +1,17 @@
|
||||
import { AsymmetricMessagePayloadType } from '@standardnotes/models'
|
||||
|
||||
const TypesUsingReplaceableIdentifiers = [
|
||||
AsymmetricMessagePayloadType.SharedVaultRootKeyChanged,
|
||||
AsymmetricMessagePayloadType.SharedVaultMetadataChanged,
|
||||
]
|
||||
|
||||
export function GetReplaceabilityIdentifier(
|
||||
type: AsymmetricMessagePayloadType,
|
||||
sharedVaultUuid: string,
|
||||
keySystemIdentifier: string,
|
||||
): string | undefined {
|
||||
if (!TypesUsingReplaceableIdentifiers.includes(type)) {
|
||||
return undefined
|
||||
}
|
||||
return [type, sharedVaultUuid, keySystemIdentifier].join(':')
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { AsymmetricMessagePayload, TrustedContactInterface } from '@standardnotes/models'
|
||||
import { DecryptMessage } from '../../Encryption/UseCase/Asymmetric/DecryptMessage'
|
||||
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
export class GetTrustedPayload implements SyncUseCaseInterface<AsymmetricMessagePayload> {
|
||||
constructor(private decryptMessage: DecryptMessage) {}
|
||||
|
||||
execute<M extends AsymmetricMessagePayload>(dto: {
|
||||
privateKey: string
|
||||
message: AsymmetricMessageServerHash
|
||||
sender: TrustedContactInterface
|
||||
}): Result<M> {
|
||||
const result = this.decryptMessage.execute<M>({
|
||||
message: dto.message.encrypted_message,
|
||||
sender: dto.sender,
|
||||
privateKey: dto.privateKey,
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { AsymmetricMessagePayload } from '@standardnotes/models'
|
||||
import { DecryptMessage } from '../../Encryption/UseCase/Asymmetric/DecryptMessage'
|
||||
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
export class GetUntrustedPayload implements SyncUseCaseInterface<AsymmetricMessagePayload> {
|
||||
constructor(private decryptMessage: DecryptMessage) {}
|
||||
|
||||
execute<M extends AsymmetricMessagePayload>(dto: {
|
||||
privateKey: string
|
||||
message: AsymmetricMessageServerHash
|
||||
}): Result<M> {
|
||||
const result = this.decryptMessage.execute<M>({
|
||||
message: dto.message.encrypted_message,
|
||||
sender: undefined,
|
||||
privateKey: dto.privateKey,
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ItemManagerInterface } from './../../Item/ItemManagerInterface'
|
||||
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
|
||||
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
|
||||
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
|
||||
import {
|
||||
KeySystemRootKeyInterface,
|
||||
@@ -7,18 +6,19 @@ import {
|
||||
FillItemContent,
|
||||
KeySystemRootKeyContent,
|
||||
VaultListingMutator,
|
||||
VaultListingInterface,
|
||||
} from '@standardnotes/models'
|
||||
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { GetVaultUseCase } from '../../Vaults/UseCase/GetVault'
|
||||
import { EncryptionProviderInterface } from '@standardnotes/encryption'
|
||||
import { GetVault } from '../../Vaults/UseCase/GetVault'
|
||||
import { EncryptionProviderInterface } from '../../Encryption/EncryptionProviderInterface'
|
||||
|
||||
export class HandleTrustedSharedVaultRootKeyChangedMessage {
|
||||
export class HandleRootKeyChangedMessage {
|
||||
constructor(
|
||||
private mutator: MutatorClientInterface,
|
||||
private items: ItemManagerInterface,
|
||||
private sync: SyncServiceInterface,
|
||||
private encryption: EncryptionProviderInterface,
|
||||
private getVault: GetVault,
|
||||
) {}
|
||||
|
||||
async execute(message: AsymmetricMessageSharedVaultRootKeyChanged): Promise<void> {
|
||||
@@ -30,9 +30,9 @@ export class HandleTrustedSharedVaultRootKeyChangedMessage {
|
||||
true,
|
||||
)
|
||||
|
||||
const vault = new GetVaultUseCase(this.items).execute({ keySystemIdentifier: rootKeyContent.systemIdentifier })
|
||||
if (vault) {
|
||||
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
|
||||
const vault = this.getVault.execute<VaultListingInterface>({ keySystemIdentifier: rootKeyContent.systemIdentifier })
|
||||
if (!vault.isFailed()) {
|
||||
await this.mutator.changeItem<VaultListingMutator>(vault.getValue(), (mutator) => {
|
||||
mutator.rootKeyParams = rootKeyContent.keyParams
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
|
||||
import { HandleTrustedSharedVaultInviteMessage } from './HandleTrustedSharedVaultInviteMessage'
|
||||
import { CreateOrEditContact } from './../../Contacts/UseCase/CreateOrEditContact'
|
||||
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
|
||||
import { ProcessAcceptedVaultInvite } from './ProcessAcceptedVaultInvite'
|
||||
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
|
||||
import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import {
|
||||
AsymmetricMessagePayloadType,
|
||||
@@ -9,32 +9,25 @@ import {
|
||||
KeySystemRootKeyContent,
|
||||
} from '@standardnotes/models'
|
||||
|
||||
describe('HandleTrustedSharedVaultInviteMessage', () => {
|
||||
let mutatorMock: jest.Mocked<MutatorClientInterface>
|
||||
let syncServiceMock: jest.Mocked<SyncServiceInterface>
|
||||
let contactServiceMock: jest.Mocked<ContactServiceInterface>
|
||||
describe('ProcessAcceptedVaultInvite', () => {
|
||||
let mutator: jest.Mocked<MutatorClientInterface>
|
||||
let sync: jest.Mocked<SyncServiceInterface>
|
||||
let createOrEditContact: jest.Mocked<CreateOrEditContact>
|
||||
|
||||
beforeEach(() => {
|
||||
mutatorMock = {
|
||||
createItem: jest.fn(),
|
||||
} as any
|
||||
mutator = {} as jest.Mocked<MutatorClientInterface>
|
||||
mutator.createItem = jest.fn()
|
||||
|
||||
syncServiceMock = {
|
||||
sync: jest.fn(),
|
||||
} as any
|
||||
sync = {} as jest.Mocked<SyncServiceInterface>
|
||||
sync.sync = jest.fn()
|
||||
|
||||
contactServiceMock = {
|
||||
createOrEditTrustedContact: jest.fn(),
|
||||
} as any
|
||||
createOrEditContact = {} as jest.Mocked<CreateOrEditContact>
|
||||
createOrEditContact.execute = jest.fn()
|
||||
})
|
||||
|
||||
it('should create root key before creating vault listing so that propagated vault listings do not appear as locked', async () => {
|
||||
const handleTrustedSharedVaultInviteMessage = new HandleTrustedSharedVaultInviteMessage(
|
||||
mutatorMock,
|
||||
syncServiceMock,
|
||||
contactServiceMock,
|
||||
)
|
||||
|
||||
const handleTrustedSharedVaultInviteMessage = new ProcessAcceptedVaultInvite(mutator, sync, createOrEditContact)
|
||||
createOrEditContact
|
||||
const testMessage = {
|
||||
type: AsymmetricMessagePayloadType.SharedVaultInvite,
|
||||
data: {
|
||||
@@ -54,11 +47,11 @@ describe('HandleTrustedSharedVaultInviteMessage', () => {
|
||||
|
||||
await handleTrustedSharedVaultInviteMessage.execute(testMessage, sharedVaultUuid, senderUuid)
|
||||
|
||||
const keySystemRootKeyCallIndex = mutatorMock.createItem.mock.calls.findIndex(
|
||||
const keySystemRootKeyCallIndex = mutator.createItem.mock.calls.findIndex(
|
||||
([contentType]) => contentType === ContentType.TYPES.KeySystemRootKey,
|
||||
)
|
||||
|
||||
const vaultListingCallIndex = mutatorMock.createItem.mock.calls.findIndex(
|
||||
const vaultListingCallIndex = mutator.createItem.mock.calls.findIndex(
|
||||
([contentType]) => contentType === ContentType.TYPES.VaultListing,
|
||||
)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ContactServiceInterface } from './../../Contacts/ContactServiceInterface'
|
||||
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
|
||||
import {
|
||||
KeySystemRootKeyInterface,
|
||||
@@ -11,12 +10,13 @@ import {
|
||||
} from '@standardnotes/models'
|
||||
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { CreateOrEditContact } from '../../Contacts/UseCase/CreateOrEditContact'
|
||||
|
||||
export class HandleTrustedSharedVaultInviteMessage {
|
||||
export class ProcessAcceptedVaultInvite {
|
||||
constructor(
|
||||
private mutator: MutatorClientInterface,
|
||||
private sync: SyncServiceInterface,
|
||||
private contacts: ContactServiceInterface,
|
||||
private createOrEditContact: CreateOrEditContact,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
@@ -47,11 +47,7 @@ export class HandleTrustedSharedVaultInviteMessage {
|
||||
await this.mutator.createItem(ContentType.TYPES.VaultListing, FillItemContentSpecialized(content), true)
|
||||
|
||||
for (const contact of trustedContacts) {
|
||||
if (contact.isMe) {
|
||||
throw new Error('Should not receive isMe contact from invite')
|
||||
}
|
||||
|
||||
await this.contacts.createOrEditTrustedContact({
|
||||
await this.createOrEditContact.execute({
|
||||
name: contact.name,
|
||||
contactUuid: contact.contactUuid,
|
||||
publicKey: contact.publicKeySet.encryption,
|
||||
@@ -0,0 +1,58 @@
|
||||
import { AsymmetricMessageServerHash, isErrorResponse } from '@standardnotes/responses'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { AsymmetricMessageServerInterface } from '@standardnotes/api'
|
||||
import { ResendMessage } from './ResendMessage'
|
||||
import { FindContact } from '../../Contacts/UseCase/FindContact'
|
||||
|
||||
export class ResendAllMessages implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private resendMessage: ResendMessage,
|
||||
private messageServer: AsymmetricMessageServerInterface,
|
||||
private findContact: FindContact,
|
||||
) {}
|
||||
|
||||
async execute(params: {
|
||||
keys: {
|
||||
encryption: PkcKeyPair
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
previousKeys?: {
|
||||
encryption: PkcKeyPair
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
}): Promise<Result<AsymmetricMessageServerHash[]>> {
|
||||
const messages = await this.messageServer.getOutboundUserMessages()
|
||||
|
||||
if (isErrorResponse(messages)) {
|
||||
return Result.fail('Failed to get outbound user messages')
|
||||
}
|
||||
|
||||
const errors: string[] = []
|
||||
|
||||
for (const message of messages.data.messages) {
|
||||
const recipient = this.findContact.execute({ userUuid: message.user_uuid })
|
||||
if (recipient) {
|
||||
errors.push(`Contact not found for invite ${message.user_uuid}`)
|
||||
continue
|
||||
}
|
||||
|
||||
await this.resendMessage.execute({
|
||||
keys: params.keys,
|
||||
previousKeys: params.previousKeys,
|
||||
message: message,
|
||||
recipient,
|
||||
})
|
||||
|
||||
await this.messageServer.deleteMessage({
|
||||
messageUuid: message.uuid,
|
||||
})
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Result.fail(errors.join(', '))
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { DecryptOwnMessage } from '../../Encryption/UseCase/Asymmetric/DecryptOwnMessage'
|
||||
import { AsymmetricMessagePayload, TrustedContactInterface } from '@standardnotes/models'
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
|
||||
import { SendMessage } from './SendMessage'
|
||||
|
||||
export class ResendMessage implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private decryptOwnMessage: DecryptOwnMessage<AsymmetricMessagePayload>,
|
||||
private sendMessage: SendMessage,
|
||||
private encryptMessage: EncryptMessage,
|
||||
) {}
|
||||
|
||||
async execute(params: {
|
||||
keys: {
|
||||
encryption: PkcKeyPair
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
previousKeys?: {
|
||||
encryption: PkcKeyPair
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
recipient: TrustedContactInterface
|
||||
message: AsymmetricMessageServerHash
|
||||
}): Promise<Result<AsymmetricMessageServerHash>> {
|
||||
const decryptionResult = this.decryptOwnMessage.execute({
|
||||
message: params.message.encrypted_message,
|
||||
privateKey: params.previousKeys?.encryption.privateKey ?? params.keys.encryption.privateKey,
|
||||
recipientPublicKey: params.recipient.publicKeySet.encryption,
|
||||
})
|
||||
|
||||
if (decryptionResult.isFailed()) {
|
||||
return Result.fail(decryptionResult.getError())
|
||||
}
|
||||
|
||||
const decryptedMessage = decryptionResult.getValue()
|
||||
|
||||
const encryptedMessage = this.encryptMessage.execute({
|
||||
message: decryptedMessage,
|
||||
keys: params.keys,
|
||||
recipientPublicKey: params.recipient.publicKeySet.encryption,
|
||||
})
|
||||
|
||||
if (encryptedMessage.isFailed()) {
|
||||
return Result.fail(encryptedMessage.getError())
|
||||
}
|
||||
|
||||
const sendMessageResult = await this.sendMessage.execute({
|
||||
recipientUuid: params.recipient.contactUuid,
|
||||
encryptedMessage: encryptedMessage.getValue(),
|
||||
replaceabilityIdentifier: params.message.replaceabilityIdentifier,
|
||||
})
|
||||
|
||||
return sendMessageResult
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
import { ClientDisplayableError, isErrorResponse, AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { isErrorResponse, AsymmetricMessageServerHash, getErrorFromErrorResponse } from '@standardnotes/responses'
|
||||
import { AsymmetricMessageServerInterface } from '@standardnotes/api'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
export class SendAsymmetricMessageUseCase {
|
||||
export class SendMessage implements UseCaseInterface<AsymmetricMessageServerHash> {
|
||||
constructor(private messageServer: AsymmetricMessageServerInterface) {}
|
||||
|
||||
async execute(params: {
|
||||
recipientUuid: string
|
||||
encryptedMessage: string
|
||||
replaceabilityIdentifier: string | undefined
|
||||
}): Promise<AsymmetricMessageServerHash | ClientDisplayableError> {
|
||||
}): Promise<Result<AsymmetricMessageServerHash>> {
|
||||
const response = await this.messageServer.createMessage({
|
||||
recipientUuid: params.recipientUuid,
|
||||
encryptedMessage: params.encryptedMessage,
|
||||
@@ -16,9 +17,9 @@ export class SendAsymmetricMessageUseCase {
|
||||
})
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromNetworkError(response)
|
||||
return Result.fail(getErrorFromErrorResponse(response).message)
|
||||
}
|
||||
|
||||
return response.data.message
|
||||
return Result.ok(response.data.message)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
import { EncryptionProviderInterface } from '@standardnotes/encryption'
|
||||
import { AsymmetricMessageServerHash, ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import {
|
||||
TrustedContactInterface,
|
||||
AsymmetricMessagePayloadType,
|
||||
AsymmetricMessageSenderKeypairChanged,
|
||||
} from '@standardnotes/models'
|
||||
import { AsymmetricMessageServer } from '@standardnotes/api'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { SendAsymmetricMessageUseCase } from './SendAsymmetricMessageUseCase'
|
||||
import { SendMessage } from './SendMessage'
|
||||
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
export class SendOwnContactChangeMessage {
|
||||
constructor(private encryption: EncryptionProviderInterface, private messageServer: AsymmetricMessageServer) {}
|
||||
export class SendOwnContactChangeMessage implements UseCaseInterface<AsymmetricMessageServerHash> {
|
||||
constructor(private encryptMessage: EncryptMessage, private sendMessage: SendMessage) {}
|
||||
|
||||
async execute(params: {
|
||||
senderOldKeyPair: PkcKeyPair
|
||||
@@ -18,7 +18,7 @@ export class SendOwnContactChangeMessage {
|
||||
senderNewKeyPair: PkcKeyPair
|
||||
senderNewSigningKeyPair: PkcKeyPair
|
||||
contact: TrustedContactInterface
|
||||
}): Promise<AsymmetricMessageServerHash | ClientDisplayableError> {
|
||||
}): Promise<Result<AsymmetricMessageServerHash>> {
|
||||
const message: AsymmetricMessageSenderKeypairChanged = {
|
||||
type: AsymmetricMessagePayloadType.SenderKeypairChanged,
|
||||
data: {
|
||||
@@ -28,17 +28,22 @@ export class SendOwnContactChangeMessage {
|
||||
},
|
||||
}
|
||||
|
||||
const encryptedMessage = this.encryption.asymmetricallyEncryptMessage({
|
||||
const encryptedMessage = this.encryptMessage.execute({
|
||||
message: message,
|
||||
senderKeyPair: params.senderOldKeyPair,
|
||||
senderSigningKeyPair: params.senderOldSigningKeyPair,
|
||||
keys: {
|
||||
encryption: params.senderOldKeyPair,
|
||||
signing: params.senderOldSigningKeyPair,
|
||||
},
|
||||
recipientPublicKey: params.contact.publicKeySet.encryption,
|
||||
})
|
||||
|
||||
const sendMessageUseCase = new SendAsymmetricMessageUseCase(this.messageServer)
|
||||
const sendMessageResult = await sendMessageUseCase.execute({
|
||||
if (encryptedMessage.isFailed()) {
|
||||
return Result.fail(encryptedMessage.getError())
|
||||
}
|
||||
|
||||
const sendMessageResult = await this.sendMessage.execute({
|
||||
recipientUuid: params.contact.contactUuid,
|
||||
encryptedMessage,
|
||||
encryptedMessage: encryptedMessage.getValue(),
|
||||
replaceabilityIdentifier: undefined,
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user