refactor: break up vault services (#2364)

This commit is contained in:
Mo
2023-07-24 07:46:20 -05:00
committed by GitHub
parent f2d089ab24
commit 3281ac9d37
50 changed files with 763 additions and 633 deletions

View File

@@ -1,6 +1,6 @@
import { Result, UseCaseInterface } from '@standardnotes/domain-core' import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { PkcKeyPair } from '@standardnotes/sncrypto-common' import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { ReuploadAllInvites } from '../../SharedVaults/UseCase/ReuploadAllInvites' import { ReuploadAllInvites } from '../../VaultInvite/UseCase/ReuploadAllInvites'
import { ResendAllMessages } from '../../AsymmetricMessage/UseCase/ResendAllMessages' import { ResendAllMessages } from '../../AsymmetricMessage/UseCase/ResendAllMessages'
export class HandleKeyPairChange implements UseCaseInterface<void> { export class HandleKeyPairChange implements UseCaseInterface<void> {

View File

@@ -1,24 +1,15 @@
import { IsVaultAdmin } from './../VaultUser/UseCase/IsVaultAdmin'
import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface' import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface'
import { GetVaultUsers } from './UseCase/GetVaultUsers'
import { RemoveVaultMember } from './UseCase/RemoveSharedVaultMember'
import { DeleteSharedVault } from './UseCase/DeleteSharedVault' import { DeleteSharedVault } from './UseCase/DeleteSharedVault'
import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault' import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault'
import { ShareContactWithVault } from './UseCase/ShareContactWithVault' import { ShareContactWithVault } from './UseCase/ShareContactWithVault'
import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault' 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 { FindContact } from './../Contacts/UseCase/FindContact'
import { GetUntrustedPayload } from './../AsymmetricMessage/UseCase/GetUntrustedPayload'
import { GetTrustedPayload } from './../AsymmetricMessage/UseCase/GetTrustedPayload'
import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessage' import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessage'
import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation' import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation'
import { HandleKeyPairChange } from './../Contacts/UseCase/HandleKeyPairChange' import { HandleKeyPairChange } from './../Contacts/UseCase/HandleKeyPairChange'
import { CreateSharedVault } from './UseCase/CreateSharedVault' import { CreateSharedVault } from './UseCase/CreateSharedVault'
import { GetVault } from './../Vaults/UseCase/GetVault' import { GetVault } from './../Vaults/UseCase/GetVault'
import { SharedVaultInvitesServer } from '@standardnotes/api'
import { SharedVaultService } from './SharedVaultService' import { SharedVaultService } from './SharedVaultService'
import { SyncServiceInterface } from '../Sync/SyncServiceInterface' import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface' import { ItemManagerInterface } from '../Item/ItemManagerInterface'
@@ -40,56 +31,37 @@ describe('SharedVaultService', () => {
const encryption = {} as jest.Mocked<EncryptionProviderInterface> const encryption = {} as jest.Mocked<EncryptionProviderInterface>
const session = {} as jest.Mocked<SessionsClientInterface> const session = {} as jest.Mocked<SessionsClientInterface>
const vaults = {} as jest.Mocked<VaultServiceInterface> const vaults = {} as jest.Mocked<VaultServiceInterface>
const invitesServer = {} as jest.Mocked<SharedVaultInvitesServer>
const getVault = {} as jest.Mocked<GetVault> const getVault = {} as jest.Mocked<GetVault>
const createSharedVaultUseCase = {} as jest.Mocked<CreateSharedVault> const createSharedVaultUseCase = {} as jest.Mocked<CreateSharedVault>
const handleKeyPairChange = {} as jest.Mocked<HandleKeyPairChange> const handleKeyPairChange = {} as jest.Mocked<HandleKeyPairChange>
const notifyVaultUsersOfKeyRotation = {} as jest.Mocked<NotifyVaultUsersOfKeyRotation> const notifyVaultUsersOfKeyRotation = {} as jest.Mocked<NotifyVaultUsersOfKeyRotation>
const sendVaultDataChangeMessage = {} as jest.Mocked<SendVaultDataChangedMessage> 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 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 deleteThirdPartyVault = {} as jest.Mocked<DeleteThirdPartyVault>
const shareContactWithVault = {} as jest.Mocked<ShareContactWithVault> const shareContactWithVault = {} as jest.Mocked<ShareContactWithVault>
const convertToSharedVault = {} as jest.Mocked<ConvertToSharedVault> const convertToSharedVault = {} as jest.Mocked<ConvertToSharedVault>
const deleteSharedVaultUseCase = {} as jest.Mocked<DeleteSharedVault> const deleteSharedVaultUseCase = {} as jest.Mocked<DeleteSharedVault>
const removeVaultMember = {} as jest.Mocked<RemoveVaultMember> const isVaultAdmin = {} as jest.Mocked<IsVaultAdmin>
const getSharedVaultUsersUseCase = {} as jest.Mocked<GetVaultUsers>
const eventBus = {} as jest.Mocked<InternalEventBusInterface> const eventBus = {} as jest.Mocked<InternalEventBusInterface>
eventBus.addEventHandler = jest.fn() eventBus.addEventHandler = jest.fn()
service = new SharedVaultService( service = new SharedVaultService(
sync,
items, items,
encryption, encryption,
session, session,
vaults, vaults,
invitesServer,
getVault, getVault,
createSharedVaultUseCase, createSharedVaultUseCase,
handleKeyPairChange, handleKeyPairChange,
notifyVaultUsersOfKeyRotation, notifyVaultUsersOfKeyRotation,
sendVaultDataChangeMessage, sendVaultDataChangeMessage,
getTrustedPayload,
getUntrustedPayload,
findContact, findContact,
getAllContacts,
getVaultContacts,
acceptVaultInvite,
inviteToVault,
leaveVault,
deleteThirdPartyVault, deleteThirdPartyVault,
shareContactWithVault, shareContactWithVault,
convertToSharedVault, convertToSharedVault,
deleteSharedVaultUseCase, deleteSharedVaultUseCase,
removeVaultMember, isVaultAdmin,
getSharedVaultUsersUseCase,
eventBus, eventBus,
) )
}) })

View File

@@ -1,114 +1,70 @@
import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData' import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData'
import { InviteToVault } from './UseCase/InviteToVault' import { ClientDisplayableError, UserEventType } from '@standardnotes/responses'
import {
ClientDisplayableError,
SharedVaultInviteServerHash,
isErrorResponse,
SharedVaultUserServerHash,
isClientDisplayableError,
SharedVaultPermission,
UserEventType,
} from '@standardnotes/responses'
import { SharedVaultInvitesServer } from '@standardnotes/api'
import { import {
DecryptedItemInterface, DecryptedItemInterface,
PayloadEmitSource, PayloadEmitSource,
TrustedContactInterface, TrustedContactInterface,
SharedVaultListingInterface, SharedVaultListingInterface,
VaultListingInterface, VaultListingInterface,
AsymmetricMessageSharedVaultInvite,
KeySystemRootKeyStorageMode, KeySystemRootKeyStorageMode,
} from '@standardnotes/models' } from '@standardnotes/models'
import { SharedVaultServiceInterface } from './SharedVaultServiceInterface' import { SharedVaultServiceInterface } from './SharedVaultServiceInterface'
import { SharedVaultServiceEvent, SharedVaultServiceEventPayload } from './SharedVaultServiceEvent' import { SharedVaultServiceEvent, SharedVaultServiceEventPayload } from './SharedVaultServiceEvent'
import { GetVaultUsers } from './UseCase/GetVaultUsers'
import { RemoveVaultMember } from './UseCase/RemoveSharedVaultMember'
import { AbstractService } from '../Service/AbstractService' import { AbstractService } from '../Service/AbstractService'
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface' import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface' import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { SessionsClientInterface } from '../Session/SessionsClientInterface' import { SessionsClientInterface } from '../Session/SessionsClientInterface'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { SyncEvent, SyncEventReceivedSharedVaultInvitesData } from '../Event/SyncEvent' import { SyncEvent } from '../Event/SyncEvent'
import { SessionEvent } from '../Session/SessionEvent' import { SessionEvent } from '../Session/SessionEvent'
import { InternalEventInterface } from '../Internal/InternalEventInterface' import { InternalEventInterface } from '../Internal/InternalEventInterface'
import { LeaveVault } from './UseCase/LeaveSharedVault'
import { VaultServiceInterface } from '../Vaults/VaultServiceInterface' import { VaultServiceInterface } from '../Vaults/VaultServiceInterface'
import { UserEventServiceEvent, UserEventServiceEventPayload } from '../UserEvent/UserEventServiceEvent' import { UserEventServiceEvent, UserEventServiceEventPayload } from '../UserEvent/UserEventServiceEvent'
import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault' import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault'
import { DeleteSharedVault } from './UseCase/DeleteSharedVault' import { DeleteSharedVault } from './UseCase/DeleteSharedVault'
import { VaultServiceEvent, VaultServiceEventPayload } from '../Vaults/VaultServiceEvent' import { VaultServiceEvent, VaultServiceEventPayload } from '../Vaults/VaultServiceEvent'
import { AcceptVaultInvite } from './UseCase/AcceptVaultInvite'
import { GetTrustedPayload } from '../AsymmetricMessage/UseCase/GetTrustedPayload'
import { PendingSharedVaultInviteRecord } from './PendingSharedVaultInviteRecord'
import { GetUntrustedPayload } from '../AsymmetricMessage/UseCase/GetUntrustedPayload'
import { ShareContactWithVault } from './UseCase/ShareContactWithVault' import { ShareContactWithVault } from './UseCase/ShareContactWithVault'
import { GetVaultContacts } from './UseCase/GetVaultContacts'
import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation' import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation'
import { CreateSharedVault } from './UseCase/CreateSharedVault' import { CreateSharedVault } from './UseCase/CreateSharedVault'
import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessage' import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessage'
import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault' import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault'
import { GetVault } from '../Vaults/UseCase/GetVault' import { GetVault } from '../Vaults/UseCase/GetVault'
import { ContentType, Result } from '@standardnotes/domain-core' import { ContentType } from '@standardnotes/domain-core'
import { HandleKeyPairChange } from '../Contacts/UseCase/HandleKeyPairChange' import { HandleKeyPairChange } from '../Contacts/UseCase/HandleKeyPairChange'
import { FindContact } from '../Contacts/UseCase/FindContact' import { FindContact } from '../Contacts/UseCase/FindContact'
import { GetAllContacts } from '../Contacts/UseCase/GetAllContacts'
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface' import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
import { IsVaultAdmin } from '../VaultUser/UseCase/IsVaultAdmin'
export class SharedVaultService export class SharedVaultService
extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload> extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload>
implements SharedVaultServiceInterface, InternalEventHandlerInterface implements SharedVaultServiceInterface, InternalEventHandlerInterface
{ {
private pendingInvites: Record<string, PendingSharedVaultInviteRecord> = {}
constructor( constructor(
private sync: SyncServiceInterface,
private items: ItemManagerInterface, private items: ItemManagerInterface,
private encryption: EncryptionProviderInterface, private encryption: EncryptionProviderInterface,
private session: SessionsClientInterface, private session: SessionsClientInterface,
private vaults: VaultServiceInterface, private vaults: VaultServiceInterface,
private invitesServer: SharedVaultInvitesServer, private _getVault: GetVault,
private getVault: GetVault, private _createSharedVaultUseCase: CreateSharedVault,
private createSharedVaultUseCase: CreateSharedVault, private _handleKeyPairChange: HandleKeyPairChange,
private handleKeyPairChange: HandleKeyPairChange, private _notifyVaultUsersOfKeyRotation: NotifyVaultUsersOfKeyRotation,
private notifyVaultUsersOfKeyRotation: NotifyVaultUsersOfKeyRotation, private _sendVaultDataChangeMessage: SendVaultDataChangedMessage,
private sendVaultDataChangeMessage: SendVaultDataChangedMessage, private _findContact: FindContact,
private getTrustedPayload: GetTrustedPayload, private _deleteThirdPartyVault: DeleteThirdPartyVault,
private getUntrustedPayload: GetUntrustedPayload, private _shareContactWithVault: ShareContactWithVault,
private findContact: FindContact, private _convertToSharedVault: ConvertToSharedVault,
private getAllContacts: GetAllContacts, private _deleteSharedVaultUseCase: DeleteSharedVault,
private getVaultContacts: GetVaultContacts, private _isVaultAdmin: IsVaultAdmin,
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, eventBus: InternalEventBusInterface,
) { ) {
super(eventBus) super(eventBus)
eventBus.addEventHandler(this, SessionEvent.UserKeyPairChanged)
eventBus.addEventHandler(this, UserEventServiceEvent.UserEventReceived)
eventBus.addEventHandler(this, VaultServiceEvent.VaultRootKeyRotated)
this.eventDisposers.push( this.eventDisposers.push(
items.addObserver<TrustedContactInterface>( items.addObserver<TrustedContactInterface>(ContentType.TYPES.TrustedContact, async ({ changed, source }) => {
ContentType.TYPES.TrustedContact, if (source === PayloadEmitSource.LocalChanged && changed.length > 0) {
async ({ changed, inserted, source }) => { void this.handleTrustedContactsChange(changed)
await this.reprocessCachedInvitesTrustStatusAfterTrustedContactsChange() }
}),
if (source === PayloadEmitSource.LocalChanged && inserted.length > 0) {
void this.handleCreationOfNewTrustedContacts(inserted)
}
if (source === PayloadEmitSource.LocalChanged && changed.length > 0) {
void this.handleTrustedContactsChange(changed)
}
},
),
) )
this.eventDisposers.push( this.eventDisposers.push(
@@ -120,54 +76,88 @@ export class SharedVaultService
) )
} }
override deinit(): void {
super.deinit()
;(this.items as unknown) = undefined
;(this.encryption as unknown) = undefined
;(this.session as unknown) = undefined
;(this.vaults 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._findContact as unknown) = undefined
;(this._deleteThirdPartyVault as unknown) = undefined
;(this._shareContactWithVault as unknown) = undefined
;(this._convertToSharedVault as unknown) = undefined
;(this._deleteSharedVaultUseCase as unknown) = undefined
;(this._isVaultAdmin as unknown) = undefined
}
async handleEvent(event: InternalEventInterface): Promise<void> { async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === SessionEvent.UserKeyPairChanged) { switch (event.type) {
void this.invitesServer.deleteAllInboundInvites() case SessionEvent.UserKeyPairChanged: {
const eventData = event.payload as UserKeyPairChangedEventData
const eventData = event.payload as UserKeyPairChangedEventData void this._handleKeyPairChange.execute({
newKeys: eventData.current,
void this.handleKeyPairChange.execute({ previousKeys: eventData.previous,
newKeys: eventData.current, })
previousKeys: eventData.previous, break
}) }
} else if (event.type === UserEventServiceEvent.UserEventReceived) { case UserEventServiceEvent.UserEventReceived:
await this.handleUserEvent(event.payload as UserEventServiceEventPayload) await this.handleUserEvent(event.payload as UserEventServiceEventPayload)
} else if (event.type === VaultServiceEvent.VaultRootKeyRotated) { break
const payload = event.payload as VaultServiceEventPayload[VaultServiceEvent.VaultRootKeyRotated] case VaultServiceEvent.VaultRootKeyRotated: {
await this.handleVaultRootKeyRotatedEvent(payload.vault) const payload = event.payload as VaultServiceEventPayload[VaultServiceEvent.VaultRootKeyRotated]
} else if (event.type === SyncEvent.ReceivedSharedVaultInvites) { await this.handleVaultRootKeyRotatedEvent(payload.vault)
await this.processInboundInvites(event.payload as SyncEventReceivedSharedVaultInvitesData) break
} else if (event.type === SyncEvent.ReceivedRemoteSharedVaults) { }
void this.notifyCollaborationStatusChanged() case SyncEvent.ReceivedRemoteSharedVaults:
void this.notifyEventSync(SharedVaultServiceEvent.SharedVaultStatusChanged)
break
} }
} }
private async handleUserEvent(event: UserEventServiceEventPayload): Promise<void> { private async handleUserEvent(event: UserEventServiceEventPayload): Promise<void> {
if (event.eventPayload.eventType === UserEventType.RemovedFromSharedVault) { switch (event.eventPayload.eventType) {
const vault = this.getVault.execute<SharedVaultListingInterface>({ case UserEventType.RemovedFromSharedVault: {
sharedVaultUuid: event.eventPayload.sharedVaultUuid, const vault = this._getVault.execute<SharedVaultListingInterface>({
}) sharedVaultUuid: event.eventPayload.sharedVaultUuid,
if (!vault.isFailed()) { })
await this.deleteThirdPartyVault.execute(vault.getValue()) if (!vault.isFailed()) {
await this._deleteThirdPartyVault.execute(vault.getValue())
}
break
} }
} else if (event.eventPayload.eventType === UserEventType.SharedVaultItemRemoved) { case UserEventType.SharedVaultItemRemoved: {
const item = this.items.findItem(event.eventPayload.itemUuid) const item = this.items.findItem(event.eventPayload.itemUuid)
if (item) { if (item) {
this.items.removeItemsLocally([item]) this.items.removeItemsLocally([item])
}
break
} }
} }
} }
private isCurrentUserVaultOwner(sharedVault: SharedVaultListingInterface): boolean {
if (!sharedVault.sharing.ownerUserUuid) {
throw new Error(`Shared vault ${sharedVault.sharing.sharedVaultUuid} does not have an owner user uuid`)
}
return sharedVault.sharing.ownerUserUuid === this.session.userUuid
}
private async handleVaultRootKeyRotatedEvent(vault: VaultListingInterface): Promise<void> { private async handleVaultRootKeyRotatedEvent(vault: VaultListingInterface): Promise<void> {
if (!vault.isSharedVaultListing()) { if (!vault.isSharedVaultListing()) {
return return
} }
if (!this.isCurrentUserSharedVaultOwner(vault)) { if (!this.isCurrentUserVaultOwner(vault)) {
return return
} }
await this.notifyVaultUsersOfKeyRotation.execute({ await this._notifyVaultUsersOfKeyRotation.execute({
sharedVault: vault, sharedVault: vault,
senderUuid: this.session.getSureUser().uuid, senderUuid: this.session.getSureUser().uuid,
keys: { keys: {
@@ -183,7 +173,7 @@ export class SharedVaultService
userInputtedPassword: string | undefined userInputtedPassword: string | undefined
storagePreference?: KeySystemRootKeyStorageMode storagePreference?: KeySystemRootKeyStorageMode
}): Promise<VaultListingInterface | ClientDisplayableError> { }): Promise<VaultListingInterface | ClientDisplayableError> {
return this.createSharedVaultUseCase.execute({ return this._createSharedVaultUseCase.execute({
vaultName: dto.name, vaultName: dto.name,
vaultDescription: dto.description, vaultDescription: dto.description,
userInputtedPassword: dto.userInputtedPassword, userInputtedPassword: dto.userInputtedPassword,
@@ -194,11 +184,7 @@ export class SharedVaultService
async convertVaultToSharedVault( async convertVaultToSharedVault(
vault: VaultListingInterface, vault: VaultListingInterface,
): Promise<SharedVaultListingInterface | ClientDisplayableError> { ): Promise<SharedVaultListingInterface | ClientDisplayableError> {
return this.convertToSharedVault.execute({ vault }) return this._convertToSharedVault.execute({ vault })
}
public getCachedPendingInviteRecords(): PendingSharedVaultInviteRecord[] {
return Object.values(this.pendingInvites)
} }
private getAllSharedVaults(): SharedVaultListingInterface[] { private getAllSharedVaults(): SharedVaultListingInterface[] {
@@ -206,38 +192,6 @@ export class SharedVaultService
return vaults as SharedVaultListingInterface[] return vaults as SharedVaultListingInterface[]
} }
private findSharedVault(sharedVaultUuid: string): SharedVaultListingInterface | undefined {
const result = this.getVault.execute<SharedVaultListingInterface>({ sharedVaultUuid })
if (result.isFailed()) {
return undefined
}
return result.getValue()
}
public isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean {
if (!sharedVault.sharing.ownerUserUuid) {
throw new Error(`Shared vault ${sharedVault.sharing.sharedVaultUuid} does not have an owner user uuid`)
}
return sharedVault.sharing.ownerUserUuid === this.session.userUuid
}
public isCurrentUserSharedVaultOwner(sharedVault: SharedVaultListingInterface): boolean {
if (!sharedVault.sharing.ownerUserUuid) {
throw new Error(`Shared vault ${sharedVault.sharing.sharedVaultUuid} does not have an owner user uuid`)
}
return sharedVault.sharing.ownerUserUuid === this.session.userUuid
}
public isSharedVaultUserSharedVaultOwner(user: SharedVaultUserServerHash): boolean {
const vault = this.findSharedVault(user.shared_vault_uuid)
return vault != undefined && vault.sharing.ownerUserUuid === user.user_uuid
}
private async handleCreationOfNewTrustedContacts(_contacts: TrustedContactInterface[]): Promise<void> {
await this.downloadInboundInvites()
}
private async handleTrustedContactsChange(contacts: TrustedContactInterface[]): Promise<void> { private async handleTrustedContactsChange(contacts: TrustedContactInterface[]): Promise<void> {
for (const contact of contacts) { for (const contact of contacts) {
if (contact.isMe) { if (contact.isMe) {
@@ -254,7 +208,7 @@ export class SharedVaultService
continue continue
} }
await this.sendVaultDataChangeMessage.execute({ await this._sendVaultDataChangeMessage.execute({
vault, vault,
senderUuid: this.session.getSureUser().uuid, senderUuid: this.session.getSureUser().uuid,
keys: { keys: {
@@ -265,220 +219,8 @@ export class SharedVaultService
} }
} }
public async downloadInboundInvites(): Promise<ClientDisplayableError | SharedVaultInviteServerHash[]> {
const response = await this.invitesServer.getInboundUserInvites()
if (isErrorResponse(response)) {
return ClientDisplayableError.FromString(`Failed to get inbound user invites ${response}`)
}
this.pendingInvites = {}
await this.processInboundInvites(response.data.invites)
return response.data.invites
}
public async getOutboundInvites(
sharedVault?: SharedVaultListingInterface,
): Promise<SharedVaultInviteServerHash[] | ClientDisplayableError> {
const response = await this.invitesServer.getOutboundUserInvites()
if (isErrorResponse(response)) {
return ClientDisplayableError.FromString(`Failed to get outbound user invites ${response}`)
}
if (sharedVault) {
return response.data.invites.filter((invite) => invite.shared_vault_uuid === sharedVault.sharing.sharedVaultUuid)
}
return response.data.invites
}
public async deleteInvite(invite: SharedVaultInviteServerHash): Promise<ClientDisplayableError | void> {
const response = await this.invitesServer.deleteInvite({
sharedVaultUuid: invite.shared_vault_uuid,
inviteUuid: invite.uuid,
})
if (isErrorResponse(response)) {
return ClientDisplayableError.FromString(`Failed to delete invite ${response}`)
}
delete this.pendingInvites[invite.uuid]
}
public async deleteSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> { public async deleteSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> {
return this.deleteSharedVaultUseCase.execute({ sharedVault }) return this._deleteSharedVaultUseCase.execute({ sharedVault })
}
private async reprocessCachedInvitesTrustStatusAfterTrustedContactsChange(): Promise<void> {
const cachedInvites = this.getCachedPendingInviteRecords().map((record) => record.invite)
await this.processInboundInvites(cachedInvites)
}
private async processInboundInvites(invites: SharedVaultInviteServerHash[]): Promise<void> {
if (invites.length === 0) {
return
}
for (const invite of invites) {
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(),
})
if (!trustedMessage.isFailed()) {
this.pendingInvites[invite.uuid] = {
invite,
message: trustedMessage.getValue(),
trusted: true,
}
continue
}
}
const untrustedMessage = this.getUntrustedPayload.execute<AsymmetricMessageSharedVaultInvite>({
message: invite,
privateKey: this.encryption.getKeyPair().privateKey,
})
if (!untrustedMessage.isFailed()) {
this.pendingInvites[invite.uuid] = {
invite,
message: untrustedMessage.getValue(),
trusted: false,
}
}
}
await this.notifyCollaborationStatusChanged()
}
private async notifyCollaborationStatusChanged(): Promise<void> {
await this.notifyEventSync(SharedVaultServiceEvent.SharedVaultStatusChanged)
}
async acceptPendingSharedVaultInvite(pendingInvite: PendingSharedVaultInviteRecord): Promise<void> {
if (!pendingInvite.trusted) {
throw new Error('Cannot accept untrusted invite')
}
await this.acceptVaultInvite.execute({ invite: pendingInvite.invite, message: pendingInvite.message })
delete this.pendingInvites[pendingInvite.invite.uuid]
void this.sync.sync()
await this.decryptErroredItemsAfterInviteAccept()
await this.sync.syncSharedVaultsFromScratch([pendingInvite.invite.shared_vault_uuid])
}
private async decryptErroredItemsAfterInviteAccept(): Promise<void> {
await this.encryption.decryptErroredPayloads()
}
public async getInvitableContactsForSharedVault(
sharedVault: SharedVaultListingInterface,
): Promise<TrustedContactInterface[]> {
const users = await this.getSharedVaultUsers(sharedVault)
if (!users) {
return []
}
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 contacts = await this.getVaultContacts.execute(sharedVault.sharing.sharedVaultUuid)
if (contacts.isFailed()) {
return []
}
return contacts.getValue()
}
async inviteContactToSharedVault(
sharedVault: SharedVaultListingInterface,
contact: TrustedContactInterface,
permissions: SharedVaultPermission,
): Promise<Result<SharedVaultInviteServerHash>> {
const sharedVaultContacts = await this.getSharedVaultContacts(sharedVault)
const result = await this.inviteToVault.execute({
keys: {
encryption: this.encryption.getKeyPair(),
signing: this.encryption.getSigningKeyPair(),
},
senderUuid: this.session.getSureUser().uuid,
sharedVault,
recipient: contact,
sharedVaultContacts,
permissions,
})
void this.notifyCollaborationStatusChanged()
await this.sync.sync()
return result
}
async removeUserFromSharedVault(
sharedVault: SharedVaultListingInterface,
userUuid: string,
): Promise<ClientDisplayableError | void> {
if (!this.isCurrentUserSharedVaultAdmin(sharedVault)) {
throw new Error('Only vault admins can remove users')
}
if (this.vaults.isVaultLocked(sharedVault)) {
throw new Error('Cannot remove user from locked vault')
}
const result = await this.removeVaultMember.execute({
sharedVaultUuid: sharedVault.sharing.sharedVaultUuid,
userUuid,
})
if (isClientDisplayableError(result)) {
return result
}
void this.notifyCollaborationStatusChanged()
await this.vaults.rotateVaultRootKey(sharedVault)
}
async leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> {
const result = await this.leaveVault.execute({
sharedVault: sharedVault,
userUuid: this.session.getSureUser().uuid,
})
if (isClientDisplayableError(result)) {
return result
}
void this.notifyCollaborationStatusChanged()
}
async getSharedVaultUsers(
sharedVault: SharedVaultListingInterface,
): Promise<SharedVaultUserServerHash[] | undefined> {
return this.getSharedVaultUsersUseCase.execute({ sharedVaultUuid: sharedVault.sharing.sharedVaultUuid })
} }
async shareContactWithVaults(contact: TrustedContactInterface): Promise<void> { async shareContactWithVaults(contact: TrustedContactInterface): Promise<void> {
@@ -486,10 +228,17 @@ export class SharedVaultService
throw new Error('Cannot share self contact') throw new Error('Cannot share self contact')
} }
const ownedVaults = this.getAllSharedVaults().filter(this.isCurrentUserSharedVaultAdmin.bind(this)) const ownedVaults = this.getAllSharedVaults().filter((vault) => {
return this._isVaultAdmin
.execute({
sharedVault: vault,
userUuid: this.session.userUuid,
})
.getValue()
})
for (const vault of ownedVaults) { for (const vault of ownedVaults) {
await this.shareContactWithVault.execute({ await this._shareContactWithVault.execute({
keys: { keys: {
encryption: this.encryption.getKeyPair(), encryption: this.encryption.getKeyPair(),
signing: this.encryption.getSigningKeyPair(), signing: this.encryption.getSigningKeyPair(),
@@ -506,7 +255,7 @@ export class SharedVaultService
return undefined return undefined
} }
const contact = this.findContact.execute({ userUuid: item.last_edited_by_uuid }) const contact = this._findContact.execute({ userUuid: item.last_edited_by_uuid })
return contact.isFailed() ? undefined : contact.getValue() return contact.isFailed() ? undefined : contact.getValue()
} }
@@ -516,37 +265,8 @@ export class SharedVaultService
return undefined return undefined
} }
const contact = this.findContact.execute({ userUuid: item.user_uuid }) const contact = this._findContact.execute({ userUuid: item.user_uuid })
return contact.isFailed() ? undefined : contact.getValue() return contact.isFailed() ? undefined : contact.getValue()
} }
override deinit(): void {
super.deinit()
;(this.sync 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

@@ -1,9 +1,4 @@
import { import { ClientDisplayableError } from '@standardnotes/responses'
ClientDisplayableError,
SharedVaultInviteServerHash,
SharedVaultUserServerHash,
SharedVaultPermission,
} from '@standardnotes/responses'
import { import {
DecryptedItemInterface, DecryptedItemInterface,
TrustedContactInterface, TrustedContactInterface,
@@ -13,8 +8,6 @@ import {
} from '@standardnotes/models' } from '@standardnotes/models'
import { AbstractService } from '../Service/AbstractService' import { AbstractService } from '../Service/AbstractService'
import { SharedVaultServiceEvent, SharedVaultServiceEventPayload } from './SharedVaultServiceEvent' import { SharedVaultServiceEvent, SharedVaultServiceEventPayload } from './SharedVaultServiceEvent'
import { PendingSharedVaultInviteRecord } from './PendingSharedVaultInviteRecord'
import { Result } from '@standardnotes/domain-core'
export interface SharedVaultServiceInterface export interface SharedVaultServiceInterface
extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload> { extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload> {
@@ -25,32 +18,8 @@ export interface SharedVaultServiceInterface
storagePreference?: KeySystemRootKeyStorageMode storagePreference?: KeySystemRootKeyStorageMode
}): Promise<VaultListingInterface | ClientDisplayableError> }): Promise<VaultListingInterface | ClientDisplayableError>
deleteSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> deleteSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void>
convertVaultToSharedVault(vault: VaultListingInterface): Promise<SharedVaultListingInterface | ClientDisplayableError> convertVaultToSharedVault(vault: VaultListingInterface): Promise<SharedVaultListingInterface | ClientDisplayableError>
inviteContactToSharedVault(
sharedVault: SharedVaultListingInterface,
contact: TrustedContactInterface,
permissions: SharedVaultPermission,
): Promise<Result<SharedVaultInviteServerHash>>
removeUserFromSharedVault(
sharedVault: SharedVaultListingInterface,
userUuid: string,
): Promise<ClientDisplayableError | void>
leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void>
getSharedVaultUsers(sharedVault: SharedVaultListingInterface): Promise<SharedVaultUserServerHash[] | undefined>
isSharedVaultUserSharedVaultOwner(user: SharedVaultUserServerHash): boolean
isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean
getItemLastEditedBy(item: DecryptedItemInterface): TrustedContactInterface | undefined getItemLastEditedBy(item: DecryptedItemInterface): TrustedContactInterface | undefined
getItemSharedBy(item: DecryptedItemInterface): TrustedContactInterface | undefined getItemSharedBy(item: DecryptedItemInterface): TrustedContactInterface | undefined
downloadInboundInvites(): Promise<ClientDisplayableError | SharedVaultInviteServerHash[]>
getOutboundInvites(
sharedVault?: SharedVaultListingInterface,
): Promise<SharedVaultInviteServerHash[] | ClientDisplayableError>
acceptPendingSharedVaultInvite(pendingInvite: PendingSharedVaultInviteRecord): Promise<void>
getCachedPendingInviteRecords(): PendingSharedVaultInviteRecord[]
getInvitableContactsForSharedVault(sharedVault: SharedVaultListingInterface): Promise<TrustedContactInterface[]>
deleteInvite(invite: SharedVaultInviteServerHash): Promise<ClientDisplayableError | void>
} }

View File

@@ -4,8 +4,8 @@ import { SharedVaultInviteServerHash, isErrorResponse } from '@standardnotes/res
import { SendVaultKeyChangedMessage } from './SendVaultKeyChangedMessage' import { SendVaultKeyChangedMessage } from './SendVaultKeyChangedMessage'
import { PkcKeyPair } from '@standardnotes/sncrypto-common' import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { Result, UseCaseInterface } from '@standardnotes/domain-core' import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { InviteToVault } from './InviteToVault' import { InviteToVault } from '../../VaultInvite/UseCase/InviteToVault'
import { GetVaultContacts } from './GetVaultContacts' import { GetVaultContacts } from '../../VaultUser/UseCase/GetVaultContacts'
import { DecryptOwnMessage } from '../../Encryption/UseCase/Asymmetric/DecryptOwnMessage' import { DecryptOwnMessage } from '../../Encryption/UseCase/Asymmetric/DecryptOwnMessage'
import { FindContact } from '../../Contacts/UseCase/FindContact' import { FindContact } from '../../Contacts/UseCase/FindContact'

View File

@@ -5,7 +5,7 @@ import {
TrustedContactInterface, TrustedContactInterface,
} from '@standardnotes/models' } from '@standardnotes/models'
import { AsymmetricMessageServerHash } from '@standardnotes/responses' import { AsymmetricMessageServerHash } from '@standardnotes/responses'
import { GetVaultUsers } from './GetVaultUsers' import { GetVaultUsers } from '../../VaultUser/UseCase/GetVaultUsers'
import { PkcKeyPair } from '@standardnotes/sncrypto-common' import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage' import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage'
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage' import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'

View File

@@ -5,7 +5,7 @@ import {
TrustedContactInterface, TrustedContactInterface,
} from '@standardnotes/models' } from '@standardnotes/models'
import { AsymmetricMessageServerHash } from '@standardnotes/responses' import { AsymmetricMessageServerHash } from '@standardnotes/responses'
import { GetVaultUsers } from './GetVaultUsers' import { GetVaultUsers } from '../../VaultUser/UseCase/GetVaultUsers'
import { PkcKeyPair } from '@standardnotes/sncrypto-common' import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage' import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage'
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage' import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'

View File

@@ -8,7 +8,7 @@ import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage'
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage' import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
import { Result, UseCaseInterface } from '@standardnotes/domain-core' import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { FindContact } from '../../Contacts/UseCase/FindContact' import { FindContact } from '../../Contacts/UseCase/FindContact'
import { GetVaultUsers } from './GetVaultUsers' import { GetVaultUsers } from '../../VaultUser/UseCase/GetVaultUsers'
export class ShareContactWithVault implements UseCaseInterface<void> { export class ShareContactWithVault implements UseCaseInterface<void> {
constructor( constructor(

View File

@@ -1,31 +0,0 @@
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.FromNetworkError(response)
}
return response.data.invite
}
}

View File

@@ -1,7 +1,7 @@
import { AsymmetricMessageSharedVaultInvite } from '@standardnotes/models' import { AsymmetricMessageSharedVaultInvite } from '@standardnotes/models'
import { SharedVaultInviteServerHash } from '@standardnotes/responses' import { SharedVaultInviteServerHash } from '@standardnotes/responses'
export type PendingSharedVaultInviteRecord = { export type InviteRecord = {
invite: SharedVaultInviteServerHash invite: SharedVaultInviteServerHash
message: AsymmetricMessageSharedVaultInvite message: AsymmetricMessageSharedVaultInvite
trusted: boolean trusted: boolean

View File

@@ -9,7 +9,7 @@ import { SendVaultInvite } from './SendVaultInvite'
import { PkcKeyPair } from '@standardnotes/sncrypto-common' import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage' import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
import { Result, UseCaseInterface } from '@standardnotes/domain-core' import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { ShareContactWithVault } from './ShareContactWithVault' import { ShareContactWithVault } from '../../SharedVaults/UseCase/ShareContactWithVault'
import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface' import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface'
export class InviteToVault implements UseCaseInterface<SharedVaultInviteServerHash> { export class InviteToVault implements UseCaseInterface<SharedVaultInviteServerHash> {

View File

@@ -1,4 +1,4 @@
import { DecryptOwnMessage } from './../../Encryption/UseCase/Asymmetric/DecryptOwnMessage' import { DecryptOwnMessage } from '../../Encryption/UseCase/Asymmetric/DecryptOwnMessage'
import { AsymmetricMessageSharedVaultInvite, TrustedContactInterface } from '@standardnotes/models' import { AsymmetricMessageSharedVaultInvite, TrustedContactInterface } from '@standardnotes/models'
import { SharedVaultInviteServerHash } from '@standardnotes/responses' import { SharedVaultInviteServerHash } from '@standardnotes/responses'
import { PkcKeyPair } from '@standardnotes/sncrypto-common' import { PkcKeyPair } from '@standardnotes/sncrypto-common'

View File

@@ -0,0 +1,274 @@
import { AcceptVaultInvite } from './UseCase/AcceptVaultInvite'
import { SyncEvent, SyncEventReceivedSharedVaultInvitesData } from './../Event/SyncEvent'
import { SessionEvent } from './../Session/SessionEvent'
import { InternalEventInterface } from './../Internal/InternalEventInterface'
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
import { ItemManagerInterface } from './../Item/ItemManagerInterface'
import { FindContact } from './../Contacts/UseCase/FindContact'
import { GetUntrustedPayload } from './../AsymmetricMessage/UseCase/GetUntrustedPayload'
import { GetTrustedPayload } from './../AsymmetricMessage/UseCase/GetTrustedPayload'
import { InviteRecord } from './InviteRecord'
import { VaultUserServiceInterface } from './../VaultUser/VaultUserServiceInterface'
import { GetVault } from './../Vaults/UseCase/GetVault'
import { InviteToVault } from './UseCase/InviteToVault'
import { GetVaultContacts } from '../VaultUser/UseCase/GetVaultContacts'
import { SyncServiceInterface } from './../Sync/SyncServiceInterface'
import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface'
import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface'
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
import { GetAllContacts } from './../Contacts/UseCase/GetAllContacts'
import {
AsymmetricMessageSharedVaultInvite,
PayloadEmitSource,
SharedVaultListingInterface,
TrustedContactInterface,
} from '@standardnotes/models'
import { VaultInviteServiceInterface } from './VaultInviteServiceInterface'
import {
ClientDisplayableError,
SharedVaultInviteServerHash,
SharedVaultPermission,
SharedVaultUserServerHash,
isErrorResponse,
} from '@standardnotes/responses'
import { AbstractService } from './../Service/AbstractService'
import { VaultInviteServiceEvent } from './VaultInviteServiceEvent'
import { ContentType, Result } from '@standardnotes/domain-core'
import { SharedVaultInvitesServer } from '@standardnotes/api'
export class VaultInviteService
extends AbstractService<VaultInviteServiceEvent>
implements VaultInviteServiceInterface, InternalEventHandlerInterface
{
private pendingInvites: Record<string, InviteRecord> = {}
constructor(
items: ItemManagerInterface,
private session: SessionsClientInterface,
private vaultUsers: VaultUserServiceInterface,
private sync: SyncServiceInterface,
private encryption: EncryptionProviderInterface,
private invitesServer: SharedVaultInvitesServer,
private _getAllContacts: GetAllContacts,
private _getVault: GetVault,
private _getVaultContacts: GetVaultContacts,
private _inviteToVault: InviteToVault,
private _getTrustedPayload: GetTrustedPayload,
private _getUntrustedPayload: GetUntrustedPayload,
private _findContact: FindContact,
private _acceptVaultInvite: AcceptVaultInvite,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
this.eventDisposers.push(
items.addObserver<TrustedContactInterface>(ContentType.TYPES.TrustedContact, async ({ inserted, source }) => {
if (source === PayloadEmitSource.LocalChanged && inserted.length > 0) {
void this.downloadInboundInvites()
}
await this.reprocessCachedInvitesTrustStatusAfterTrustedContactsChange()
}),
)
}
override deinit(): void {
super.deinit()
;(this.session as unknown) = undefined
;(this.vaultUsers as unknown) = undefined
;(this.sync as unknown) = undefined
;(this.encryption as unknown) = undefined
;(this.invitesServer as unknown) = undefined
;(this._getAllContacts as unknown) = undefined
;(this._getVault as unknown) = undefined
;(this._getVaultContacts as unknown) = undefined
;(this._inviteToVault as unknown) = undefined
;(this._getTrustedPayload as unknown) = undefined
;(this._getUntrustedPayload as unknown) = undefined
;(this._findContact as unknown) = undefined
;(this._acceptVaultInvite as unknown) = undefined
this.pendingInvites = {}
}
async handleEvent(event: InternalEventInterface): Promise<void> {
switch (event.type) {
case SessionEvent.UserKeyPairChanged:
void this.invitesServer.deleteAllInboundInvites()
break
case SyncEvent.ReceivedSharedVaultInvites:
await this.processInboundInvites(event.payload as SyncEventReceivedSharedVaultInvitesData)
break
}
}
public getCachedPendingInviteRecords(): InviteRecord[] {
return Object.values(this.pendingInvites)
}
public async downloadInboundInvites(): Promise<ClientDisplayableError | SharedVaultInviteServerHash[]> {
const response = await this.invitesServer.getInboundUserInvites()
if (isErrorResponse(response)) {
return ClientDisplayableError.FromString(`Failed to get inbound user invites ${response}`)
}
this.pendingInvites = {}
await this.processInboundInvites(response.data.invites)
return response.data.invites
}
public async getOutboundInvites(
sharedVault?: SharedVaultListingInterface,
): Promise<SharedVaultInviteServerHash[] | ClientDisplayableError> {
const response = await this.invitesServer.getOutboundUserInvites()
if (isErrorResponse(response)) {
return ClientDisplayableError.FromString(`Failed to get outbound user invites ${response}`)
}
if (sharedVault) {
return response.data.invites.filter((invite) => invite.shared_vault_uuid === sharedVault.sharing.sharedVaultUuid)
}
return response.data.invites
}
public async acceptInvite(pendingInvite: InviteRecord): Promise<void> {
if (!pendingInvite.trusted) {
throw new Error('Cannot accept untrusted invite')
}
await this._acceptVaultInvite.execute({ invite: pendingInvite.invite, message: pendingInvite.message })
delete this.pendingInvites[pendingInvite.invite.uuid]
void this.sync.sync()
await this.encryption.decryptErroredPayloads()
await this.sync.syncSharedVaultsFromScratch([pendingInvite.invite.shared_vault_uuid])
}
public async getInvitableContactsForSharedVault(
sharedVault: SharedVaultListingInterface,
): Promise<TrustedContactInterface[]> {
const users = await this.vaultUsers.getSharedVaultUsers(sharedVault)
if (!users) {
return []
}
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
})
}
public async inviteContactToSharedVault(
sharedVault: SharedVaultListingInterface,
contact: TrustedContactInterface,
permissions: SharedVaultPermission,
): Promise<Result<SharedVaultInviteServerHash>> {
const contactsResult = await this._getVaultContacts.execute(sharedVault.sharing.sharedVaultUuid)
if (contactsResult.isFailed()) {
return Result.fail(contactsResult.getError())
}
const contacts = contactsResult.getValue()
const result = await this._inviteToVault.execute({
keys: {
encryption: this.encryption.getKeyPair(),
signing: this.encryption.getSigningKeyPair(),
},
senderUuid: this.session.getSureUser().uuid,
sharedVault,
recipient: contact,
sharedVaultContacts: contacts,
permissions,
})
void this.notifyEvent(VaultInviteServiceEvent.InviteSent)
await this.sync.sync()
return result
}
public isVaultUserOwner(user: SharedVaultUserServerHash): boolean {
const result = this._getVault.execute<SharedVaultListingInterface>({ sharedVaultUuid: user.shared_vault_uuid })
if (result.isFailed()) {
return false
}
const vault = result.getValue()
return vault != undefined && vault.sharing.ownerUserUuid === user.user_uuid
}
public async deleteInvite(invite: SharedVaultInviteServerHash): Promise<ClientDisplayableError | void> {
const response = await this.invitesServer.deleteInvite({
sharedVaultUuid: invite.shared_vault_uuid,
inviteUuid: invite.uuid,
})
if (isErrorResponse(response)) {
return ClientDisplayableError.FromString(`Failed to delete invite ${response}`)
}
delete this.pendingInvites[invite.uuid]
}
private async reprocessCachedInvitesTrustStatusAfterTrustedContactsChange(): Promise<void> {
const cachedInvites = this.getCachedPendingInviteRecords().map((record) => record.invite)
await this.processInboundInvites(cachedInvites)
}
private async processInboundInvites(invites: SharedVaultInviteServerHash[]): Promise<void> {
if (invites.length === 0) {
return
}
for (const invite of invites) {
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(),
})
if (!trustedMessage.isFailed()) {
this.pendingInvites[invite.uuid] = {
invite,
message: trustedMessage.getValue(),
trusted: true,
}
continue
}
}
const untrustedMessage = this._getUntrustedPayload.execute<AsymmetricMessageSharedVaultInvite>({
message: invite,
privateKey: this.encryption.getKeyPair().privateKey,
})
if (!untrustedMessage.isFailed()) {
this.pendingInvites[invite.uuid] = {
invite,
message: untrustedMessage.getValue(),
trusted: false,
}
}
}
void this.notifyEvent(VaultInviteServiceEvent.InvitesReloaded)
}
}

View File

@@ -0,0 +1,4 @@
export enum VaultInviteServiceEvent {
InviteSent = 'VaultInviteServiceEvent.InviteSent',
InvitesReloaded = 'VaultInviteServiceEvent.InvitesReloaded',
}

View File

@@ -0,0 +1,22 @@
import { InviteRecord } from './InviteRecord'
import { ApplicationServiceInterface } from '../Service/ApplicationServiceInterface'
import { SharedVaultListingInterface, TrustedContactInterface } from '@standardnotes/models'
import { ClientDisplayableError, SharedVaultInviteServerHash, SharedVaultPermission } from '@standardnotes/responses'
import { VaultInviteServiceEvent } from './VaultInviteServiceEvent'
import { Result } from '@standardnotes/domain-core'
export interface VaultInviteServiceInterface extends ApplicationServiceInterface<VaultInviteServiceEvent, unknown> {
getInvitableContactsForSharedVault(sharedVault: SharedVaultListingInterface): Promise<TrustedContactInterface[]>
inviteContactToSharedVault(
sharedVault: SharedVaultListingInterface,
contact: TrustedContactInterface,
permissions: SharedVaultPermission,
): Promise<Result<SharedVaultInviteServerHash>>
getCachedPendingInviteRecords(): InviteRecord[]
deleteInvite(invite: SharedVaultInviteServerHash): Promise<ClientDisplayableError | void>
downloadInboundInvites(): Promise<ClientDisplayableError | SharedVaultInviteServerHash[]>
getOutboundInvites(
sharedVault?: SharedVaultListingInterface,
): Promise<SharedVaultInviteServerHash[] | ClientDisplayableError>
acceptInvite(pendingInvite: InviteRecord): Promise<void>
}

View File

@@ -0,0 +1,12 @@
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
import { SharedVaultListingInterface } from '@standardnotes/models'
export class IsVaultAdmin implements SyncUseCaseInterface<boolean> {
execute(dto: { sharedVault: SharedVaultListingInterface; userUuid: string }): Result<boolean> {
if (!dto.sharedVault.sharing.ownerUserUuid) {
throw new Error(`Shared vault ${dto.sharedVault.sharing.sharedVaultUuid} does not have an owner user uuid`)
}
return Result.ok(dto.sharedVault.sharing.ownerUserUuid === dto.userUuid)
}
}

View File

@@ -1,6 +1,6 @@
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses' import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultUsersServerInterface } from '@standardnotes/api' import { SharedVaultUsersServerInterface } from '@standardnotes/api'
import { DeleteThirdPartyVault } from './DeleteExternalSharedVault' import { DeleteThirdPartyVault } from '../../SharedVaults/UseCase/DeleteExternalSharedVault'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface' import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { SharedVaultListingInterface } from '@standardnotes/models' import { SharedVaultListingInterface } from '@standardnotes/models'

View File

@@ -1,17 +1,20 @@
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses' import { getErrorFromErrorResponse, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultUsersServerInterface } from '@standardnotes/api' import { SharedVaultUsersServerInterface } from '@standardnotes/api'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
export class RemoveVaultMember { export class RemoveVaultMember implements UseCaseInterface<void> {
constructor(private vaultUserServer: SharedVaultUsersServerInterface) {} constructor(private vaultUserServer: SharedVaultUsersServerInterface) {}
async execute(params: { sharedVaultUuid: string; userUuid: string }): Promise<ClientDisplayableError | void> { async execute(params: { sharedVaultUuid: string; userUuid: string }): Promise<Result<void>> {
const response = await this.vaultUserServer.deleteSharedVaultUser({ const response = await this.vaultUserServer.deleteSharedVaultUser({
sharedVaultUuid: params.sharedVaultUuid, sharedVaultUuid: params.sharedVaultUuid,
userUuid: params.userUuid, userUuid: params.userUuid,
}) })
if (isErrorResponse(response)) { if (isErrorResponse(response)) {
return ClientDisplayableError.FromNetworkError(response) return Result.fail(getErrorFromErrorResponse(response).message)
} }
return Result.ok()
} }
} }

View File

@@ -0,0 +1,103 @@
import { LeaveVault } from './UseCase/LeaveSharedVault'
import { GetVault } from './../Vaults/UseCase/GetVault'
import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface'
import { RemoveVaultMember } from './UseCase/RemoveSharedVaultMember'
import { VaultServiceInterface } from './../Vaults/VaultServiceInterface'
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
import { GetVaultUsers } from './UseCase/GetVaultUsers'
import { SharedVaultListingInterface } from '@standardnotes/models'
import { VaultUserServiceInterface } from './VaultUserServiceInterface'
import { ClientDisplayableError, SharedVaultUserServerHash, isClientDisplayableError } from '@standardnotes/responses'
import { AbstractService } from './../Service/AbstractService'
import { VaultUserServiceEvent } from './VaultUserServiceEvent'
import { Result } from '@standardnotes/domain-core'
import { IsVaultAdmin } from './UseCase/IsVaultAdmin'
export class VaultUserService extends AbstractService<VaultUserServiceEvent> implements VaultUserServiceInterface {
constructor(
private session: SessionsClientInterface,
private vaults: VaultServiceInterface,
private _getVaultUsers: GetVaultUsers,
private _removeVaultMember: RemoveVaultMember,
private _isVaultAdmin: IsVaultAdmin,
private _getVault: GetVault,
private _leaveVault: LeaveVault,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
}
override deinit(): void {
super.deinit()
;(this.session as unknown) = undefined
;(this.vaults as unknown) = undefined
;(this._getVaultUsers as unknown) = undefined
;(this._removeVaultMember as unknown) = undefined
;(this._isVaultAdmin as unknown) = undefined
;(this._getVault as unknown) = undefined
;(this._leaveVault as unknown) = undefined
}
public async getSharedVaultUsers(
sharedVault: SharedVaultListingInterface,
): Promise<SharedVaultUserServerHash[] | undefined> {
return this._getVaultUsers.execute({ sharedVaultUuid: sharedVault.sharing.sharedVaultUuid })
}
public isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean {
return this._isVaultAdmin
.execute({
sharedVault,
userUuid: this.session.userUuid,
})
.getValue()
}
async removeUserFromSharedVault(sharedVault: SharedVaultListingInterface, userUuid: string): Promise<Result<void>> {
if (!this.isCurrentUserSharedVaultAdmin(sharedVault)) {
throw new Error('Only vault admins can remove users')
}
if (this.vaults.isVaultLocked(sharedVault)) {
throw new Error('Cannot remove user from locked vault')
}
const result = await this._removeVaultMember.execute({
sharedVaultUuid: sharedVault.sharing.sharedVaultUuid,
userUuid,
})
if (result.isFailed()) {
return result
}
void this.notifyEvent(VaultUserServiceEvent.UsersChanged)
await this.vaults.rotateVaultRootKey(sharedVault)
return result
}
public isVaultUserOwner(user: SharedVaultUserServerHash): boolean {
const result = this._getVault.execute<SharedVaultListingInterface>({ sharedVaultUuid: user.shared_vault_uuid })
if (result.isFailed()) {
return false
}
const vault = result.getValue()
return vault != undefined && vault.sharing.ownerUserUuid === user.user_uuid
}
async leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> {
const result = await this._leaveVault.execute({
sharedVault: sharedVault,
userUuid: this.session.getSureUser().uuid,
})
if (isClientDisplayableError(result)) {
return result
}
void this.notifyEvent(VaultUserServiceEvent.UsersChanged)
}
}

View File

@@ -0,0 +1,3 @@
export enum VaultUserServiceEvent {
UsersChanged = 'VaultUserServiceEvent.UsersChanged',
}

View File

@@ -0,0 +1,13 @@
import { ApplicationServiceInterface } from './../Service/ApplicationServiceInterface'
import { SharedVaultListingInterface } from '@standardnotes/models'
import { ClientDisplayableError, SharedVaultUserServerHash } from '@standardnotes/responses'
import { VaultUserServiceEvent } from './VaultUserServiceEvent'
import { Result } from '@standardnotes/domain-core'
export interface VaultUserServiceInterface extends ApplicationServiceInterface<VaultUserServiceEvent, unknown> {
getSharedVaultUsers(sharedVault: SharedVaultListingInterface): Promise<SharedVaultUserServerHash[] | undefined>
isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean
removeUserFromSharedVault(sharedVault: SharedVaultListingInterface, userUuid: string): Promise<Result<void>>
leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void>
isVaultUserOwner(user: SharedVaultUserServerHash): boolean
}

View File

@@ -57,15 +57,15 @@ export * from './Device/MobileDeviceInterface'
export * from './Device/TypeCheck' export * from './Device/TypeCheck'
export * from './Device/WebOrDesktopDeviceInterface' export * from './Device/WebOrDesktopDeviceInterface'
export * from './Diagnostics/ServiceDiagnostics' export * from './Diagnostics/ServiceDiagnostics'
export * from './Encryption/UseCase/DecryptBackupFile'
export * from './Encryption/EncryptionService'
export * from './Encryption/EncryptionProviderInterface' export * from './Encryption/EncryptionProviderInterface'
export * from './Encryption/EncryptionService'
export * from './Encryption/EncryptionServiceEvent' export * from './Encryption/EncryptionServiceEvent'
export * from './Encryption/Functions' export * from './Encryption/Functions'
export * from './Encryption/UseCase/Asymmetric/DecryptMessage' export * from './Encryption/UseCase/Asymmetric/DecryptMessage'
export * from './Encryption/UseCase/Asymmetric/DecryptOwnMessage' export * from './Encryption/UseCase/Asymmetric/DecryptOwnMessage'
export * from './Encryption/UseCase/Asymmetric/EncryptMessage' export * from './Encryption/UseCase/Asymmetric/EncryptMessage'
export * from './Encryption/UseCase/Asymmetric/GetMessageAdditionalData' export * from './Encryption/UseCase/Asymmetric/GetMessageAdditionalData'
export * from './Encryption/UseCase/DecryptBackupFile'
export * from './Encryption/UseCase/ItemsKey/CreateNewDefaultItemsKey' export * from './Encryption/UseCase/ItemsKey/CreateNewDefaultItemsKey'
export * from './Encryption/UseCase/ItemsKey/CreateNewItemsKeyWithRollback' export * from './Encryption/UseCase/ItemsKey/CreateNewItemsKeyWithRollback'
export * from './Encryption/UseCase/ItemsKey/FindDefaultItemsKey' export * from './Encryption/UseCase/ItemsKey/FindDefaultItemsKey'
@@ -111,6 +111,7 @@ export * from './Item/ItemRelationshipDirection'
export * from './Item/ItemsServerInterface' export * from './Item/ItemsServerInterface'
export * from './Item/StaticItemCounter' export * from './Item/StaticItemCounter'
export * from './ItemsEncryption/ItemsEncryption' export * from './ItemsEncryption/ItemsEncryption'
export * from './ItemsEncryption/ItemsEncryption'
export * from './KeySystem/KeySystemKeyManager' export * from './KeySystem/KeySystemKeyManager'
export * from './Mutator/ImportDataUseCase' export * from './Mutator/ImportDataUseCase'
export * from './Mutator/MutatorClientInterface' export * from './Mutator/MutatorClientInterface'
@@ -121,39 +122,25 @@ export * from './Protection/ProtectionClientInterface'
export * from './Protection/TimingDisplayOption' export * from './Protection/TimingDisplayOption'
export * from './Revision/RevisionClientInterface' export * from './Revision/RevisionClientInterface'
export * from './Revision/RevisionManager' export * from './Revision/RevisionManager'
export * from './RootKeyManager/RootKeyManager'
export * from './RootKeyManager/KeyMode' export * from './RootKeyManager/KeyMode'
export * from './ItemsEncryption/ItemsEncryption' export * from './RootKeyManager/RootKeyManager'
export * from './Service/AbstractService' export * from './Service/AbstractService'
export * from './Service/ApplicationServiceInterface' export * from './Service/ApplicationServiceInterface'
export * from './Session/SessionEvent' export * from './Session/SessionEvent'
export * from './Session/SessionManagerResponse' export * from './Session/SessionManagerResponse'
export * from './Session/SessionsClientInterface' export * from './Session/SessionsClientInterface'
export * from './Session/UserKeyPairChangedEventData' export * from './Session/UserKeyPairChangedEventData'
export * from './SharedVaults/PendingSharedVaultInviteRecord'
export * from './SharedVaults/SharedVaultService' export * from './SharedVaults/SharedVaultService'
export * from './SharedVaults/SharedVaultServiceEvent' export * from './SharedVaults/SharedVaultServiceEvent'
export * from './SharedVaults/SharedVaultServiceInterface' export * from './SharedVaults/SharedVaultServiceInterface'
export * from './SharedVaults/UseCase/AcceptVaultInvite'
export * from './SharedVaults/UseCase/ConvertToSharedVault' export * from './SharedVaults/UseCase/ConvertToSharedVault'
export * from './SharedVaults/UseCase/CreateSharedVault' export * from './SharedVaults/UseCase/CreateSharedVault'
export * from './SharedVaults/UseCase/DeleteExternalSharedVault' export * from './SharedVaults/UseCase/DeleteExternalSharedVault'
export * from './SharedVaults/UseCase/DeleteSharedVault' export * from './SharedVaults/UseCase/DeleteSharedVault'
export * from './SharedVaults/UseCase/GetVaultContacts'
export * from './SharedVaults/UseCase/GetVaultContacts'
export * from './SharedVaults/UseCase/GetVaultUsers'
export * from './SharedVaults/UseCase/InviteToVault'
export * from './SharedVaults/UseCase/LeaveSharedVault'
export * from './SharedVaults/UseCase/NotifyVaultUsersOfKeyRotation' export * from './SharedVaults/UseCase/NotifyVaultUsersOfKeyRotation'
export * from './SharedVaults/UseCase/RemoveSharedVaultMember'
export * from './SharedVaults/UseCase/ReuploadAllInvites'
export * from './SharedVaults/UseCase/ReuploadInvite'
export * from './SharedVaults/UseCase/ReuploadVaultInvites'
export * from './SharedVaults/UseCase/SendVaultDataChangedMessage' export * from './SharedVaults/UseCase/SendVaultDataChangedMessage'
export * from './SharedVaults/UseCase/SendVaultInvite'
export * from './SharedVaults/UseCase/SendVaultKeyChangedMessage' export * from './SharedVaults/UseCase/SendVaultKeyChangedMessage'
export * from './SharedVaults/UseCase/ShareContactWithVault' export * from './SharedVaults/UseCase/ShareContactWithVault'
export * from './SharedVaults/UseCase/UpdateSharedVaultInvite'
export * from './Singleton/SingletonManagerInterface' export * from './Singleton/SingletonManagerInterface'
export * from './Status/StatusService' export * from './Status/StatusService'
export * from './Status/StatusServiceInterface' export * from './Status/StatusServiceInterface'
@@ -186,6 +173,16 @@ export * from './User/UserClientInterface'
export * from './User/UserService' export * from './User/UserService'
export * from './UserEvent/UserEventService' export * from './UserEvent/UserEventService'
export * from './UserEvent/UserEventServiceEvent' export * from './UserEvent/UserEventServiceEvent'
export * from './VaultInvite/InviteRecord'
export * from './VaultInvite/UseCase/AcceptVaultInvite'
export * from './VaultInvite/UseCase/InviteToVault'
export * from './VaultInvite/UseCase/ReuploadAllInvites'
export * from './VaultInvite/UseCase/ReuploadInvite'
export * from './VaultInvite/UseCase/ReuploadVaultInvites'
export * from './VaultInvite/UseCase/SendVaultInvite'
export * from './VaultInvite/VaultInviteService'
export * from './VaultInvite/VaultInviteServiceEvent'
export * from './VaultInvite/VaultInviteServiceInterface'
export * from './Vaults/ChangeVaultOptionsDTO' export * from './Vaults/ChangeVaultOptionsDTO'
export * from './Vaults/UseCase/ChangeVaultKeyOptions' export * from './Vaults/UseCase/ChangeVaultKeyOptions'
export * from './Vaults/UseCase/CreateVault' export * from './Vaults/UseCase/CreateVault'
@@ -197,3 +194,12 @@ export * from './Vaults/UseCase/RotateVaultKey'
export * from './Vaults/VaultService' export * from './Vaults/VaultService'
export * from './Vaults/VaultServiceEvent' export * from './Vaults/VaultServiceEvent'
export * from './Vaults/VaultServiceInterface' export * from './Vaults/VaultServiceInterface'
export * from './VaultUser/UseCase/GetVaultContacts'
export * from './VaultUser/UseCase/GetVaultContacts'
export * from './VaultUser/UseCase/GetVaultUsers'
export * from './VaultUser/UseCase/IsVaultAdmin'
export * from './VaultUser/UseCase/LeaveSharedVault'
export * from './VaultUser/UseCase/RemoveSharedVaultMember'
export * from './VaultUser/VaultUserService'
export * from './VaultUser/VaultUserServiceEvent'
export * from './VaultUser/VaultUserServiceInterface'

View File

@@ -71,6 +71,10 @@ import {
HistoryServiceInterface, HistoryServiceInterface,
InternalEventPublishStrategy, InternalEventPublishStrategy,
EncryptionProviderInterface, EncryptionProviderInterface,
VaultUserServiceInterface,
VaultInviteServiceInterface,
UserEventServiceEvent,
VaultServiceEvent,
} from '@standardnotes/services' } from '@standardnotes/services'
import { import {
PayloadEmitSource, PayloadEmitSource,
@@ -1130,7 +1134,15 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.events.addEventHandler(this.dependencies.get(TYPES.UserService), AccountEvent.SignedInOrRegistered) this.events.addEventHandler(this.dependencies.get(TYPES.UserService), AccountEvent.SignedInOrRegistered)
this.events.addEventHandler(this.dependencies.get(TYPES.SessionManager), ApiServiceEvent.SessionRefreshed) this.events.addEventHandler(this.dependencies.get(TYPES.SessionManager), ApiServiceEvent.SessionRefreshed)
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SyncEvent.ReceivedSharedVaultInvites) this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SyncEvent.ReceivedSharedVaultInvites)
this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SessionEvent.UserKeyPairChanged)
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SessionEvent.UserKeyPairChanged)
this.events.addEventHandler(
this.dependencies.get(TYPES.SharedVaultService),
UserEventServiceEvent.UserEventReceived,
)
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), VaultServiceEvent.VaultRootKeyRotated)
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SyncEvent.ReceivedRemoteSharedVaults) this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SyncEvent.ReceivedRemoteSharedVaults)
this.events.addEventHandler( this.events.addEventHandler(
@@ -1332,14 +1344,26 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
return this.dependencies.get<HistoryServiceInterface>(TYPES.HistoryManager) return this.dependencies.get<HistoryServiceInterface>(TYPES.HistoryManager)
} }
private get migrations(): MigrationService {
return this.dependencies.get<MigrationService>(TYPES.MigrationService)
}
public get encryption(): EncryptionProviderInterface { public get encryption(): EncryptionProviderInterface {
return this.dependencies.get<EncryptionProviderInterface>(TYPES.EncryptionService) return this.dependencies.get<EncryptionProviderInterface>(TYPES.EncryptionService)
} }
public get events(): InternalEventBusInterface {
return this.dependencies.get<InternalEventBusInterface>(TYPES.InternalEventBus)
}
public get vaultUsers(): VaultUserServiceInterface {
return this.dependencies.get<VaultUserServiceInterface>(TYPES.VaultUserService)
}
public get vaultInvites(): VaultInviteServiceInterface {
return this.dependencies.get<VaultInviteServiceInterface>(TYPES.VaultInviteService)
}
private get migrations(): MigrationService {
return this.dependencies.get<MigrationService>(TYPES.MigrationService)
}
private get legacyApi(): LegacyApiService { private get legacyApi(): LegacyApiService {
return this.dependencies.get<LegacyApiService>(TYPES.LegacyApiService) return this.dependencies.get<LegacyApiService>(TYPES.LegacyApiService)
} }
@@ -1352,10 +1376,6 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
return this.dependencies.get<WebSocketsService>(TYPES.WebSocketsService) return this.dependencies.get<WebSocketsService>(TYPES.WebSocketsService)
} }
public get events(): InternalEventBusInterface {
return this.dependencies.get<InternalEventBusInterface>(TYPES.InternalEventBus)
}
private get mfa(): SNMfaService { private get mfa(): SNMfaService {
return this.dependencies.get<SNMfaService>(TYPES.MfaService) return this.dependencies.get<SNMfaService>(TYPES.MfaService)
} }

View File

@@ -108,6 +108,9 @@ import {
RootKeyManager, RootKeyManager,
ItemsEncryptionService, ItemsEncryptionService,
DecryptBackupFile, DecryptBackupFile,
VaultUserService,
IsVaultAdmin,
VaultInviteService,
} from '@standardnotes/services' } from '@standardnotes/services'
import { ItemManager } from '../../Services/Items/ItemManager' import { ItemManager } from '../../Services/Items/ItemManager'
import { PayloadManager } from '../../Services/Payloads/PayloadManager' import { PayloadManager } from '../../Services/Payloads/PayloadManager'
@@ -204,6 +207,10 @@ export class Dependencies {
) )
}) })
this.factory.set(TYPES.IsVaultAdmin, () => {
return new IsVaultAdmin()
})
this.factory.set(TYPES.DecryptBackupFile, () => { this.factory.set(TYPES.DecryptBackupFile, () => {
return new DecryptBackupFile(this.get(TYPES.EncryptionService)) return new DecryptBackupFile(this.get(TYPES.EncryptionService))
}) })
@@ -608,6 +615,39 @@ export class Dependencies {
return new SharedVaultUsersServer(this.get(TYPES.HttpService)) return new SharedVaultUsersServer(this.get(TYPES.HttpService))
}) })
this.factory.set(TYPES.VaultUserService, () => {
return new VaultUserService(
this.get(TYPES.SessionManager),
this.get(TYPES.VaultService),
this.get(TYPES.GetVaultUsers),
this.get(TYPES.RemoveVaultMember),
this.get(TYPES.IsVaultAdmin),
this.get(TYPES.GetVault),
this.get(TYPES.LeaveVault),
this.get(TYPES.InternalEventBus),
)
})
this.factory.set(TYPES.VaultInviteService, () => {
return new VaultInviteService(
this.get(TYPES.ItemManager),
this.get(TYPES.SessionManager),
this.get(TYPES.VaultUserService),
this.get(TYPES.SyncService),
this.get(TYPES.EncryptionService),
this.get(TYPES.SharedVaultInvitesServer),
this.get(TYPES.GetAllContacts),
this.get(TYPES.GetVault),
this.get(TYPES.GetVaultContacts),
this.get(TYPES.InviteToVault),
this.get(TYPES.GetTrustedPayload),
this.get(TYPES.GetUntrustedPayload),
this.get(TYPES.FindContact),
this.get(TYPES.AcceptVaultInvite),
this.get(TYPES.InternalEventBus),
)
})
this.factory.set(TYPES.AsymmetricMessageService, () => { this.factory.set(TYPES.AsymmetricMessageService, () => {
return new AsymmetricMessageService( return new AsymmetricMessageService(
this.get(TYPES.AsymmetricMessageServer), this.get(TYPES.AsymmetricMessageServer),
@@ -630,31 +670,21 @@ export class Dependencies {
this.factory.set(TYPES.SharedVaultService, () => { this.factory.set(TYPES.SharedVaultService, () => {
return new SharedVaultService( return new SharedVaultService(
this.get(TYPES.SyncService),
this.get(TYPES.ItemManager), this.get(TYPES.ItemManager),
this.get(TYPES.EncryptionService), this.get(TYPES.EncryptionService),
this.get(TYPES.SessionManager), this.get(TYPES.SessionManager),
this.get(TYPES.VaultService), this.get(TYPES.VaultService),
this.get(TYPES.SharedVaultInvitesServer),
this.get(TYPES.GetVault), this.get(TYPES.GetVault),
this.get(TYPES.CreateSharedVault), this.get(TYPES.CreateSharedVault),
this.get(TYPES.HandleKeyPairChange), this.get(TYPES.HandleKeyPairChange),
this.get(TYPES.NotifyVaultUsersOfKeyRotation), this.get(TYPES.NotifyVaultUsersOfKeyRotation),
this.get(TYPES.SendVaultDataChangedMessage), this.get(TYPES.SendVaultDataChangedMessage),
this.get(TYPES.GetTrustedPayload),
this.get(TYPES.GetUntrustedPayload),
this.get(TYPES.FindContact), this.get(TYPES.FindContact),
this.get(TYPES.GetAllContacts),
this.get(TYPES.GetVaultContacts),
this.get(TYPES.AcceptVaultInvite),
this.get(TYPES.InviteToVault),
this.get(TYPES.LeaveVault),
this.get(TYPES.DeleteThirdPartyVault), this.get(TYPES.DeleteThirdPartyVault),
this.get(TYPES.ShareContactWithVault), this.get(TYPES.ShareContactWithVault),
this.get(TYPES.ConvertToSharedVault), this.get(TYPES.ConvertToSharedVault),
this.get(TYPES.DeleteSharedVault), this.get(TYPES.DeleteSharedVault),
this.get(TYPES.RemoveVaultMember), this.get(TYPES.IsVaultAdmin),
this.get(TYPES.GetVaultUsers),
this.get(TYPES.InternalEventBus), this.get(TYPES.InternalEventBus),
) )
}) })

View File

@@ -59,6 +59,8 @@ export const TYPES = {
EncryptionOperators: Symbol.for('EncryptionOperators'), EncryptionOperators: Symbol.for('EncryptionOperators'),
RootKeyManager: Symbol.for('RootKeyManager'), RootKeyManager: Symbol.for('RootKeyManager'),
ItemsEncryptionService: Symbol.for('ItemsEncryptionService'), ItemsEncryptionService: Symbol.for('ItemsEncryptionService'),
VaultUserService: Symbol.for('VaultUserService'),
VaultInviteService: Symbol.for('VaultInviteService'),
// Servers // Servers
RevisionServer: Symbol.for('RevisionServer'), RevisionServer: Symbol.for('RevisionServer'),
@@ -140,6 +142,7 @@ export const TYPES = {
EncryptTypeAPayload: Symbol.for('EncryptTypeAPayload'), EncryptTypeAPayload: Symbol.for('EncryptTypeAPayload'),
EncryptTypeAPayloadWithKeyLookup: Symbol.for('EncryptTypeAPayloadWithKeyLookup'), EncryptTypeAPayloadWithKeyLookup: Symbol.for('EncryptTypeAPayloadWithKeyLookup'),
DecryptBackupFile: Symbol.for('DecryptBackupFile'), DecryptBackupFile: Symbol.for('DecryptBackupFile'),
IsVaultAdmin: Symbol.for('IsVaultAdmin'),
// Mappers // Mappers
SessionStorageMapper: Symbol.for('SessionStorageMapper'), SessionStorageMapper: Symbol.for('SessionStorageMapper'),

View File

@@ -97,6 +97,14 @@ export class AppContext {
return this.application.sharedVaults return this.application.sharedVaults
} }
get vaultUsers() {
return this.application.vaultUsers
}
get vaultInvites() {
return this.application.vaultInvites
}
get files() { get files() {
return this.application.files return this.application.files
} }
@@ -210,16 +218,6 @@ export class AppContext {
}) })
} }
resolveWhenSharedVaultUserKeysResolved() {
return new Promise((resolve) => {
this.application.vaults.collaboration.addEventObserver((eventName) => {
if (eventName === SharedVaultServiceEvent.SharedVaultStatusChanged) {
resolve()
}
})
})
}
async awaitSignInEvent() { async awaitSignInEvent() {
return new Promise((resolve) => { return new Promise((resolve) => {
this.application.user.addEventObserver((eventName) => { this.application.user.addEventObserver((eventName) => {
@@ -394,7 +392,7 @@ export class AppContext {
resolveWhenAllInboundSharedVaultInvitesAreDeleted() { resolveWhenAllInboundSharedVaultInvitesAreDeleted() {
return new Promise((resolve) => { return new Promise((resolve) => {
const objectToSpy = this.application.sharedVaults.invitesServer const objectToSpy = this.application.vaultInvites.invitesServer
sinon.stub(objectToSpy, 'deleteAllInboundInvites').callsFake(async (params) => { sinon.stub(objectToSpy, 'deleteAllInboundInvites').callsFake(async (params) => {
objectToSpy.deleteAllInboundInvites.restore() objectToSpy.deleteAllInboundInvites.restore()
const result = await objectToSpy.deleteAllInboundInvites(params) const result = await objectToSpy.deleteAllInboundInvites(params)

View File

@@ -26,13 +26,13 @@ export const createTrustedContactForUserOfContext = async (
} }
export const acceptAllInvites = async (context) => { export const acceptAllInvites = async (context) => {
const inviteRecords = context.sharedVaults.getCachedPendingInviteRecords() const inviteRecords = context.vaultInvites.getCachedPendingInviteRecords()
if (inviteRecords.length === 0) { if (inviteRecords.length === 0) {
throw new Error('No pending invites to accept') throw new Error('No pending invites to accept')
} }
for (const record of inviteRecords) { for (const record of inviteRecords) {
await context.sharedVaults.acceptPendingSharedVaultInvite(record) await context.vaultInvites.acceptInvite(record)
} }
} }
@@ -76,7 +76,7 @@ export const createSharedVaultWithUnacceptedButTrustedInvite = async (
const contact = await createTrustedContactForUserOfContext(context, contactContext) const contact = await createTrustedContactForUserOfContext(context, contactContext)
await createTrustedContactForUserOfContext(contactContext, context) await createTrustedContactForUserOfContext(contactContext, context)
const invite = (await context.sharedVaults.inviteContactToSharedVault(sharedVault, contact, permissions)).getValue() const invite = (await context.vaultInvites.inviteContactToSharedVault(sharedVault, contact, permissions)).getValue()
await contactContext.sync() await contactContext.sync()
return { sharedVault, contact, contactContext, deinitContactContext, invite } return { sharedVault, contact, contactContext, deinitContactContext, invite }
@@ -91,7 +91,7 @@ export const createSharedVaultWithUnacceptedAndUntrustedInvite = async (
const { contactContext, deinitContactContext } = await createContactContext() const { contactContext, deinitContactContext } = await createContactContext()
const contact = await createTrustedContactForUserOfContext(context, contactContext) const contact = await createTrustedContactForUserOfContext(context, contactContext)
const invite = (await context.sharedVaults.inviteContactToSharedVault(sharedVault, contact, permissions)).getValue() const invite = (await context.vaultInvites.inviteContactToSharedVault(sharedVault, contact, permissions)).getValue()
await contactContext.sync() await contactContext.sync()
return { sharedVault, contact, contactContext, deinitContactContext, invite } return { sharedVault, contact, contactContext, deinitContactContext, invite }
@@ -103,7 +103,7 @@ export const inviteNewPartyToSharedVault = async (context, sharedVault, permissi
const thirdPartyContact = await createTrustedContactForUserOfContext(context, thirdPartyContext) const thirdPartyContact = await createTrustedContactForUserOfContext(context, thirdPartyContext)
await createTrustedContactForUserOfContext(thirdPartyContext, context) await createTrustedContactForUserOfContext(thirdPartyContext, context)
await context.sharedVaults.inviteContactToSharedVault(sharedVault, thirdPartyContact, permissions) await context.vaultInvites.inviteContactToSharedVault(sharedVault, thirdPartyContact, permissions)
await thirdPartyContext.sync() await thirdPartyContext.sync()

View File

@@ -28,7 +28,7 @@ describe('shared vault conflicts', function () {
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
contactContext.lockSyncing() contactContext.lockSyncing()
await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid) await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
const promise = contactContext.resolveWithConflicts() const promise = contactContext.resolveWithConflicts()
contactContext.unlockSyncing() contactContext.unlockSyncing()
await contactContext.changeNoteTitleAndSync(note, 'new title') await contactContext.changeNoteTitleAndSync(note, 'new title')
@@ -98,7 +98,7 @@ describe('shared vault conflicts', function () {
const { sharedVault, note, contactContext, deinitContactContext } = const { sharedVault, note, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid) await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
await contactContext.changeNoteTitleAndSync(note, 'new title') await contactContext.changeNoteTitleAndSync(note, 'new title')
const notes = contactContext.notes const notes = contactContext.notes

View File

@@ -90,7 +90,7 @@ describe('shared vault deletion', function () {
const contactNote = contactContext.items.findItem(note.uuid) const contactNote = contactContext.items.findItem(note.uuid)
expect(contactNote).to.not.be.undefined expect(contactNote).to.not.be.undefined
await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid) await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
await contactContext.sync() await contactContext.sync()
@@ -108,7 +108,7 @@ describe('shared vault deletion', function () {
expect(originalNote).to.not.be.undefined expect(originalNote).to.not.be.undefined
const contactVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier }) const contactVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
await contactContext.sharedVaults.leaveSharedVault(contactVault) await contactContext.vaultUsers.leaveSharedVault(contactVault)
const updatedContactNote = contactContext.items.findItem(note.uuid) const updatedContactNote = contactContext.items.findItem(note.uuid)
expect(updatedContactNote).to.be.undefined expect(updatedContactNote).to.be.undefined
@@ -140,14 +140,13 @@ describe('shared vault deletion', function () {
const { sharedVault, contactContext, deinitContactContext } = const { sharedVault, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context) await Collaboration.createSharedVaultWithAcceptedInvite(context)
const originalSharedVaultUsers = await sharedVaults.getSharedVaultUsers(sharedVault) const originalSharedVaultUsers = await context.vaultUsers.getSharedVaultUsers(sharedVault)
expect(originalSharedVaultUsers.length).to.equal(2) expect(originalSharedVaultUsers.length).to.equal(2)
const result = await sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid) const result = await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
expect(result.isFailed()).to.be.false
expect(isClientDisplayableError(result)).to.be.false const updatedSharedVaultUsers = await context.vaultUsers.getSharedVaultUsers(sharedVault)
const updatedSharedVaultUsers = await sharedVaults.getSharedVaultUsers(sharedVault)
expect(updatedSharedVaultUsers.length).to.equal(1) expect(updatedSharedVaultUsers.length).to.equal(1)
await deinitContactContext() await deinitContactContext()

View File

@@ -249,7 +249,7 @@ describe('shared vault files', function () {
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, sharedVault) const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, sharedVault)
await contactContext.sync() await contactContext.sync()
await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid) await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
const file = contactContext.items.findItem(uploadedFile.uuid) const file = contactContext.items.findItem(uploadedFile.uuid)
await Factory.expectThrowsAsync(() => Files.downloadFile(contactContext.files, file), 'Could not download file') await Factory.expectThrowsAsync(() => Files.downloadFile(contactContext.files, file), 'Could not download file')

View File

@@ -8,7 +8,6 @@ describe('shared vault invites', function () {
this.timeout(Factory.TwentySecondTimeout) this.timeout(Factory.TwentySecondTimeout)
let context let context
let sharedVaults
afterEach(async function () { afterEach(async function () {
await context.deinit() await context.deinit()
@@ -21,8 +20,6 @@ describe('shared vault invites', function () {
context = await Factory.createAppContextWithRealCrypto() context = await Factory.createAppContextWithRealCrypto()
await context.launch() await context.launch()
await context.register() await context.register()
sharedVaults = context.sharedVaults
}) })
it('should invite contact to vault', async () => { it('should invite contact to vault', async () => {
@@ -31,7 +28,7 @@ describe('shared vault invites', function () {
const contact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext) const contact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
const vaultInvite = ( const vaultInvite = (
await sharedVaults.inviteContactToSharedVault(sharedVault, contact, SharedVaultPermission.Write) await context.vaultInvites.inviteContactToSharedVault(sharedVault, contact, SharedVaultPermission.Write)
).getValue() ).getValue()
expect(vaultInvite).to.not.be.undefined expect(vaultInvite).to.not.be.undefined
@@ -49,7 +46,7 @@ describe('shared vault invites', function () {
const { contactContext, deinitContactContext } = const { contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithUnacceptedButTrustedInvite(context) await Collaboration.createSharedVaultWithUnacceptedButTrustedInvite(context)
const invites = contactContext.sharedVaults.getCachedPendingInviteRecords() const invites = contactContext.vaultInvites.getCachedPendingInviteRecords()
expect(invites[0].trusted).to.be.true expect(invites[0].trusted).to.be.true
@@ -60,7 +57,7 @@ describe('shared vault invites', function () {
const { contactContext, deinitContactContext } = const { contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithUnacceptedAndUntrustedInvite(context) await Collaboration.createSharedVaultWithUnacceptedAndUntrustedInvite(context)
const invites = contactContext.sharedVaults.getCachedPendingInviteRecords() const invites = contactContext.vaultInvites.getCachedPendingInviteRecords()
expect(invites[0].trusted).to.be.false expect(invites[0].trusted).to.be.false
@@ -76,7 +73,7 @@ describe('shared vault invites', function () {
sharedVault, sharedVault,
) )
const invites = thirdPartyContext.sharedVaults.getCachedPendingInviteRecords() const invites = thirdPartyContext.vaultInvites.getCachedPendingInviteRecords()
const message = invites[0].message const message = invites[0].message
const delegatedContacts = message.data.trustedContacts const delegatedContacts = message.data.trustedContacts
@@ -103,7 +100,7 @@ describe('shared vault invites', function () {
/** Sync the contact context so that they wouldn't naturally receive changes made before this point */ /** Sync the contact context so that they wouldn't naturally receive changes made before this point */
await contactContext.sync() await contactContext.sync()
await sharedVaults.inviteContactToSharedVault(sharedVault, contact, SharedVaultPermission.Write) await context.vaultInvites.inviteContactToSharedVault(sharedVault, contact, SharedVaultPermission.Write)
/** Contact should now sync and expect to find note */ /** Contact should now sync and expect to find note */
const promise = contactContext.awaitNextSyncSharedVaultFromScratchEvent() const promise = contactContext.awaitNextSyncSharedVaultFromScratchEvent()
@@ -125,10 +122,14 @@ describe('shared vault invites', function () {
const sharedVault = await Collaboration.createSharedVault(context) const sharedVault = await Collaboration.createSharedVault(context)
const currentContextContact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext) const currentContextContact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
await sharedVaults.inviteContactToSharedVault(sharedVault, currentContextContact, SharedVaultPermission.Write) await context.vaultInvites.inviteContactToSharedVault(
sharedVault,
currentContextContact,
SharedVaultPermission.Write,
)
await contactContext.sharedVaults.downloadInboundInvites() await contactContext.vaultInvites.downloadInboundInvites()
expect(contactContext.sharedVaults.getCachedPendingInviteRecords()[0].trusted).to.be.false expect(contactContext.vaultInvites.getCachedPendingInviteRecords()[0].trusted).to.be.false
await deinitContactContext() await deinitContactContext()
}) })
@@ -139,14 +140,18 @@ describe('shared vault invites', function () {
const sharedVault = await Collaboration.createSharedVault(context) const sharedVault = await Collaboration.createSharedVault(context)
const currentContextContact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext) const currentContextContact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
await sharedVaults.inviteContactToSharedVault(sharedVault, currentContextContact, SharedVaultPermission.Write) await context.vaultInvites.inviteContactToSharedVault(
sharedVault,
currentContextContact,
SharedVaultPermission.Write,
)
await contactContext.sharedVaults.downloadInboundInvites() await contactContext.vaultInvites.downloadInboundInvites()
expect(contactContext.sharedVaults.getCachedPendingInviteRecords()[0].trusted).to.be.false expect(contactContext.vaultInvites.getCachedPendingInviteRecords()[0].trusted).to.be.false
await Collaboration.createTrustedContactForUserOfContext(contactContext, context) await Collaboration.createTrustedContactForUserOfContext(contactContext, context)
expect(contactContext.sharedVaults.getCachedPendingInviteRecords()[0].trusted).to.be.true expect(contactContext.vaultInvites.getCachedPendingInviteRecords()[0].trusted).to.be.true
await deinitContactContext() await deinitContactContext()
}) })
@@ -184,12 +189,12 @@ describe('shared vault invites', function () {
const { invite, contactContext, deinitContactContext } = const { invite, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithUnacceptedButTrustedInvite(context) await Collaboration.createSharedVaultWithUnacceptedButTrustedInvite(context)
const preInvites = await contactContext.sharedVaults.downloadInboundInvites() const preInvites = await contactContext.vaultInvites.downloadInboundInvites()
expect(preInvites.length).to.equal(1) expect(preInvites.length).to.equal(1)
await sharedVaults.deleteInvite(invite) await context.vaultInvites.deleteInvite(invite)
const postInvites = await contactContext.sharedVaults.downloadInboundInvites() const postInvites = await contactContext.vaultInvites.downloadInboundInvites()
expect(postInvites.length).to.equal(0) expect(postInvites.length).to.equal(0)
await deinitContactContext() await deinitContactContext()
@@ -208,7 +213,7 @@ describe('shared vault invites', function () {
await contactContext.changePassword('new-password') await contactContext.changePassword('new-password')
await promise await promise
const invites = await contactContext.sharedVaults.downloadInboundInvites() const invites = await contactContext.vaultInvites.downloadInboundInvites()
expect(invites.length).to.equal(0) expect(invites.length).to.equal(0)
await deinitContactContext() await deinitContactContext()

View File

@@ -8,7 +8,6 @@ describe('shared vault items', function () {
this.timeout(Factory.TwentySecondTimeout) this.timeout(Factory.TwentySecondTimeout)
let context let context
let sharedVaults
afterEach(async function () { afterEach(async function () {
await context.deinit() await context.deinit()
@@ -22,8 +21,6 @@ describe('shared vault items', function () {
await context.launch() await context.launch()
await context.register() await context.register()
sharedVaults = context.sharedVaults
}) })
it('should add item to shared vault with no other members', async () => { it('should add item to shared vault with no other members', async () => {
@@ -60,7 +57,11 @@ describe('shared vault items', function () {
const currentContextContact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext) const currentContextContact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
contactContext.lockSyncing() contactContext.lockSyncing()
await sharedVaults.inviteContactToSharedVault(sharedVault, currentContextContact, SharedVaultPermission.Write) await context.vaultInvites.inviteContactToSharedVault(
sharedVault,
currentContextContact,
SharedVaultPermission.Write,
)
await Collaboration.moveItemToVault(context, sharedVault, note) await Collaboration.moveItemToVault(context, sharedVault, note)
const promise = contactContext.awaitNextSyncSharedVaultFromScratchEvent() const promise = contactContext.awaitNextSyncSharedVaultFromScratchEvent()

View File

@@ -8,8 +8,6 @@ describe('shared vault key rotation', function () {
this.timeout(Factory.TwentySecondTimeout) this.timeout(Factory.TwentySecondTimeout)
let context let context
let vaults
let sharedVaults
afterEach(async function () { afterEach(async function () {
await context.deinit() await context.deinit()
@@ -23,9 +21,6 @@ describe('shared vault key rotation', function () {
await context.launch() await context.launch()
await context.register() await context.register()
vaults = context.vaults
sharedVaults = context.sharedVaults
}) })
it('should reencrypt all items keys belonging to key system', async () => { it('should reencrypt all items keys belonging to key system', async () => {
@@ -37,7 +32,7 @@ describe('shared vault key rotation', function () {
const spy = sinon.spy(context.keys, 'reencryptKeySystemItemsKeysForVault') const spy = sinon.spy(context.keys, 'reencryptKeySystemItemsKeysForVault')
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault) const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
await vaults.rotateVaultRootKey(sharedVault) await context.vaults.rotateVaultRootKey(sharedVault)
await promise await promise
expect(spy.callCount).to.equal(1) expect(spy.callCount).to.equal(1)
@@ -52,7 +47,7 @@ describe('shared vault key rotation', function () {
contactContext.lockSyncing() contactContext.lockSyncing()
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault) const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
await vaults.rotateVaultRootKey(sharedVault) await context.vaults.rotateVaultRootKey(sharedVault)
await promise await promise
const outboundMessages = await context.asymmetric.getOutboundMessages() const outboundMessages = await context.asymmetric.getOutboundMessages()
@@ -79,7 +74,7 @@ describe('shared vault key rotation', function () {
contactContext.lockSyncing() contactContext.lockSyncing()
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault) const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
await vaults.rotateVaultRootKey(sharedVault) await context.vaults.rotateVaultRootKey(sharedVault)
await promise await promise
const rootKey = context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier) const rootKey = context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)
@@ -107,7 +102,7 @@ describe('shared vault key rotation', function () {
expect(previousPrimaryItemsKey).to.not.be.undefined expect(previousPrimaryItemsKey).to.not.be.undefined
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault) const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
await vaults.rotateVaultRootKey(sharedVault) await context.vaults.rotateVaultRootKey(sharedVault)
await promise await promise
const contactPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes() const contactPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
@@ -129,15 +124,15 @@ describe('shared vault key rotation', function () {
await Collaboration.createSharedVaultWithUnacceptedButTrustedInvite(context) await Collaboration.createSharedVaultWithUnacceptedButTrustedInvite(context)
contactContext.lockSyncing() contactContext.lockSyncing()
const originalOutboundInvites = await sharedVaults.getOutboundInvites() const originalOutboundInvites = await context.vaultInvites.getOutboundInvites()
expect(originalOutboundInvites.length).to.equal(1) expect(originalOutboundInvites.length).to.equal(1)
const originalInviteMessage = originalOutboundInvites[0].encrypted_message const originalInviteMessage = originalOutboundInvites[0].encrypted_message
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault) const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
await vaults.rotateVaultRootKey(sharedVault) await context.vaults.rotateVaultRootKey(sharedVault)
await promise await promise
const updatedOutboundInvites = await sharedVaults.getOutboundInvites() const updatedOutboundInvites = await context.vaultInvites.getOutboundInvites()
expect(updatedOutboundInvites.length).to.equal(1) expect(updatedOutboundInvites.length).to.equal(1)
const joinInvite = updatedOutboundInvites[0] const joinInvite = updatedOutboundInvites[0]
@@ -150,7 +145,7 @@ describe('shared vault key rotation', function () {
it('new key system items key in rotated shared vault should belong to shared vault', async () => { it('new key system items key in rotated shared vault should belong to shared vault', async () => {
const sharedVault = await Collaboration.createSharedVault(context) const sharedVault = await Collaboration.createSharedVault(context)
await vaults.rotateVaultRootKey(sharedVault) await context.vaults.rotateVaultRootKey(sharedVault)
const keySystemItemsKeys = context.keys const keySystemItemsKeys = context.keys
.getAllKeySystemItemsKeys() .getAllKeySystemItemsKeys()
@@ -170,7 +165,7 @@ describe('shared vault key rotation', function () {
contactContext.lockSyncing() contactContext.lockSyncing()
const firstPromise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault) const firstPromise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
await vaults.rotateVaultRootKey(sharedVault) await context.vaults.rotateVaultRootKey(sharedVault)
await firstPromise await firstPromise
const asymmetricMessageAfterFirstChange = await context.asymmetric.getOutboundMessages() const asymmetricMessageAfterFirstChange = await context.asymmetric.getOutboundMessages()
@@ -180,7 +175,7 @@ describe('shared vault key rotation', function () {
const messageAfterFirstChange = asymmetricMessageAfterFirstChange[0] const messageAfterFirstChange = asymmetricMessageAfterFirstChange[0]
const secondPromise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault) const secondPromise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
await vaults.rotateVaultRootKey(sharedVault) await context.vaults.rotateVaultRootKey(sharedVault)
await secondPromise await secondPromise
const asymmetricMessageAfterSecondChange = await context.asymmetric.getOutboundMessages() const asymmetricMessageAfterSecondChange = await context.asymmetric.getOutboundMessages()
@@ -204,7 +199,7 @@ describe('shared vault key rotation', function () {
contactContext.lockSyncing() contactContext.lockSyncing()
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault) const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
await vaults.rotateVaultRootKey(sharedVault) await context.vaults.rotateVaultRootKey(sharedVault)
await promise await promise
const acceptMessage = sinon.spy(contactContext.asymmetric, 'handleTrustedSharedVaultRootKeyChangedMessage') const acceptMessage = sinon.spy(contactContext.asymmetric, 'handleTrustedSharedVaultRootKeyChangedMessage')
@@ -223,7 +218,7 @@ describe('shared vault key rotation', function () {
const originalKeySystemRootKey = context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier) const originalKeySystemRootKey = context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)
await sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid) await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
const newKeySystemRootKey = context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier) const newKeySystemRootKey = context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)

View File

@@ -8,7 +8,6 @@ describe('shared vault permissions', function () {
this.timeout(Factory.TwentySecondTimeout) this.timeout(Factory.TwentySecondTimeout)
let context let context
let sharedVaults
afterEach(async function () { afterEach(async function () {
await context.deinit() await context.deinit()
@@ -22,8 +21,6 @@ describe('shared vault permissions', function () {
await context.launch() await context.launch()
await context.register() await context.register()
sharedVaults = context.sharedVaults
}) })
it('non-admin user should not be able to invite user', async () => { it('non-admin user should not be able to invite user', async () => {
@@ -37,7 +34,7 @@ describe('shared vault permissions', function () {
contactContext, contactContext,
thirdParty.contactContext, thirdParty.contactContext,
) )
const result = await contactContext.sharedVaults.inviteContactToSharedVault( const result = await contactContext.vaultInvites.inviteContactToSharedVault(
sharedVault, sharedVault,
thirdPartyContact, thirdPartyContact,
SharedVaultPermission.Write, SharedVaultPermission.Write,
@@ -53,16 +50,15 @@ describe('shared vault permissions', function () {
const sharedVault = await Collaboration.createSharedVault(context) const sharedVault = await Collaboration.createSharedVault(context)
const result = await sharedVaults.removeUserFromSharedVault(sharedVault, context.userUuid) const result = await context.vaultUsers.removeUserFromSharedVault(sharedVault, context.userUuid)
expect(result.isFailed()).to.be.true
expect(isClientDisplayableError(result)).to.be.true
}) })
it('should be able to leave shared vault as added admin', async () => { it('should be able to leave shared vault as added admin', async () => {
const { contactVault, contactContext, deinitContactContext } = const { contactVault, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context, SharedVaultPermission.Admin) await Collaboration.createSharedVaultWithAcceptedInvite(context, SharedVaultPermission.Admin)
const result = await contactContext.sharedVaults.leaveSharedVault(contactVault) const result = await contactContext.vaultUsers.leaveSharedVault(contactVault)
expect(isClientDisplayableError(result)).to.be.false expect(isClientDisplayableError(result)).to.be.false

View File

@@ -54,9 +54,8 @@ describe('shared vaults', function () {
const { sharedVault, contactContext, deinitContactContext } = const { sharedVault, contactContext, deinitContactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context) await Collaboration.createSharedVaultWithAcceptedInvite(context)
const result = await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid) const result = await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
expect(result.isFailed()).to.be.false
expect(result).to.be.undefined
const promise = contactContext.resolveWhenUserMessagesProcessingCompletes() const promise = contactContext.resolveWhenUserMessagesProcessingCompletes()
await contactContext.sync() await contactContext.sync()

View File

@@ -61,14 +61,14 @@ export const PreprocessingStep = ({
useEffect(() => { useEffect(() => {
const processPendingInvites = async () => { const processPendingInvites = async () => {
await application.sharedVaults.downloadInboundInvites() await application.vaultInvites.downloadInboundInvites()
const hasPendingInvites = application.sharedVaults.getCachedPendingInviteRecords().length > 0 const hasPendingInvites = application.vaultInvites.getCachedPendingInviteRecords().length > 0
setNeedsUserConfirmation(hasPendingInvites ? 'yes' : 'no') setNeedsUserConfirmation(hasPendingInvites ? 'yes' : 'no')
setIsProcessingInvites(false) setIsProcessingInvites(false)
} }
void processPendingInvites() void processPendingInvites()
}, [application.sharedVaults]) }, [application])
const isProcessing = isProcessingSync || isProcessingMessages || isProcessingInvites const isProcessing = isProcessingSync || isProcessingMessages || isProcessingInvites

View File

@@ -2,10 +2,10 @@ import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 're
import Modal, { ModalAction } from '@/Components/Modal/Modal' import Modal, { ModalAction } from '@/Components/Modal/Modal'
import DecoratedInput from '@/Components/Input/DecoratedInput' import DecoratedInput from '@/Components/Input/DecoratedInput'
import { useApplication } from '@/Components/ApplicationProvider' import { useApplication } from '@/Components/ApplicationProvider'
import { PendingSharedVaultInviteRecord, TrustedContactInterface } from '@standardnotes/snjs' import { InviteRecord, TrustedContactInterface } from '@standardnotes/snjs'
type Props = { type Props = {
fromInvite?: PendingSharedVaultInviteRecord fromInvite?: InviteRecord
editContactUuid?: string editContactUuid?: string
onCloseDialog: () => void onCloseDialog: () => void
onAddContact?: (contact: TrustedContactInterface) => void onAddContact?: (contact: TrustedContactInterface) => void

View File

@@ -16,11 +16,11 @@ const ContactInviteModal: FunctionComponent<Props> = ({ vault, onCloseDialog })
useEffect(() => { useEffect(() => {
const loadContacts = async () => { const loadContacts = async () => {
const contacts = await application.sharedVaults.getInvitableContactsForSharedVault(vault) const contacts = await application.vaultInvites.getInvitableContactsForSharedVault(vault)
setContacts(contacts) setContacts(contacts)
} }
void loadContacts() void loadContacts()
}, [application.sharedVaults, vault]) }, [application.vaultInvites, vault])
const handleDialogClose = useCallback(() => { const handleDialogClose = useCallback(() => {
onCloseDialog() onCloseDialog()
@@ -28,10 +28,10 @@ const ContactInviteModal: FunctionComponent<Props> = ({ vault, onCloseDialog })
const inviteSelectedContacts = useCallback(async () => { const inviteSelectedContacts = useCallback(async () => {
for (const contact of selectedContacts) { for (const contact of selectedContacts) {
await application.sharedVaults.inviteContactToSharedVault(vault, contact, SharedVaultPermission.Write) await application.vaultInvites.inviteContactToSharedVault(vault, contact, SharedVaultPermission.Write)
} }
handleDialogClose() handleDialogClose()
}, [application.sharedVaults, vault, handleDialogClose, selectedContacts]) }, [application.vaultInvites, vault, handleDialogClose, selectedContacts])
const toggleContact = useCallback( const toggleContact = useCallback(
(contact: TrustedContactInterface) => { (contact: TrustedContactInterface) => {

View File

@@ -2,13 +2,13 @@ import { useApplication } from '@/Components/ApplicationProvider'
import Button from '@/Components/Button/Button' import Button from '@/Components/Button/Button'
import Icon from '@/Components/Icon/Icon' import Icon from '@/Components/Icon/Icon'
import ModalOverlay from '@/Components/Modal/ModalOverlay' import ModalOverlay from '@/Components/Modal/ModalOverlay'
import { PendingSharedVaultInviteRecord } from '@standardnotes/snjs' import { InviteRecord } from '@standardnotes/snjs'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import EditContactModal from '../Contacts/EditContactModal' import EditContactModal from '../Contacts/EditContactModal'
import { CheckmarkCircle } from '../../../../UIElements/CheckmarkCircle' import { CheckmarkCircle } from '../../../../UIElements/CheckmarkCircle'
type Props = { type Props = {
inviteRecord: PendingSharedVaultInviteRecord inviteRecord: InviteRecord
} }
const InviteItem = ({ inviteRecord }: Props) => { const InviteItem = ({ inviteRecord }: Props) => {
@@ -23,8 +23,8 @@ const InviteItem = ({ inviteRecord }: Props) => {
}, []) }, [])
const acceptInvite = useCallback(async () => { const acceptInvite = useCallback(async () => {
await application.sharedVaults.acceptPendingSharedVaultInvite(inviteRecord) await application.vaultInvites.acceptInvite(inviteRecord)
}, [application.sharedVaults, inviteRecord]) }, [application, inviteRecord])
const closeAddContactModal = () => setIsAddContactModalOpen(false) const closeAddContactModal = () => setIsAddContactModalOpen(false)
const collaborationId = application.contacts.getCollaborationIDFromInvite(inviteRecord.invite) const collaborationId = application.contacts.getCollaborationIDFromInvite(inviteRecord.invite)

View File

@@ -10,9 +10,10 @@ import { useCallback, useEffect, useState } from 'react'
import { import {
VaultListingInterface, VaultListingInterface,
TrustedContactInterface, TrustedContactInterface,
PendingSharedVaultInviteRecord, InviteRecord,
ContentType, ContentType,
SharedVaultServiceEvent, SharedVaultServiceEvent,
VaultUserServiceEvent,
} from '@standardnotes/snjs' } from '@standardnotes/snjs'
import VaultItem from './Vaults/VaultItem' import VaultItem from './Vaults/VaultItem'
import Button from '@/Components/Button/Button' import Button from '@/Components/Button/Button'
@@ -23,7 +24,7 @@ const Vaults = () => {
const application = useApplication() const application = useApplication()
const [vaults, setVaults] = useState<VaultListingInterface[]>([]) const [vaults, setVaults] = useState<VaultListingInterface[]>([])
const [invites, setInvites] = useState<PendingSharedVaultInviteRecord[]>([]) const [invites, setInvites] = useState<InviteRecord[]>([])
const [contacts, setContacts] = useState<TrustedContactInterface[]>([]) const [contacts, setContacts] = useState<TrustedContactInterface[]>([])
const [isAddContactModalOpen, setIsAddContactModalOpen] = useState(false) const [isAddContactModalOpen, setIsAddContactModalOpen] = useState(false)
@@ -33,7 +34,6 @@ const Vaults = () => {
const closeVaultModal = () => setIsVaultModalOpen(false) const closeVaultModal = () => setIsVaultModalOpen(false)
const vaultService = application.vaults const vaultService = application.vaults
const sharedVaultService = application.sharedVaults
const contactService = application.contacts const contactService = application.contacts
const updateVaults = useCallback(async () => { const updateVaults = useCallback(async () => {
@@ -41,8 +41,8 @@ const Vaults = () => {
}, [vaultService]) }, [vaultService])
const updateInvites = useCallback(async () => { const updateInvites = useCallback(async () => {
setInvites(sharedVaultService.getCachedPendingInviteRecords()) setInvites(application.vaultInvites.getCachedPendingInviteRecords())
}, [sharedVaultService]) }, [application.vaultInvites])
const updateContacts = useCallback(async () => { const updateContacts = useCallback(async () => {
setContacts(contactService.getAllContacts()) setContacts(contactService.getAllContacts())
@@ -60,6 +60,20 @@ const Vaults = () => {
}) })
}, [application.sharedVaults, updateAllData]) }, [application.sharedVaults, updateAllData])
useEffect(() => {
return application.vaultUsers.addEventObserver((event) => {
if (event === VaultUserServiceEvent.UsersChanged) {
void updateAllData()
}
})
}, [application.vaultUsers, updateAllData])
useEffect(() => {
return application.vaultInvites.addEventObserver(() => {
void updateAllData()
})
}, [application.vaultInvites, updateAllData])
useEffect(() => { useEffect(() => {
return application.streamItems([ContentType.TYPES.VaultListing, ContentType.TYPES.TrustedContact], () => { return application.streamItems([ContentType.TYPES.VaultListing, ContentType.TYPES.TrustedContact], () => {
void updateAllData() void updateAllData()
@@ -67,9 +81,9 @@ const Vaults = () => {
}, [application, updateAllData]) }, [application, updateAllData])
useEffect(() => { useEffect(() => {
void sharedVaultService.downloadInboundInvites() void application.vaultInvites.downloadInboundInvites()
void updateAllData() void updateAllData()
}, [updateAllData, sharedVaultService]) }, [updateAllData, application.vaultInvites])
const createNewVault = useCallback(async () => { const createNewVault = useCallback(async () => {
setIsVaultModalOpen(true) setIsVaultModalOpen(true)

View File

@@ -20,7 +20,7 @@ const VaultItem = ({ vault }: Props) => {
const [isVaultModalOpen, setIsVaultModalOpen] = useState(false) const [isVaultModalOpen, setIsVaultModalOpen] = useState(false)
const closeVaultModal = () => setIsVaultModalOpen(false) const closeVaultModal = () => setIsVaultModalOpen(false)
const isAdmin = !vault.isSharedVaultListing() ? true : application.sharedVaults.isCurrentUserSharedVaultAdmin(vault) const isAdmin = !vault.isSharedVaultListing() ? true : application.vaultUsers.isCurrentUserSharedVaultAdmin(vault)
const deleteVault = useCallback(async () => { const deleteVault = useCallback(async () => {
const confirm = await application.alerts.confirm( const confirm = await application.alerts.confirm(
@@ -61,11 +61,11 @@ const VaultItem = ({ vault }: Props) => {
return return
} }
const success = await application.sharedVaults.leaveSharedVault(vault) const success = await application.vaultUsers.leaveSharedVault(vault)
if (!success) { if (!success) {
void application.alerts.alert('Unable to leave vault. Please try again.') void application.alerts.alert('Unable to leave vault. Please try again.')
} }
}, [application.alerts, application.sharedVaults, vault]) }, [application, vault])
const convertToSharedVault = useCallback(async () => { const convertToSharedVault = useCallback(async () => {
await application.sharedVaults.convertVaultToSharedVault(vault) await application.sharedVaults.convertVaultToSharedVault(vault)

View File

@@ -54,20 +54,20 @@ const EditVaultModal: FunctionComponent<Props> = ({ onCloseDialog, existingVault
if (existingVault.isSharedVaultListing()) { if (existingVault.isSharedVaultListing()) {
setIsAdmin( setIsAdmin(
existingVault.isSharedVaultListing() && application.sharedVaults.isCurrentUserSharedVaultAdmin(existingVault), existingVault.isSharedVaultListing() && application.vaultUsers.isCurrentUserSharedVaultAdmin(existingVault),
) )
const users = await application.sharedVaults.getSharedVaultUsers(existingVault) const users = await application.vaultUsers.getSharedVaultUsers(existingVault)
if (users) { if (users) {
setMembers(users) setMembers(users)
} }
const invites = await application.sharedVaults.getOutboundInvites(existingVault) const invites = await application.vaultInvites.getOutboundInvites(existingVault)
if (!isClientDisplayableError(invites)) { if (!isClientDisplayableError(invites)) {
setInvites(invites) setInvites(invites)
} }
} }
}, [application.sharedVaults, existingVault]) }, [application, existingVault])
useEffect(() => { useEffect(() => {
void reloadVaultInfo() void reloadVaultInfo()

View File

@@ -17,10 +17,10 @@ export const VaultModalInvites = ({
const deleteInvite = useCallback( const deleteInvite = useCallback(
async (invite: SharedVaultInviteServerHash) => { async (invite: SharedVaultInviteServerHash) => {
await application.sharedVaults.deleteInvite(invite) await application.vaultInvites.deleteInvite(invite)
onChange() onChange()
}, },
[application.sharedVaults, onChange], [application.vaultInvites, onChange],
) )
return ( return (

View File

@@ -20,18 +20,18 @@ export const VaultModalMembers = ({
const removeMemberFromVault = useCallback( const removeMemberFromVault = useCallback(
async (memberItem: SharedVaultUserServerHash) => { async (memberItem: SharedVaultUserServerHash) => {
if (vault.isSharedVaultListing()) { if (vault.isSharedVaultListing()) {
await application.sharedVaults.removeUserFromSharedVault(vault, memberItem.user_uuid) await application.vaultUsers.removeUserFromSharedVault(vault, memberItem.user_uuid)
onChange() onChange()
} }
}, },
[application.sharedVaults, vault, onChange], [application.vaultUsers, vault, onChange],
) )
return ( return (
<div className="mb-3"> <div className="mb-3">
<div className="mb-3 text-lg">Vault Members</div> <div className="mb-3 text-lg">Vault Members</div>
{members.map((member) => { {members.map((member) => {
if (application.sharedVaults.isSharedVaultUserSharedVaultOwner(member)) { if (application.vaultUsers.isVaultUserOwner(member)) {
return null return null
} }