refactor: break up vault services (#2364)
This commit is contained in:
@@ -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> {
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
@@ -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> {
|
||||||
@@ -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'
|
||||||
274
packages/services/src/Domain/VaultInvite/VaultInviteService.ts
Normal file
274
packages/services/src/Domain/VaultInvite/VaultInviteService.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export enum VaultInviteServiceEvent {
|
||||||
|
InviteSent = 'VaultInviteServiceEvent.InviteSent',
|
||||||
|
InvitesReloaded = 'VaultInviteServiceEvent.InvitesReloaded',
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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'
|
||||||
|
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
103
packages/services/src/Domain/VaultUser/VaultUserService.ts
Normal file
103
packages/services/src/Domain/VaultUser/VaultUserService.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export enum VaultUserServiceEvent {
|
||||||
|
UsersChanged = 'VaultUserServiceEvent.UsersChanged',
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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'
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user