Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type DesignateSurvivorResponse = {
success: boolean
}
2 changes: 2 additions & 0 deletions packages/api/src/Domain/Server/SharedVaultUsers/Paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
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'
import { DeleteSharedVaultUserResponse } from '../../Response/SharedVaultUsers/DeleteSharedVaultUserResponse'
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))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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>>
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
})
})
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface DesignateSurvivorDTO {
sharedVaultUuid: string
sharedVaultMemberUuid: string
}
15 changes: 15 additions & 0 deletions packages/services/src/Domain/VaultUser/VaultUserService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
Expand All @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion packages/services/src/Domain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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'
Expand Down
13 changes: 10 additions & 3 deletions packages/snjs/lib/Application/Dependencies/Dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -158,6 +162,7 @@ import {
SharedVaultInvitesServer,
SharedVaultServer,
SharedVaultUsersServer,
SharedVaultUsersServerInterface,
SubscriptionApiService,
SubscriptionServer,
UserApiService,
Expand All @@ -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>()
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
)
})
Expand Down
1 change: 1 addition & 0 deletions packages/snjs/lib/Application/Dependencies/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down