Compare commits

..

3 Commits

Author SHA1 Message Date
standardci
15a914e25e chore(release): publish new version
- @standardnotes/home-server@1.11.28
 - @standardnotes/syncing-server@1.55.0
2023-07-06 10:12:49 +00:00
Karol Sójko
912a29d091 feat: update shared vault invite. (#644)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-06 11:58:45 +02:00
Karol Sójko
b2c32ce70e feat: shared vault users controller. (#643)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-06 11:41:36 +02:00
14 changed files with 333 additions and 5 deletions

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.28](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.27...@standardnotes/home-server@1.11.28) (2023-07-06)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.27](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.26...@standardnotes/home-server@1.11.27) (2023-07-06)
**Note:** Version bump only for package @standardnotes/home-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/home-server",
"version": "1.11.27",
"version": "1.11.28",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.55.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.54.0...@standardnotes/syncing-server@1.55.0) (2023-07-06)
### Features
* shared vault users controller. ([#643](https://github.com/standardnotes/syncing-server-js/issues/643)) ([b2c32ce](https://github.com/standardnotes/syncing-server-js/commit/b2c32ce70e9020b8d755a65432cb286b624a009c))
* update shared vault invite. ([#644](https://github.com/standardnotes/syncing-server-js/issues/644)) ([912a29d](https://github.com/standardnotes/syncing-server-js/commit/912a29d091ed1ca0af1712cbd09986a1c173a960))
# [1.54.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.53.0...@standardnotes/syncing-server@1.54.0) (2023-07-06)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.54.0",
"version": "1.55.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -42,6 +42,8 @@ const TYPES = {
Sync_CreateSharedVault: Symbol.for('Sync_CreateSharedVault'),
Sync_DeleteSharedVault: Symbol.for('Sync_DeleteSharedVault'),
Sync_CreateSharedVaultFileValetToken: Symbol.for('Sync_CreateSharedVaultFileValetToken'),
Sync_GetSharedVaultUsers: Symbol.for('Sync_GetSharedVaultUsers'),
Sync_RemoveSharedVaultUser: Symbol.for('Sync_RemoveSharedVaultUser'),
// Handlers
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),

View File

@@ -0,0 +1,141 @@
import { TimerInterface } from '@standardnotes/time'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { UpdateSharedVaultInvite } from './UpdateSharedVaultInvite'
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
import { Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
describe('UpdateSharedVaultInvite', () => {
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let timer: TimerInterface
let invite: SharedVaultInvite
const createUseCase = () => new UpdateSharedVaultInvite(sharedVaultInviteRepository, timer)
beforeEach(() => {
invite = SharedVaultInvite.create({
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
encryptedMessage: 'encrypted message',
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
sharedVaultInviteRepository.findByUuid = jest.fn().mockResolvedValue(invite)
sharedVaultInviteRepository.save = jest.fn().mockResolvedValue(null)
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
})
it('should update the invite', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: '00000000-0000-0000-0000-000000000000',
encryptedMessage: 'new encrypted message',
})
expect(result.isFailed()).toBe(false)
expect(sharedVaultInviteRepository.save).toHaveBeenCalled()
})
it('should update the invite with new permission', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: '00000000-0000-0000-0000-000000000000',
encryptedMessage: 'new encrypted message',
permission: SharedVaultUserPermission.PERMISSIONS.Write,
})
expect(result.isFailed()).toBe(false)
expect(sharedVaultInviteRepository.save).toHaveBeenCalled()
})
it('should fail if invite is not found', async () => {
sharedVaultInviteRepository.findByUuid = jest.fn().mockResolvedValue(null)
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: '00000000-0000-0000-0000-000000000000',
encryptedMessage: 'new encrypted message',
})
expect(result.isFailed()).toBe(true)
expect(sharedVaultInviteRepository.save).not.toHaveBeenCalled()
})
it('should fail if sender is not the same as the invite sender', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: '00000000-0000-0000-0000-000000000001',
encryptedMessage: 'new encrypted message',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Only the sender can update the invite')
expect(sharedVaultInviteRepository.save).not.toHaveBeenCalled()
})
it('should fail if the invite uuid is not valid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: 'invalid-uuid',
senderUuid: '00000000-0000-0000-0000-000000000000',
encryptedMessage: 'new encrypted message',
})
expect(result.isFailed()).toBe(true)
expect(sharedVaultInviteRepository.save).not.toHaveBeenCalled()
})
it('should fail if the sender uuid is not valid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: 'invalid-uuid',
encryptedMessage: 'new encrypted message',
})
expect(result.isFailed()).toBe(true)
expect(sharedVaultInviteRepository.save).not.toHaveBeenCalled()
})
it('should fail if the encrypted message is not valid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: '00000000-0000-0000-0000-000000000000',
encryptedMessage: '',
})
expect(result.isFailed()).toBe(true)
expect(sharedVaultInviteRepository.save).not.toHaveBeenCalled()
})
it('should fail if the permission is not valid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: '00000000-0000-0000-0000-000000000000',
encryptedMessage: 'new encrypted message',
permission: 'invalid-permission',
})
expect(result.isFailed()).toBe(true)
expect(sharedVaultInviteRepository.save).not.toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,62 @@
import { Result, Timestamps, UseCaseInterface, Uuid, Validator } from '@standardnotes/domain-core'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { UpdateSharedVaultInviteDTO } from './UpdateSharedVaultInviteDTO'
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
import { TimerInterface } from '@standardnotes/time'
export class UpdateSharedVaultInvite implements UseCaseInterface<void> {
constructor(
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
private timer: TimerInterface,
) {}
async execute(dto: UpdateSharedVaultInviteDTO): Promise<Result<void>> {
const inviteUuidOrError = Uuid.create(dto.inviteUuid)
if (inviteUuidOrError.isFailed()) {
return Result.fail(inviteUuidOrError.getError())
}
const inviteUuid = inviteUuidOrError.getValue()
const senderUuidOrError = Uuid.create(dto.senderUuid)
if (senderUuidOrError.isFailed()) {
return Result.fail(senderUuidOrError.getError())
}
const senderUuid = senderUuidOrError.getValue()
const emptyMessageValidation = Validator.isNotEmpty(dto.encryptedMessage)
if (emptyMessageValidation.isFailed()) {
return Result.fail(emptyMessageValidation.getError())
}
const invite = await this.sharedVaultInviteRepository.findByUuid(inviteUuid)
if (!invite) {
return Result.fail('Invite not found')
}
if (!invite.props.senderUuid.equals(senderUuid)) {
return Result.fail('Only the sender can update the invite')
}
invite.props.encryptedMessage = dto.encryptedMessage
if (dto.permission !== undefined) {
const permissionOrError = SharedVaultUserPermission.create(dto.permission)
if (permissionOrError.isFailed()) {
return Result.fail(permissionOrError.getError())
}
const permission = permissionOrError.getValue()
invite.props.permission = permission
}
invite.props.timestamps = Timestamps.create(
invite.props.timestamps.createdAt,
this.timer.getTimestampInMicroseconds(),
).getValue()
await this.sharedVaultInviteRepository.save(invite)
return Result.ok()
}
}

View File

@@ -0,0 +1,6 @@
export interface UpdateSharedVaultInviteDTO {
encryptedMessage: string
inviteUuid: string
senderUuid: string
permission?: string
}

View File

@@ -0,0 +1,73 @@
import { Request, Response } from 'express'
import { BaseHttpController, results } from 'inversify-express-utils'
import { HttpStatusCode } from '@standardnotes/responses'
import { ControllerContainerInterface, MapperInterface } from '@standardnotes/domain-core'
import { SharedVaultUser } from '../../../Domain/SharedVault/User/SharedVaultUser'
import { SharedVaultUserHttpRepresentation } from '../../../Mapping/Http/SharedVaultUserHttpRepresentation'
import { GetSharedVaultUsers } from '../../../Domain/UseCase/GetSharedVaultUsers/GetSharedVaultUsers'
import { RemoveUserFromSharedVault } from '../../../Domain/UseCase/RemoveUserFromSharedVault/RemoveUserFromSharedVault'
export class HomeServerSharedVaultUsersController extends BaseHttpController {
constructor(
protected getSharedVaultUsersUseCase: GetSharedVaultUsers,
protected removeUserFromSharedVaultUseCase: RemoveUserFromSharedVault,
protected sharedVaultUserHttpMapper: MapperInterface<SharedVaultUser, SharedVaultUserHttpRepresentation>,
private controllerContainer?: ControllerContainerInterface,
) {
super()
if (this.controllerContainer !== undefined) {
this.controllerContainer.register('sync.shared-vault-users.get-users', this.getSharedVaultUsers.bind(this))
this.controllerContainer.register(
'sync.shared-vault-users.remove-user',
this.removeUserFromSharedVault.bind(this),
)
}
}
async getSharedVaultUsers(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.getSharedVaultUsersUseCase.execute({
originatorUuid: response.locals.user.uuid,
sharedVaultUuid: request.params.sharedVaultUuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
users: result.getValue().map((sharedVault) => this.sharedVaultUserHttpMapper.toProjection(sharedVault)),
})
}
async removeUserFromSharedVault(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.removeUserFromSharedVaultUseCase.execute({
sharedVaultUuid: request.params.sharedVaultUuid,
userUuid: request.params.userUuid,
originatorUuid: response.locals.user.uuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
success: true,
})
}
}

View File

@@ -8,7 +8,7 @@ import { SharedVault } from '../../../Domain/SharedVault/SharedVault'
import { SharedVaultHttpRepresentation } from '../../../Mapping/Http/SharedVaultHttpRepresentation'
import { CreateSharedVault } from '../../../Domain/UseCase/CreateSharedVault/CreateSharedVault'
import { SharedVaultUser } from '../../../Domain/SharedVault/User/SharedVaultUser'
import { SharedVaultUserHttpRepresentation } from '../../../Mapping/Http/SharedVaultUserHttpRepresentation copy'
import { SharedVaultUserHttpRepresentation } from '../../../Mapping/Http/SharedVaultUserHttpRepresentation'
import { DeleteSharedVault } from '../../../Domain/UseCase/DeleteSharedVault/DeleteSharedVault'
import { CreateSharedVaultFileValetToken } from '../../../Domain/UseCase/CreateSharedVaultFileValetToken/CreateSharedVaultFileValetToken'

View File

@@ -0,0 +1,33 @@
import { controller, httpDelete, httpGet, results } from 'inversify-express-utils'
import { inject } from 'inversify'
import { MapperInterface } from '@standardnotes/domain-core'
import { Request, Response } from 'express'
import { HomeServerSharedVaultUsersController } from './HomeServer/HomeServerSharedVaultUsersController'
import TYPES from '../../Bootstrap/Types'
import { SharedVaultUser } from '../../Domain/SharedVault/User/SharedVaultUser'
import { SharedVaultUserHttpRepresentation } from '../../Mapping/Http/SharedVaultUserHttpRepresentation'
import { GetSharedVaultUsers } from '../../Domain/UseCase/GetSharedVaultUsers/GetSharedVaultUsers'
import { RemoveUserFromSharedVault } from '../../Domain/UseCase/RemoveUserFromSharedVault/RemoveUserFromSharedVault'
@controller('/shared-vaults/:sharedVaultUuid/users', TYPES.Sync_AuthMiddleware)
export class InversifyExpressSharedVaultUsersController extends HomeServerSharedVaultUsersController {
constructor(
@inject(TYPES.Sync_GetSharedVaultUsers) override getSharedVaultUsersUseCase: GetSharedVaultUsers,
@inject(TYPES.Sync_RemoveSharedVaultUser) override removeUserFromSharedVaultUseCase: RemoveUserFromSharedVault,
@inject(TYPES.Sync_SharedVaultUserHttpMapper)
override sharedVaultUserHttpMapper: MapperInterface<SharedVaultUser, SharedVaultUserHttpRepresentation>,
) {
super(getSharedVaultUsersUseCase, removeUserFromSharedVaultUseCase, sharedVaultUserHttpMapper)
}
@httpGet('/')
override async getSharedVaultUsers(request: Request, response: Response): Promise<results.JsonResult> {
return super.getSharedVaultUsers(request, response)
}
@httpDelete('/:userUuid')
override async removeUserFromSharedVault(request: Request, response: Response): Promise<results.JsonResult> {
return super.removeUserFromSharedVault(request, response)
}
}

View File

@@ -12,7 +12,7 @@ import { CreateSharedVaultFileValetToken } from '../../Domain/UseCase/CreateShar
import { DeleteSharedVault } from '../../Domain/UseCase/DeleteSharedVault/DeleteSharedVault'
import { GetSharedVaults } from '../../Domain/UseCase/GetSharedVaults/GetSharedVaults'
import { SharedVaultHttpRepresentation } from '../../Mapping/Http/SharedVaultHttpRepresentation'
import { SharedVaultUserHttpRepresentation } from '../../Mapping/Http/SharedVaultUserHttpRepresentation copy'
import { SharedVaultUserHttpRepresentation } from '../../Mapping/Http/SharedVaultUserHttpRepresentation'
@controller('/shared-vaults', TYPES.Sync_AuthMiddleware)
export class InversifyExpressSharedVaultsController extends HomeServerSharedVaultsController {

View File

@@ -1,7 +1,7 @@
import { MapperInterface } from '@standardnotes/domain-core'
import { SharedVaultUser } from '../../Domain/SharedVault/User/SharedVaultUser'
import { SharedVaultUserHttpRepresentation } from './SharedVaultUserHttpRepresentation copy'
import { SharedVaultUserHttpRepresentation } from './SharedVaultUserHttpRepresentation'
export class SharedVaultUserHttpMapper implements MapperInterface<SharedVaultUser, SharedVaultUserHttpRepresentation> {
toDomain(_projection: SharedVaultUserHttpRepresentation): SharedVaultUser {