chore: add method to designate a shared vault survivor (#2538)
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
export type DesignateSurvivorResponse = {
|
||||
success: boolean
|
||||
}
|
||||
@@ -2,4 +2,6 @@ export const SharedVaultUsersPaths = {
|
||||
getSharedVaultUsers: (sharedVaultUuid: string) => `/v1/shared-vaults/${sharedVaultUuid}/users`,
|
||||
deleteSharedVaultUser: (sharedVaultUuid: string, userUuid: string) =>
|
||||
`/v1/shared-vaults/${sharedVaultUuid}/users/${userUuid}`,
|
||||
designateSurvivor: (sharedVaultUuid: string, sharedVaultMemberUuid: string) =>
|
||||
`/v1/shared-vaults/${sharedVaultUuid}/users/${sharedVaultMemberUuid}/designate-survivor`,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { HttpResponse } from '@standardnotes/responses'
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { HttpServiceInterface } from '../../Http'
|
||||
import { GetSharedVaultUsersRequestParams } from '../../Request/SharedVaultUser/GetSharedVaultUsersRequestParams'
|
||||
import { DeleteSharedVaultUserRequestParams } from '../../Request/SharedVaultUser/DeleteSharedVaultUserRequestParams'
|
||||
@@ -6,10 +8,20 @@ import { DeleteSharedVaultUserResponse } from '../../Response/SharedVaultUsers/D
|
||||
import { SharedVaultUsersServerInterface } from './SharedVaultUsersServerInterface'
|
||||
import { SharedVaultUsersPaths } from './Paths'
|
||||
import { GetSharedVaultUsersResponse } from '../../Response/SharedVaultUsers/GetSharedVaultUsersResponse'
|
||||
import { DesignateSurvivorResponse } from '../../Response/SharedVaultUsers/DesignateSurvivorResponse'
|
||||
|
||||
export class SharedVaultUsersServer implements SharedVaultUsersServerInterface {
|
||||
constructor(private httpService: HttpServiceInterface) {}
|
||||
|
||||
async designateSurvivor(params: {
|
||||
sharedVaultUuid: Uuid
|
||||
sharedVaultMemberUuid: Uuid
|
||||
}): Promise<HttpResponse<DesignateSurvivorResponse>> {
|
||||
return this.httpService.post(
|
||||
SharedVaultUsersPaths.designateSurvivor(params.sharedVaultUuid.value, params.sharedVaultMemberUuid.value),
|
||||
)
|
||||
}
|
||||
|
||||
getSharedVaultUsers(params: GetSharedVaultUsersRequestParams): Promise<HttpResponse<GetSharedVaultUsersResponse>> {
|
||||
return this.httpService.get(SharedVaultUsersPaths.getSharedVaultUsers(params.sharedVaultUuid))
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { HttpResponse } from '@standardnotes/responses'
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { GetSharedVaultUsersRequestParams } from '../../Request/SharedVaultUser/GetSharedVaultUsersRequestParams'
|
||||
import { DeleteSharedVaultUserRequestParams } from '../../Request/SharedVaultUser/DeleteSharedVaultUserRequestParams'
|
||||
import { DeleteSharedVaultUserResponse } from '../../Response/SharedVaultUsers/DeleteSharedVaultUserResponse'
|
||||
import { GetSharedVaultUsersResponse } from '../../Response/SharedVaultUsers/GetSharedVaultUsersResponse'
|
||||
import { DesignateSurvivorResponse } from '../../Response/SharedVaultUsers/DesignateSurvivorResponse'
|
||||
|
||||
export interface SharedVaultUsersServerInterface {
|
||||
getSharedVaultUsers(params: GetSharedVaultUsersRequestParams): Promise<HttpResponse<GetSharedVaultUsersResponse>>
|
||||
|
||||
designateSurvivor(params: {
|
||||
sharedVaultUuid: Uuid
|
||||
sharedVaultMemberUuid: Uuid
|
||||
}): Promise<HttpResponse<DesignateSurvivorResponse>>
|
||||
deleteSharedVaultUser(
|
||||
params: DeleteSharedVaultUserRequestParams,
|
||||
): Promise<HttpResponse<DeleteSharedVaultUserResponse>>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { SharedVaultUsersServerInterface } from '@standardnotes/api'
|
||||
|
||||
import { DesignateSurvivor } from './DesignateSurvivor'
|
||||
|
||||
describe('DesignateSurvivor', () => {
|
||||
let sharedVaultUserServer: SharedVaultUsersServerInterface
|
||||
|
||||
const createUseCase = () => new DesignateSurvivor(
|
||||
sharedVaultUserServer,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
sharedVaultUserServer = {} as jest.Mocked<SharedVaultUsersServerInterface>
|
||||
sharedVaultUserServer.designateSurvivor = jest.fn().mockReturnValue({
|
||||
status: 200,
|
||||
})
|
||||
})
|
||||
|
||||
it('should mark designated survivor', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultMemberUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
})
|
||||
|
||||
it('should fail if shared vault uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: 'invalid',
|
||||
sharedVaultMemberUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
|
||||
it('should fail if shared vault member uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultMemberUuid: 'invalid',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
|
||||
it('should fail if shared vault user server fails', async () => {
|
||||
sharedVaultUserServer.designateSurvivor = jest.fn().mockReturnValue({
|
||||
status: 500,
|
||||
})
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultMemberUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { SharedVaultUsersServerInterface } from '@standardnotes/api'
|
||||
import { HttpStatusCode } from '@standardnotes/responses'
|
||||
|
||||
import { DesignateSurvivorDTO } from './DesignateSurvivorDTO'
|
||||
|
||||
export class DesignateSurvivor implements UseCaseInterface<void> {
|
||||
constructor(private sharedVaultUserServer: SharedVaultUsersServerInterface) {}
|
||||
|
||||
async execute(dto: DesignateSurvivorDTO): Promise<Result<void>> {
|
||||
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultUuidOrError.getError())
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
|
||||
const sharedVaultMemberUuidOrError = Uuid.create(dto.sharedVaultMemberUuid)
|
||||
if (sharedVaultMemberUuidOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultMemberUuidOrError.getError())
|
||||
}
|
||||
const sharedVaultMemberUuid = sharedVaultMemberUuidOrError.getValue()
|
||||
|
||||
const response = await this.sharedVaultUserServer.designateSurvivor({
|
||||
sharedVaultUuid,
|
||||
sharedVaultMemberUuid,
|
||||
})
|
||||
|
||||
if (response.status !== HttpStatusCode.Success) {
|
||||
return Result.fail('Failed to mark designated survivor on the server')
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface DesignateSurvivorDTO {
|
||||
sharedVaultUuid: string
|
||||
sharedVaultMemberUuid: string
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import { Result } from '@standardnotes/domain-core'
|
||||
import { IsVaultOwner } from './UseCase/IsVaultOwner'
|
||||
import { IsReadonlyVaultMember } from './UseCase/IsReadonlyVaultMember'
|
||||
import { IsVaultAdmin } from './UseCase/IsVaultAdmin'
|
||||
import { DesignateSurvivor } from './UseCase/DesignateSurvivor/DesignateSurvivor'
|
||||
|
||||
export class VaultUserService extends AbstractService<VaultUserServiceEvent> implements VaultUserServiceInterface {
|
||||
constructor(
|
||||
@@ -26,6 +27,7 @@ export class VaultUserService extends AbstractService<VaultUserServiceEvent> imp
|
||||
private _isReadonlyVaultMember: IsReadonlyVaultMember,
|
||||
private _getVault: GetVault,
|
||||
private _leaveVault: LeaveVault,
|
||||
private designateSurvivorUseCase: DesignateSurvivor,
|
||||
eventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(eventBus)
|
||||
@@ -41,6 +43,19 @@ export class VaultUserService extends AbstractService<VaultUserServiceEvent> imp
|
||||
;(this._leaveVault as unknown) = undefined
|
||||
}
|
||||
|
||||
async designateSurvivor(sharedVault: SharedVaultListingInterface, userUuid: string): Promise<Result<void>> {
|
||||
const result = await this.designateSurvivorUseCase.execute({
|
||||
sharedVaultMemberUuid: userUuid,
|
||||
sharedVaultUuid: sharedVault.sharing.sharedVaultUuid,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return Result.fail(`Could not designate survivor: ${result.getError()}`)
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
async invalidateVaultUsersCache(sharedVaultUuid?: string) {
|
||||
if (sharedVaultUuid) {
|
||||
await this._getVaultUsers.execute({
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface VaultUserServiceInterface extends ApplicationServiceInterface<V
|
||||
isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean
|
||||
isCurrentUserReadonlyVaultMember(vault: VaultListingInterface): boolean
|
||||
removeUserFromSharedVault(sharedVault: SharedVaultListingInterface, userUuid: string): Promise<Result<void>>
|
||||
designateSurvivor(sharedVault: SharedVaultListingInterface, userUuid: string): Promise<Result<void>>
|
||||
leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void>
|
||||
isVaultUserOwner(user: SharedVaultUserServerHash): boolean
|
||||
getFormattedMemberPermission(permission: string): string
|
||||
|
||||
@@ -203,6 +203,7 @@ export * from './User/UserServiceInterface'
|
||||
export * from './User/UserServiceInterface'
|
||||
export * from './UserEvent/NotificationService'
|
||||
export * from './UserEvent/NotificationServiceEvent'
|
||||
export * from './Vault/UseCase/AuthorizeVaultDeletion'
|
||||
export * from './Vault/UseCase/ChangeVaultKeyOptions'
|
||||
export * from './Vault/UseCase/ChangeVaultKeyOptionsDTO'
|
||||
export * from './Vault/UseCase/ChangeVaultStorageMode'
|
||||
@@ -231,9 +232,12 @@ export * from './VaultLock/UseCase/ValidateVaultPassword'
|
||||
export * from './VaultLock/VaultLockService'
|
||||
export * from './VaultLock/VaultLockServiceEvent'
|
||||
export * from './VaultLock/VaultLockServiceInterface'
|
||||
export * from './VaultUser/UseCase/GetVaultContacts'
|
||||
export * from './VaultUser/UseCase/DesignateSurvivor/DesignateSurvivor'
|
||||
export * from './VaultUser/UseCase/DesignateSurvivor/DesignateSurvivorDTO'
|
||||
export * from './VaultUser/UseCase/GetVaultContacts'
|
||||
export * from './VaultUser/UseCase/GetVaultUsers'
|
||||
export * from './VaultUser/UseCase/IsReadonlyVaultMember'
|
||||
export * from './VaultUser/UseCase/IsVaultAdmin'
|
||||
export * from './VaultUser/UseCase/IsVaultOwner'
|
||||
export * from './VaultUser/UseCase/LeaveSharedVault'
|
||||
export * from './VaultUser/UseCase/RemoveSharedVaultMember'
|
||||
|
||||
@@ -141,6 +141,10 @@ import {
|
||||
CreateEncryptedBackupFile,
|
||||
SyncLocalVaultsWithRemoteSharedVaults,
|
||||
WebSocketsService,
|
||||
AuthorizeVaultDeletion,
|
||||
IsVaultAdmin,
|
||||
IsReadonlyVaultMember,
|
||||
DesignateSurvivor,
|
||||
} from '@standardnotes/services'
|
||||
import { ItemManager } from '../../Services/Items/ItemManager'
|
||||
import { PayloadManager } from '../../Services/Payloads/PayloadManager'
|
||||
@@ -158,6 +162,7 @@ import {
|
||||
SharedVaultInvitesServer,
|
||||
SharedVaultServer,
|
||||
SharedVaultUsersServer,
|
||||
SharedVaultUsersServerInterface,
|
||||
SubscriptionApiService,
|
||||
SubscriptionServer,
|
||||
UserApiService,
|
||||
@@ -171,9 +176,6 @@ import { Logger, isNotUndefined, isDeinitable, LoggerInterface } from '@standard
|
||||
import { EncryptionOperators } from '@standardnotes/encryption'
|
||||
import { AsymmetricMessagePayload, AsymmetricMessageSharedVaultInvite } from '@standardnotes/models'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import { AuthorizeVaultDeletion } from '@standardnotes/services/src/Domain/Vault/UseCase/AuthorizeVaultDeletion'
|
||||
import { IsVaultAdmin } from '@standardnotes/services/src/Domain/VaultUser/UseCase/IsVaultAdmin'
|
||||
import { IsReadonlyVaultMember } from '@standardnotes/services/src/Domain/VaultUser/UseCase/IsReadonlyVaultMember'
|
||||
|
||||
export class Dependencies {
|
||||
private factory = new Map<symbol, () => unknown>()
|
||||
@@ -683,6 +685,10 @@ export class Dependencies {
|
||||
return new RemoveVaultMember(this.get<SharedVaultUsersServer>(TYPES.SharedVaultUsersServer))
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.DesignateSurvivor, () => {
|
||||
return new DesignateSurvivor(this.get<SharedVaultUsersServerInterface>(TYPES.SharedVaultUsersServer))
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.GetVaultUsers, () => {
|
||||
return new GetVaultUsers(
|
||||
this.get<SharedVaultUsersServer>(TYPES.SharedVaultUsersServer),
|
||||
@@ -861,6 +867,7 @@ export class Dependencies {
|
||||
this.get<IsReadonlyVaultMember>(TYPES.IsReadonlyVaultMember),
|
||||
this.get<GetVault>(TYPES.GetVault),
|
||||
this.get<LeaveVault>(TYPES.LeaveVault),
|
||||
this.get<DesignateSurvivor>(TYPES.DesignateSurvivor),
|
||||
this.get<InternalEventBus>(TYPES.InternalEventBus),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -125,6 +125,7 @@ export const TYPES = {
|
||||
ConvertToSharedVault: Symbol.for('ConvertToSharedVault'),
|
||||
DeleteSharedVault: Symbol.for('DeleteSharedVault'),
|
||||
RemoveVaultMember: Symbol.for('RemoveVaultMember'),
|
||||
DesignateSurvivor: Symbol.for('DesignateSurvivor'),
|
||||
GetVaultUsers: Symbol.for('GetSharedVaultUsers'),
|
||||
ResendAllMessages: Symbol.for('ResendAllMessages'),
|
||||
ReuploadAllInvites: Symbol.for('ReuploadAllInvites'),
|
||||
|
||||
Reference in New Issue
Block a user