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

@@ -0,0 +1,109 @@
import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface'
import { GetVaultUsers } from './UseCase/GetVaultUsers'
import { RemoveVaultMember } from './UseCase/RemoveSharedVaultMember'
import { DeleteSharedVault } from './UseCase/DeleteSharedVault'
import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault'
import { ShareContactWithVault } from './UseCase/ShareContactWithVault'
import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault'
import { LeaveVault } from './UseCase/LeaveSharedVault'
import { InviteToVault } from './UseCase/InviteToVault'
import { AcceptVaultInvite } from './UseCase/AcceptVaultInvite'
import { GetVaultContacts } from './UseCase/GetVaultContacts'
import { GetAllContacts } from './../Contacts/UseCase/GetAllContacts'
import { FindContact } from './../Contacts/UseCase/FindContact'
import { GetUntrustedPayload } from './../AsymmetricMessage/UseCase/GetUntrustedPayload'
import { GetTrustedPayload } from './../AsymmetricMessage/UseCase/GetTrustedPayload'
import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessage'
import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation'
import { HandleKeyPairChange } from './../Contacts/UseCase/HandleKeyPairChange'
import { CreateSharedVault } from './UseCase/CreateSharedVault'
import { GetVault } from './../Vaults/UseCase/GetVault'
import { SharedVaultInvitesServer } from '@standardnotes/api'
import { SharedVaultService } from './SharedVaultService'
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
import { VaultServiceInterface } from '../Vaults/VaultServiceInterface'
import { InternalEventBusInterface } from '../..'
import { ContactPublicKeySetInterface, TrustedContactInterface } from '@standardnotes/models'
describe('SharedVaultService', () => {
let service: SharedVaultService
beforeEach(() => {
const sync = {} as jest.Mocked<SyncServiceInterface>
sync.addEventObserver = jest.fn()
const items = {} as jest.Mocked<ItemManagerInterface>
items.addObserver = jest.fn()
const encryption = {} as jest.Mocked<EncryptionProviderInterface>
const session = {} as jest.Mocked<SessionsClientInterface>
const vaults = {} as jest.Mocked<VaultServiceInterface>
const invitesServer = {} as jest.Mocked<SharedVaultInvitesServer>
const getVault = {} as jest.Mocked<GetVault>
const createSharedVaultUseCase = {} as jest.Mocked<CreateSharedVault>
const handleKeyPairChange = {} as jest.Mocked<HandleKeyPairChange>
const notifyVaultUsersOfKeyRotation = {} as jest.Mocked<NotifyVaultUsersOfKeyRotation>
const sendVaultDataChangeMessage = {} as jest.Mocked<SendVaultDataChangedMessage>
const getTrustedPayload = {} as jest.Mocked<GetTrustedPayload>
const getUntrustedPayload = {} as jest.Mocked<GetUntrustedPayload>
const findContact = {} as jest.Mocked<FindContact>
const getAllContacts = {} as jest.Mocked<GetAllContacts>
const getVaultContacts = {} as jest.Mocked<GetVaultContacts>
const acceptVaultInvite = {} as jest.Mocked<AcceptVaultInvite>
const inviteToVault = {} as jest.Mocked<InviteToVault>
const leaveVault = {} as jest.Mocked<LeaveVault>
const deleteThirdPartyVault = {} as jest.Mocked<DeleteThirdPartyVault>
const shareContactWithVault = {} as jest.Mocked<ShareContactWithVault>
const convertToSharedVault = {} as jest.Mocked<ConvertToSharedVault>
const deleteSharedVaultUseCase = {} as jest.Mocked<DeleteSharedVault>
const removeVaultMember = {} as jest.Mocked<RemoveVaultMember>
const getSharedVaultUsersUseCase = {} as jest.Mocked<GetVaultUsers>
const eventBus = {} as jest.Mocked<InternalEventBusInterface>
eventBus.addEventHandler = jest.fn()
service = new SharedVaultService(
sync,
items,
encryption,
session,
vaults,
invitesServer,
getVault,
createSharedVaultUseCase,
handleKeyPairChange,
notifyVaultUsersOfKeyRotation,
sendVaultDataChangeMessage,
getTrustedPayload,
getUntrustedPayload,
findContact,
getAllContacts,
getVaultContacts,
acceptVaultInvite,
inviteToVault,
leaveVault,
deleteThirdPartyVault,
shareContactWithVault,
convertToSharedVault,
deleteSharedVaultUseCase,
removeVaultMember,
getSharedVaultUsersUseCase,
eventBus,
)
})
describe('shareContactWithVaults', () => {
it('should throw if attempting to share self contact', async () => {
const contact = {
name: 'Other',
contactUuid: '456',
publicKeySet: {} as ContactPublicKeySetInterface,
isMe: true,
} as jest.Mocked<TrustedContactInterface>
await expect(service.shareContactWithVaults(contact)).rejects.toThrow('Cannot share self contact')
})
})
})

View File

@@ -1,6 +1,5 @@
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
import { StorageServiceInterface } from './../Storage/StorageServiceInterface'
import { InviteContactToSharedVaultUseCase } from './UseCase/InviteContactToSharedVault'
import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData'
import { InviteToVault } from './UseCase/InviteToVault'
import {
ClientDisplayableError,
SharedVaultInviteServerHash,
@@ -10,17 +9,7 @@ import {
SharedVaultPermission,
UserEventType,
} from '@standardnotes/responses'
import {
HttpServiceInterface,
SharedVaultServerInterface,
SharedVaultUsersServerInterface,
SharedVaultInvitesServerInterface,
SharedVaultUsersServer,
SharedVaultInvitesServer,
SharedVaultServer,
AsymmetricMessageServerInterface,
AsymmetricMessageServer,
} from '@standardnotes/api'
import { SharedVaultInvitesServer } from '@standardnotes/api'
import {
DecryptedItemInterface,
PayloadEmitSource,
@@ -32,61 +21,72 @@ import {
} from '@standardnotes/models'
import { SharedVaultServiceInterface } from './SharedVaultServiceInterface'
import { SharedVaultServiceEvent, SharedVaultServiceEventPayload } from './SharedVaultServiceEvent'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { GetSharedVaultUsersUseCase } from './UseCase/GetSharedVaultUsers'
import { RemoveVaultMemberUseCase } from './UseCase/RemoveSharedVaultMember'
import { GetVaultUsers } from './UseCase/GetVaultUsers'
import { RemoveVaultMember } from './UseCase/RemoveSharedVaultMember'
import { AbstractService } from '../Service/AbstractService'
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
import { ContactServiceInterface } from '../Contacts/ContactServiceInterface'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { SyncEvent, SyncEventReceivedSharedVaultInvitesData } from '../Event/SyncEvent'
import { SessionEvent } from '../Session/SessionEvent'
import { InternalEventInterface } from '../Internal/InternalEventInterface'
import { FilesClientInterface } from '@standardnotes/files'
import { LeaveVaultUseCase } from './UseCase/LeaveSharedVault'
import { LeaveVault } from './UseCase/LeaveSharedVault'
import { VaultServiceInterface } from '../Vaults/VaultServiceInterface'
import { UserEventServiceEvent, UserEventServiceEventPayload } from '../UserEvent/UserEventServiceEvent'
import { DeleteExternalSharedVaultUseCase } from './UseCase/DeleteExternalSharedVault'
import { DeleteSharedVaultUseCase } from './UseCase/DeleteSharedVault'
import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault'
import { DeleteSharedVault } from './UseCase/DeleteSharedVault'
import { VaultServiceEvent, VaultServiceEventPayload } from '../Vaults/VaultServiceEvent'
import { AcceptTrustedSharedVaultInvite } from './UseCase/AcceptTrustedSharedVaultInvite'
import { GetAsymmetricMessageTrustedPayload } from '../AsymmetricMessage/UseCase/GetAsymmetricMessageTrustedPayload'
import { AcceptVaultInvite } from './UseCase/AcceptVaultInvite'
import { GetTrustedPayload } from '../AsymmetricMessage/UseCase/GetTrustedPayload'
import { PendingSharedVaultInviteRecord } from './PendingSharedVaultInviteRecord'
import { GetAsymmetricMessageUntrustedPayload } from '../AsymmetricMessage/UseCase/GetAsymmetricMessageUntrustedPayload'
import { ShareContactWithAllMembersOfSharedVaultUseCase } from './UseCase/ShareContactWithAllMembersOfSharedVault'
import { GetSharedVaultTrustedContacts } from './UseCase/GetSharedVaultTrustedContacts'
import { NotifySharedVaultUsersOfRootKeyRotationUseCase } from './UseCase/NotifySharedVaultUsersOfRootKeyRotation'
import { CreateSharedVaultUseCase } from './UseCase/CreateSharedVault'
import { SendSharedVaultMetadataChangedMessageToAll } from './UseCase/SendSharedVaultMetadataChangedMessageToAll'
import { ConvertToSharedVaultUseCase } from './UseCase/ConvertToSharedVault'
import { GetVaultUseCase } from '../Vaults/UseCase/GetVault'
import { ContentType } from '@standardnotes/domain-core'
import { GetUntrustedPayload } from '../AsymmetricMessage/UseCase/GetUntrustedPayload'
import { ShareContactWithVault } from './UseCase/ShareContactWithVault'
import { GetVaultContacts } from './UseCase/GetVaultContacts'
import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation'
import { CreateSharedVault } from './UseCase/CreateSharedVault'
import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessage'
import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault'
import { GetVault } from '../Vaults/UseCase/GetVault'
import { ContentType, Result } from '@standardnotes/domain-core'
import { HandleKeyPairChange } from '../Contacts/UseCase/HandleKeyPairChange'
import { FindContact } from '../Contacts/UseCase/FindContact'
import { GetAllContacts } from '../Contacts/UseCase/GetAllContacts'
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
export class SharedVaultService
extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload>
implements SharedVaultServiceInterface, InternalEventHandlerInterface
{
private server: SharedVaultServerInterface
private usersServer: SharedVaultUsersServerInterface
private invitesServer: SharedVaultInvitesServerInterface
private messageServer: AsymmetricMessageServerInterface
private pendingInvites: Record<string, PendingSharedVaultInviteRecord> = {}
constructor(
http: HttpServiceInterface,
private sync: SyncServiceInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private encryption: EncryptionProviderInterface,
private session: SessionsClientInterface,
private contacts: ContactServiceInterface,
private files: FilesClientInterface,
private vaults: VaultServiceInterface,
private storage: StorageServiceInterface,
private invitesServer: SharedVaultInvitesServer,
private getVault: GetVault,
private createSharedVaultUseCase: CreateSharedVault,
private handleKeyPairChange: HandleKeyPairChange,
private notifyVaultUsersOfKeyRotation: NotifyVaultUsersOfKeyRotation,
private sendVaultDataChangeMessage: SendVaultDataChangedMessage,
private getTrustedPayload: GetTrustedPayload,
private getUntrustedPayload: GetUntrustedPayload,
private findContact: FindContact,
private getAllContacts: GetAllContacts,
private getVaultContacts: GetVaultContacts,
private acceptVaultInvite: AcceptVaultInvite,
private inviteToVault: InviteToVault,
private leaveVault: LeaveVault,
private deleteThirdPartyVault: DeleteThirdPartyVault,
private shareContactWithVault: ShareContactWithVault,
private convertToSharedVault: ConvertToSharedVault,
private deleteSharedVaultUseCase: DeleteSharedVault,
private removeVaultMember: RemoveVaultMember,
private getSharedVaultUsersUseCase: GetVaultUsers,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
@@ -95,21 +95,6 @@ export class SharedVaultService
eventBus.addEventHandler(this, UserEventServiceEvent.UserEventReceived)
eventBus.addEventHandler(this, VaultServiceEvent.VaultRootKeyRotated)
this.server = new SharedVaultServer(http)
this.usersServer = new SharedVaultUsersServer(http)
this.invitesServer = new SharedVaultInvitesServer(http)
this.messageServer = new AsymmetricMessageServer(http)
this.eventDisposers.push(
sync.addEventObserver(async (event, data) => {
if (event === SyncEvent.ReceivedSharedVaultInvites) {
void this.processInboundInvites(data as SyncEventReceivedSharedVaultInvitesData)
} else if (event === SyncEvent.ReceivedRemoteSharedVaults) {
void this.notifyCollaborationStatusChanged()
}
}),
)
this.eventDisposers.push(
items.addObserver<TrustedContactInterface>(
ContentType.TYPES.TrustedContact,
@@ -138,26 +123,32 @@ export class SharedVaultService
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === SessionEvent.UserKeyPairChanged) {
void this.invitesServer.deleteAllInboundInvites()
const eventData = event.payload as UserKeyPairChangedEventData
void this.handleKeyPairChange.execute({
newKeys: eventData.current,
previousKeys: eventData.previous,
})
} else if (event.type === UserEventServiceEvent.UserEventReceived) {
await this.handleUserEvent(event.payload as UserEventServiceEventPayload)
} else if (event.type === VaultServiceEvent.VaultRootKeyRotated) {
const payload = event.payload as VaultServiceEventPayload[VaultServiceEvent.VaultRootKeyRotated]
await this.handleVaultRootKeyRotatedEvent(payload.vault)
} else if (event.type === SyncEvent.ReceivedSharedVaultInvites) {
await this.processInboundInvites(event.payload as SyncEventReceivedSharedVaultInvitesData)
} else if (event.type === SyncEvent.ReceivedRemoteSharedVaults) {
void this.notifyCollaborationStatusChanged()
}
}
private async handleUserEvent(event: UserEventServiceEventPayload): Promise<void> {
if (event.eventPayload.eventType === UserEventType.RemovedFromSharedVault) {
const vault = new GetVaultUseCase(this.items).execute({ sharedVaultUuid: event.eventPayload.sharedVaultUuid })
if (vault) {
const useCase = new DeleteExternalSharedVaultUseCase(
this.items,
this.mutator,
this.encryption,
this.storage,
this.sync,
)
await useCase.execute(vault)
const vault = this.getVault.execute<SharedVaultListingInterface>({
sharedVaultUuid: event.eventPayload.sharedVaultUuid,
})
if (!vault.isFailed()) {
await this.deleteThirdPartyVault.execute(vault.getValue())
}
} else if (event.eventPayload.eventType === UserEventType.SharedVaultItemRemoved) {
const item = this.items.findItem(event.eventPayload.itemUuid)
@@ -176,15 +167,14 @@ export class SharedVaultService
return
}
const usecase = new NotifySharedVaultUsersOfRootKeyRotationUseCase(
this.usersServer,
this.invitesServer,
this.messageServer,
this.encryption,
this.contacts,
)
await usecase.execute({ sharedVault: vault, userUuid: this.session.getSureUser().uuid })
await this.notifyVaultUsersOfKeyRotation.execute({
sharedVault: vault,
senderUuid: this.session.getSureUser().uuid,
keys: {
encryption: this.encryption.getKeyPair(),
signing: this.encryption.getSigningKeyPair(),
},
})
}
async createSharedVault(dto: {
@@ -193,16 +183,7 @@ export class SharedVaultService
userInputtedPassword: string | undefined
storagePreference?: KeySystemRootKeyStorageMode
}): Promise<VaultListingInterface | ClientDisplayableError> {
const usecase = new CreateSharedVaultUseCase(
this.encryption,
this.items,
this.mutator,
this.sync,
this.files,
this.server,
)
return usecase.execute({
return this.createSharedVaultUseCase.execute({
vaultName: dto.name,
vaultDescription: dto.description,
userInputtedPassword: dto.userInputtedPassword,
@@ -213,9 +194,7 @@ export class SharedVaultService
async convertVaultToSharedVault(
vault: VaultListingInterface,
): Promise<SharedVaultListingInterface | ClientDisplayableError> {
const usecase = new ConvertToSharedVaultUseCase(this.items, this.mutator, this.sync, this.files, this.server)
return usecase.execute({ vault })
return this.convertToSharedVault.execute({ vault })
}
public getCachedPendingInviteRecords(): PendingSharedVaultInviteRecord[] {
@@ -228,7 +207,12 @@ export class SharedVaultService
}
private findSharedVault(sharedVaultUuid: string): SharedVaultListingInterface | undefined {
return this.getAllSharedVaults().find((vault) => vault.sharing.sharedVaultUuid === sharedVaultUuid)
const result = this.getVault.execute<SharedVaultListingInterface>({ sharedVaultUuid })
if (result.isFailed()) {
return undefined
}
return result.getValue()
}
public isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean {
@@ -256,7 +240,11 @@ export class SharedVaultService
private async handleTrustedContactsChange(contacts: TrustedContactInterface[]): Promise<void> {
for (const contact of contacts) {
await this.shareContactWithUserAdministeredSharedVaults(contact)
if (contact.isMe) {
continue
}
await this.shareContactWithVaults(contact)
}
}
@@ -266,18 +254,13 @@ export class SharedVaultService
continue
}
const usecase = new SendSharedVaultMetadataChangedMessageToAll(
this.encryption,
this.contacts,
this.usersServer,
this.messageServer,
)
await usecase.execute({
await this.sendVaultDataChangeMessage.execute({
vault,
senderUuid: this.session.getSureUser().uuid,
senderEncryptionKeyPair: this.encryption.getKeyPair(),
senderSigningKeyPair: this.encryption.getSigningKeyPair(),
keys: {
encryption: this.encryption.getKeyPair(),
signing: this.encryption.getSigningKeyPair(),
},
})
}
}
@@ -326,8 +309,7 @@ export class SharedVaultService
}
public async deleteSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> {
const useCase = new DeleteSharedVaultUseCase(this.server, this.items, this.mutator, this.sync, this.encryption)
return useCase.execute({ sharedVault })
return this.deleteSharedVaultUseCase.execute({ sharedVault })
}
private async reprocessCachedInvitesTrustStatusAfterTrustedContactsChange(): Promise<void> {
@@ -342,39 +324,34 @@ export class SharedVaultService
}
for (const invite of invites) {
const trustedMessageUseCase = new GetAsymmetricMessageTrustedPayload<AsymmetricMessageSharedVaultInvite>(
this.encryption,
this.contacts,
)
const sender = this.findContact.execute({ userUuid: invite.sender_uuid })
if (!sender.isFailed()) {
const trustedMessage = this.getTrustedPayload.execute<AsymmetricMessageSharedVaultInvite>({
message: invite,
privateKey: this.encryption.getKeyPair().privateKey,
sender: sender.getValue(),
})
const trustedMessage = trustedMessageUseCase.execute({
message: invite,
privateKey: this.encryption.getKeyPair().privateKey,
})
if (!trustedMessage.isFailed()) {
this.pendingInvites[invite.uuid] = {
invite,
message: trustedMessage.getValue(),
trusted: true,
}
if (trustedMessage) {
this.pendingInvites[invite.uuid] = {
invite,
message: trustedMessage,
trusted: true,
continue
}
continue
}
const untrustedMessageUseCase = new GetAsymmetricMessageUntrustedPayload<AsymmetricMessageSharedVaultInvite>(
this.encryption,
)
const untrustedMessage = untrustedMessageUseCase.execute({
const untrustedMessage = this.getUntrustedPayload.execute<AsymmetricMessageSharedVaultInvite>({
message: invite,
privateKey: this.encryption.getKeyPair().privateKey,
})
if (untrustedMessage) {
if (!untrustedMessage.isFailed()) {
this.pendingInvites[invite.uuid] = {
invite,
message: untrustedMessage,
message: untrustedMessage.getValue(),
trusted: false,
}
}
@@ -392,8 +369,7 @@ export class SharedVaultService
throw new Error('Cannot accept untrusted invite')
}
const useCase = new AcceptTrustedSharedVaultInvite(this.invitesServer, this.mutator, this.sync, this.contacts)
await useCase.execute({ invite: pendingInvite.invite, message: pendingInvite.message })
await this.acceptVaultInvite.execute({ invite: pendingInvite.invite, message: pendingInvite.message })
delete this.pendingInvites[pendingInvite.invite.uuid]
@@ -416,35 +392,38 @@ export class SharedVaultService
return []
}
const contacts = this.contacts.getAllContacts()
return contacts.filter((contact) => {
const contacts = this.getAllContacts.execute()
if (contacts.isFailed()) {
return []
}
return contacts.getValue().filter((contact) => {
const isContactAlreadyInVault = users.some((user) => user.user_uuid === contact.contactUuid)
return !isContactAlreadyInVault
})
}
private async getSharedVaultContacts(sharedVault: SharedVaultListingInterface): Promise<TrustedContactInterface[]> {
const usecase = new GetSharedVaultTrustedContacts(this.contacts, this.usersServer)
const contacts = await usecase.execute(sharedVault)
if (!contacts) {
const contacts = await this.getVaultContacts.execute(sharedVault.sharing.sharedVaultUuid)
if (contacts.isFailed()) {
return []
}
return contacts
return contacts.getValue()
}
async inviteContactToSharedVault(
sharedVault: SharedVaultListingInterface,
contact: TrustedContactInterface,
permissions: SharedVaultPermission,
): Promise<SharedVaultInviteServerHash | ClientDisplayableError> {
): Promise<Result<SharedVaultInviteServerHash>> {
const sharedVaultContacts = await this.getSharedVaultContacts(sharedVault)
const useCase = new InviteContactToSharedVaultUseCase(this.encryption, this.invitesServer)
const result = await useCase.execute({
senderKeyPair: this.encryption.getKeyPair(),
senderSigningKeyPair: this.encryption.getSigningKeyPair(),
const result = await this.inviteToVault.execute({
keys: {
encryption: this.encryption.getKeyPair(),
signing: this.encryption.getSigningKeyPair(),
},
senderUuid: this.session.getSureUser().uuid,
sharedVault,
recipient: contact,
sharedVaultContacts,
@@ -470,8 +449,10 @@ export class SharedVaultService
throw new Error('Cannot remove user from locked vault')
}
const useCase = new RemoveVaultMemberUseCase(this.usersServer)
const result = await useCase.execute({ sharedVaultUuid: sharedVault.sharing.sharedVaultUuid, userUuid })
const result = await this.removeVaultMember.execute({
sharedVaultUuid: sharedVault.sharing.sharedVaultUuid,
userUuid,
})
if (isClientDisplayableError(result)) {
return result
}
@@ -482,15 +463,7 @@ export class SharedVaultService
}
async leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> {
const useCase = new LeaveVaultUseCase(
this.usersServer,
this.items,
this.mutator,
this.encryption,
this.storage,
this.sync,
)
const result = await useCase.execute({
const result = await this.leaveVault.execute({
sharedVault: sharedVault,
userUuid: this.session.getSureUser().uuid,
})
@@ -505,28 +478,22 @@ export class SharedVaultService
async getSharedVaultUsers(
sharedVault: SharedVaultListingInterface,
): Promise<SharedVaultUserServerHash[] | undefined> {
const useCase = new GetSharedVaultUsersUseCase(this.usersServer)
return useCase.execute({ sharedVaultUuid: sharedVault.sharing.sharedVaultUuid })
return this.getSharedVaultUsersUseCase.execute({ sharedVaultUuid: sharedVault.sharing.sharedVaultUuid })
}
private async shareContactWithUserAdministeredSharedVaults(contact: TrustedContactInterface): Promise<void> {
const sharedVaults = this.getAllSharedVaults()
async shareContactWithVaults(contact: TrustedContactInterface): Promise<void> {
if (contact.isMe) {
throw new Error('Cannot share self contact')
}
const useCase = new ShareContactWithAllMembersOfSharedVaultUseCase(
this.contacts,
this.encryption,
this.usersServer,
this.messageServer,
)
const ownedVaults = this.getAllSharedVaults().filter(this.isCurrentUserSharedVaultAdmin.bind(this))
for (const vault of sharedVaults) {
if (!this.isCurrentUserSharedVaultAdmin(vault)) {
continue
}
await useCase.execute({
senderKeyPair: this.encryption.getKeyPair(),
senderSigningKeyPair: this.encryption.getSigningKeyPair(),
for (const vault of ownedVaults) {
await this.shareContactWithVault.execute({
keys: {
encryption: this.encryption.getKeyPair(),
signing: this.encryption.getSigningKeyPair(),
},
sharedVault: vault,
contactToShare: contact,
senderUserUuid: this.session.getSureUser().uuid,
@@ -539,9 +506,9 @@ export class SharedVaultService
return undefined
}
const contact = this.contacts.findTrustedContact(item.last_edited_by_uuid)
const contact = this.findContact.execute({ userUuid: item.last_edited_by_uuid })
return contact
return contact.isFailed() ? undefined : contact.getValue()
}
getItemSharedBy(item: DecryptedItemInterface): TrustedContactInterface | undefined {
@@ -549,23 +516,37 @@ export class SharedVaultService
return undefined
}
const contact = this.contacts.findTrustedContact(item.user_uuid)
const contact = this.findContact.execute({ userUuid: item.user_uuid })
return contact
return contact.isFailed() ? undefined : contact.getValue()
}
override deinit(): void {
super.deinit()
;(this.contacts as unknown) = undefined
;(this.encryption as unknown) = undefined
;(this.files as unknown) = undefined
;(this.invitesServer as unknown) = undefined
;(this.items as unknown) = undefined
;(this.messageServer as unknown) = undefined
;(this.server as unknown) = undefined
;(this.session as unknown) = undefined
;(this.sync as unknown) = undefined
;(this.usersServer as unknown) = undefined
;(this.items as unknown) = undefined
;(this.encryption as unknown) = undefined
;(this.session as unknown) = undefined
;(this.vaults as unknown) = undefined
;(this.invitesServer as unknown) = undefined
;(this.getVault as unknown) = undefined
;(this.createSharedVaultUseCase as unknown) = undefined
;(this.handleKeyPairChange as unknown) = undefined
;(this.notifyVaultUsersOfKeyRotation as unknown) = undefined
;(this.sendVaultDataChangeMessage as unknown) = undefined
;(this.getTrustedPayload as unknown) = undefined
;(this.getUntrustedPayload as unknown) = undefined
;(this.findContact as unknown) = undefined
;(this.getAllContacts as unknown) = undefined
;(this.getVaultContacts as unknown) = undefined
;(this.acceptVaultInvite as unknown) = undefined
;(this.inviteToVault as unknown) = undefined
;(this.leaveVault as unknown) = undefined
;(this.deleteThirdPartyVault as unknown) = undefined
;(this.shareContactWithVault as unknown) = undefined
;(this.convertToSharedVault as unknown) = undefined
;(this.deleteSharedVaultUseCase as unknown) = undefined
;(this.removeVaultMember as unknown) = undefined
;(this.getSharedVaultUsersUseCase as unknown) = undefined
}
}

View File

@@ -14,6 +14,7 @@ import {
import { AbstractService } from '../Service/AbstractService'
import { SharedVaultServiceEvent, SharedVaultServiceEventPayload } from './SharedVaultServiceEvent'
import { PendingSharedVaultInviteRecord } from './PendingSharedVaultInviteRecord'
import { Result } from '@standardnotes/domain-core'
export interface SharedVaultServiceInterface
extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload> {
@@ -31,7 +32,7 @@ export interface SharedVaultServiceInterface
sharedVault: SharedVaultListingInterface,
contact: TrustedContactInterface,
permissions: SharedVaultPermission,
): Promise<SharedVaultInviteServerHash | ClientDisplayableError>
): Promise<Result<SharedVaultInviteServerHash>>
removeUserFromSharedVault(
sharedVault: SharedVaultListingInterface,
userUuid: string,

View File

@@ -1,29 +0,0 @@
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
import { AsymmetricMessageSharedVaultInvite } from '@standardnotes/models'
import { SharedVaultInvitesServerInterface } from '@standardnotes/api'
import { SharedVaultInviteServerHash } from '@standardnotes/responses'
import { HandleTrustedSharedVaultInviteMessage } from '../../AsymmetricMessage/UseCase/HandleTrustedSharedVaultInviteMessage'
import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface'
export class AcceptTrustedSharedVaultInvite {
constructor(
private vaultInvitesServer: SharedVaultInvitesServerInterface,
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private contacts: ContactServiceInterface,
) {}
async execute(dto: {
invite: SharedVaultInviteServerHash
message: AsymmetricMessageSharedVaultInvite
}): Promise<void> {
const useCase = new HandleTrustedSharedVaultInviteMessage(this.mutator, this.sync, this.contacts)
await useCase.execute(dto.message, dto.invite.shared_vault_uuid, dto.invite.sender_uuid)
await this.vaultInvitesServer.acceptInvite({
sharedVaultUuid: dto.invite.shared_vault_uuid,
inviteUuid: dto.invite.uuid,
})
}
}

View File

@@ -0,0 +1,23 @@
import { AsymmetricMessageSharedVaultInvite } from '@standardnotes/models'
import { SharedVaultInvitesServerInterface } from '@standardnotes/api'
import { SharedVaultInviteServerHash } from '@standardnotes/responses'
import { ProcessAcceptedVaultInvite } from '../../AsymmetricMessage/UseCase/ProcessAcceptedVaultInvite'
export class AcceptVaultInvite {
constructor(
private inviteServer: SharedVaultInvitesServerInterface,
private processInvite: ProcessAcceptedVaultInvite,
) {}
async execute(dto: {
invite: SharedVaultInviteServerHash
message: AsymmetricMessageSharedVaultInvite
}): Promise<void> {
await this.processInvite.execute(dto.message, dto.invite.shared_vault_uuid, dto.invite.sender_uuid)
await this.inviteServer.acceptInvite({
sharedVaultUuid: dto.invite.shared_vault_uuid,
inviteUuid: dto.invite.uuid,
})
}
}

View File

@@ -1,19 +1,16 @@
import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
import { SharedVaultListingInterface, VaultListingInterface, VaultListingMutator } from '@standardnotes/models'
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultServerInterface } from '@standardnotes/api'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { MoveItemsToVaultUseCase } from '../../Vaults/UseCase/MoveItemsToVault'
import { FilesClientInterface } from '@standardnotes/files'
import { MoveItemsToVault } from '../../Vaults/UseCase/MoveItemsToVault'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
export class ConvertToSharedVaultUseCase {
export class ConvertToSharedVault {
constructor(
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private files: FilesClientInterface,
private sharedVaultServer: SharedVaultServerInterface,
private moveItemsToVault: MoveItemsToVault,
) {}
async execute(dto: { vault: VaultListingInterface }): Promise<SharedVaultListingInterface | ClientDisplayableError> {
@@ -39,8 +36,8 @@ export class ConvertToSharedVaultUseCase {
)
const vaultItems = this.items.itemsBelongingToKeySystem(sharedVaultListing.systemIdentifier)
const moveToVaultUsecase = new MoveItemsToVaultUseCase(this.mutator, this.sync, this.files)
await moveToVaultUsecase.execute({ vault: sharedVaultListing, items: vaultItems })
await this.moveItemsToVault.execute({ vault: sharedVaultListing, items: vaultItems })
return sharedVaultListing as SharedVaultListingInterface
}

View File

@@ -1,4 +1,3 @@
import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
import {
KeySystemRootKeyStorageMode,
SharedVaultListingInterface,
@@ -6,22 +5,19 @@ import {
VaultListingMutator,
} from '@standardnotes/models'
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { SharedVaultServerInterface } from '@standardnotes/api'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { CreateVaultUseCase } from '../../Vaults/UseCase/CreateVault'
import { MoveItemsToVaultUseCase } from '../../Vaults/UseCase/MoveItemsToVault'
import { FilesClientInterface } from '@standardnotes/files'
import { CreateVault } from '../../Vaults/UseCase/CreateVault'
import { MoveItemsToVault } from '../../Vaults/UseCase/MoveItemsToVault'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
export class CreateSharedVaultUseCase {
export class CreateSharedVault {
constructor(
private encryption: EncryptionProviderInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private files: FilesClientInterface,
private sharedVaultServer: SharedVaultServerInterface,
private createVault: CreateVault,
private moveItemsToVault: MoveItemsToVault,
) {}
async execute(dto: {
@@ -30,8 +26,7 @@ export class CreateSharedVaultUseCase {
userInputtedPassword: string | undefined
storagePreference: KeySystemRootKeyStorageMode
}): Promise<SharedVaultListingInterface | ClientDisplayableError> {
const usecase = new CreateVaultUseCase(this.mutator, this.encryption, this.sync)
const privateVault = await usecase.execute({
const privateVault = await this.createVault.execute({
vaultName: dto.vaultName,
vaultDescription: dto.vaultDescription,
userInputtedPassword: dto.userInputtedPassword,
@@ -56,8 +51,8 @@ export class CreateSharedVaultUseCase {
)
const vaultItems = this.items.itemsBelongingToKeySystem(sharedVaultListing.systemIdentifier)
const moveToVaultUsecase = new MoveItemsToVaultUseCase(this.mutator, this.sync, this.files)
await moveToVaultUsecase.execute({ vault: sharedVaultListing, items: vaultItems })
await this.moveItemsToVault.execute({ vault: sharedVaultListing, items: vaultItems })
return sharedVaultListing as SharedVaultListingInterface
}

View File

@@ -1,26 +1,23 @@
import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
import { StorageServiceInterface } from '../../Storage/StorageServiceInterface'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { AnyItemInterface, VaultListingInterface } from '@standardnotes/models'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
import { RemoveItemsLocallyUseCase } from '../../UseCase/RemoveItemsLocally'
export class DeleteExternalSharedVaultUseCase {
private removeItemsLocallyUsecase = new RemoveItemsLocallyUseCase(this.items, this.storage)
import { RemoveItemsLocally } from '../../UseCase/RemoveItemsLocally'
import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface'
export class DeleteThirdPartyVault {
constructor(
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private encryption: EncryptionProviderInterface,
private storage: StorageServiceInterface,
private keys: KeySystemKeyManagerInterface,
private sync: SyncServiceInterface,
private removeItemsLocally: RemoveItemsLocally,
) {}
async execute(vault: VaultListingInterface): Promise<void> {
await this.deleteDataSharedByVaultUsers(vault)
await this.deleteDataOwnedByThisUser(vault)
await this.encryption.keys.deleteNonPersistentSystemRootKeysForVault(vault.systemIdentifier)
await this.keys.deleteNonPersistentSystemRootKeysForVault(vault.systemIdentifier)
void this.sync.sync({ sourceDescription: 'Not awaiting due to this event handler running from sync response' })
}
@@ -34,13 +31,13 @@ export class DeleteExternalSharedVaultUseCase {
this.items.allTrackedItems().filter((item) => item.key_system_identifier === vault.systemIdentifier)
)
const itemsKeys = this.encryption.keys.getKeySystemItemsKeys(vault.systemIdentifier)
const itemsKeys = this.keys.getKeySystemItemsKeys(vault.systemIdentifier)
await this.removeItemsLocallyUsecase.execute([...vaultItems, ...itemsKeys])
await this.removeItemsLocally.execute([...vaultItems, ...itemsKeys])
}
private async deleteDataOwnedByThisUser(vault: VaultListingInterface): Promise<void> {
const rootKeys = this.encryption.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
const rootKeys = this.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
await this.mutator.setItemsToBeDeleted(rootKeys)
await this.mutator.setItemToBeDeleted(vault)

View File

@@ -1,19 +1,14 @@
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultServerInterface } from '@standardnotes/api'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { SharedVaultListingInterface } from '@standardnotes/models'
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import { DeleteVaultUseCase } from '../../Vaults/UseCase/DeleteVault'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { DeleteVault } from '../../Vaults/UseCase/DeleteVault'
export class DeleteSharedVaultUseCase {
export class DeleteSharedVault {
constructor(
private sharedVaultServer: SharedVaultServerInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private encryption: EncryptionProviderInterface,
private deleteVault: DeleteVault,
) {}
async execute(params: { sharedVault: SharedVaultListingInterface }): Promise<ClientDisplayableError | void> {
@@ -25,8 +20,7 @@ export class DeleteSharedVaultUseCase {
return ClientDisplayableError.FromString(`Failed to delete vault ${response}`)
}
const deleteUsecase = new DeleteVaultUseCase(this.items, this.mutator, this.encryption)
await deleteUsecase.execute(params.sharedVault)
await this.deleteVault.execute(params.sharedVault)
await this.sync.sync()
}

View File

@@ -1,23 +0,0 @@
import { SharedVaultUsersServerInterface } from '@standardnotes/api'
import { GetSharedVaultUsersUseCase } from './GetSharedVaultUsers'
import { SharedVaultListingInterface, TrustedContactInterface } from '@standardnotes/models'
import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface'
import { isNotUndefined } from '@standardnotes/utils'
export class GetSharedVaultTrustedContacts {
constructor(
private contacts: ContactServiceInterface,
private sharedVaultUsersServer: SharedVaultUsersServerInterface,
) {}
async execute(vault: SharedVaultListingInterface): Promise<TrustedContactInterface[] | undefined> {
const useCase = new GetSharedVaultUsersUseCase(this.sharedVaultUsersServer)
const users = await useCase.execute({ sharedVaultUuid: vault.sharing.sharedVaultUuid })
if (!users) {
return undefined
}
const contacts = users.map((user) => this.contacts.findTrustedContact(user.user_uuid)).filter(isNotUndefined)
return contacts
}
}

View File

@@ -0,0 +1,23 @@
import { GetVaultUsers } from './GetVaultUsers'
import { TrustedContactInterface } from '@standardnotes/models'
import { isNotUndefined } from '@standardnotes/utils'
import { FindContact } from '../../Contacts/UseCase/FindContact'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
export class GetVaultContacts implements UseCaseInterface<TrustedContactInterface[]> {
constructor(private findContact: FindContact, private getVaultUsers: GetVaultUsers) {}
async execute(sharedVaultUuid: string): Promise<Result<TrustedContactInterface[]>> {
const users = await this.getVaultUsers.execute({ sharedVaultUuid })
if (!users) {
return Result.fail('Failed to get vault users')
}
const contacts = users
.map((user) => this.findContact.execute({ userUuid: user.user_uuid }))
.map((result) => (result.isFailed() ? undefined : result.getValue()))
.filter(isNotUndefined)
return Result.ok(contacts)
}
}

View File

@@ -1,7 +1,7 @@
import { SharedVaultUserServerHash, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultUsersServerInterface } from '@standardnotes/api'
export class GetSharedVaultUsersUseCase {
export class GetVaultUsers {
constructor(private vaultUsersServer: SharedVaultUsersServerInterface) {}
async execute(params: { sharedVaultUuid: string }): Promise<SharedVaultUserServerHash[] | undefined> {

View File

@@ -1,63 +0,0 @@
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { ClientDisplayableError, SharedVaultInviteServerHash, SharedVaultPermission } from '@standardnotes/responses'
import {
TrustedContactInterface,
SharedVaultListingInterface,
AsymmetricMessagePayloadType,
} from '@standardnotes/models'
import { SharedVaultInvitesServerInterface } from '@standardnotes/api'
import { SendSharedVaultInviteUseCase } from './SendSharedVaultInviteUseCase'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
export class InviteContactToSharedVaultUseCase {
constructor(
private encryption: EncryptionProviderInterface,
private sharedVaultInviteServer: SharedVaultInvitesServerInterface,
) {}
async execute(params: {
senderKeyPair: PkcKeyPair
senderSigningKeyPair: PkcKeyPair
sharedVault: SharedVaultListingInterface
sharedVaultContacts: TrustedContactInterface[]
recipient: TrustedContactInterface
permissions: SharedVaultPermission
}): Promise<SharedVaultInviteServerHash | ClientDisplayableError> {
const keySystemRootKey = this.encryption.keys.getPrimaryKeySystemRootKey(params.sharedVault.systemIdentifier)
if (!keySystemRootKey) {
return ClientDisplayableError.FromString('Cannot add contact; key system root key not found')
}
const delegatedContacts = params.sharedVaultContacts.filter(
(contact) => !contact.isMe && contact.contactUuid !== params.recipient.contactUuid,
)
const encryptedMessage = this.encryption.asymmetricallyEncryptMessage({
message: {
type: AsymmetricMessagePayloadType.SharedVaultInvite,
data: {
recipientUuid: params.recipient.contactUuid,
rootKey: keySystemRootKey.content,
trustedContacts: delegatedContacts.map((contact) => contact.content),
metadata: {
name: params.sharedVault.name,
description: params.sharedVault.description,
},
},
},
senderKeyPair: params.senderKeyPair,
senderSigningKeyPair: params.senderSigningKeyPair,
recipientPublicKey: params.recipient.publicKeySet.encryption,
})
const createInviteUseCase = new SendSharedVaultInviteUseCase(this.sharedVaultInviteServer)
const createInviteResult = await createInviteUseCase.execute({
sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid,
recipientUuid: params.recipient.contactUuid,
encryptedMessage,
permissions: params.permissions,
})
return createInviteResult
}
}

View File

@@ -0,0 +1,135 @@
import { SharedVaultInviteServerHash, SharedVaultPermission } from '@standardnotes/responses'
import {
TrustedContactInterface,
SharedVaultListingInterface,
AsymmetricMessagePayloadType,
VaultInviteDelegatedContact,
} from '@standardnotes/models'
import { SendVaultInvite } from './SendVaultInvite'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { ShareContactWithVault } from './ShareContactWithVault'
import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface'
export class InviteToVault implements UseCaseInterface<SharedVaultInviteServerHash> {
constructor(
private keyManager: KeySystemKeyManagerInterface,
private encryptMessage: EncryptMessage,
private sendInvite: SendVaultInvite,
private shareContact: ShareContactWithVault,
) {}
async execute(params: {
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
senderUuid: string
sharedVault: SharedVaultListingInterface
sharedVaultContacts: TrustedContactInterface[]
recipient: TrustedContactInterface
permissions: SharedVaultPermission
}): Promise<Result<SharedVaultInviteServerHash>> {
const createInviteResult = await this.inviteContact(params)
if (createInviteResult.isFailed()) {
return createInviteResult
}
await this.shareContactWithOtherVaultMembers({
contact: params.recipient,
senderUuid: params.senderUuid,
keys: params.keys,
sharedVault: params.sharedVault,
})
return createInviteResult
}
private async shareContactWithOtherVaultMembers(params: {
contact: TrustedContactInterface
senderUuid: string
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
sharedVault: SharedVaultListingInterface
}): Promise<Result<void>> {
const result = await this.shareContact.execute({
keys: params.keys,
senderUserUuid: params.senderUuid,
sharedVault: params.sharedVault,
contactToShare: params.contact,
})
return result
}
private async inviteContact(params: {
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
sharedVault: SharedVaultListingInterface
sharedVaultContacts: TrustedContactInterface[]
recipient: TrustedContactInterface
permissions: SharedVaultPermission
}): Promise<Result<SharedVaultInviteServerHash>> {
const keySystemRootKey = this.keyManager.getPrimaryKeySystemRootKey(params.sharedVault.systemIdentifier)
if (!keySystemRootKey) {
return Result.fail('Cannot invite contact; key system root key not found')
}
const meContact = params.sharedVaultContacts.find((contact) => contact.isMe)
if (!meContact) {
return Result.fail('Cannot invite contact; me contact not found')
}
const meContactContent: VaultInviteDelegatedContact = {
name: undefined,
contactUuid: meContact.contactUuid,
publicKeySet: meContact.publicKeySet,
}
const delegatedContacts: VaultInviteDelegatedContact[] = params.sharedVaultContacts
.filter((contact) => !contact.isMe && contact.contactUuid !== params.recipient.contactUuid)
.map((contact) => {
return {
name: contact.name,
contactUuid: contact.contactUuid,
publicKeySet: contact.publicKeySet,
}
})
const encryptedMessage = this.encryptMessage.execute({
message: {
type: AsymmetricMessagePayloadType.SharedVaultInvite,
data: {
recipientUuid: params.recipient.contactUuid,
rootKey: keySystemRootKey.content,
trustedContacts: [meContactContent, ...delegatedContacts],
metadata: {
name: params.sharedVault.name,
description: params.sharedVault.description,
},
},
},
keys: params.keys,
recipientPublicKey: params.recipient.publicKeySet.encryption,
})
if (encryptedMessage.isFailed()) {
return Result.fail(encryptedMessage.getError())
}
const createInviteResult = await this.sendInvite.execute({
sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid,
recipientUuid: params.recipient.contactUuid,
encryptedMessage: encryptedMessage.getValue(),
permissions: params.permissions,
})
return createInviteResult
}
}

View File

@@ -1,21 +1,14 @@
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
import { StorageServiceInterface } from './../../Storage/StorageServiceInterface'
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultUsersServerInterface } from '@standardnotes/api'
import { DeleteExternalSharedVaultUseCase } from './DeleteExternalSharedVault'
import { DeleteThirdPartyVault } from './DeleteExternalSharedVault'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { SharedVaultListingInterface } from '@standardnotes/models'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
export class LeaveVaultUseCase {
export class LeaveVault {
constructor(
private vaultUserServer: SharedVaultUsersServerInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private encryption: EncryptionProviderInterface,
private storage: StorageServiceInterface,
private sync: SyncServiceInterface,
private deleteThirdPartyVault: DeleteThirdPartyVault,
) {}
async execute(params: {
@@ -36,13 +29,6 @@ export class LeaveVaultUseCase {
return ClientDisplayableError.FromString(`Failed to leave vault ${JSON.stringify(response)}`)
}
const removeLocalItems = new DeleteExternalSharedVaultUseCase(
this.items,
this.mutator,
this.encryption,
this.storage,
this.sync,
)
await removeLocalItems.execute(latestVaultListing)
await this.deleteThirdPartyVault.execute(latestVaultListing)
}
}

View File

@@ -1,62 +0,0 @@
import {
AsymmetricMessageServerInterface,
SharedVaultInvitesServerInterface,
SharedVaultUsersServerInterface,
} from '@standardnotes/api'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface'
import { SharedVaultListingInterface } from '@standardnotes/models'
import { ClientDisplayableError } from '@standardnotes/responses'
import { ReuploadSharedVaultInvitesAfterKeyRotationUseCase } from './ReuploadSharedVaultInvitesAfterKeyRotation'
import { SendSharedVaultRootKeyChangedMessageToAll } from './SendSharedVaultRootKeyChangedMessageToAll'
export class NotifySharedVaultUsersOfRootKeyRotationUseCase {
constructor(
private sharedVaultUsersServer: SharedVaultUsersServerInterface,
private sharedVaultInvitesServer: SharedVaultInvitesServerInterface,
private messageServer: AsymmetricMessageServerInterface,
private encryption: EncryptionProviderInterface,
private contacts: ContactServiceInterface,
) {}
async execute(params: {
sharedVault: SharedVaultListingInterface
userUuid: string
}): Promise<ClientDisplayableError[]> {
const errors: ClientDisplayableError[] = []
const updatePendingInvitesUseCase = new ReuploadSharedVaultInvitesAfterKeyRotationUseCase(
this.encryption,
this.contacts,
this.sharedVaultInvitesServer,
this.sharedVaultUsersServer,
)
const updateExistingResults = await updatePendingInvitesUseCase.execute({
sharedVault: params.sharedVault,
senderUuid: params.userUuid,
senderEncryptionKeyPair: this.encryption.getKeyPair(),
senderSigningKeyPair: this.encryption.getSigningKeyPair(),
})
errors.push(...updateExistingResults)
const shareKeyUseCase = new SendSharedVaultRootKeyChangedMessageToAll(
this.encryption,
this.contacts,
this.sharedVaultUsersServer,
this.messageServer,
)
const shareKeyResults = await shareKeyUseCase.execute({
keySystemIdentifier: params.sharedVault.systemIdentifier,
sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid,
senderUuid: params.userUuid,
senderEncryptionKeyPair: this.encryption.getKeyPair(),
senderSigningKeyPair: this.encryption.getSigningKeyPair(),
})
errors.push(...shareKeyResults)
return errors
}
}

View File

@@ -0,0 +1,116 @@
import { SharedVaultInvitesServerInterface } from '@standardnotes/api'
import { AsymmetricMessageSharedVaultInvite, SharedVaultListingInterface } from '@standardnotes/models'
import { SharedVaultInviteServerHash, isErrorResponse } from '@standardnotes/responses'
import { SendVaultKeyChangedMessage } from './SendVaultKeyChangedMessage'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { InviteToVault } from './InviteToVault'
import { GetVaultContacts } from './GetVaultContacts'
import { DecryptOwnMessage } from '../../Encryption/UseCase/Asymmetric/DecryptOwnMessage'
import { FindContact } from '../../Contacts/UseCase/FindContact'
type Params = {
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
sharedVault: SharedVaultListingInterface
senderUuid: string
}
export class NotifyVaultUsersOfKeyRotation implements UseCaseInterface<void> {
constructor(
private findContact: FindContact,
private sendKeyChangedMessage: SendVaultKeyChangedMessage,
private inviteToVault: InviteToVault,
private inviteServer: SharedVaultInvitesServerInterface,
private getVaultContacts: GetVaultContacts,
private decryptOwnMessage: DecryptOwnMessage<AsymmetricMessageSharedVaultInvite>,
) {}
async execute(params: Params): Promise<Result<void>> {
await this.reinvitePendingInvites(params)
await this.performSendKeyChangeMessage(params)
return Result.ok()
}
private async reinvitePendingInvites(params: Params): Promise<Result<void>> {
const existingInvites = await this.getExistingInvites(params.sharedVault.sharing.sharedVaultUuid)
if (existingInvites.isFailed()) {
return existingInvites
}
await this.deleteAllInvites(params.sharedVault.sharing.sharedVaultUuid)
const contacts = await this.getVaultContacts.execute(params.sharedVault.sharing.sharedVaultUuid)
for (const invite of existingInvites.getValue()) {
const recipient = this.findContact.execute({ userUuid: invite.user_uuid })
if (recipient.isFailed()) {
continue
}
const decryptedPreviousInvite = this.decryptOwnMessage.execute({
message: invite.encrypted_message,
privateKey: params.keys.encryption.privateKey,
recipientPublicKey: recipient.getValue().publicKeySet.encryption,
})
if (decryptedPreviousInvite.isFailed()) {
return Result.fail(decryptedPreviousInvite.getError())
}
await this.inviteToVault.execute({
keys: params.keys,
sharedVault: params.sharedVault,
sharedVaultContacts: !contacts.isFailed() ? contacts.getValue() : [],
recipient: recipient.getValue(),
permissions: invite.permissions,
senderUuid: params.senderUuid,
})
}
return Result.ok()
}
private async performSendKeyChangeMessage(params: Params): Promise<Result<void>> {
const result = await this.sendKeyChangedMessage.execute({
keySystemIdentifier: params.sharedVault.systemIdentifier,
sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid,
senderUuid: params.senderUuid,
keys: params.keys,
})
if (result.isFailed()) {
return result
}
return Result.ok()
}
private async deleteAllInvites(sharedVaultUuid: string): Promise<Result<void>> {
const response = await this.inviteServer.deleteAllSharedVaultInvites({
sharedVaultUuid: sharedVaultUuid,
})
if (isErrorResponse(response)) {
return Result.fail(`Failed to delete existing invites ${response}`)
}
return Result.ok()
}
private async getExistingInvites(sharedVaultUuid: string): Promise<Result<SharedVaultInviteServerHash[]>> {
const response = await this.inviteServer.getOutboundUserInvites()
if (isErrorResponse(response)) {
return Result.fail(`Failed to get outbound user invites ${response}`)
}
const invites = response.data.invites
return Result.ok(invites.filter((invite) => invite.shared_vault_uuid === sharedVaultUuid))
}
}

View File

@@ -1,7 +1,7 @@
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultUsersServerInterface } from '@standardnotes/api'
export class RemoveVaultMemberUseCase {
export class RemoveVaultMember {
constructor(private vaultUserServer: SharedVaultUsersServerInterface) {}
async execute(params: { sharedVaultUuid: string; userUuid: string }): Promise<ClientDisplayableError | void> {

View File

@@ -0,0 +1,86 @@
import { SharedVaultInviteServerHash, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultInvitesServerInterface } from '@standardnotes/api'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { ReuploadInvite } from './ReuploadInvite'
import { FindContact } from '../../Contacts/UseCase/FindContact'
type ReuploadAllInvitesDTO = {
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
previousKeys?: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
}
export class ReuploadAllInvites implements UseCaseInterface<void> {
constructor(
private reuploadInvite: ReuploadInvite,
private findContact: FindContact,
private inviteServer: SharedVaultInvitesServerInterface,
) {}
async execute(params: ReuploadAllInvitesDTO): Promise<Result<void>> {
const invites = await this.getExistingInvites()
if (invites.isFailed()) {
return invites
}
const deleteResult = await this.deleteExistingInvites()
if (deleteResult.isFailed()) {
return deleteResult
}
const errors: string[] = []
for (const invite of invites.getValue()) {
const recipient = this.findContact.execute({ userUuid: invite.user_uuid })
if (recipient.isFailed()) {
errors.push(`Contact not found for invite ${invite.user_uuid}`)
continue
}
const result = await this.reuploadInvite.execute({
keys: params.keys,
previousKeys: params.previousKeys,
recipient: recipient.getValue(),
previousInvite: invite,
})
if (result.isFailed()) {
errors.push(result.getError())
}
}
if (errors.length > 0) {
return Result.fail(errors.join(', '))
}
return Result.ok()
}
private async getExistingInvites(): Promise<Result<SharedVaultInviteServerHash[]>> {
const response = await this.inviteServer.getOutboundUserInvites()
if (isErrorResponse(response)) {
return Result.fail(`Failed to get outbound user invites ${response}`)
}
const invites = response.data.invites
return Result.ok(invites)
}
private async deleteExistingInvites(): Promise<Result<void>> {
const response = await this.inviteServer.deleteAllOutboundInvites()
if (isErrorResponse(response)) {
return Result.fail(`Failed to delete existing invites ${response}`)
}
return Result.ok()
}
}

View File

@@ -0,0 +1,57 @@
import { DecryptOwnMessage } from './../../Encryption/UseCase/Asymmetric/DecryptOwnMessage'
import { AsymmetricMessageSharedVaultInvite, TrustedContactInterface } from '@standardnotes/models'
import { SharedVaultInviteServerHash } from '@standardnotes/responses'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { SendVaultInvite } from './SendVaultInvite'
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
export class ReuploadInvite implements UseCaseInterface<void> {
constructor(
private decryptOwnMessage: DecryptOwnMessage<AsymmetricMessageSharedVaultInvite>,
private sendInvite: SendVaultInvite,
private encryptMessage: EncryptMessage,
) {}
async execute(params: {
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
previousKeys?: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
recipient: TrustedContactInterface
previousInvite: SharedVaultInviteServerHash
}): Promise<Result<SharedVaultInviteServerHash>> {
const decryptedPreviousInvite = this.decryptOwnMessage.execute({
message: params.previousInvite.encrypted_message,
privateKey: params.previousKeys?.encryption.privateKey ?? params.keys.encryption.privateKey,
recipientPublicKey: params.recipient.publicKeySet.encryption,
})
if (decryptedPreviousInvite.isFailed()) {
return Result.fail(decryptedPreviousInvite.getError())
}
const encryptedMessage = this.encryptMessage.execute({
message: decryptedPreviousInvite.getValue(),
keys: params.keys,
recipientPublicKey: params.recipient.publicKeySet.encryption,
})
if (encryptedMessage.isFailed()) {
return Result.fail(encryptedMessage.getError())
}
const createInviteResult = await this.sendInvite.execute({
sharedVaultUuid: params.previousInvite.shared_vault_uuid,
recipientUuid: params.recipient.contactUuid,
encryptedMessage: encryptedMessage.getValue(),
permissions: params.previousInvite.permissions,
})
return createInviteResult
}
}

View File

@@ -1,144 +0,0 @@
import {
KeySystemRootKeyContentSpecialized,
SharedVaultListingInterface,
TrustedContactInterface,
} from '@standardnotes/models'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import {
ClientDisplayableError,
SharedVaultInviteServerHash,
isClientDisplayableError,
isErrorResponse,
} from '@standardnotes/responses'
import { SharedVaultInvitesServerInterface, SharedVaultUsersServerInterface } from '@standardnotes/api'
import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { InviteContactToSharedVaultUseCase } from './InviteContactToSharedVault'
import { GetSharedVaultTrustedContacts } from './GetSharedVaultTrustedContacts'
type ReuploadAllSharedVaultInvitesDTO = {
sharedVault: SharedVaultListingInterface
senderUuid: string
senderEncryptionKeyPair: PkcKeyPair
senderSigningKeyPair: PkcKeyPair
}
export class ReuploadSharedVaultInvitesAfterKeyRotationUseCase {
constructor(
private encryption: EncryptionProviderInterface,
private contacts: ContactServiceInterface,
private vaultInvitesServer: SharedVaultInvitesServerInterface,
private vaultUserServer: SharedVaultUsersServerInterface,
) {}
async execute(params: ReuploadAllSharedVaultInvitesDTO): Promise<ClientDisplayableError[]> {
const keySystemRootKey = this.encryption.keys.getPrimaryKeySystemRootKey(params.sharedVault.systemIdentifier)
if (!keySystemRootKey) {
throw new Error(`Vault key not found for keySystemIdentifier ${params.sharedVault.systemIdentifier}`)
}
const existingInvites = await this.getExistingInvites(params.sharedVault.sharing.sharedVaultUuid)
if (isClientDisplayableError(existingInvites)) {
return [existingInvites]
}
const deleteResult = await this.deleteExistingInvites(params.sharedVault.sharing.sharedVaultUuid)
if (isClientDisplayableError(deleteResult)) {
return [deleteResult]
}
const vaultContacts = await this.getVaultContacts(params.sharedVault)
if (vaultContacts.length === 0) {
return []
}
const errors: ClientDisplayableError[] = []
for (const invite of existingInvites) {
const contact = this.contacts.findTrustedContact(invite.user_uuid)
if (!contact) {
errors.push(ClientDisplayableError.FromString(`Contact not found for invite ${invite.user_uuid}`))
continue
}
const result = await this.sendNewInvite({
usecaseDTO: params,
contact: contact,
previousInvite: invite,
keySystemRootKeyData: keySystemRootKey.content,
sharedVaultContacts: vaultContacts,
})
if (isClientDisplayableError(result)) {
errors.push(result)
}
}
return errors
}
private async getVaultContacts(sharedVault: SharedVaultListingInterface): Promise<TrustedContactInterface[]> {
const usecase = new GetSharedVaultTrustedContacts(this.contacts, this.vaultUserServer)
const contacts = await usecase.execute(sharedVault)
if (!contacts) {
return []
}
return contacts
}
private async getExistingInvites(
sharedVaultUuid: string,
): Promise<SharedVaultInviteServerHash[] | ClientDisplayableError> {
const response = await this.vaultInvitesServer.getOutboundUserInvites()
if (isErrorResponse(response)) {
return ClientDisplayableError.FromString(`Failed to get outbound user invites ${response}`)
}
const invites = response.data.invites
return invites.filter((invite) => invite.shared_vault_uuid === sharedVaultUuid)
}
private async deleteExistingInvites(sharedVaultUuid: string): Promise<ClientDisplayableError | void> {
const response = await this.vaultInvitesServer.deleteAllSharedVaultInvites({
sharedVaultUuid: sharedVaultUuid,
})
if (isErrorResponse(response)) {
return ClientDisplayableError.FromString(`Failed to delete existing invites ${response}`)
}
}
private async sendNewInvite(params: {
usecaseDTO: ReuploadAllSharedVaultInvitesDTO
contact: TrustedContactInterface
previousInvite: SharedVaultInviteServerHash
keySystemRootKeyData: KeySystemRootKeyContentSpecialized
sharedVaultContacts: TrustedContactInterface[]
}): Promise<ClientDisplayableError | void> {
const signatureResult = this.encryption.asymmetricSignatureVerifyDetached(params.previousInvite.encrypted_message)
if (!signatureResult.signatureVerified) {
return ClientDisplayableError.FromString('Failed to verify signature of previous invite')
}
if (signatureResult.signaturePublicKey !== params.usecaseDTO.senderSigningKeyPair.publicKey) {
return ClientDisplayableError.FromString('Sender public key does not match signature')
}
const usecase = new InviteContactToSharedVaultUseCase(this.encryption, this.vaultInvitesServer)
const result = await usecase.execute({
senderKeyPair: params.usecaseDTO.senderEncryptionKeyPair,
senderSigningKeyPair: params.usecaseDTO.senderSigningKeyPair,
sharedVault: params.usecaseDTO.sharedVault,
sharedVaultContacts: params.sharedVaultContacts,
recipient: params.contact,
permissions: params.previousInvite.permissions,
})
if (isClientDisplayableError(result)) {
return result
}
}
}

View File

@@ -0,0 +1,86 @@
import { SharedVaultListingInterface } from '@standardnotes/models'
import { SharedVaultInviteServerHash, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultInvitesServerInterface } from '@standardnotes/api'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { ReuploadInvite } from './ReuploadInvite'
import { FindContact } from '../../Contacts/UseCase/FindContact'
type ReuploadVaultInvitesDTO = {
sharedVault: SharedVaultListingInterface
senderUuid: string
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
}
export class ReuploadVaultInvites implements UseCaseInterface<void> {
constructor(
private reuploadInvite: ReuploadInvite,
private findContact: FindContact,
private inviteServer: SharedVaultInvitesServerInterface,
) {}
async execute(params: ReuploadVaultInvitesDTO): Promise<Result<void>> {
const existingInvites = await this.getExistingInvites(params.sharedVault.sharing.sharedVaultUuid)
if (existingInvites.isFailed()) {
return existingInvites
}
const deleteResult = await this.deleteExistingInvites(params.sharedVault.sharing.sharedVaultUuid)
if (deleteResult.isFailed()) {
return deleteResult
}
const errors: string[] = []
for (const invite of existingInvites.getValue()) {
const recipient = this.findContact.execute({ userUuid: invite.user_uuid })
if (recipient.isFailed()) {
errors.push(`Contact not found for invite ${invite.user_uuid}`)
continue
}
const result = await this.reuploadInvite.execute({
keys: params.keys,
recipient: recipient.getValue(),
previousInvite: invite,
})
if (result.isFailed()) {
errors.push(result.getError())
}
}
if (errors.length > 0) {
return Result.fail(errors.join(', '))
}
return Result.ok()
}
private async getExistingInvites(sharedVaultUuid: string): Promise<Result<SharedVaultInviteServerHash[]>> {
const response = await this.inviteServer.getOutboundUserInvites()
if (isErrorResponse(response)) {
return Result.fail(`Failed to get outbound user invites ${response}`)
}
const invites = response.data.invites
return Result.ok(invites.filter((invite) => invite.shared_vault_uuid === sharedVaultUuid))
}
private async deleteExistingInvites(sharedVaultUuid: string): Promise<Result<void>> {
const response = await this.inviteServer.deleteAllSharedVaultInvites({
sharedVaultUuid: sharedVaultUuid,
})
if (isErrorResponse(response)) {
return Result.fail(`Failed to delete existing invites ${response}`)
}
return Result.ok()
}
}

View File

@@ -1,100 +0,0 @@
import {
AsymmetricMessagePayloadType,
AsymmetricMessageSharedVaultMetadataChanged,
SharedVaultListingInterface,
TrustedContactInterface,
} from '@standardnotes/models'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { AsymmetricMessageServerHash, ClientDisplayableError, isClientDisplayableError } from '@standardnotes/responses'
import { AsymmetricMessageServerInterface, SharedVaultUsersServerInterface } from '@standardnotes/api'
import { GetSharedVaultUsersUseCase } from './GetSharedVaultUsers'
import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { SendAsymmetricMessageUseCase } from '../../AsymmetricMessage/UseCase/SendAsymmetricMessageUseCase'
export class SendSharedVaultMetadataChangedMessageToAll {
constructor(
private encryption: EncryptionProviderInterface,
private contacts: ContactServiceInterface,
private vaultUsersServer: SharedVaultUsersServerInterface,
private messageServer: AsymmetricMessageServerInterface,
) {}
async execute(params: {
vault: SharedVaultListingInterface
senderUuid: string
senderEncryptionKeyPair: PkcKeyPair
senderSigningKeyPair: PkcKeyPair
}): Promise<ClientDisplayableError[]> {
const errors: ClientDisplayableError[] = []
const getUsersUseCase = new GetSharedVaultUsersUseCase(this.vaultUsersServer)
const users = await getUsersUseCase.execute({ sharedVaultUuid: params.vault.sharing.sharedVaultUuid })
if (!users) {
return [ClientDisplayableError.FromString('Cannot send metadata changed message; users not found')]
}
for (const user of users) {
if (user.user_uuid === params.senderUuid) {
continue
}
const trustedContact = this.contacts.findTrustedContact(user.user_uuid)
if (!trustedContact) {
continue
}
const sendMessageResult = await this.sendToContact({
vault: params.vault,
senderKeyPair: params.senderEncryptionKeyPair,
senderSigningKeyPair: params.senderSigningKeyPair,
contact: trustedContact,
})
if (isClientDisplayableError(sendMessageResult)) {
errors.push(sendMessageResult)
}
}
return errors
}
private async sendToContact(params: {
vault: SharedVaultListingInterface
senderKeyPair: PkcKeyPair
senderSigningKeyPair: PkcKeyPair
contact: TrustedContactInterface
}): Promise<AsymmetricMessageServerHash | ClientDisplayableError> {
const message: AsymmetricMessageSharedVaultMetadataChanged = {
type: AsymmetricMessagePayloadType.SharedVaultMetadataChanged,
data: {
recipientUuid: params.contact.contactUuid,
sharedVaultUuid: params.vault.sharing.sharedVaultUuid,
name: params.vault.name,
description: params.vault.description,
},
}
const encryptedMessage = this.encryption.asymmetricallyEncryptMessage({
message: message,
senderKeyPair: params.senderKeyPair,
senderSigningKeyPair: params.senderSigningKeyPair,
recipientPublicKey: params.contact.publicKeySet.encryption,
})
const replaceabilityIdentifier = [
AsymmetricMessagePayloadType.SharedVaultMetadataChanged,
params.vault.sharing.sharedVaultUuid,
params.vault.systemIdentifier,
].join(':')
const sendMessageUseCase = new SendAsymmetricMessageUseCase(this.messageServer)
const sendMessageResult = await sendMessageUseCase.execute({
recipientUuid: params.contact.contactUuid,
encryptedMessage,
replaceabilityIdentifier,
})
return sendMessageResult
}
}

View File

@@ -1,103 +0,0 @@
import {
AsymmetricMessagePayloadType,
AsymmetricMessageSharedVaultRootKeyChanged,
KeySystemIdentifier,
TrustedContactInterface,
} from '@standardnotes/models'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { AsymmetricMessageServerHash, ClientDisplayableError, isClientDisplayableError } from '@standardnotes/responses'
import { AsymmetricMessageServerInterface, SharedVaultUsersServerInterface } from '@standardnotes/api'
import { GetSharedVaultUsersUseCase } from './GetSharedVaultUsers'
import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { SendAsymmetricMessageUseCase } from '../../AsymmetricMessage/UseCase/SendAsymmetricMessageUseCase'
export class SendSharedVaultRootKeyChangedMessageToAll {
constructor(
private encryption: EncryptionProviderInterface,
private contacts: ContactServiceInterface,
private vaultUsersServer: SharedVaultUsersServerInterface,
private messageServer: AsymmetricMessageServerInterface,
) {}
async execute(params: {
keySystemIdentifier: KeySystemIdentifier
sharedVaultUuid: string
senderUuid: string
senderEncryptionKeyPair: PkcKeyPair
senderSigningKeyPair: PkcKeyPair
}): Promise<ClientDisplayableError[]> {
const errors: ClientDisplayableError[] = []
const getUsersUseCase = new GetSharedVaultUsersUseCase(this.vaultUsersServer)
const users = await getUsersUseCase.execute({ sharedVaultUuid: params.sharedVaultUuid })
if (!users) {
return [ClientDisplayableError.FromString('Cannot send root key changed message; users not found')]
}
for (const user of users) {
if (user.user_uuid === params.senderUuid) {
continue
}
const trustedContact = this.contacts.findTrustedContact(user.user_uuid)
if (!trustedContact) {
continue
}
const sendMessageResult = await this.sendToContact({
keySystemIdentifier: params.keySystemIdentifier,
sharedVaultUuid: params.sharedVaultUuid,
senderKeyPair: params.senderEncryptionKeyPair,
senderSigningKeyPair: params.senderSigningKeyPair,
contact: trustedContact,
})
if (isClientDisplayableError(sendMessageResult)) {
errors.push(sendMessageResult)
}
}
return errors
}
private async sendToContact(params: {
keySystemIdentifier: KeySystemIdentifier
sharedVaultUuid: string
senderKeyPair: PkcKeyPair
senderSigningKeyPair: PkcKeyPair
contact: TrustedContactInterface
}): Promise<AsymmetricMessageServerHash | ClientDisplayableError> {
const keySystemRootKey = this.encryption.keys.getPrimaryKeySystemRootKey(params.keySystemIdentifier)
if (!keySystemRootKey) {
throw new Error(`Vault key not found for keySystemIdentifier ${params.keySystemIdentifier}`)
}
const message: AsymmetricMessageSharedVaultRootKeyChanged = {
type: AsymmetricMessagePayloadType.SharedVaultRootKeyChanged,
data: { recipientUuid: params.contact.contactUuid, rootKey: keySystemRootKey.content },
}
const encryptedMessage = this.encryption.asymmetricallyEncryptMessage({
message: message,
senderKeyPair: params.senderKeyPair,
senderSigningKeyPair: params.senderSigningKeyPair,
recipientPublicKey: params.contact.publicKeySet.encryption,
})
const replaceabilityIdentifier = [
AsymmetricMessagePayloadType.SharedVaultRootKeyChanged,
params.sharedVaultUuid,
params.keySystemIdentifier,
].join(':')
const sendMessageUseCase = new SendAsymmetricMessageUseCase(this.messageServer)
const sendMessageResult = await sendMessageUseCase.execute({
recipientUuid: params.contact.contactUuid,
encryptedMessage,
replaceabilityIdentifier,
})
return sendMessageResult
}
}

View File

@@ -0,0 +1,108 @@
import {
AsymmetricMessagePayloadType,
AsymmetricMessageSharedVaultMetadataChanged,
SharedVaultListingInterface,
TrustedContactInterface,
} from '@standardnotes/models'
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
import { GetVaultUsers } from './GetVaultUsers'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage'
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { GetReplaceabilityIdentifier } from '../../AsymmetricMessage/UseCase/GetReplaceabilityIdentifier'
import { FindContact } from '../../Contacts/UseCase/FindContact'
export class SendVaultDataChangedMessage implements UseCaseInterface<void> {
constructor(
private encryptMessage: EncryptMessage,
private findContact: FindContact,
private getVaultUsers: GetVaultUsers,
private sendMessage: SendMessage,
) {}
async execute(params: {
vault: SharedVaultListingInterface
senderUuid: string
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
}): Promise<Result<void>> {
const users = await this.getVaultUsers.execute({ sharedVaultUuid: params.vault.sharing.sharedVaultUuid })
if (!users) {
return Result.fail('Cannot send metadata changed message; users not found')
}
const errors: string[] = []
for (const user of users) {
if (user.user_uuid === params.senderUuid) {
continue
}
const trustedContact = this.findContact.execute({ userUuid: user.user_uuid })
if (trustedContact.isFailed()) {
continue
}
const sendMessageResult = await this.sendToContact({
vault: params.vault,
keys: params.keys,
contact: trustedContact.getValue(),
})
if (sendMessageResult.isFailed()) {
errors.push(sendMessageResult.getError())
}
}
if (errors.length > 0) {
return Result.fail(errors.join(', '))
}
return Result.ok()
}
private async sendToContact(params: {
vault: SharedVaultListingInterface
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
contact: TrustedContactInterface
}): Promise<Result<AsymmetricMessageServerHash>> {
const message: AsymmetricMessageSharedVaultMetadataChanged = {
type: AsymmetricMessagePayloadType.SharedVaultMetadataChanged,
data: {
recipientUuid: params.contact.contactUuid,
sharedVaultUuid: params.vault.sharing.sharedVaultUuid,
name: params.vault.name,
description: params.vault.description,
},
}
const encryptedMessage = this.encryptMessage.execute({
message: message,
keys: params.keys,
recipientPublicKey: params.contact.publicKeySet.encryption,
})
if (encryptedMessage.isFailed()) {
return Result.fail(encryptedMessage.getError())
}
const replaceabilityIdentifier = GetReplaceabilityIdentifier(
AsymmetricMessagePayloadType.SharedVaultMetadataChanged,
params.vault.sharing.sharedVaultUuid,
params.vault.systemIdentifier,
)
const sendMessageResult = await this.sendMessage.execute({
recipientUuid: params.contact.contactUuid,
encryptedMessage: encryptedMessage.getValue(),
replaceabilityIdentifier,
})
return sendMessageResult
}
}

View File

@@ -1,12 +1,13 @@
import {
ClientDisplayableError,
SharedVaultInviteServerHash,
isErrorResponse,
SharedVaultPermission,
getErrorFromErrorResponse,
} from '@standardnotes/responses'
import { SharedVaultInvitesServerInterface } from '@standardnotes/api'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
export class SendSharedVaultInviteUseCase {
export class SendVaultInvite implements UseCaseInterface<SharedVaultInviteServerHash> {
constructor(private vaultInvitesServer: SharedVaultInvitesServerInterface) {}
async execute(params: {
@@ -14,7 +15,7 @@ export class SendSharedVaultInviteUseCase {
recipientUuid: string
encryptedMessage: string
permissions: SharedVaultPermission
}): Promise<SharedVaultInviteServerHash | ClientDisplayableError> {
}): Promise<Result<SharedVaultInviteServerHash>> {
const response = await this.vaultInvitesServer.createInvite({
sharedVaultUuid: params.sharedVaultUuid,
recipientUuid: params.recipientUuid,
@@ -23,9 +24,9 @@ export class SendSharedVaultInviteUseCase {
})
if (isErrorResponse(response)) {
return ClientDisplayableError.FromNetworkError(response)
return Result.fail(getErrorFromErrorResponse(response).message)
}
return response.data.invite
return Result.ok(response.data.invite)
}
}

View File

@@ -0,0 +1,114 @@
import {
AsymmetricMessagePayloadType,
AsymmetricMessageSharedVaultRootKeyChanged,
KeySystemIdentifier,
TrustedContactInterface,
} from '@standardnotes/models'
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
import { GetVaultUsers } from './GetVaultUsers'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage'
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { GetReplaceabilityIdentifier } from '../../AsymmetricMessage/UseCase/GetReplaceabilityIdentifier'
import { FindContact } from '../../Contacts/UseCase/FindContact'
import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface'
export class SendVaultKeyChangedMessage implements UseCaseInterface<void> {
constructor(
private encryptMessage: EncryptMessage,
private keyManager: KeySystemKeyManagerInterface,
private findContact: FindContact,
private sendMessage: SendMessage,
private getVaultUsers: GetVaultUsers,
) {}
async execute(params: {
keySystemIdentifier: KeySystemIdentifier
sharedVaultUuid: string
senderUuid: string
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
}): Promise<Result<void>> {
const users = await this.getVaultUsers.execute({ sharedVaultUuid: params.sharedVaultUuid })
if (!users) {
return Result.fail('Cannot send root key changed message; users not found')
}
const errors: string[] = []
for (const user of users) {
if (user.user_uuid === params.senderUuid) {
continue
}
const trustedContact = this.findContact.execute({ userUuid: user.user_uuid })
if (trustedContact.isFailed()) {
continue
}
const result = await this.sendToContact({
keySystemIdentifier: params.keySystemIdentifier,
sharedVaultUuid: params.sharedVaultUuid,
keys: params.keys,
contact: trustedContact.getValue(),
})
if (result.isFailed()) {
errors.push(result.getError())
}
}
if (errors.length > 0) {
return Result.fail(errors.join(', '))
}
return Result.ok()
}
private async sendToContact(params: {
keySystemIdentifier: KeySystemIdentifier
sharedVaultUuid: string
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
contact: TrustedContactInterface
}): Promise<Result<AsymmetricMessageServerHash>> {
const keySystemRootKey = this.keyManager.getPrimaryKeySystemRootKey(params.keySystemIdentifier)
if (!keySystemRootKey) {
throw new Error(`Vault key not found for keySystemIdentifier ${params.keySystemIdentifier}`)
}
const message: AsymmetricMessageSharedVaultRootKeyChanged = {
type: AsymmetricMessagePayloadType.SharedVaultRootKeyChanged,
data: { recipientUuid: params.contact.contactUuid, rootKey: keySystemRootKey.content },
}
const encryptedMessage = this.encryptMessage.execute({
message: message,
keys: params.keys,
recipientPublicKey: params.contact.publicKeySet.encryption,
})
if (encryptedMessage.isFailed()) {
return Result.fail(encryptedMessage.getError())
}
const replaceabilityIdentifier = GetReplaceabilityIdentifier(
AsymmetricMessagePayloadType.SharedVaultRootKeyChanged,
params.sharedVaultUuid,
params.keySystemIdentifier,
)
const sendMessageResult = await this.sendMessage.execute({
recipientUuid: params.contact.contactUuid,
encryptedMessage: encryptedMessage.getValue(),
replaceabilityIdentifier,
})
return sendMessageResult
}
}

View File

@@ -1,78 +0,0 @@
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
import {
TrustedContactInterface,
SharedVaultListingInterface,
AsymmetricMessagePayloadType,
} from '@standardnotes/models'
import { AsymmetricMessageServerInterface, SharedVaultUsersServerInterface } from '@standardnotes/api'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface'
import { SendAsymmetricMessageUseCase } from '../../AsymmetricMessage/UseCase/SendAsymmetricMessageUseCase'
export class ShareContactWithAllMembersOfSharedVaultUseCase {
constructor(
private contacts: ContactServiceInterface,
private encryption: EncryptionProviderInterface,
private sharedVaultUsersServer: SharedVaultUsersServerInterface,
private messageServer: AsymmetricMessageServerInterface,
) {}
async execute(params: {
senderKeyPair: PkcKeyPair
senderSigningKeyPair: PkcKeyPair
senderUserUuid: string
sharedVault: SharedVaultListingInterface
contactToShare: TrustedContactInterface
}): Promise<void | ClientDisplayableError> {
if (params.sharedVault.sharing.ownerUserUuid !== params.senderUserUuid) {
return ClientDisplayableError.FromString('Cannot share contact; user is not the owner of the shared vault')
}
const usersResponse = await this.sharedVaultUsersServer.getSharedVaultUsers({
sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid,
})
if (isErrorResponse(usersResponse)) {
return ClientDisplayableError.FromString('Cannot share contact; shared vault users not found')
}
const users = usersResponse.data.users
if (users.length === 0) {
return
}
const messageSendUseCase = new SendAsymmetricMessageUseCase(this.messageServer)
for (const vaultUser of users) {
if (vaultUser.user_uuid === params.senderUserUuid) {
continue
}
if (vaultUser.user_uuid === params.contactToShare.contactUuid) {
continue
}
const vaultUserAsContact = this.contacts.findTrustedContact(vaultUser.user_uuid)
if (!vaultUserAsContact) {
continue
}
const encryptedMessage = this.encryption.asymmetricallyEncryptMessage({
message: {
type: AsymmetricMessagePayloadType.ContactShare,
data: { recipientUuid: vaultUserAsContact.contactUuid, trustedContact: params.contactToShare.content },
},
senderKeyPair: params.senderKeyPair,
senderSigningKeyPair: params.senderSigningKeyPair,
recipientPublicKey: vaultUserAsContact.publicKeySet.encryption,
})
await messageSendUseCase.execute({
recipientUuid: vaultUserAsContact.contactUuid,
encryptedMessage,
replaceabilityIdentifier: undefined,
})
}
}
}

View File

@@ -0,0 +1,85 @@
import {
TrustedContactInterface,
SharedVaultListingInterface,
AsymmetricMessagePayloadType,
} from '@standardnotes/models'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage'
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { FindContact } from '../../Contacts/UseCase/FindContact'
import { GetVaultUsers } from './GetVaultUsers'
export class ShareContactWithVault implements UseCaseInterface<void> {
constructor(
private findContact: FindContact,
private encryptMessage: EncryptMessage,
private sendMessage: SendMessage,
private getVaultUsers: GetVaultUsers,
) {}
async execute(params: {
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
senderUserUuid: string
sharedVault: SharedVaultListingInterface
contactToShare: TrustedContactInterface
}): Promise<Result<void>> {
if (params.sharedVault.sharing.ownerUserUuid !== params.senderUserUuid) {
return Result.fail('Cannot share contact; user is not the owner of the shared vault')
}
const users = await this.getVaultUsers.execute({
sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid,
})
if (!users) {
return Result.fail('Cannot share contact; shared vault users not found')
}
if (users.length === 0) {
return Result.ok()
}
for (const vaultUser of users) {
if (vaultUser.user_uuid === params.senderUserUuid) {
continue
}
if (vaultUser.user_uuid === params.contactToShare.contactUuid) {
continue
}
const vaultUserAsContact = this.findContact.execute({ userUuid: vaultUser.user_uuid })
if (vaultUserAsContact.isFailed()) {
continue
}
const encryptedMessage = this.encryptMessage.execute({
message: {
type: AsymmetricMessagePayloadType.ContactShare,
data: {
recipientUuid: vaultUserAsContact.getValue().contactUuid,
trustedContact: params.contactToShare.content,
},
},
keys: params.keys,
recipientPublicKey: vaultUserAsContact.getValue().publicKeySet.encryption,
})
if (encryptedMessage.isFailed()) {
continue
}
await this.sendMessage.execute({
recipientUuid: vaultUserAsContact.getValue().contactUuid,
encryptedMessage: encryptedMessage.getValue(),
replaceabilityIdentifier: undefined,
})
}
return Result.ok()
}
}