internal: incomplete vault systems behind feature flag (#2340)

This commit is contained in:
Mo
2023-06-30 09:01:56 -05:00
committed by GitHub
parent d16e401bb9
commit b032eb9c9b
638 changed files with 20321 additions and 4813 deletions

View File

@@ -0,0 +1,29 @@
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,47 @@
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 { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
export class ConvertToSharedVaultUseCase {
constructor(
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private files: FilesClientInterface,
private sharedVaultServer: SharedVaultServerInterface,
) {}
async execute(dto: { vault: VaultListingInterface }): Promise<SharedVaultListingInterface | ClientDisplayableError> {
if (dto.vault.isSharedVaultListing()) {
throw new Error('Cannot convert a shared vault to a shared vault')
}
const serverResult = await this.sharedVaultServer.createSharedVault()
if (isErrorResponse(serverResult)) {
return ClientDisplayableError.FromString(`Failed to create shared vault ${serverResult}`)
}
const serverVaultHash = serverResult.data.sharedVault
const sharedVaultListing = await this.mutator.changeItem<VaultListingMutator, VaultListingInterface>(
dto.vault,
(mutator) => {
mutator.sharing = {
sharedVaultUuid: serverVaultHash.uuid,
ownerUserUuid: serverVaultHash.user_uuid,
}
},
)
const vaultItems = this.items.itemsBelongingToKeySystem(sharedVaultListing.systemIdentifier)
const moveToVaultUsecase = new MoveItemsToVaultUseCase(this.mutator, this.sync, this.files)
await moveToVaultUsecase.execute({ vault: sharedVaultListing, items: vaultItems })
return sharedVaultListing as SharedVaultListingInterface
}
}

View File

@@ -0,0 +1,64 @@
import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
import {
KeySystemRootKeyStorageMode,
SharedVaultListingInterface,
VaultListingInterface,
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 { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
export class CreateSharedVaultUseCase {
constructor(
private encryption: EncryptionProviderInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private files: FilesClientInterface,
private sharedVaultServer: SharedVaultServerInterface,
) {}
async execute(dto: {
vaultName: string
vaultDescription?: string
userInputtedPassword: string | undefined
storagePreference: KeySystemRootKeyStorageMode
}): Promise<SharedVaultListingInterface | ClientDisplayableError> {
const usecase = new CreateVaultUseCase(this.mutator, this.encryption, this.sync)
const privateVault = await usecase.execute({
vaultName: dto.vaultName,
vaultDescription: dto.vaultDescription,
userInputtedPassword: dto.userInputtedPassword,
storagePreference: dto.storagePreference,
})
const serverResult = await this.sharedVaultServer.createSharedVault()
if (isErrorResponse(serverResult)) {
return ClientDisplayableError.FromString(`Failed to create shared vault ${JSON.stringify(serverResult)}`)
}
const serverVaultHash = serverResult.data.sharedVault
const sharedVaultListing = await this.mutator.changeItem<VaultListingMutator, VaultListingInterface>(
privateVault,
(mutator) => {
mutator.sharing = {
sharedVaultUuid: serverVaultHash.uuid,
ownerUserUuid: serverVaultHash.user_uuid,
}
},
)
const vaultItems = this.items.itemsBelongingToKeySystem(sharedVaultListing.systemIdentifier)
const moveToVaultUsecase = new MoveItemsToVaultUseCase(this.mutator, this.sync, this.files)
await moveToVaultUsecase.execute({ vault: sharedVaultListing, items: vaultItems })
return sharedVaultListing as SharedVaultListingInterface
}
}

View File

@@ -0,0 +1,48 @@
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 { Uuids } from '@standardnotes/utils'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
export class DeleteExternalSharedVaultUseCase {
constructor(
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private encryption: EncryptionProviderInterface,
private storage: StorageServiceInterface,
private sync: SyncServiceInterface,
) {}
async execute(vault: VaultListingInterface): Promise<void> {
await this.deleteDataSharedByVaultUsers(vault)
await this.deleteDataOwnedByThisUser(vault)
await this.encryption.keys.deleteNonPersistentSystemRootKeysForVault(vault.systemIdentifier)
void this.sync.sync({ sourceDescription: 'Not awaiting due to this event handler running from sync response' })
}
/**
* This data is shared with all vault users and does not belong to this particular user
* The data will be removed locally without syncing the items
*/
private async deleteDataSharedByVaultUsers(vault: VaultListingInterface): Promise<void> {
const vaultItems = this.items
.allTrackedItems()
.filter((item) => item.key_system_identifier === vault.systemIdentifier)
this.items.removeItemsLocally(vaultItems as AnyItemInterface[])
const itemsKeys = this.encryption.keys.getKeySystemItemsKeys(vault.systemIdentifier)
this.items.removeItemsLocally(itemsKeys)
await this.storage.deletePayloadsWithUuids([...Uuids(vaultItems), ...Uuids(itemsKeys)])
}
private async deleteDataOwnedByThisUser(vault: VaultListingInterface): Promise<void> {
const rootKeys = this.encryption.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
await this.mutator.setItemsToBeDeleted(rootKeys)
await this.mutator.setItemToBeDeleted(vault)
}
}

View File

@@ -0,0 +1,33 @@
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'
export class DeleteSharedVaultUseCase {
constructor(
private sharedVaultServer: SharedVaultServerInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private encryption: EncryptionProviderInterface,
) {}
async execute(params: { sharedVault: SharedVaultListingInterface }): Promise<ClientDisplayableError | void> {
const response = await this.sharedVaultServer.deleteSharedVault({
sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid,
})
if (isErrorResponse(response)) {
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.sync.sync()
}
}

View File

@@ -0,0 +1,23 @@
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,16 @@
import { SharedVaultUserServerHash, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultUsersServerInterface } from '@standardnotes/api'
export class GetSharedVaultUsersUseCase {
constructor(private vaultUsersServer: SharedVaultUsersServerInterface) {}
async execute(params: { sharedVaultUuid: string }): Promise<SharedVaultUserServerHash[] | undefined> {
const response = await this.vaultUsersServer.getSharedVaultUsers({ sharedVaultUuid: params.sharedVaultUuid })
if (isErrorResponse(response)) {
return undefined
}
return response.data.users
}
}

View File

@@ -0,0 +1,63 @@
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,48 @@
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 { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { SharedVaultListingInterface } from '@standardnotes/models'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
export class LeaveVaultUseCase {
constructor(
private vaultUserServer: SharedVaultUsersServerInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private encryption: EncryptionProviderInterface,
private storage: StorageServiceInterface,
private sync: SyncServiceInterface,
) {}
async execute(params: {
sharedVault: SharedVaultListingInterface
userUuid: string
}): Promise<ClientDisplayableError | void> {
const latestVaultListing = this.items.findItem<SharedVaultListingInterface>(params.sharedVault.uuid)
if (!latestVaultListing) {
throw new Error(`LeaveVaultUseCase: Could not find vault ${params.sharedVault.uuid}`)
}
const response = await this.vaultUserServer.deleteSharedVaultUser({
sharedVaultUuid: latestVaultListing.sharing.sharedVaultUuid,
userUuid: params.userUuid,
})
if (isErrorResponse(response)) {
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)
}
}

View File

@@ -0,0 +1,62 @@
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,17 @@
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultUsersServerInterface } from '@standardnotes/api'
export class RemoveVaultMemberUseCase {
constructor(private vaultUserServer: SharedVaultUsersServerInterface) {}
async execute(params: { sharedVaultUuid: string; userUuid: string }): Promise<ClientDisplayableError | void> {
const response = await this.vaultUserServer.deleteSharedVaultUser({
sharedVaultUuid: params.sharedVaultUuid,
userUuid: params.userUuid,
})
if (isErrorResponse(response)) {
return ClientDisplayableError.FromNetworkError(response)
}
}
}

View File

@@ -0,0 +1,144 @@
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,31 @@
import {
ClientDisplayableError,
SharedVaultInviteServerHash,
isErrorResponse,
SharedVaultPermission,
} from '@standardnotes/responses'
import { SharedVaultInvitesServerInterface } from '@standardnotes/api'
export class SendSharedVaultInviteUseCase {
constructor(private vaultInvitesServer: SharedVaultInvitesServerInterface) {}
async execute(params: {
sharedVaultUuid: string
recipientUuid: string
encryptedMessage: string
permissions: SharedVaultPermission
}): Promise<SharedVaultInviteServerHash | ClientDisplayableError> {
const response = await this.vaultInvitesServer.createInvite({
sharedVaultUuid: params.sharedVaultUuid,
recipientUuid: params.recipientUuid,
encryptedMessage: params.encryptedMessage,
permissions: params.permissions,
})
if (isErrorResponse(response)) {
return ClientDisplayableError.FromError(response.data.error)
}
return response.data.invite
}
}

View File

@@ -0,0 +1,100 @@
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

@@ -0,0 +1,103 @@
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,78 @@
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,31 @@
import {
ClientDisplayableError,
SharedVaultInviteServerHash,
isErrorResponse,
SharedVaultPermission,
} from '@standardnotes/responses'
import { SharedVaultInvitesServerInterface } from '@standardnotes/api'
export class UpdateSharedVaultInviteUseCase {
constructor(private vaultInvitesServer: SharedVaultInvitesServerInterface) {}
async execute(params: {
sharedVaultUuid: string
inviteUuid: string
encryptedMessage: string
permissions: SharedVaultPermission
}): Promise<SharedVaultInviteServerHash | ClientDisplayableError> {
const response = await this.vaultInvitesServer.updateInvite({
sharedVaultUuid: params.sharedVaultUuid,
inviteUuid: params.inviteUuid,
encryptedMessage: params.encryptedMessage,
permissions: params.permissions,
})
if (isErrorResponse(response)) {
return ClientDisplayableError.FromError(response.data.error)
}
return response.data.invite
}
}