From cd92f7827cb15561bb3269697b81ff064d039080 Mon Sep 17 00:00:00 2001 From: Mo Date: Sun, 28 May 2023 10:30:10 -0500 Subject: [PATCH] refactor: rename groups to vaults --- packages/api-gateway/bin/server.ts | 2 +- packages/api-gateway/src/Controller/index.ts | 2 +- ...roupsController.ts => VaultsController.ts} | 4 +- .../mysql/1665047863774-remove-groups.ts | 4 +- .../common/src/Domain/Content/ContentType.ts | 2 +- packages/files/bin/server.ts | 2 +- packages/files/src/Bootstrap/Container.ts | 4 +- packages/files/src/Bootstrap/Types.ts | 2 +- ...sController.ts => VaultFilesController.ts} | 12 +- ...re.ts => VaultValetTokenAuthMiddleware.ts} | 15 +- .../GetFileMetadata/GetFileMetadata.ts | 7 +- .../GetFileMetadata/GetFileMetadataDTO.ts | 13 +- .../StreamDownloadFile/StreamDownloadFile.ts | 9 +- .../StreamDownloadFileDTO.ts | 15 +- .../src/Domain/Token/GroupValetTokenData.ts | 8 - .../src/Domain/Token/VaultValetTokenData.ts | 7 + packages/security/src/Domain/index.ts | 2 +- packages/syncing-server/README.md | 2 +- packages/syncing-server/bin/server.ts | 6 +- ...group.ts => 1684173017359-create_vault.ts} | 10 +- ....ts => 1684173023883-create_vault_user.ts} | 10 +- ... 1684318117584-add_vault_uuid_to_items.ts} | 8 +- ... => 1684528646896-create_vault_invites.ts} | 12 +- ...6859271-allow_item_user_uuid_to_be_null.ts | 31 ++++ .../Bootstrap/CommonContainerConfigLoader.ts | 64 +++---- .../src/Bootstrap/DataSource.ts | 8 +- .../Bootstrap/ServerContainerConfigLoader.ts | 86 ++++----- .../syncing-server/src/Bootstrap/Types.ts | 32 ++-- .../Bootstrap/WorkerContainerConfigLoader.ts | 2 +- .../src/Controller/GroupsController.ts | 135 -------------- .../src/Controller/ItemsController.ts | 2 +- ...ontroller.ts => VaultInvitesController.ts} | 80 ++++---- ...sController.ts => VaultUsersController.ts} | 30 +-- .../src/Controller/VaultsController.ts | 135 ++++++++++++++ .../src/Domain/Group/Factory/GroupFactory.ts | 31 ---- .../Group/Factory/GroupFactoryInterface.ts | 7 - .../Repository/GroupRepositoryInterface.ts | 19 -- .../Repository/TypeORMGroupRepository.ts | 56 ------ .../src/Domain/Group/Service/GroupService.ts | 99 ---------- .../Group/Service/GroupServiceInterface.ts | 27 --- .../GroupInvite/Factory/GroupInviteFactory.ts | 35 ---- .../Factory/GroupInviteFactoryInterface.ts | 7 - .../GroupInvite/Factory/GroupInviteHash.ts | 15 -- .../GroupInviteRepositoryInterface.ts | 25 --- .../TypeORMGroupInviteRepository.ts | 71 -------- .../GroupInvite/Service/CreateInviteDTO.ts | 12 -- .../GroupInvite/Service/GroupInviteService.ts | 172 ------------------ .../Service/GroupInviteServiceInterface.ts | 26 --- .../GroupInvite/Service/UpdateInviteDTO.ts | 9 - .../GroupUser/Factory/GroupUserFactory.ts | 31 ---- .../Factory/GroupUserFactoryInterface.ts | 7 - .../Domain/GroupUser/Factory/GroupUserHash.ts | 10 - .../GroupUser/Model/GroupUserPermission.ts | 1 - .../GroupUserRepositoryInterface.ts | 24 --- .../Repository/TypeORMGroupUserRepository.ts | 65 ------- .../GroupUser/Service/GroupUserService.ts | 111 ----------- .../Service/GroupUserServiceInterface.ts | 24 --- .../UserCredentialsChangedEventHandler.ts | 12 +- .../src/Domain/Item/GetItemsDTO.ts | 2 +- .../syncing-server/src/Domain/Item/Item.ts | 8 +- .../src/Domain/Item/ItemFactory.ts | 4 +- .../src/Domain/Item/ItemHash.ts | 2 +- .../src/Domain/Item/ItemQuery.ts | 4 +- .../src/Domain/Item/ItemService.ts | 47 +++-- .../Domain/Item/SaveRule/OwnershipFilter.ts | 60 +++--- .../Domain/Item/SaveRule/SnjsVersionFilter.ts | 8 +- .../Item/SyncResponse/SyncResponse20200115.ts | 8 +- .../SyncResponseFactory20200115.ts | 28 +-- .../CreateGroupFileReadValetTokenDTO.ts | 6 - .../CreateVaultFileReadValetToken.ts} | 29 ++- .../CreateVaultFileReadValetTokenDTO.ts | 6 + .../src/Domain/UseCase/SyncItems.ts | 22 +-- .../src/Domain/UseCase/SyncItemsDTO.ts | 2 +- .../src/Domain/UseCase/SyncItemsResponse.ts | 8 +- .../src/Domain/Vault/Factory/VaultFactory.ts | 31 ++++ .../Vault/Factory/VaultFactoryInterface.ts | 7 + .../Factory/VaultHash.ts} | 4 +- .../Model/Group.ts => Vault/Model/Vault.ts} | 10 +- .../Repository/TypeORMVaultRepository.ts | 56 ++++++ .../Repository/VaultRepositoryInterface.ts | 19 ++ .../src/Domain/Vault/Service/VaultService.ts | 99 ++++++++++ .../Vault/Service/VaultServiceInterface.ts | 27 +++ .../VaultInvite/Factory/VaultInviteFactory.ts | 35 ++++ .../Factory/VaultInviteFactoryInterface.ts | 7 + .../VaultInvite/Factory/VaultInviteHash.ts | 15 ++ .../Model/VaultInvite.ts} | 20 +- .../Model/VaultInviteType.ts} | 2 +- .../TypeORMVaultInviteRepository.ts | 71 ++++++++ .../VaultInviteRepositoryInterface.ts | 25 +++ .../VaultInvite/Service/CreateInviteDTO.ts | 12 ++ .../Service/GetUserVaultInvitesDTO.ts} | 2 +- .../VaultInvite/Service/UpdateInviteDTO.ts | 9 + .../VaultInvite/Service/VaultInviteService.ts | 172 ++++++++++++++++++ .../Service/VaultInviteServiceInterface.ts | 26 +++ .../VaultUser/Factory/VaultUserFactory.ts | 31 ++++ .../Factory/VaultUserFactoryInterface.ts | 7 + .../Domain/VaultUser/Factory/VaultUserHash.ts | 10 + .../Model/VaultUser.ts} | 12 +- .../VaultUser/Model/VaultUserPermission.ts | 1 + .../Repository/TypeORMVaultUserRepository.ts | 65 +++++++ .../VaultUserRepositoryInterface.ts | 24 +++ .../Service/GetVaultUsersDTO.ts} | 2 +- .../VaultUser/Service/VaultUserService.ts | 111 +++++++++++ .../Service/VaultUserServiceInterface.ts | 24 +++ .../Infra/TypeORM/TypeORMItemRepository.ts | 8 +- .../src/Projection/GroupInviteProjection.ts | 14 -- .../src/Projection/GroupInviteProjector.ts | 33 ---- .../src/Projection/GroupProjector.ts | 29 --- .../src/Projection/GroupUserProjector.ts | 43 ----- .../src/Projection/ItemProjection.ts | 4 +- .../src/Projection/ItemProjectionWithUser.ts | 2 +- .../src/Projection/ItemProjector.ts | 2 +- .../src/Projection/SavedItemProjection.ts | 2 +- .../src/Projection/SavedItemProjector.ts | 2 +- .../src/Projection/VaultInviteProjection.ts | 14 ++ .../src/Projection/VaultInviteProjector.ts | 33 ++++ ...{GroupProjection.ts => VaultProjection.ts} | 4 +- .../src/Projection/VaultProjector.ts | 29 +++ ...erProjection.ts => VaultUserProjection.ts} | 8 +- .../src/Projection/VaultUserProjector.ts | 43 +++++ 120 files changed, 1592 insertions(+), 1517 deletions(-) rename packages/api-gateway/src/Controller/v1/{GroupsController.ts => VaultsController.ts} (87%) rename packages/files/src/Controller/{GroupFilesController.ts => VaultFilesController.ts} (87%) rename packages/files/src/Controller/{GroupValetTokenAuthMiddleware.ts => VaultValetTokenAuthMiddleware.ts} (80%) delete mode 100644 packages/security/src/Domain/Token/GroupValetTokenData.ts create mode 100644 packages/security/src/Domain/Token/VaultValetTokenData.ts rename packages/syncing-server/migrations/mysql/{1684173017359-create_group.ts => 1684173017359-create_vault.ts} (83%) rename packages/syncing-server/migrations/mysql/{1684173023883-create_group_user.ts => 1684173023883-create_vault_user.ts} (84%) rename packages/syncing-server/migrations/mysql/{1684318117584-add_group_uuid_to_items.ts => 1684318117584-add_vault_uuid_to_items.ts} (77%) rename packages/syncing-server/migrations/mysql/{1684528646896-create_group_invites.ts => 1684528646896-create_vault_invites.ts} (86%) create mode 100644 packages/syncing-server/migrations/mysql/1685276859271-allow_item_user_uuid_to_be_null.ts delete mode 100644 packages/syncing-server/src/Controller/GroupsController.ts rename packages/syncing-server/src/Controller/{GroupInvitesController.ts => VaultInvitesController.ts} (61%) rename packages/syncing-server/src/Controller/{GroupUsersController.ts => VaultUsersController.ts} (55%) create mode 100644 packages/syncing-server/src/Controller/VaultsController.ts delete mode 100644 packages/syncing-server/src/Domain/Group/Factory/GroupFactory.ts delete mode 100644 packages/syncing-server/src/Domain/Group/Factory/GroupFactoryInterface.ts delete mode 100644 packages/syncing-server/src/Domain/Group/Repository/GroupRepositoryInterface.ts delete mode 100644 packages/syncing-server/src/Domain/Group/Repository/TypeORMGroupRepository.ts delete mode 100644 packages/syncing-server/src/Domain/Group/Service/GroupService.ts delete mode 100644 packages/syncing-server/src/Domain/Group/Service/GroupServiceInterface.ts delete mode 100644 packages/syncing-server/src/Domain/GroupInvite/Factory/GroupInviteFactory.ts delete mode 100644 packages/syncing-server/src/Domain/GroupInvite/Factory/GroupInviteFactoryInterface.ts delete mode 100644 packages/syncing-server/src/Domain/GroupInvite/Factory/GroupInviteHash.ts delete mode 100644 packages/syncing-server/src/Domain/GroupInvite/Repository/GroupInviteRepositoryInterface.ts delete mode 100644 packages/syncing-server/src/Domain/GroupInvite/Repository/TypeORMGroupInviteRepository.ts delete mode 100644 packages/syncing-server/src/Domain/GroupInvite/Service/CreateInviteDTO.ts delete mode 100644 packages/syncing-server/src/Domain/GroupInvite/Service/GroupInviteService.ts delete mode 100644 packages/syncing-server/src/Domain/GroupInvite/Service/GroupInviteServiceInterface.ts delete mode 100644 packages/syncing-server/src/Domain/GroupInvite/Service/UpdateInviteDTO.ts delete mode 100644 packages/syncing-server/src/Domain/GroupUser/Factory/GroupUserFactory.ts delete mode 100644 packages/syncing-server/src/Domain/GroupUser/Factory/GroupUserFactoryInterface.ts delete mode 100644 packages/syncing-server/src/Domain/GroupUser/Factory/GroupUserHash.ts delete mode 100644 packages/syncing-server/src/Domain/GroupUser/Model/GroupUserPermission.ts delete mode 100644 packages/syncing-server/src/Domain/GroupUser/Repository/GroupUserRepositoryInterface.ts delete mode 100644 packages/syncing-server/src/Domain/GroupUser/Repository/TypeORMGroupUserRepository.ts delete mode 100644 packages/syncing-server/src/Domain/GroupUser/Service/GroupUserService.ts delete mode 100644 packages/syncing-server/src/Domain/GroupUser/Service/GroupUserServiceInterface.ts delete mode 100644 packages/syncing-server/src/Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileReadValetTokenDTO.ts rename packages/syncing-server/src/Domain/UseCase/{CreateGroupFileValetToken/CreateGroupFileReadValetToken.ts => CreateVaultFileValetToken/CreateVaultFileReadValetToken.ts} (60%) create mode 100644 packages/syncing-server/src/Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileReadValetTokenDTO.ts create mode 100644 packages/syncing-server/src/Domain/Vault/Factory/VaultFactory.ts create mode 100644 packages/syncing-server/src/Domain/Vault/Factory/VaultFactoryInterface.ts rename packages/syncing-server/src/Domain/{Group/Factory/GroupHash.ts => Vault/Factory/VaultHash.ts} (70%) rename packages/syncing-server/src/Domain/{Group/Model/Group.ts => Vault/Model/Vault.ts} (78%) create mode 100644 packages/syncing-server/src/Domain/Vault/Repository/TypeORMVaultRepository.ts create mode 100644 packages/syncing-server/src/Domain/Vault/Repository/VaultRepositoryInterface.ts create mode 100644 packages/syncing-server/src/Domain/Vault/Service/VaultService.ts create mode 100644 packages/syncing-server/src/Domain/Vault/Service/VaultServiceInterface.ts create mode 100644 packages/syncing-server/src/Domain/VaultInvite/Factory/VaultInviteFactory.ts create mode 100644 packages/syncing-server/src/Domain/VaultInvite/Factory/VaultInviteFactoryInterface.ts create mode 100644 packages/syncing-server/src/Domain/VaultInvite/Factory/VaultInviteHash.ts rename packages/syncing-server/src/Domain/{GroupInvite/Model/GroupInvite.ts => VaultInvite/Model/VaultInvite.ts} (65%) rename packages/syncing-server/src/Domain/{GroupInvite/Model/GroupInviteType.ts => VaultInvite/Model/VaultInviteType.ts} (61%) create mode 100644 packages/syncing-server/src/Domain/VaultInvite/Repository/TypeORMVaultInviteRepository.ts create mode 100644 packages/syncing-server/src/Domain/VaultInvite/Repository/VaultInviteRepositoryInterface.ts create mode 100644 packages/syncing-server/src/Domain/VaultInvite/Service/CreateInviteDTO.ts rename packages/syncing-server/src/Domain/{GroupInvite/Service/GetUserGroupInvitesDTO.ts => VaultInvite/Service/GetUserVaultInvitesDTO.ts} (51%) create mode 100644 packages/syncing-server/src/Domain/VaultInvite/Service/UpdateInviteDTO.ts create mode 100644 packages/syncing-server/src/Domain/VaultInvite/Service/VaultInviteService.ts create mode 100644 packages/syncing-server/src/Domain/VaultInvite/Service/VaultInviteServiceInterface.ts create mode 100644 packages/syncing-server/src/Domain/VaultUser/Factory/VaultUserFactory.ts create mode 100644 packages/syncing-server/src/Domain/VaultUser/Factory/VaultUserFactoryInterface.ts create mode 100644 packages/syncing-server/src/Domain/VaultUser/Factory/VaultUserHash.ts rename packages/syncing-server/src/Domain/{GroupUser/Model/GroupUser.ts => VaultUser/Model/VaultUser.ts} (70%) create mode 100644 packages/syncing-server/src/Domain/VaultUser/Model/VaultUserPermission.ts create mode 100644 packages/syncing-server/src/Domain/VaultUser/Repository/TypeORMVaultUserRepository.ts create mode 100644 packages/syncing-server/src/Domain/VaultUser/Repository/VaultUserRepositoryInterface.ts rename packages/syncing-server/src/Domain/{GroupUser/Service/GetGroupUsersDTO.ts => VaultUser/Service/GetVaultUsersDTO.ts} (55%) create mode 100644 packages/syncing-server/src/Domain/VaultUser/Service/VaultUserService.ts create mode 100644 packages/syncing-server/src/Domain/VaultUser/Service/VaultUserServiceInterface.ts delete mode 100644 packages/syncing-server/src/Projection/GroupInviteProjection.ts delete mode 100644 packages/syncing-server/src/Projection/GroupInviteProjector.ts delete mode 100644 packages/syncing-server/src/Projection/GroupProjector.ts delete mode 100644 packages/syncing-server/src/Projection/GroupUserProjector.ts create mode 100644 packages/syncing-server/src/Projection/VaultInviteProjection.ts create mode 100644 packages/syncing-server/src/Projection/VaultInviteProjector.ts rename packages/syncing-server/src/Projection/{GroupProjection.ts => VaultProjection.ts} (68%) create mode 100644 packages/syncing-server/src/Projection/VaultProjector.ts rename packages/syncing-server/src/Projection/{GroupUserProjection.ts => VaultUserProjection.ts} (66%) create mode 100644 packages/syncing-server/src/Projection/VaultUserProjector.ts diff --git a/packages/api-gateway/bin/server.ts b/packages/api-gateway/bin/server.ts index a6ca07a06..ce8d2832c 100644 --- a/packages/api-gateway/bin/server.ts +++ b/packages/api-gateway/bin/server.ts @@ -12,7 +12,7 @@ import '../src/Controller/v1/InvoicesController' import '../src/Controller/v1/RevisionsController' import '../src/Controller/v1/ItemsController' import '../src/Controller/v1/ContactsController' -import '../src/Controller/v1/GroupsController' +import '../src/Controller/v1/VaultsController' import '../src/Controller/v1/PaymentsController' import '../src/Controller/v1/WebSocketsController' import '../src/Controller/v1/TokensController' diff --git a/packages/api-gateway/src/Controller/index.ts b/packages/api-gateway/src/Controller/index.ts index 924fdaf36..63a828e31 100644 --- a/packages/api-gateway/src/Controller/index.ts +++ b/packages/api-gateway/src/Controller/index.ts @@ -10,7 +10,7 @@ export * from './v1/FilesController' export * from './v1/InvoicesController' export * from './v1/ItemsController' export * from './v1/ContactsController' -export * from './v1/GroupsController' +export * from './v1/VaultsController' export * from './v1/OfflineController' export * from './v1/PaymentsController' export * from './v1/RevisionsController' diff --git a/packages/api-gateway/src/Controller/v1/GroupsController.ts b/packages/api-gateway/src/Controller/v1/VaultsController.ts similarity index 87% rename from packages/api-gateway/src/Controller/v1/GroupsController.ts rename to packages/api-gateway/src/Controller/v1/VaultsController.ts index ba04b22fd..bc4911679 100644 --- a/packages/api-gateway/src/Controller/v1/GroupsController.ts +++ b/packages/api-gateway/src/Controller/v1/VaultsController.ts @@ -4,8 +4,8 @@ import { BaseHttpController, controller, all } from 'inversify-express-utils' import { TYPES } from '../../Bootstrap/Types' import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface' -@controller('/v1/groups') -export class GroupsController extends BaseHttpController { +@controller('/v1/vaults') +export class VaultsController extends BaseHttpController { constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) { super() } diff --git a/packages/auth/migrations/mysql/1665047863774-remove-groups.ts b/packages/auth/migrations/mysql/1665047863774-remove-groups.ts index b4878c703..634462667 100644 --- a/packages/auth/migrations/mysql/1665047863774-remove-groups.ts +++ b/packages/auth/migrations/mysql/1665047863774-remove-groups.ts @@ -1,7 +1,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm' -export class removeGroups1665047863774 implements MigrationInterface { - name = 'removeGroups1665047863774' +export class removeVaults1665047863774 implements MigrationInterface { + name = 'removeVaults1665047863774' public async up(queryRunner: QueryRunner): Promise { await queryRunner.query('ALTER TABLE `group_users` DROP FOREIGN KEY `FK_9d1bcb8c649eb05d7a2eb62114e`') diff --git a/packages/common/src/Domain/Content/ContentType.ts b/packages/common/src/Domain/Content/ContentType.ts index 909c47cf2..1e37230ac 100644 --- a/packages/common/src/Domain/Content/ContentType.ts +++ b/packages/common/src/Domain/Content/ContentType.ts @@ -3,7 +3,7 @@ export enum ContentType { Any = '*', Item = 'SF|Item', SharedItemsKey = 'SN|SharedItemsKey', - GroupKey = 'SN|GroupKey', + VaultKey = 'SN|VaultKey', TrustedContact = 'SN|TrustedContact', RootKey = 'SN|RootKey|NoSync', ItemsKey = 'SN|ItemsKey', diff --git a/packages/files/bin/server.ts b/packages/files/bin/server.ts index b53388346..2130bec8e 100644 --- a/packages/files/bin/server.ts +++ b/packages/files/bin/server.ts @@ -6,7 +6,7 @@ import * as busboy from 'connect-busboy' import '../src/Controller/HealthCheckController' import '../src/Controller/FilesController' -import '../src/Controller/GroupFilesController' +import '../src/Controller/VaultFilesController' import helmet from 'helmet' import * as cors from 'cors' diff --git a/packages/files/src/Bootstrap/Container.ts b/packages/files/src/Bootstrap/Container.ts index 25e67ce39..b45eb5275 100644 --- a/packages/files/src/Bootstrap/Container.ts +++ b/packages/files/src/Bootstrap/Container.ts @@ -44,7 +44,7 @@ import { MarkFilesToBeRemoved } from '../Domain/UseCase/MarkFilesToBeRemoved/Mar import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountDeletionRequestedEventHandler' import { SharedSubscriptionInvitationCanceledEventHandler } from '../Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler' import { InMemoryUploadRepository } from '../Infra/InMemory/InMemoryUploadRepository' -import { GroupValetTokenAuthMiddleware } from '../Controller/GroupValetTokenAuthMiddleware' +import { VaultValetTokenAuthMiddleware } from '../Controller/VaultValetTokenAuthMiddleware' export class ContainerConfigLoader { async load(): Promise { @@ -150,7 +150,7 @@ export class ContainerConfigLoader { // middleware container.bind(TYPES.ValetTokenAuthMiddleware).to(ValetTokenAuthMiddleware) - container.bind(TYPES.GroupValetTokenAuthMiddleware).to(GroupValetTokenAuthMiddleware) + container.bind(TYPES.VaultValetTokenAuthMiddleware).to(VaultValetTokenAuthMiddleware) // services container diff --git a/packages/files/src/Bootstrap/Types.ts b/packages/files/src/Bootstrap/Types.ts index aaf49f70e..06d67e69f 100644 --- a/packages/files/src/Bootstrap/Types.ts +++ b/packages/files/src/Bootstrap/Types.ts @@ -29,7 +29,7 @@ const TYPES = { // middleware ValetTokenAuthMiddleware: Symbol.for('ValetTokenAuthMiddleware'), - GroupValetTokenAuthMiddleware: Symbol.for('GroupValetTokenAuthMiddleware'), + VaultValetTokenAuthMiddleware: Symbol.for('VaultValetTokenAuthMiddleware'), // env vars S3_ENDPOINT: Symbol.for('S3_ENDPOINT'), diff --git a/packages/files/src/Controller/GroupFilesController.ts b/packages/files/src/Controller/VaultFilesController.ts similarity index 87% rename from packages/files/src/Controller/GroupFilesController.ts rename to packages/files/src/Controller/VaultFilesController.ts index 6b5f64adf..906ef5c79 100644 --- a/packages/files/src/Controller/GroupFilesController.ts +++ b/packages/files/src/Controller/VaultFilesController.ts @@ -5,10 +5,10 @@ import { Writable } from 'stream' import TYPES from '../Bootstrap/Types' import { StreamDownloadFile } from '../Domain/UseCase/StreamDownloadFile/StreamDownloadFile' import { GetFileMetadata } from '../Domain/UseCase/GetFileMetadata/GetFileMetadata' -import { GroupValetTokenData, ValetTokenOperation } from '@standardnotes/security' +import { VaultValetTokenData, ValetTokenOperation } from '@standardnotes/security' -@controller('/v1/groups/files', TYPES.GroupValetTokenAuthMiddleware) -export class GroupFilesController extends BaseHttpController { +@controller('/v1/vault/files', TYPES.VaultValetTokenAuthMiddleware) +export class VaultFilesController extends BaseHttpController { constructor( @inject(TYPES.StreamDownloadFile) private streamDownloadFile: StreamDownloadFile, @inject(TYPES.GetFileMetadata) private getFileMetadata: GetFileMetadata, @@ -36,10 +36,10 @@ export class GroupFilesController extends BaseHttpController { chunkSize = this.maxChunkBytes } - const locals = response.locals as GroupValetTokenData + const locals = response.locals as VaultValetTokenData const fileMetadata = await this.getFileMetadata.execute({ - userUuid: locals.fileOwnerUuid, + vaultUuid: locals.vaultUuid, resourceRemoteIdentifier: locals.remoteIdentifier, }) @@ -60,7 +60,7 @@ export class GroupFilesController extends BaseHttpController { response.writeHead(206, headers) const result = await this.streamDownloadFile.execute({ - userUuid: locals.fileOwnerUuid, + vaultUuid: locals.vaultUuid, resourceRemoteIdentifier: locals.remoteIdentifier, startRange, endRange, diff --git a/packages/files/src/Controller/GroupValetTokenAuthMiddleware.ts b/packages/files/src/Controller/VaultValetTokenAuthMiddleware.ts similarity index 80% rename from packages/files/src/Controller/GroupValetTokenAuthMiddleware.ts rename to packages/files/src/Controller/VaultValetTokenAuthMiddleware.ts index 10a9b4c82..dbef1a1d7 100644 --- a/packages/files/src/Controller/GroupValetTokenAuthMiddleware.ts +++ b/packages/files/src/Controller/VaultValetTokenAuthMiddleware.ts @@ -1,4 +1,4 @@ -import { GroupValetTokenData, TokenDecoderInterface } from '@standardnotes/security' +import { VaultValetTokenData, TokenDecoderInterface } from '@standardnotes/security' import { Uuid } from '@standardnotes/domain-core' import { NextFunction, Request, Response } from 'express' import { inject, injectable } from 'inversify' @@ -7,9 +7,9 @@ import { Logger } from 'winston' import TYPES from '../Bootstrap/Types' @injectable() -export class GroupValetTokenAuthMiddleware extends BaseMiddleware { +export class VaultValetTokenAuthMiddleware extends BaseMiddleware { constructor( - @inject(TYPES.ValetTokenDecoder) private tokenDecoder: TokenDecoderInterface, + @inject(TYPES.ValetTokenDecoder) private tokenDecoder: TokenDecoderInterface, @inject(TYPES.Logger) private logger: Logger, ) { super() @@ -19,7 +19,7 @@ export class GroupValetTokenAuthMiddleware extends BaseMiddleware { try { const valetToken = request.headers['x-valet-token'] || request.body.valetToken || request.query.valetToken if (!valetToken) { - this.logger.debug('GroupValetTokenAuthMiddleware missing valet token.') + this.logger.debug('VaultValetTokenAuthMiddleware missing valet token.') response.status(401).send({ error: { @@ -34,7 +34,7 @@ export class GroupValetTokenAuthMiddleware extends BaseMiddleware { const valetTokenData = this.tokenDecoder.decodeToken(valetToken) if (valetTokenData === undefined) { - this.logger.debug('GroupValetTokenAuthMiddleware authentication failure.') + this.logger.debug('VaultValetTokenAuthMiddleware authentication failure.') response.status(401).send({ error: { @@ -60,9 +60,8 @@ export class GroupValetTokenAuthMiddleware extends BaseMiddleware { return } - const whitelistedData: GroupValetTokenData = { - groupUuid: valetTokenData.groupUuid, - fileOwnerUuid: valetTokenData.fileOwnerUuid, + const whitelistedData: VaultValetTokenData = { + vaultUuid: valetTokenData.vaultUuid, remoteIdentifier: valetTokenData.remoteIdentifier, permittedOperation: valetTokenData.permittedOperation, } diff --git a/packages/files/src/Domain/UseCase/GetFileMetadata/GetFileMetadata.ts b/packages/files/src/Domain/UseCase/GetFileMetadata/GetFileMetadata.ts index bca0d05db..51b0e6ffd 100644 --- a/packages/files/src/Domain/UseCase/GetFileMetadata/GetFileMetadata.ts +++ b/packages/files/src/Domain/UseCase/GetFileMetadata/GetFileMetadata.ts @@ -3,7 +3,7 @@ import { Logger } from 'winston' import TYPES from '../../../Bootstrap/Types' import { FileDownloaderInterface } from '../../Services/FileDownloaderInterface' import { UseCaseInterface } from '../UseCaseInterface' -import { GetFileMetadataDTO } from './GetFileMetadataDTO' +import { GetFileMetadataDTO, isGetFileMetadataVaultOwned } from './GetFileMetadataDTO' import { GetFileMetadataResponse } from './GetFileMetadataResponse' @injectable() @@ -14,15 +14,16 @@ export class GetFileMetadata implements UseCaseInterface { ) {} async execute(dto: GetFileMetadataDTO): Promise { + const ownerUuid = isGetFileMetadataVaultOwned(dto) ? dto.vaultUuid : dto.userUuid try { - const size = await this.fileDownloader.getFileSize(`${dto.userUuid}/${dto.resourceRemoteIdentifier}`) + const size = await this.fileDownloader.getFileSize(`${ownerUuid}/${dto.resourceRemoteIdentifier}`) return { success: true, size, } } catch (error) { - this.logger.error(`Could not get file metadata for resource: ${dto.userUuid}/${dto.resourceRemoteIdentifier}`) + this.logger.error(`Could not get file metadata for resource: ${ownerUuid}/${dto.resourceRemoteIdentifier}`) return { success: false, message: 'Could not get file metadata.', diff --git a/packages/files/src/Domain/UseCase/GetFileMetadata/GetFileMetadataDTO.ts b/packages/files/src/Domain/UseCase/GetFileMetadata/GetFileMetadataDTO.ts index 37263cda0..965fd62ac 100644 --- a/packages/files/src/Domain/UseCase/GetFileMetadata/GetFileMetadataDTO.ts +++ b/packages/files/src/Domain/UseCase/GetFileMetadata/GetFileMetadataDTO.ts @@ -1,4 +1,15 @@ -export type GetFileMetadataDTO = { +export type GetFileMetadataDTO = GetVaultFileMetadataDTO | GetUserFileMetadataDTO + +export type GetVaultFileMetadataDTO = { + vaultUuid: string + resourceRemoteIdentifier: string +} + +export type GetUserFileMetadataDTO = { userUuid: string resourceRemoteIdentifier: string } + +export function isGetFileMetadataVaultOwned(dto: GetFileMetadataDTO): dto is GetVaultFileMetadataDTO { + return (dto as GetVaultFileMetadataDTO).vaultUuid !== undefined +} diff --git a/packages/files/src/Domain/UseCase/StreamDownloadFile/StreamDownloadFile.ts b/packages/files/src/Domain/UseCase/StreamDownloadFile/StreamDownloadFile.ts index eb27a87b0..5578c5b59 100644 --- a/packages/files/src/Domain/UseCase/StreamDownloadFile/StreamDownloadFile.ts +++ b/packages/files/src/Domain/UseCase/StreamDownloadFile/StreamDownloadFile.ts @@ -3,7 +3,7 @@ import { Logger } from 'winston' import TYPES from '../../../Bootstrap/Types' import { FileDownloaderInterface } from '../../Services/FileDownloaderInterface' import { UseCaseInterface } from '../UseCaseInterface' -import { StreamDownloadFileDTO } from './StreamDownloadFileDTO' +import { StreamDownloadFileDTO, isStreamDownloadFileVaultOwned } from './StreamDownloadFileDTO' import { StreamDownloadFileResponse } from './StreamDownloadFileResponse' @injectable() @@ -14,9 +14,10 @@ export class StreamDownloadFile implements UseCaseInterface { ) {} async execute(dto: StreamDownloadFileDTO): Promise { + const ownerUuid = isStreamDownloadFileVaultOwned(dto) ? dto.vaultUuid : dto.userUuid try { const readStream = await this.fileDownloader.createDownloadStream( - `${dto.userUuid}/${dto.resourceRemoteIdentifier}`, + `${ownerUuid}/${dto.resourceRemoteIdentifier}`, dto.startRange, dto.endRange, ) @@ -26,9 +27,7 @@ export class StreamDownloadFile implements UseCaseInterface { readStream, } } catch (error) { - this.logger.error( - `Could not create a download stream for resource: ${dto.userUuid}/${dto.resourceRemoteIdentifier}`, - ) + this.logger.error(`Could not create a download stream for resource: ${ownerUuid}/${dto.resourceRemoteIdentifier}`) return { success: false, diff --git a/packages/files/src/Domain/UseCase/StreamDownloadFile/StreamDownloadFileDTO.ts b/packages/files/src/Domain/UseCase/StreamDownloadFile/StreamDownloadFileDTO.ts index bc17fe208..abcd6fd17 100644 --- a/packages/files/src/Domain/UseCase/StreamDownloadFile/StreamDownloadFileDTO.ts +++ b/packages/files/src/Domain/UseCase/StreamDownloadFile/StreamDownloadFileDTO.ts @@ -1,6 +1,19 @@ -export type StreamDownloadFileDTO = { +export type StreamDownloadFileDTO = StreamDownloadVaultFileDTO | StreamDownloadUserFileDTO + +export type StreamDownloadVaultFileDTO = { + vaultUuid: string + resourceRemoteIdentifier: string + startRange: number + endRange: number +} + +export type StreamDownloadUserFileDTO = { userUuid: string resourceRemoteIdentifier: string startRange: number endRange: number } + +export function isStreamDownloadFileVaultOwned(dto: StreamDownloadFileDTO): dto is StreamDownloadVaultFileDTO { + return (dto as StreamDownloadVaultFileDTO).vaultUuid !== undefined +} diff --git a/packages/security/src/Domain/Token/GroupValetTokenData.ts b/packages/security/src/Domain/Token/GroupValetTokenData.ts deleted file mode 100644 index e00b78e50..000000000 --- a/packages/security/src/Domain/Token/GroupValetTokenData.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ValetTokenOperation } from './ValetTokenOperation' - -export type GroupValetTokenData = { - fileOwnerUuid: string - groupUuid: string - permittedOperation: ValetTokenOperation.Read - remoteIdentifier: string -} diff --git a/packages/security/src/Domain/Token/VaultValetTokenData.ts b/packages/security/src/Domain/Token/VaultValetTokenData.ts new file mode 100644 index 000000000..fd1c7a0af --- /dev/null +++ b/packages/security/src/Domain/Token/VaultValetTokenData.ts @@ -0,0 +1,7 @@ +import { ValetTokenOperation } from './ValetTokenOperation' + +export type VaultValetTokenData = { + vaultUuid: string + permittedOperation: ValetTokenOperation + remoteIdentifier: string +} diff --git a/packages/security/src/Domain/index.ts b/packages/security/src/Domain/index.ts index 5cb83f87a..fc9a1c22b 100644 --- a/packages/security/src/Domain/index.ts +++ b/packages/security/src/Domain/index.ts @@ -10,7 +10,7 @@ export * from './Token/CrossServiceTokenData' export * from './Token/OfflineFeaturesTokenData' export * from './Token/OfflineUserTokenData' export * from './Token/SessionTokenData' -export * from './Token/GroupValetTokenData' +export * from './Token/VaultValetTokenData' export * from './Token/ValetTokenData' export * from './Token/ValetTokenOperation' export * from './Token/WebSocketConnectionToken' diff --git a/packages/syncing-server/README.md b/packages/syncing-server/README.md index 8b0d0be5b..4dffed2c3 100644 --- a/packages/syncing-server/README.md +++ b/packages/syncing-server/README.md @@ -3,5 +3,5 @@ ### Generating a migration ``` -yarn build && yarn typeorm migration:generate -d dist/src/Bootstrap/DataSource.js migrations/mysql/add_group_uuid_to_items +yarn build && yarn typeorm migration:generate -d dist/src/Bootstrap/DataSource.js migrations/mysql/add_vault_uuid_to_items ``` \ No newline at end of file diff --git a/packages/syncing-server/bin/server.ts b/packages/syncing-server/bin/server.ts index 53339f240..aa9c8814d 100644 --- a/packages/syncing-server/bin/server.ts +++ b/packages/syncing-server/bin/server.ts @@ -4,9 +4,9 @@ import 'newrelic' import '../src/Controller/HealthCheckController' import '../src/Controller/ItemsController' -import '../src/Controller/GroupsController' -import '../src/Controller/GroupUsersController' -import '../src/Controller/GroupInvitesController' +import '../src/Controller/VaultsController' +import '../src/Controller/VaultUsersController' +import '../src/Controller/VaultInvitesController' import '../src/Controller/ContactsController' import helmet from 'helmet' diff --git a/packages/syncing-server/migrations/mysql/1684173017359-create_group.ts b/packages/syncing-server/migrations/mysql/1684173017359-create_vault.ts similarity index 83% rename from packages/syncing-server/migrations/mysql/1684173017359-create_group.ts rename to packages/syncing-server/migrations/mysql/1684173017359-create_vault.ts index 1f153962c..3f1ce7b73 100644 --- a/packages/syncing-server/migrations/mysql/1684173017359-create_group.ts +++ b/packages/syncing-server/migrations/mysql/1684173017359-create_vault.ts @@ -1,11 +1,11 @@ import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm' -export class CreateGroup1684173017359 implements MigrationInterface { - name = 'createGroup1684173017359' +export class CreateVault1684173017359 implements MigrationInterface { + name = 'createVault1684173017359' public async up(queryRunner: QueryRunner): Promise { const table = new Table({ - name: 'groups', + name: 'vaults', columns: [ new TableColumn({ name: 'uuid', @@ -26,7 +26,7 @@ export class CreateGroup1684173017359 implements MigrationInterface { isNullable: false, }), new TableColumn({ - name: 'group_key_timestamp', + name: 'vault_key_timestamp', type: 'bigint', }), new TableColumn({ @@ -44,6 +44,6 @@ export class CreateGroup1684173017359 implements MigrationInterface { } public async down(queryRunner: QueryRunner): Promise { - await queryRunner.dropTable('groups') + await queryRunner.dropTable('vaults') } } diff --git a/packages/syncing-server/migrations/mysql/1684173023883-create_group_user.ts b/packages/syncing-server/migrations/mysql/1684173023883-create_vault_user.ts similarity index 84% rename from packages/syncing-server/migrations/mysql/1684173023883-create_group_user.ts rename to packages/syncing-server/migrations/mysql/1684173023883-create_vault_user.ts index 670791e2a..e4b30d648 100644 --- a/packages/syncing-server/migrations/mysql/1684173023883-create_group_user.ts +++ b/packages/syncing-server/migrations/mysql/1684173023883-create_vault_user.ts @@ -1,11 +1,11 @@ import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm' -export class CreateGroupUser1684173023883 implements MigrationInterface { - name = 'createGroupUser1684173023883' +export class CreateVaultUser1684173023883 implements MigrationInterface { + name = 'createVaultUser1684173023883' public async up(queryRunner: QueryRunner): Promise { const table = new Table({ - name: 'group_users', + name: 'vault_users', columns: [ new TableColumn({ name: 'uuid', @@ -14,7 +14,7 @@ export class CreateGroupUser1684173023883 implements MigrationInterface { isPrimary: true, }), new TableColumn({ - name: 'group_uuid', + name: 'vault_uuid', type: 'varchar', length: '36', isNullable: false, @@ -46,6 +46,6 @@ export class CreateGroupUser1684173023883 implements MigrationInterface { } public async down(queryRunner: QueryRunner): Promise { - await queryRunner.dropTable('group_users') + await queryRunner.dropTable('vault_users') } } diff --git a/packages/syncing-server/migrations/mysql/1684318117584-add_group_uuid_to_items.ts b/packages/syncing-server/migrations/mysql/1684318117584-add_vault_uuid_to_items.ts similarity index 77% rename from packages/syncing-server/migrations/mysql/1684318117584-add_group_uuid_to_items.ts rename to packages/syncing-server/migrations/mysql/1684318117584-add_vault_uuid_to_items.ts index 4412ed52c..631a63343 100644 --- a/packages/syncing-server/migrations/mysql/1684318117584-add_group_uuid_to_items.ts +++ b/packages/syncing-server/migrations/mysql/1684318117584-add_vault_uuid_to_items.ts @@ -1,13 +1,13 @@ import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm' -export class AddGroupUuidToItems1684318117584 implements MigrationInterface { - name = 'addGroupUuidToItems1684318117584' +export class AddVaultUuidToItems1684318117584 implements MigrationInterface { + name = 'addVaultUuidToItems1684318117584' public async up(queryRunner: QueryRunner): Promise { await queryRunner.addColumn( 'items', new TableColumn({ - name: 'group_uuid', + name: 'vault_uuid', type: 'varchar', length: '36', isNullable: true, @@ -25,7 +25,7 @@ export class AddGroupUuidToItems1684318117584 implements MigrationInterface { } public async down(queryRunner: QueryRunner): Promise { - await queryRunner.dropColumn('items', 'group_uuid') + await queryRunner.dropColumn('items', 'vault_uuid') await queryRunner.dropColumn('items', 'last_edited_by_uuid') } } diff --git a/packages/syncing-server/migrations/mysql/1684528646896-create_group_invites.ts b/packages/syncing-server/migrations/mysql/1684528646896-create_vault_invites.ts similarity index 86% rename from packages/syncing-server/migrations/mysql/1684528646896-create_group_invites.ts rename to packages/syncing-server/migrations/mysql/1684528646896-create_vault_invites.ts index 1581da4ff..fd2a50cf7 100644 --- a/packages/syncing-server/migrations/mysql/1684528646896-create_group_invites.ts +++ b/packages/syncing-server/migrations/mysql/1684528646896-create_vault_invites.ts @@ -1,11 +1,11 @@ import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm' -export class CreateGroupInvites1684528646896 implements MigrationInterface { - name = 'createGroupInvites1684528646896' +export class CreateVaultInvites1684528646896 implements MigrationInterface { + name = 'createVaultInvites1684528646896' public async up(queryRunner: QueryRunner): Promise { const table = new Table({ - name: 'group_invites', + name: 'vault_invites', columns: [ new TableColumn({ name: 'uuid', @@ -14,7 +14,7 @@ export class CreateGroupInvites1684528646896 implements MigrationInterface { isPrimary: true, }), new TableColumn({ - name: 'group_uuid', + name: 'vault_uuid', type: 'varchar', length: '36', isNullable: false, @@ -44,7 +44,7 @@ export class CreateGroupInvites1684528646896 implements MigrationInterface { isNullable: false, }), new TableColumn({ - name: 'encrypted_group_data', + name: 'encrypted_vault_data', type: 'text', isNullable: false, }), @@ -69,6 +69,6 @@ export class CreateGroupInvites1684528646896 implements MigrationInterface { } public async down(queryRunner: QueryRunner): Promise { - await queryRunner.dropTable('group_invites') + await queryRunner.dropTable('vault_invites') } } diff --git a/packages/syncing-server/migrations/mysql/1685276859271-allow_item_user_uuid_to_be_null.ts b/packages/syncing-server/migrations/mysql/1685276859271-allow_item_user_uuid_to_be_null.ts new file mode 100644 index 000000000..4f3042b48 --- /dev/null +++ b/packages/syncing-server/migrations/mysql/1685276859271-allow_item_user_uuid_to_be_null.ts @@ -0,0 +1,31 @@ +import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm' + +export class AllowItemUserUuidToBeNull1685276859271 implements MigrationInterface { + name = 'allowItemUserUuidToBeNull1685276859271' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.changeColumn( + 'items', + 'user_uuid', + new TableColumn({ + name: 'user_uuid', + type: 'varchar', + length: '36', + isNullable: true, + }), + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.changeColumn( + 'items', + 'user_uuid', + new TableColumn({ + name: 'user_uuid', + type: 'varchar', + length: '36', + isNullable: false, + }), + ) + } +} diff --git a/packages/syncing-server/src/Bootstrap/CommonContainerConfigLoader.ts b/packages/syncing-server/src/Bootstrap/CommonContainerConfigLoader.ts index 5289c156a..eb990e600 100644 --- a/packages/syncing-server/src/Bootstrap/CommonContainerConfigLoader.ts +++ b/packages/syncing-server/src/Bootstrap/CommonContainerConfigLoader.ts @@ -1,9 +1,9 @@ -import { TypeORMGroupInviteRepository } from './../Domain/GroupInvite/Repository/TypeORMGroupInviteRepository' +import { TypeORMVaultInviteRepository } from '../Domain/VaultInvite/Repository/TypeORMVaultInviteRepository' import { Contact } from './../Domain/Contact/Model/Contact' -import { GroupInvite } from './../Domain/GroupInvite/Model/GroupInvite' -import { GroupUser } from '../Domain/GroupUser/Model/GroupUser' -import { Group } from './../Domain/Group/Model/Group' -import { TypeORMGroupRepository } from './../Domain/Group/Repository/TypeORMGroupRepository' +import { VaultInvite } from '../Domain/VaultInvite/Model/VaultInvite' +import { VaultUser } from '../Domain/VaultUser/Model/VaultUser' +import { Vault } from '../Domain/Vault/Model/Vault' +import { TypeORMVaultRepository } from '../Domain/Vault/Repository/TypeORMVaultRepository' import * as winston from 'winston' import { Container, interfaces } from 'inversify' @@ -24,18 +24,18 @@ import { DomainEventFactory } from '../Domain/Event/DomainEventFactory' import { Timer, TimerInterface } from '@standardnotes/time' import { ItemTransferCalculatorInterface } from '../Domain/Item/ItemTransferCalculatorInterface' import { ItemTransferCalculator } from '../Domain/Item/ItemTransferCalculator' -import { GroupsRepositoryInterface } from '../Domain/Group/Repository/GroupRepositoryInterface' -import { GroupUserRepositoryInterface } from '../Domain/GroupUser/Repository/GroupUserRepositoryInterface' -import { TypeORMGroupUserRepository } from '../Domain/GroupUser/Repository/TypeORMGroupUserRepository' -import { GroupUserProjection } from '../Projection/GroupUserProjection' -import { GroupUserProjector } from '../Projection/GroupUserProjector' -import { GroupProjection } from '../Projection/GroupProjection' -import { GroupProjector } from '../Projection/GroupProjector' -import { GroupInviteProjection } from '../Projection/GroupInviteProjection' -import { GroupInviteProjector } from '../Projection/GroupInviteProjector' +import { VaultsRepositoryInterface } from '../Domain/Vault/Repository/VaultRepositoryInterface' +import { VaultUserRepositoryInterface } from '../Domain/VaultUser/Repository/VaultUserRepositoryInterface' +import { TypeORMVaultUserRepository } from '../Domain/VaultUser/Repository/TypeORMVaultUserRepository' +import { VaultUserProjection } from '../Projection/VaultUserProjection' +import { VaultUserProjector } from '../Projection/VaultUserProjector' +import { VaultProjection } from '../Projection/VaultProjection' +import { VaultProjector } from '../Projection/VaultProjector' +import { VaultInviteProjection } from '../Projection/VaultInviteProjection' +import { VaultInviteProjector } from '../Projection/VaultInviteProjector' import { ContactProjection } from '../Projection/ContactProjection' import { ContactProjector } from '../Projection/ContactProjector' -import { GroupInviteRepositoryInterface } from '../Domain/GroupInvite/Repository/GroupInviteRepositoryInterface' +import { VaultInviteRepositoryInterface } from '../Domain/VaultInvite/Repository/VaultInviteRepositoryInterface' import { ContactsRepositoryInterface } from '../Domain/Contact/Repository/ContactRepositoryInterface' import { TypeORMContactRepository } from '../Domain/Contact/Repository/TypeORMContactRepository' // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -96,18 +96,18 @@ export class CommonContainerConfigLoader { container.bind(TYPES.ItemRepository).toDynamicValue((context: interfaces.Context) => { return new TypeORMItemRepository(context.container.get(TYPES.ORMItemRepository)) }) - container.bind(TYPES.GroupRepository).toDynamicValue((context: interfaces.Context) => { - return new TypeORMGroupRepository(context.container.get(TYPES.ORMGroupRepository)) + container.bind(TYPES.VaultRepository).toDynamicValue((context: interfaces.Context) => { + return new TypeORMVaultRepository(context.container.get(TYPES.ORMVaultRepository)) }) container - .bind(TYPES.GroupUserRepository) + .bind(TYPES.VaultUserRepository) .toDynamicValue((context: interfaces.Context) => { - return new TypeORMGroupUserRepository(context.container.get(TYPES.ORMGroupUserRepository)) + return new TypeORMVaultUserRepository(context.container.get(TYPES.ORMVaultUserRepository)) }) container - .bind(TYPES.GroupInviteRepository) + .bind(TYPES.VaultInviteRepository) .toDynamicValue((context: interfaces.Context) => { - return new TypeORMGroupInviteRepository(context.container.get(TYPES.ORMGroupInviteRepository)) + return new TypeORMVaultInviteRepository(context.container.get(TYPES.ORMVaultInviteRepository)) }) container .bind(TYPES.ContactRepository) @@ -117,13 +117,13 @@ export class CommonContainerConfigLoader { // ORM container.bind>(TYPES.ORMItemRepository).toDynamicValue(() => AppDataSource.getRepository(Item)) - container.bind>(TYPES.ORMGroupRepository).toDynamicValue(() => AppDataSource.getRepository(Group)) + container.bind>(TYPES.ORMVaultRepository).toDynamicValue(() => AppDataSource.getRepository(Vault)) container - .bind>(TYPES.ORMGroupUserRepository) - .toDynamicValue(() => AppDataSource.getRepository(GroupUser)) + .bind>(TYPES.ORMVaultUserRepository) + .toDynamicValue(() => AppDataSource.getRepository(VaultUser)) container - .bind>(TYPES.ORMGroupInviteRepository) - .toDynamicValue(() => AppDataSource.getRepository(GroupInvite)) + .bind>(TYPES.ORMVaultInviteRepository) + .toDynamicValue(() => AppDataSource.getRepository(VaultInvite)) container .bind>(TYPES.ORMContactRepository) .toDynamicValue(() => AppDataSource.getRepository(Contact)) @@ -134,16 +134,16 @@ export class CommonContainerConfigLoader { .toDynamicValue((context: interfaces.Context) => { return new ItemProjector(context.container.get(TYPES.Timer)) }) - container.bind>(TYPES.GroupProjector).toDynamicValue(() => { - return new GroupProjector() + container.bind>(TYPES.VaultProjector).toDynamicValue(() => { + return new VaultProjector() }) - container.bind>(TYPES.GroupUserProjector).toDynamicValue(() => { - return new GroupUserProjector() + container.bind>(TYPES.VaultUserProjector).toDynamicValue(() => { + return new VaultUserProjector() }) container - .bind>(TYPES.GroupInviteProjector) + .bind>(TYPES.VaultInviteProjector) .toDynamicValue(() => { - return new GroupInviteProjector() + return new VaultInviteProjector() }) container.bind>(TYPES.ContactProjector).toDynamicValue(() => { return new ContactProjector() diff --git a/packages/syncing-server/src/Bootstrap/DataSource.ts b/packages/syncing-server/src/Bootstrap/DataSource.ts index e75ee062c..7d55a8416 100644 --- a/packages/syncing-server/src/Bootstrap/DataSource.ts +++ b/packages/syncing-server/src/Bootstrap/DataSource.ts @@ -1,12 +1,12 @@ import { Contact } from './../Domain/Contact/Model/Contact' -import { GroupInvite } from './../Domain/GroupInvite/Model/GroupInvite' -import { GroupUser } from '../Domain/GroupUser/Model/GroupUser' +import { VaultInvite } from '../Domain/VaultInvite/Model/VaultInvite' +import { VaultUser } from '../Domain/VaultUser/Model/VaultUser' import { DataSource, LoggerOptions } from 'typeorm' import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions' import { Item } from '../Domain/Item/Item' import { Env } from './Env' import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions' -import { Group } from '../Domain/Group/Model/Group' +import { Vault } from '../Domain/Vault/Model/Vault' const env: Env = new Env() env.load() @@ -42,7 +42,7 @@ const replicationConfig = { const commonDataSourceOptions = { maxQueryExecutionTime, - entities: [Item, Group, GroupUser, GroupInvite, Contact], + entities: [Item, Vault, VaultUser, VaultInvite, Contact], migrations: [`dist/migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`], migrationsRun: true, logging: env.get('DB_DEBUG_LEVEL'), diff --git a/packages/syncing-server/src/Bootstrap/ServerContainerConfigLoader.ts b/packages/syncing-server/src/Bootstrap/ServerContainerConfigLoader.ts index 4d28e7833..893283fd7 100644 --- a/packages/syncing-server/src/Bootstrap/ServerContainerConfigLoader.ts +++ b/packages/syncing-server/src/Bootstrap/ServerContainerConfigLoader.ts @@ -1,9 +1,9 @@ import { GetGlobalItem } from './../Domain/UseCase/GetGlobalItem/GetGlobalItem' import { ContactFactory } from './../Domain/Contact/Factory/ContactFactory' -import { GroupServiceInterface } from './../Domain/Group/Service/GroupServiceInterface' -import { GroupUserFactory } from '../Domain/GroupUser/Factory/GroupUserFactory' -import { GroupFactoryInterface } from '../Domain/Group/Factory/GroupFactoryInterface' +import { VaultServiceInterface } from '../Domain/Vault/Service/VaultServiceInterface' +import { VaultUserFactory } from '../Domain/VaultUser/Factory/VaultUserFactory' +import { VaultFactoryInterface } from '../Domain/Vault/Factory/VaultFactoryInterface' import { Container, interfaces } from 'inversify' import { Env } from './Env' @@ -36,18 +36,18 @@ import { ItemConflict } from '../Domain/Item/ItemConflict' import { ItemConflictProjection } from '../Projection/ItemConflictProjection' import { CommonContainerConfigLoader } from './CommonContainerConfigLoader' import { TokenEncoder, TokenEncoderInterface, ValetTokenData } from '@standardnotes/security' -import { GroupFactory } from '../Domain/Group/Factory/GroupFactory' -import { GroupService } from '../Domain/Group/Service/GroupService' -import { GroupUserService } from '../Domain/GroupUser/Service/GroupUserService' -import { GroupUserServiceInterface } from '../Domain/GroupUser/Service/GroupUserServiceInterface' -import { GroupInviteServiceInterface } from '../Domain/GroupInvite/Service/GroupInviteServiceInterface' -import { GroupInviteService } from '../Domain/GroupInvite/Service/GroupInviteService' -import { GroupInviteFactory } from '../Domain/GroupInvite/Factory/GroupInviteFactory' +import { VaultFactory } from '../Domain/Vault/Factory/VaultFactory' +import { VaultService } from '../Domain/Vault/Service/VaultService' +import { VaultUserService } from '../Domain/VaultUser/Service/VaultUserService' +import { VaultUserServiceInterface } from '../Domain/VaultUser/Service/VaultUserServiceInterface' +import { VaultInviteServiceInterface } from '../Domain/VaultInvite/Service/VaultInviteServiceInterface' +import { VaultInviteService } from '../Domain/VaultInvite/Service/VaultInviteService' +import { VaultInviteFactory } from '../Domain/VaultInvite/Factory/VaultInviteFactory' import { ContactServiceInterface } from '../Domain/Contact/Service/ContactServiceInterface' import { ContactService } from '../Domain/Contact/Service/ContactService' import { ContactFactoryInterface } from '../Domain/Contact/Factory/ContactFactoryInterface' import { SnjsVersionFilter } from '../Domain/Item/SaveRule/SnjsVersionFilter' -import { CreateGroupFileReadValetToken } from '../Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileReadValetToken' +import { CreateVaultFileReadValetToken } from '../Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileReadValetToken' export class ServerContainerConfigLoader extends CommonContainerConfigLoader { private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000 @@ -99,8 +99,8 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader { container.bind(TYPES.SyncItems).toDynamicValue((context: interfaces.Context) => { return new SyncItems( context.container.get(TYPES.ItemService), - context.container.get(TYPES.GroupService), - context.container.get(TYPES.GroupInviteService), + context.container.get(TYPES.VaultService), + context.container.get(TYPES.VaultInviteService), context.container.get(TYPES.ContactService), ) }) @@ -115,11 +115,11 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader { }) container - .bind(TYPES.CreateGroupFileReadValetToken) + .bind(TYPES.CreateVaultFileReadValetToken) .toDynamicValue((context: interfaces.Context) => { - return new CreateGroupFileReadValetToken( + return new CreateVaultFileReadValetToken( context.container.get(TYPES.GetGlobalItem), - context.container.get(TYPES.GroupUserService), + context.container.get(TYPES.VaultUserService), context.container.get(TYPES.ValetTokenEncoder), context.container.get(TYPES.VALET_TOKEN_TTL), ) @@ -143,36 +143,36 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader { context.container.get(TYPES.Timer), context.container.get(TYPES.ItemProjector), context.container.get(TYPES.MAX_ITEMS_LIMIT), - context.container.get(TYPES.GroupUserRepository), + context.container.get(TYPES.VaultUserRepository), context.container.get(TYPES.Logger), ) }) // Services - container.bind(TYPES.GroupService).toDynamicValue((context: interfaces.Context) => { - return new GroupService( - context.container.get(TYPES.GroupRepository), - context.container.get(TYPES.GroupFactory), - context.container.get(TYPES.GroupUserService), - context.container.get(TYPES.GroupInviteService), + container.bind(TYPES.VaultService).toDynamicValue((context: interfaces.Context) => { + return new VaultService( + context.container.get(TYPES.VaultRepository), + context.container.get(TYPES.VaultFactory), + context.container.get(TYPES.VaultUserService), + context.container.get(TYPES.VaultInviteService), context.container.get(TYPES.Timer), ) }) - container.bind(TYPES.GroupUserService).toDynamicValue((context: interfaces.Context) => { - return new GroupUserService( - context.container.get(TYPES.GroupRepository), - context.container.get(TYPES.GroupUserRepository), - context.container.get(TYPES.GroupUserFactory), + container.bind(TYPES.VaultUserService).toDynamicValue((context: interfaces.Context) => { + return new VaultUserService( + context.container.get(TYPES.VaultRepository), + context.container.get(TYPES.VaultUserRepository), + context.container.get(TYPES.VaultUserFactory), context.container.get(TYPES.Timer), ) }) container - .bind(TYPES.GroupInviteService) + .bind(TYPES.VaultInviteService) .toDynamicValue((context: interfaces.Context) => { - return new GroupInviteService( - context.container.get(TYPES.GroupRepository), - context.container.get(TYPES.GroupInviteRepository), - context.container.get(TYPES.GroupInviteFactory), - context.container.get(TYPES.GroupUserService), + return new VaultInviteService( + context.container.get(TYPES.VaultRepository), + context.container.get(TYPES.VaultInviteRepository), + context.container.get(TYPES.VaultInviteFactory), + context.container.get(TYPES.VaultUserService), context.container.get(TYPES.Timer), ) }) @@ -196,9 +196,9 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader { context.container.get(TYPES.ItemProjector), context.container.get(TYPES.ItemConflictProjector), context.container.get(TYPES.SavedItemProjector), - context.container.get(TYPES.GroupInviteProjector), + context.container.get(TYPES.VaultInviteProjector), context.container.get(TYPES.ContactProjector), - context.container.get(TYPES.GroupProjector), + context.container.get(TYPES.VaultProjector), ) }) container @@ -213,14 +213,14 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader { container.bind(TYPES.ItemFactory).toDynamicValue((context: interfaces.Context) => { return new ItemFactory(context.container.get(TYPES.Timer), context.container.get(TYPES.ItemProjector)) }) - container.bind(TYPES.GroupFactory).toDynamicValue((context: interfaces.Context) => { - return new GroupFactory(context.container.get(TYPES.Timer)) + container.bind(TYPES.VaultFactory).toDynamicValue((context: interfaces.Context) => { + return new VaultFactory(context.container.get(TYPES.Timer)) }) - container.bind(TYPES.GroupUserFactory).toDynamicValue((context: interfaces.Context) => { - return new GroupUserFactory(context.container.get(TYPES.Timer)) + container.bind(TYPES.VaultUserFactory).toDynamicValue((context: interfaces.Context) => { + return new VaultUserFactory(context.container.get(TYPES.Timer)) }) - container.bind(TYPES.GroupInviteFactory).toDynamicValue((context: interfaces.Context) => { - return new GroupInviteFactory(context.container.get(TYPES.Timer)) + container.bind(TYPES.VaultInviteFactory).toDynamicValue((context: interfaces.Context) => { + return new VaultInviteFactory(context.container.get(TYPES.Timer)) }) container.bind(TYPES.ContactFactory).toDynamicValue((context: interfaces.Context) => { return new ContactFactory(context.container.get(TYPES.Timer)) @@ -230,7 +230,7 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader { .bind(TYPES.OwnershipFilter) .toDynamicValue( (context: interfaces.Context) => - new OwnershipFilter(context.container.get(TYPES.GroupService), context.container.get(TYPES.GroupUserService)), + new OwnershipFilter(context.container.get(TYPES.VaultService), context.container.get(TYPES.VaultUserService)), ) container .bind(TYPES.TimeDifferenceFilter) diff --git a/packages/syncing-server/src/Bootstrap/Types.ts b/packages/syncing-server/src/Bootstrap/Types.ts index 309577430..9b3764158 100644 --- a/packages/syncing-server/src/Bootstrap/Types.ts +++ b/packages/syncing-server/src/Bootstrap/Types.ts @@ -9,17 +9,17 @@ const TYPES = { // Repositories ItemRepository: Symbol.for('ItemRepository'), - GroupRepository: Symbol.for('GroupRepository'), - GroupUserRepository: Symbol.for('GroupUserRepository'), + VaultRepository: Symbol.for('VaultRepository'), + VaultUserRepository: Symbol.for('VaultUserRepository'), ContactRepository: Symbol.for('ContactRepository'), - GroupInviteRepository: Symbol.for('GroupInviteRepository'), + VaultInviteRepository: Symbol.for('VaultInviteRepository'), // ORM ORMItemRepository: Symbol.for('ORMItemRepository'), - ORMGroupRepository: Symbol.for('ORMGroupRepository'), - ORMGroupUserRepository: Symbol.for('ORMGroupUserRepository'), + ORMVaultRepository: Symbol.for('ORMVaultRepository'), + ORMVaultUserRepository: Symbol.for('ORMVaultUserRepository'), ORMContactRepository: Symbol.for('ORMContactRepository'), - ORMGroupInviteRepository: Symbol.for('ORMGroupInviteRepository'), + ORMVaultInviteRepository: Symbol.for('ORMVaultInviteRepository'), // Middleware AuthMiddleware: Symbol.for('AuthMiddleware'), @@ -28,23 +28,23 @@ const TYPES = { ItemProjector: Symbol.for('ItemProjector'), SavedItemProjector: Symbol.for('SavedItemProjector'), ItemConflictProjector: Symbol.for('ItemConflictProjector'), - GroupProjector: Symbol.for('GroupProjector'), - GroupUserProjector: Symbol.for('GroupUserProjector'), + VaultProjector: Symbol.for('VaultProjector'), + VaultUserProjector: Symbol.for('VaultUserProjector'), ContactProjector: Symbol.for('ContactProjector'), - GroupInviteProjector: Symbol.for('GroupInviteProjector'), + VaultInviteProjector: Symbol.for('VaultInviteProjector'), // Services - GroupService: Symbol.for('GroupService'), - GroupUserService: Symbol.for('GroupUserService'), + VaultService: Symbol.for('VaultService'), + VaultUserService: Symbol.for('VaultUserService'), ContactService: Symbol.for('ContactService'), - GroupInviteService: Symbol.for('GroupInviteService'), + VaultInviteService: Symbol.for('VaultInviteService'), // Factories ItemFactory: Symbol.for('ItemFactory'), - GroupFactory: Symbol.for('GroupFactory'), - GroupUserFactory: Symbol.for('GroupUserFactory'), + VaultFactory: Symbol.for('VaultFactory'), + VaultUserFactory: Symbol.for('VaultUserFactory'), ContactFactory: Symbol.for('ContactFactory'), - GroupInviteFactory: Symbol.for('GroupInviteFactory'), + VaultInviteFactory: Symbol.for('VaultInviteFactory'), // env vars REDIS_URL: Symbol.for('REDIS_URL'), @@ -71,7 +71,7 @@ const TYPES = { CheckIntegrity: Symbol.for('CheckIntegrity'), GetItem: Symbol.for('GetItem'), GetGlobalItem: Symbol.for('GetGlobalItem'), - CreateGroupFileReadValetToken: Symbol.for('CreateGroupFileReadValetToken'), + CreateVaultFileReadValetToken: Symbol.for('CreateVaultFileReadValetToken'), // Handlers UserCredentialsChangedEventHandler: Symbol.for('UserCredentialsChangedEventHandler'), AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'), diff --git a/packages/syncing-server/src/Bootstrap/WorkerContainerConfigLoader.ts b/packages/syncing-server/src/Bootstrap/WorkerContainerConfigLoader.ts index 20499c733..30216290f 100644 --- a/packages/syncing-server/src/Bootstrap/WorkerContainerConfigLoader.ts +++ b/packages/syncing-server/src/Bootstrap/WorkerContainerConfigLoader.ts @@ -138,7 +138,7 @@ export class WorkerContainerConfigLoader extends CommonContainerConfigLoader { .toDynamicValue((context: interfaces.Context) => { return new UserCredentialsChangedEventHandler( context.container.get(TYPES.ContactRepository), - context.container.get(TYPES.GroupInviteRepository), + context.container.get(TYPES.VaultInviteRepository), context.container.get(TYPES.Timer), ) }) diff --git a/packages/syncing-server/src/Controller/GroupsController.ts b/packages/syncing-server/src/Controller/GroupsController.ts deleted file mode 100644 index 2459588c9..000000000 --- a/packages/syncing-server/src/Controller/GroupsController.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { GroupUser } from '../Domain/GroupUser/Model/GroupUser' -import { GroupServiceInterface } from './../Domain/Group/Service/GroupServiceInterface' -import { Request, Response } from 'express' -import { - BaseHttpController, - controller, - httpPost, - results, - httpDelete, - httpPatch, - httpGet, -} from 'inversify-express-utils' -import TYPES from '../Bootstrap/Types' -import { inject } from 'inversify' -import { ProjectorInterface } from '../Projection/ProjectorInterface' -import { Group } from '../Domain/Group/Model/Group' -import { GroupProjection } from '../Projection/GroupProjection' -import { GroupUserProjection } from '../Projection/GroupUserProjection' -import { GroupUserServiceInterface } from '../Domain/GroupUser/Service/GroupUserServiceInterface' -import { CreateGroupFileReadValetToken } from '../Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileReadValetToken' - -@controller('/groups') -export class GroupsController extends BaseHttpController { - constructor( - @inject(TYPES.GroupService) private groupService: GroupServiceInterface, - @inject(TYPES.GroupUserService) private groupUserService: GroupUserServiceInterface, - @inject(TYPES.GroupProjector) private groupProjector: ProjectorInterface, - @inject(TYPES.GroupUserProjector) private groupUserProjector: ProjectorInterface, - @inject(TYPES.CreateGroupFileReadValetToken) private createGroupFileReadValetToken: CreateGroupFileReadValetToken, - ) { - super() - } - - @httpGet('/', TYPES.AuthMiddleware) - public async getGroups(_request: Request, response: Response): Promise { - const groups = await this.groupService.getGroups({ - userUuid: response.locals.user.uuid, - }) - - return this.json({ - groups: groups.map((group) => this.groupProjector.projectFull(group)), - }) - } - - @httpPost('/:groupUuid/valet-tokens', TYPES.AuthMiddleware) - public async getValetTokenForGroupFile( - request: Request, - response: Response, - ): Promise { - const valetTokenResult = await this.createGroupFileReadValetToken.execute({ - userUuid: response.locals.user.uuid, - groupUuid: request.params.groupUuid, - fileUuid: request.body.file_uuid, - remoteIdentifier: request.body.remote_identifier, - }) - - if (valetTokenResult.success === false) { - return this.errorResponse(400, `Failed to create group valet token: ${valetTokenResult.reason}`) - } - - return this.json({ - valetToken: valetTokenResult.valetToken, - }) - } - - @httpPost('/', TYPES.AuthMiddleware) - public async createGroup(request: Request, response: Response): Promise { - const result = await this.groupService.createGroup({ - userUuid: response.locals.user.uuid, - groupUuid: request.body.group_uuid, - groupKeyTimestamp: request.body.group_key_timestamp, - specifiedItemsKeyUuid: request.body.specified_items_key_uuid, - }) - - if (!result) { - return this.errorResponse(400, 'Could not create group') - } - - const groupUser = await this.groupUserService.addGroupUser({ - groupUuid: result.uuid, - userUuid: response.locals.user.uuid, - permissions: 'admin', - }) - - if (!groupUser) { - return this.errorResponse(400, 'Could not add user to group') - } - - return this.json({ - group: this.groupProjector.projectFull(result), - groupUser: this.groupUserProjector.projectFull(groupUser), - }) - } - - @httpPatch('/:groupUuid', TYPES.AuthMiddleware) - public async updateGroup(request: Request, response: Response): Promise { - const result = await this.groupService.updateGroup({ - groupUuid: request.params.groupUuid, - originatorUuid: response.locals.user.uuid, - groupKeyTimestamp: request.body.group_key_timestamp, - specifiedItemsKeyUuid: request.body.specified_items_key_uuid, - }) - - if (!result) { - return this.errorResponse(400, 'Could not update group') - } - - return this.json({ - group: this.groupProjector.projectFull(result), - }) - } - - @httpDelete('/:groupUuid', TYPES.AuthMiddleware) - public async deleteGroup(request: Request, response: Response): Promise { - const result = await this.groupService.deleteGroup({ - groupUuid: request.params.groupUuid, - originatorUuid: response.locals.user.uuid, - }) - - if (!result) { - return this.errorResponse(400, 'Could not delete group') - } - - return this.json({ success: true }) - } - - private errorResponse(status: number, message?: string, tag?: string) { - return this.json( - { - error: { message, tag }, - }, - status, - ) - } -} diff --git a/packages/syncing-server/src/Controller/ItemsController.ts b/packages/syncing-server/src/Controller/ItemsController.ts index cd76e659a..6d2ea9dc3 100644 --- a/packages/syncing-server/src/Controller/ItemsController.ts +++ b/packages/syncing-server/src/Controller/ItemsController.ts @@ -37,7 +37,7 @@ export class ItemsController extends BaseHttpController { computeIntegrityHash: request.body.compute_integrity === true, syncToken: request.body.sync_token, cursorToken: request.body.cursor_token, - groupUuids: request.body.group_uuids, + vaultUuids: request.body.vault_uuids, limit: request.body.limit, contentType: request.body.content_type, apiVersion: request.body.api ?? ApiVersion.v20161215, diff --git a/packages/syncing-server/src/Controller/GroupInvitesController.ts b/packages/syncing-server/src/Controller/VaultInvitesController.ts similarity index 61% rename from packages/syncing-server/src/Controller/GroupInvitesController.ts rename to packages/syncing-server/src/Controller/VaultInvitesController.ts index 288c34bb1..cad21a7f2 100644 --- a/packages/syncing-server/src/Controller/GroupInvitesController.ts +++ b/packages/syncing-server/src/Controller/VaultInvitesController.ts @@ -10,29 +10,29 @@ import { } from 'inversify-express-utils' import TYPES from '../Bootstrap/Types' import { inject } from 'inversify' -import { GroupInviteServiceInterface } from '../Domain/GroupInvite/Service/GroupInviteServiceInterface' -import { GroupInviteProjector } from '../Projection/GroupInviteProjector' +import { VaultInviteServiceInterface } from '../Domain/VaultInvite/Service/VaultInviteServiceInterface' +import { VaultInviteProjector } from '../Projection/VaultInviteProjector' -@controller('/groups') -export class GroupInvitesController extends BaseHttpController { +@controller('/vaults') +export class VaultInvitesController extends BaseHttpController { constructor( - @inject(TYPES.GroupInviteService) private groupInviteService: GroupInviteServiceInterface, - @inject(TYPES.GroupInviteProjector) private groupInviteProjector: GroupInviteProjector, + @inject(TYPES.VaultInviteService) private vaultInviteService: VaultInviteServiceInterface, + @inject(TYPES.VaultInviteProjector) private vaultInviteProjector: VaultInviteProjector, ) { super() } - @httpPost('/:groupUuid/invites', TYPES.AuthMiddleware) - public async createGroupInvite( + @httpPost('/:vaultUuid/invites', TYPES.AuthMiddleware) + public async createVaultInvite( request: Request, response: Response, ): Promise { - const result = await this.groupInviteService.createInvite({ + const result = await this.vaultInviteService.createInvite({ originatorUuid: response.locals.user.uuid, - groupUuid: request.params.groupUuid, + vaultUuid: request.params.vaultUuid, userUuid: request.body.invitee_uuid, inviterPublicKey: request.body.inviter_public_key, - encryptedGroupData: request.body.encrypted_group_data, + encryptedVaultData: request.body.encrypted_vault_data, inviteType: request.body.invite_type, permissions: request.body.permissions, }) @@ -41,19 +41,19 @@ export class GroupInvitesController extends BaseHttpController { return this.errorResponse(400, 'Could not create invite') } - return this.json({ invite: this.groupInviteProjector.projectFull(result) }) + return this.json({ invite: this.vaultInviteProjector.projectFull(result) }) } - @httpPatch('/:groupUuid/invites/:inviteUuid', TYPES.AuthMiddleware) - public async updateGroupInvite( + @httpPatch('/:vaultUuid/invites/:inviteUuid', TYPES.AuthMiddleware) + public async updateVaultInvite( request: Request, response: Response, ): Promise { - const result = await this.groupInviteService.updateInvite({ + const result = await this.vaultInviteService.updateInvite({ originatorUuid: response.locals.user.uuid, inviteUuid: request.params.inviteUuid, inviterPublicKey: request.body.inviter_public_key, - encryptedGroupData: request.body.encrypted_group_data, + encryptedVaultData: request.body.encrypted_vault_data, permissions: request.body.permissions, }) @@ -61,15 +61,15 @@ export class GroupInvitesController extends BaseHttpController { return this.errorResponse(400, 'Could not update invite') } - return this.json({ invite: this.groupInviteProjector.projectFull(result) }) + return this.json({ invite: this.vaultInviteProjector.projectFull(result) }) } - @httpPost('/:groupUuid/invites/:inviteUuid/accept', TYPES.AuthMiddleware) - public async acceptGroupInvite( + @httpPost('/:vaultUuid/invites/:inviteUuid/accept', TYPES.AuthMiddleware) + public async acceptVaultInvite( request: Request, response: Response, ): Promise { - const result = await this.groupInviteService.acceptInvite({ + const result = await this.vaultInviteService.acceptInvite({ originatorUuid: response.locals.user.uuid, inviteUuid: request.params.inviteUuid, }) @@ -81,12 +81,12 @@ export class GroupInvitesController extends BaseHttpController { return this.json({ success: true }) } - @httpPost('/:groupUuid/invites/:inviteUuid/decline', TYPES.AuthMiddleware) - public async declineGroupInvite( + @httpPost('/:vaultUuid/invites/:inviteUuid/decline', TYPES.AuthMiddleware) + public async declineVaultInvite( request: Request, response: Response, ): Promise { - const result = await this.groupInviteService.declineInvite({ + const result = await this.vaultInviteService.declineInvite({ originatorUuid: response.locals.user.uuid, inviteUuid: request.params.inviteUuid, }) @@ -103,7 +103,7 @@ export class GroupInvitesController extends BaseHttpController { _request: Request, response: Response, ): Promise { - await this.groupInviteService.deleteAllInboundInvites({ + await this.vaultInviteService.deleteAllInboundInvites({ userUuid: response.locals.user.uuid, }) @@ -115,15 +115,15 @@ export class GroupInvitesController extends BaseHttpController { _request: Request, response: Response, ): Promise { - const result = await this.groupInviteService.getOutboundInvitesForUser({ + const result = await this.vaultInviteService.getOutboundInvitesForUser({ userUuid: response.locals.user.uuid, }) if (!result) { - return this.errorResponse(400, 'Could not get group invites') + return this.errorResponse(400, 'Could not get vault invites') } - const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite)) + const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite)) return this.json({ invites: projected }) } @@ -133,46 +133,46 @@ export class GroupInvitesController extends BaseHttpController { _request: Request, response: Response, ): Promise { - const result = await this.groupInviteService.getInvitesForUser({ + const result = await this.vaultInviteService.getInvitesForUser({ userUuid: response.locals.user.uuid, }) if (!result) { - return this.errorResponse(400, 'Could not get group invites') + return this.errorResponse(400, 'Could not get vault invites') } - const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite)) + const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite)) return this.json({ invites: projected }) } - @httpGet('/:groupUuid/invites', TYPES.AuthMiddleware) - public async getGroupInvites( + @httpGet('/:vaultUuid/invites', TYPES.AuthMiddleware) + public async getVaultInvites( request: Request, response: Response, ): Promise { - const result = await this.groupInviteService.getInvitesForGroup({ + const result = await this.vaultInviteService.getInvitesForVault({ originatorUuid: response.locals.user.uuid, - groupUuid: request.params.groupUuid, + vaultUuid: request.params.vaultUuid, }) if (!result) { - return this.errorResponse(400, 'Could not get group invites') + return this.errorResponse(400, 'Could not get vault invites') } - const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite)) + const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite)) return this.json({ invites: projected }) } - @httpDelete('/:groupUuid/invites/:inviteUuid', TYPES.AuthMiddleware) - public async deleteGroupInvite( + @httpDelete('/:vaultUuid/invites/:inviteUuid', TYPES.AuthMiddleware) + public async deleteVaultInvite( request: Request, response: Response, ): Promise { - const result = await this.groupInviteService.deleteInvite({ + const result = await this.vaultInviteService.deleteInvite({ originatorUuid: response.locals.user.uuid, - groupUuid: request.params.groupUuid, + vaultUuid: request.params.vaultUuid, inviteUuid: request.params.inviteUuid, }) diff --git a/packages/syncing-server/src/Controller/GroupUsersController.ts b/packages/syncing-server/src/Controller/VaultUsersController.ts similarity index 55% rename from packages/syncing-server/src/Controller/GroupUsersController.ts rename to packages/syncing-server/src/Controller/VaultUsersController.ts index 6b9babdae..b87feaa16 100644 --- a/packages/syncing-server/src/Controller/GroupUsersController.ts +++ b/packages/syncing-server/src/Controller/VaultUsersController.ts @@ -2,48 +2,48 @@ import { Request, Response } from 'express' import { BaseHttpController, controller, results, httpDelete, httpGet } from 'inversify-express-utils' import TYPES from '../Bootstrap/Types' import { inject } from 'inversify' -import { GroupUserServiceInterface } from '../Domain/GroupUser/Service/GroupUserServiceInterface' -import { GroupUserProjector } from '../Projection/GroupUserProjector' +import { VaultUserServiceInterface } from '../Domain/VaultUser/Service/VaultUserServiceInterface' +import { VaultUserProjector } from '../Projection/VaultUserProjector' -@controller('/groups/:groupUuid/users') -export class GroupUsersController extends BaseHttpController { +@controller('/vaults/:vaultUuid/users') +export class VaultUsersController extends BaseHttpController { constructor( - @inject(TYPES.GroupUserService) private groupUserService: GroupUserServiceInterface, - @inject(TYPES.GroupUserProjector) private groupUserProjector: GroupUserProjector, + @inject(TYPES.VaultUserService) private vaultUserService: VaultUserServiceInterface, + @inject(TYPES.VaultUserProjector) private vaultUserProjector: VaultUserProjector, ) { super() } @httpGet('/', TYPES.AuthMiddleware) - public async getGroupUsers( + public async getVaultUsers( request: Request, response: Response, ): Promise { - const result = await this.groupUserService.getGroupUsersForGroup({ + const result = await this.vaultUserService.getVaultUsersForVault({ originatorUuid: response.locals.user.uuid, - groupUuid: request.params.groupUuid, + vaultUuid: request.params.vaultUuid, }) if (!result) { - return this.errorResponse(400, 'Could not get group users') + return this.errorResponse(400, 'Could not get vault users') } const { users, isAdmin } = result - const projected = users.map((groupUser) => - this.groupUserProjector.projectAsDisplayableUserForOtherGroupMembers(groupUser, isAdmin), + const projected = users.map((vaultUser) => + this.vaultUserProjector.projectAsDisplayableUserForOtherVaultMembers(vaultUser, isAdmin), ) return this.json({ users: projected }) } @httpDelete('/:userUuid', TYPES.AuthMiddleware) - public async deleteGroupUser( + public async deleteVaultUser( request: Request, response: Response, ): Promise { - const result = await this.groupUserService.deleteGroupUser({ - groupUuid: request.params.groupUuid, + const result = await this.vaultUserService.deleteVaultUser({ + vaultUuid: request.params.vaultUuid, userUuid: request.params.userUuid, originatorUuid: response.locals.user.uuid, }) diff --git a/packages/syncing-server/src/Controller/VaultsController.ts b/packages/syncing-server/src/Controller/VaultsController.ts new file mode 100644 index 000000000..323362ee8 --- /dev/null +++ b/packages/syncing-server/src/Controller/VaultsController.ts @@ -0,0 +1,135 @@ +import { VaultUser } from '../Domain/VaultUser/Model/VaultUser' +import { VaultServiceInterface } from '../Domain/Vault/Service/VaultServiceInterface' +import { Request, Response } from 'express' +import { + BaseHttpController, + controller, + httpPost, + results, + httpDelete, + httpPatch, + httpGet, +} from 'inversify-express-utils' +import TYPES from '../Bootstrap/Types' +import { inject } from 'inversify' +import { ProjectorInterface } from '../Projection/ProjectorInterface' +import { Vault } from '../Domain/Vault/Model/Vault' +import { VaultProjection } from '../Projection/VaultProjection' +import { VaultUserProjection } from '../Projection/VaultUserProjection' +import { VaultUserServiceInterface } from '../Domain/VaultUser/Service/VaultUserServiceInterface' +import { CreateVaultFileReadValetToken } from '../Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileReadValetToken' + +@controller('/vaults') +export class VaultsController extends BaseHttpController { + constructor( + @inject(TYPES.VaultService) private vaultService: VaultServiceInterface, + @inject(TYPES.VaultUserService) private vaultUserService: VaultUserServiceInterface, + @inject(TYPES.VaultProjector) private vaultProjector: ProjectorInterface, + @inject(TYPES.VaultUserProjector) private vaultUserProjector: ProjectorInterface, + @inject(TYPES.CreateVaultFileReadValetToken) private createVaultFileReadValetToken: CreateVaultFileReadValetToken, + ) { + super() + } + + @httpGet('/', TYPES.AuthMiddleware) + public async getVaults(_request: Request, response: Response): Promise { + const vaults = await this.vaultService.getVaults({ + userUuid: response.locals.user.uuid, + }) + + return this.json({ + vaults: vaults.map((vault) => this.vaultProjector.projectFull(vault)), + }) + } + + @httpPost('/:vaultUuid/valet-tokens', TYPES.AuthMiddleware) + public async getValetTokenForVaultFile( + request: Request, + response: Response, + ): Promise { + const valetTokenResult = await this.createVaultFileReadValetToken.execute({ + userUuid: response.locals.user.uuid, + vaultUuid: request.params.vaultUuid, + fileUuid: request.body.file_uuid, + remoteIdentifier: request.body.remote_identifier, + }) + + if (valetTokenResult.success === false) { + return this.errorResponse(400, `Failed to create vault valet token: ${valetTokenResult.reason}`) + } + + return this.json({ + valetToken: valetTokenResult.valetToken, + }) + } + + @httpPost('/', TYPES.AuthMiddleware) + public async createVault(request: Request, response: Response): Promise { + const result = await this.vaultService.createVault({ + userUuid: response.locals.user.uuid, + vaultUuid: request.body.vault_uuid, + vaultKeyTimestamp: request.body.vault_key_timestamp, + specifiedItemsKeyUuid: request.body.specified_items_key_uuid, + }) + + if (!result) { + return this.errorResponse(400, 'Could not create vault') + } + + const vaultUser = await this.vaultUserService.addVaultUser({ + vaultUuid: result.uuid, + userUuid: response.locals.user.uuid, + permissions: 'admin', + }) + + if (!vaultUser) { + return this.errorResponse(400, 'Could not add user to vault') + } + + return this.json({ + vault: this.vaultProjector.projectFull(result), + vaultUser: this.vaultUserProjector.projectFull(vaultUser), + }) + } + + @httpPatch('/:vaultUuid', TYPES.AuthMiddleware) + public async updateVault(request: Request, response: Response): Promise { + const result = await this.vaultService.updateVault({ + vaultUuid: request.params.vaultUuid, + originatorUuid: response.locals.user.uuid, + vaultKeyTimestamp: request.body.vault_key_timestamp, + specifiedItemsKeyUuid: request.body.specified_items_key_uuid, + }) + + if (!result) { + return this.errorResponse(400, 'Could not update vault') + } + + return this.json({ + vault: this.vaultProjector.projectFull(result), + }) + } + + @httpDelete('/:vaultUuid', TYPES.AuthMiddleware) + public async deleteVault(request: Request, response: Response): Promise { + const result = await this.vaultService.deleteVault({ + vaultUuid: request.params.vaultUuid, + originatorUuid: response.locals.user.uuid, + }) + + if (!result) { + return this.errorResponse(400, 'Could not delete vault') + } + + return this.json({ success: true }) + } + + private errorResponse(status: number, message?: string, tag?: string) { + return this.json( + { + error: { message, tag }, + }, + status, + ) + } +} diff --git a/packages/syncing-server/src/Domain/Group/Factory/GroupFactory.ts b/packages/syncing-server/src/Domain/Group/Factory/GroupFactory.ts deleted file mode 100644 index 422aaa8ee..000000000 --- a/packages/syncing-server/src/Domain/Group/Factory/GroupFactory.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { TimerInterface } from '@standardnotes/time' -import { GroupFactoryInterface } from './GroupFactoryInterface' -import { GroupHash } from './GroupHash' -import { Group } from '../Model/Group' - -export class GroupFactory implements GroupFactoryInterface { - constructor(private timer: TimerInterface) {} - - create(dto: { userUuid: string; groupHash: GroupHash }): Group { - const newGroup = new Group() - newGroup.uuid = dto.groupHash.uuid - newGroup.userUuid = dto.userUuid - newGroup.specifiedItemsKeyUuid = dto.groupHash.specified_items_key_uuid - newGroup.groupKeyTimestamp = dto.groupHash.group_key_timestamp - - const now = this.timer.getTimestampInMicroseconds() - newGroup.updatedAtTimestamp = now - newGroup.createdAtTimestamp = now - - if (dto.groupHash.created_at_timestamp) { - newGroup.createdAtTimestamp = dto.groupHash.created_at_timestamp - } - - return newGroup - } - - createStub(dto: { userUuid: string; groupHash: GroupHash }): Group { - const item = this.create(dto) - return item - } -} diff --git a/packages/syncing-server/src/Domain/Group/Factory/GroupFactoryInterface.ts b/packages/syncing-server/src/Domain/Group/Factory/GroupFactoryInterface.ts deleted file mode 100644 index ae8852f53..000000000 --- a/packages/syncing-server/src/Domain/Group/Factory/GroupFactoryInterface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Group } from '../Model/Group' -import { GroupHash } from './GroupHash' - -export interface GroupFactoryInterface { - create(dto: { userUuid: string; groupHash: GroupHash }): Group - createStub(dto: { userUuid: string; groupHash: GroupHash }): Group -} diff --git a/packages/syncing-server/src/Domain/Group/Repository/GroupRepositoryInterface.ts b/packages/syncing-server/src/Domain/Group/Repository/GroupRepositoryInterface.ts deleted file mode 100644 index 46628533e..000000000 --- a/packages/syncing-server/src/Domain/Group/Repository/GroupRepositoryInterface.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Group } from '../Model/Group' - -export type GroupQuery = { - userUuid: string -} - -export type UserGroupsQuery = { - userUuid?: string - groupUuids?: string[] - lastSyncTime?: number -} - -export interface GroupsRepositoryInterface { - findByUuid(uuid: string): Promise - create(group: Group): Promise - save(group: Group): Promise - remove(group: Group): Promise - findAll(query: UserGroupsQuery): Promise -} diff --git a/packages/syncing-server/src/Domain/Group/Repository/TypeORMGroupRepository.ts b/packages/syncing-server/src/Domain/Group/Repository/TypeORMGroupRepository.ts deleted file mode 100644 index 84d1ae5b4..000000000 --- a/packages/syncing-server/src/Domain/Group/Repository/TypeORMGroupRepository.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Repository, SelectQueryBuilder } from 'typeorm' -import { GroupsRepositoryInterface, UserGroupsQuery } from './GroupRepositoryInterface' -import { Group } from '../Model/Group' - -export class TypeORMGroupRepository implements GroupsRepositoryInterface { - constructor(private ormRepository: Repository) {} - - async create(group: Group): Promise { - return this.ormRepository.save(group) - } - - async save(group: Group): Promise { - return this.ormRepository.save(group) - } - - findByUuid(uuid: string): Promise { - return this.ormRepository - .createQueryBuilder('group') - .where('group.uuid = :uuid', { - uuid, - }) - .getOne() - } - - async remove(group: Group): Promise { - return this.ormRepository.remove(group) - } - - async findAll(query: UserGroupsQuery): Promise { - return this.createFindAllQueryBuilder(query).getMany() - } - - private createFindAllQueryBuilder(query: UserGroupsQuery): SelectQueryBuilder { - const queryBuilder = this.ormRepository.createQueryBuilder('group') - - if (!query.userUuid && !query.groupUuids) { - throw new Error('Either userUuid or groupUuids must be provided') - } - - if (query.userUuid) { - queryBuilder.where('group.user_uuid = :userUuid', { userUuid: query.userUuid }) - } - - if (query.groupUuids) { - queryBuilder.where('group.uuid IN (:...groupUuids)', { groupUuids: query.groupUuids }) - } - - if (query.lastSyncTime) { - queryBuilder.andWhere('group.updated_at_timestamp > :lastSyncTime', { - lastSyncTime: query.lastSyncTime, - }) - } - - return queryBuilder - } -} diff --git a/packages/syncing-server/src/Domain/Group/Service/GroupService.ts b/packages/syncing-server/src/Domain/Group/Service/GroupService.ts deleted file mode 100644 index 1a075c5c5..000000000 --- a/packages/syncing-server/src/Domain/Group/Service/GroupService.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { TimerInterface } from '@standardnotes/time' -import { Group } from '../Model/Group' -import { GroupsRepositoryInterface } from '../Repository/GroupRepositoryInterface' -import { CreateGroupDTO, GroupServiceInterface, UpdateGroupDTO } from './GroupServiceInterface' -import { GroupFactoryInterface } from '../Factory/GroupFactoryInterface' -import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface' -import { GroupInviteServiceInterface } from '../../GroupInvite/Service/GroupInviteServiceInterface' - -export class GroupService implements GroupServiceInterface { - constructor( - private groupRepository: GroupsRepositoryInterface, - private groupFactory: GroupFactoryInterface, - private groupUserService: GroupUserServiceInterface, - private groupInviteService: GroupInviteServiceInterface, - private timer: TimerInterface, - ) {} - - async createGroup(dto: CreateGroupDTO): Promise { - const existingGroup = await this.groupRepository.findByUuid(dto.groupUuid) - if (existingGroup) { - return null - } - - const timestamp = this.timer.getTimestampInMicroseconds() - const group = this.groupFactory.create({ - userUuid: dto.userUuid, - groupHash: { - uuid: dto.groupUuid, - user_uuid: dto.userUuid, - specified_items_key_uuid: dto.specifiedItemsKeyUuid, - group_key_timestamp: dto.groupKeyTimestamp, - created_at_timestamp: timestamp, - updated_at_timestamp: timestamp, - }, - }) - - const savedGroup = await this.groupRepository.create(group) - - return savedGroup - } - - async getGroup(dto: { groupUuid: string }): Promise { - const group = await this.groupRepository.findByUuid(dto.groupUuid) - - return group - } - - async getGroups(dto: { userUuid: string; lastSyncTime?: number }): Promise { - const groupUsers = await this.groupUserService.getAllGroupUsersForUser({ - userUuid: dto.userUuid, - }) - - const groupUuids = groupUsers.map((groupUser) => groupUser.groupUuid) - - if (groupUuids.length === 0) { - return [] - } - - return this.groupRepository.findAll({ groupUuids, lastSyncTime: dto.lastSyncTime }) - } - - async updateGroup(dto: UpdateGroupDTO): Promise { - const group = await this.groupRepository.findByUuid(dto.groupUuid) - if (!group || group.userUuid !== dto.originatorUuid) { - return null - } - - if (dto.groupKeyTimestamp < group.groupKeyTimestamp) { - return null - } - - group.specifiedItemsKeyUuid = dto.specifiedItemsKeyUuid - group.groupKeyTimestamp = dto.groupKeyTimestamp - group.updatedAtTimestamp = this.timer.getTimestampInMicroseconds() - - const savedGroup = await this.groupRepository.save(group) - - return savedGroup - } - - async deleteGroup(dto: { groupUuid: string; originatorUuid: string }): Promise { - const group = await this.groupRepository.findByUuid(dto.groupUuid) - if (!group || group.userUuid !== dto.originatorUuid) { - return false - } - - await this.groupRepository.remove(group) - await this.groupUserService.deleteAllGroupUsersForGroup({ - groupUuid: dto.groupUuid, - originatorUuid: dto.originatorUuid, - }) - await this.groupInviteService.deleteAllInvitesForGroup({ - groupUuid: dto.groupUuid, - originatorUuid: dto.originatorUuid, - }) - - return true - } -} diff --git a/packages/syncing-server/src/Domain/Group/Service/GroupServiceInterface.ts b/packages/syncing-server/src/Domain/Group/Service/GroupServiceInterface.ts deleted file mode 100644 index 39d915b35..000000000 --- a/packages/syncing-server/src/Domain/Group/Service/GroupServiceInterface.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Group } from '../Model/Group' - -export type CreateGroupDTO = { - userUuid: string - groupUuid: string - groupKeyTimestamp: number - specifiedItemsKeyUuid: string -} - -export type UpdateGroupDTO = { - originatorUuid: string - groupUuid: string - groupKeyTimestamp: number - specifiedItemsKeyUuid: string -} - -export interface GroupServiceInterface { - createGroup(dto: CreateGroupDTO): Promise - - updateGroup(dto: UpdateGroupDTO): Promise - - deleteGroup(dto: { groupUuid: string; originatorUuid: string }): Promise - - getGroup(dto: { groupUuid: string }): Promise - - getGroups(dto: { userUuid: string; lastSyncTime?: number }): Promise -} diff --git a/packages/syncing-server/src/Domain/GroupInvite/Factory/GroupInviteFactory.ts b/packages/syncing-server/src/Domain/GroupInvite/Factory/GroupInviteFactory.ts deleted file mode 100644 index 625208681..000000000 --- a/packages/syncing-server/src/Domain/GroupInvite/Factory/GroupInviteFactory.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TimerInterface } from '@standardnotes/time' -import { GroupInviteFactoryInterface } from './GroupInviteFactoryInterface' -import { GroupInviteHash } from './GroupInviteHash' -import { GroupInvite } from '../Model/GroupInvite' - -export class GroupInviteFactory implements GroupInviteFactoryInterface { - constructor(private timer: TimerInterface) {} - - create(dto: GroupInviteHash): GroupInvite { - const newGroupInvite = new GroupInvite() - newGroupInvite.uuid = dto.uuid - newGroupInvite.userUuid = dto.user_uuid - newGroupInvite.groupUuid = dto.group_uuid - newGroupInvite.inviterUuid = dto.inviter_uuid - newGroupInvite.inviterPublicKey = dto.inviter_public_key - newGroupInvite.encryptedGroupData = dto.encrypted_group_data - newGroupInvite.inviteType = dto.invite_type - newGroupInvite.permissions = dto.permissions - - const now = this.timer.getTimestampInMicroseconds() - newGroupInvite.updatedAtTimestamp = now - newGroupInvite.createdAtTimestamp = now - - if (dto.created_at_timestamp) { - newGroupInvite.createdAtTimestamp = dto.created_at_timestamp - } - - return newGroupInvite - } - - createStub(dto: GroupInviteHash): GroupInvite { - const item = this.create(dto) - return item - } -} diff --git a/packages/syncing-server/src/Domain/GroupInvite/Factory/GroupInviteFactoryInterface.ts b/packages/syncing-server/src/Domain/GroupInvite/Factory/GroupInviteFactoryInterface.ts deleted file mode 100644 index cc20a6929..000000000 --- a/packages/syncing-server/src/Domain/GroupInvite/Factory/GroupInviteFactoryInterface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { GroupInvite } from '../Model/GroupInvite' -import { GroupInviteHash } from './GroupInviteHash' - -export interface GroupInviteFactoryInterface { - create(dto: GroupInviteHash): GroupInvite - createStub(dto: GroupInviteHash): GroupInvite -} diff --git a/packages/syncing-server/src/Domain/GroupInvite/Factory/GroupInviteHash.ts b/packages/syncing-server/src/Domain/GroupInvite/Factory/GroupInviteHash.ts deleted file mode 100644 index ad0c381e2..000000000 --- a/packages/syncing-server/src/Domain/GroupInvite/Factory/GroupInviteHash.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission' -import { GroupInviteType } from '../Model/GroupInviteType' - -export type GroupInviteHash = { - uuid: string - user_uuid: string - group_uuid: string - inviter_uuid: string - inviter_public_key: string - encrypted_group_data: string - invite_type: GroupInviteType - permissions: GroupUserPermission - created_at_timestamp?: number - updated_at_timestamp?: number -} diff --git a/packages/syncing-server/src/Domain/GroupInvite/Repository/GroupInviteRepositoryInterface.ts b/packages/syncing-server/src/Domain/GroupInvite/Repository/GroupInviteRepositoryInterface.ts deleted file mode 100644 index f27ee5bfd..000000000 --- a/packages/syncing-server/src/Domain/GroupInvite/Repository/GroupInviteRepositoryInterface.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { GroupInvite } from '../Model/GroupInvite' - -export type GroupInviteQuery = { - userUuid: string -} - -export type GroupInviteFindAllForUserQuery = { - userUuid?: string - lastSyncTime?: number - inviterUuid?: string -} - -export type GroupInviteFindAllForGroup = { - groupUuid: string -} - -export interface GroupInviteRepositoryInterface { - findByUuid(groupInviteUuid: string): Promise - create(group: GroupInvite): Promise - save(groupInvite: GroupInvite): Promise - remove(group: GroupInvite): Promise - findAll(query: GroupInviteFindAllForUserQuery): Promise - findAllForGroup(query: GroupInviteFindAllForGroup): Promise - findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise -} diff --git a/packages/syncing-server/src/Domain/GroupInvite/Repository/TypeORMGroupInviteRepository.ts b/packages/syncing-server/src/Domain/GroupInvite/Repository/TypeORMGroupInviteRepository.ts deleted file mode 100644 index b95d62a5b..000000000 --- a/packages/syncing-server/src/Domain/GroupInvite/Repository/TypeORMGroupInviteRepository.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { GroupInvite } from '../Model/GroupInvite' -import { Repository, SelectQueryBuilder } from 'typeorm' -import { - GroupInviteFindAllForGroup, - GroupInviteFindAllForUserQuery, - GroupInviteRepositoryInterface, -} from './GroupInviteRepositoryInterface' - -export class TypeORMGroupInviteRepository implements GroupInviteRepositoryInterface { - constructor(private ormRepository: Repository) {} - - async create(groupInvite: GroupInvite): Promise { - return this.ormRepository.save(groupInvite) - } - - async save(groupInvite: GroupInvite): Promise { - return this.ormRepository.save(groupInvite) - } - - findByUuid(uuid: string): Promise { - return this.ormRepository - .createQueryBuilder('group_invite') - .where('group_invite.uuid = :uuid', { - uuid, - }) - .getOne() - } - - findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise { - return this.ormRepository - .createQueryBuilder('group_invite') - .where('group_invite.user_uuid = :userUuid', { userUuid }) - .andWhere('group_invite.group_uuid = :groupUuid', { groupUuid }) - .getOne() - } - - async remove(groupInvite: GroupInvite): Promise { - return this.ormRepository.remove(groupInvite) - } - - findAllForGroup(query: GroupInviteFindAllForGroup): Promise { - return this.ormRepository - .createQueryBuilder('group_invite') - .where('group_invite.group_uuid = :groupUuid', { groupUuid: query.groupUuid }) - .getMany() - } - - async findAll(query: GroupInviteFindAllForUserQuery): Promise { - return this.createFindAllQueryBuilder(query).getMany() - } - - private createFindAllQueryBuilder(query: GroupInviteFindAllForUserQuery): SelectQueryBuilder { - const queryBuilder = this.ormRepository.createQueryBuilder('group_invite') - - if (query.userUuid) { - queryBuilder.where('group_invite.user_uuid = :userUuid', { userUuid: query.userUuid }) - } else if (query.inviterUuid) { - queryBuilder.where('group_invite.inviter_uuid = :inviterUuid', { - inviterUuid: query.inviterUuid, - }) - } - - if (query.lastSyncTime) { - queryBuilder.andWhere('group_invite.updated_at_timestamp > :lastSyncTime', { - lastSyncTime: query.lastSyncTime, - }) - } - - return queryBuilder - } -} diff --git a/packages/syncing-server/src/Domain/GroupInvite/Service/CreateInviteDTO.ts b/packages/syncing-server/src/Domain/GroupInvite/Service/CreateInviteDTO.ts deleted file mode 100644 index 6aada1a34..000000000 --- a/packages/syncing-server/src/Domain/GroupInvite/Service/CreateInviteDTO.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { GroupInviteType } from '../Model/GroupInviteType' -import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission' - -export type CreateInviteDTO = { - originatorUuid: string - groupUuid: string - userUuid: string - inviterPublicKey: string - encryptedGroupData: string - inviteType: GroupInviteType - permissions: GroupUserPermission -} diff --git a/packages/syncing-server/src/Domain/GroupInvite/Service/GroupInviteService.ts b/packages/syncing-server/src/Domain/GroupInvite/Service/GroupInviteService.ts deleted file mode 100644 index 4c7c58e5e..000000000 --- a/packages/syncing-server/src/Domain/GroupInvite/Service/GroupInviteService.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { TimerInterface } from '@standardnotes/time' -import { GroupInvite } from '../Model/GroupInvite' -import { GroupInviteFactoryInterface } from '../Factory/GroupInviteFactoryInterface' -import { v4 as uuidv4 } from 'uuid' -import { GroupInviteRepositoryInterface } from '../Repository/GroupInviteRepositoryInterface' -import { GroupInviteServiceInterface } from './GroupInviteServiceInterface' -import { GetUserGroupInvitesDTO } from './GetUserGroupInvitesDTO' -import { GroupsRepositoryInterface } from '../../Group/Repository/GroupRepositoryInterface' -import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface' -import { GroupInviteType } from '../Model/GroupInviteType' -import { CreateInviteDTO } from './CreateInviteDTO' -import { UpdateInviteDTO } from './UpdateInviteDTO' - -export class GroupInviteService implements GroupInviteServiceInterface { - constructor( - private groupRepository: GroupsRepositoryInterface, - private groupInviteRepository: GroupInviteRepositoryInterface, - private groupInviteFactory: GroupInviteFactoryInterface, - private groupUserService: GroupUserServiceInterface, - private timer: TimerInterface, - ) {} - - async createInvite(dto: CreateInviteDTO): Promise { - const group = await this.groupRepository.findByUuid(dto.groupUuid) - if (!group || group.userUuid !== dto.originatorUuid) { - return null - } - - const timestamp = this.timer.getTimestampInMicroseconds() - - const groupInvite = this.groupInviteFactory.create({ - uuid: uuidv4(), - user_uuid: dto.userUuid, - group_uuid: dto.groupUuid, - inviter_uuid: dto.originatorUuid, - inviter_public_key: dto.inviterPublicKey, - encrypted_group_data: dto.encryptedGroupData, - invite_type: dto.inviteType, - permissions: dto.permissions, - created_at_timestamp: timestamp, - updated_at_timestamp: timestamp, - }) - - return this.groupInviteRepository.create(groupInvite) - } - - async updateInvite(dto: UpdateInviteDTO): Promise { - const groupInvite = await this.groupInviteRepository.findByUuid(dto.inviteUuid) - if (!groupInvite || groupInvite.inviterUuid !== dto.originatorUuid) { - return null - } - - groupInvite.inviterPublicKey = dto.inviterPublicKey - groupInvite.encryptedGroupData = dto.encryptedGroupData - if (dto.permissions) { - groupInvite.permissions = dto.permissions - } - groupInvite.updatedAtTimestamp = this.timer.getTimestampInMicroseconds() - - return this.groupInviteRepository.save(groupInvite) - } - - getInvitesForUser(dto: GetUserGroupInvitesDTO): Promise { - return this.groupInviteRepository.findAll(dto) - } - - getOutboundInvitesForUser(dto: { userUuid: string }): Promise { - return this.groupInviteRepository.findAll({ - inviterUuid: dto.userUuid, - }) - } - - async deleteAllInboundInvites(dto: { userUuid: string }): Promise { - const inboundInvites = await this.groupInviteRepository.findAll({ - userUuid: dto.userUuid, - }) - - for (const invite of inboundInvites) { - await this.groupInviteRepository.remove(invite) - } - } - - async getInvitesForGroup(dto: { groupUuid: string; originatorUuid: string }): Promise { - const group = await this.groupRepository.findByUuid(dto.groupUuid) - if (!group) { - return undefined - } - - const isUserGroupAdmin = group && group.userUuid === dto.originatorUuid - if (!isUserGroupAdmin) { - return undefined - } - - const users = await this.groupInviteRepository.findAllForGroup({ groupUuid: dto.groupUuid }) - - return users - } - - async acceptInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise { - const invite = await this.groupInviteRepository.findByUuid(dto.inviteUuid) - if (!invite) { - return false - } - - const isAuthorized = invite.userUuid === dto.originatorUuid - if (!isAuthorized) { - return false - } - - if (invite.inviteType === GroupInviteType.Join) { - const addedUser = await this.groupUserService.addGroupUser({ - userUuid: dto.originatorUuid, - groupUuid: invite.groupUuid, - permissions: invite.permissions, - }) - - if (!addedUser) { - return false - } - } - - await this.groupInviteRepository.remove(invite) - - return true - } - - async declineInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise { - const invite = await this.groupInviteRepository.findByUuid(dto.inviteUuid) - if (!invite) { - return false - } - - const isAuthorized = invite.userUuid === dto.originatorUuid - if (!isAuthorized) { - return false - } - - await this.groupInviteRepository.remove(invite) - - return true - } - - async deleteInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise { - const invite = await this.groupInviteRepository.findByUuid(dto.inviteUuid) - if (!invite) { - return false - } - - const isAuthorized = invite.inviterUuid === dto.originatorUuid || invite.userUuid === dto.originatorUuid - if (!isAuthorized) { - return false - } - - await this.groupInviteRepository.remove(invite) - - return true - } - - async deleteAllInvitesForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise { - const group = await this.groupRepository.findByUuid(dto.groupUuid) - if (!group || group.userUuid !== dto.originatorUuid) { - return false - } - - const groupInvites = await this.groupInviteRepository.findAllForGroup({ groupUuid: dto.groupUuid }) - for (const groupInvite of groupInvites) { - await this.groupInviteRepository.remove(groupInvite) - } - - return true - } -} diff --git a/packages/syncing-server/src/Domain/GroupInvite/Service/GroupInviteServiceInterface.ts b/packages/syncing-server/src/Domain/GroupInvite/Service/GroupInviteServiceInterface.ts deleted file mode 100644 index 1dc4fc6c6..000000000 --- a/packages/syncing-server/src/Domain/GroupInvite/Service/GroupInviteServiceInterface.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { GroupInvite } from '../Model/GroupInvite' -import { CreateInviteDTO } from './CreateInviteDTO' -import { GetUserGroupInvitesDTO } from './GetUserGroupInvitesDTO' -import { UpdateInviteDTO } from './UpdateInviteDTO' - -export interface GroupInviteServiceInterface { - createInvite(dto: CreateInviteDTO): Promise - - updateInvite(dto: UpdateInviteDTO): Promise - - getInvitesForUser(dto: GetUserGroupInvitesDTO): Promise - - getInvitesForGroup(dto: { groupUuid: string; originatorUuid: string }): Promise - - getOutboundInvitesForUser(dto: { userUuid: string }): Promise - - acceptInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise - - declineInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise - - deleteInvite(dto: { originatorUuid: string; groupUuid: string; inviteUuid: string }): Promise - - deleteAllInvitesForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise - - deleteAllInboundInvites(dto: { userUuid: string }): Promise -} diff --git a/packages/syncing-server/src/Domain/GroupInvite/Service/UpdateInviteDTO.ts b/packages/syncing-server/src/Domain/GroupInvite/Service/UpdateInviteDTO.ts deleted file mode 100644 index 7b723ff3e..000000000 --- a/packages/syncing-server/src/Domain/GroupInvite/Service/UpdateInviteDTO.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission' - -export type UpdateInviteDTO = { - originatorUuid: string - inviteUuid: string - inviterPublicKey: string - encryptedGroupData: string - permissions?: GroupUserPermission -} diff --git a/packages/syncing-server/src/Domain/GroupUser/Factory/GroupUserFactory.ts b/packages/syncing-server/src/Domain/GroupUser/Factory/GroupUserFactory.ts deleted file mode 100644 index bbe177d50..000000000 --- a/packages/syncing-server/src/Domain/GroupUser/Factory/GroupUserFactory.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { TimerInterface } from '@standardnotes/time' -import { GroupUserFactoryInterface } from './GroupUserFactoryInterface' -import { GroupUserHash } from './GroupUserHash' -import { GroupUser } from '../Model/GroupUser' - -export class GroupUserFactory implements GroupUserFactoryInterface { - constructor(private timer: TimerInterface) {} - - create(dto: GroupUserHash): GroupUser { - const newGroupUser = new GroupUser() - newGroupUser.uuid = dto.uuid - newGroupUser.userUuid = dto.user_uuid - newGroupUser.groupUuid = dto.group_uuid - newGroupUser.permissions = dto.permissions - - const now = this.timer.getTimestampInMicroseconds() - newGroupUser.updatedAtTimestamp = now - newGroupUser.createdAtTimestamp = now - - if (dto.created_at_timestamp) { - newGroupUser.createdAtTimestamp = dto.created_at_timestamp - } - - return newGroupUser - } - - createStub(dto: GroupUserHash): GroupUser { - const item = this.create(dto) - return item - } -} diff --git a/packages/syncing-server/src/Domain/GroupUser/Factory/GroupUserFactoryInterface.ts b/packages/syncing-server/src/Domain/GroupUser/Factory/GroupUserFactoryInterface.ts deleted file mode 100644 index b6fd68005..000000000 --- a/packages/syncing-server/src/Domain/GroupUser/Factory/GroupUserFactoryInterface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { GroupUser } from '../Model/GroupUser' -import { GroupUserHash } from './GroupUserHash' - -export interface GroupUserFactoryInterface { - create(dto: GroupUserHash): GroupUser - createStub(dto: GroupUserHash): GroupUser -} diff --git a/packages/syncing-server/src/Domain/GroupUser/Factory/GroupUserHash.ts b/packages/syncing-server/src/Domain/GroupUser/Factory/GroupUserHash.ts deleted file mode 100644 index d56bd172c..000000000 --- a/packages/syncing-server/src/Domain/GroupUser/Factory/GroupUserHash.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { GroupUserPermission } from '../Model/GroupUserPermission' - -export type GroupUserHash = { - uuid: string - user_uuid: string - group_uuid: string - permissions: GroupUserPermission - created_at_timestamp?: number - updated_at_timestamp?: number -} diff --git a/packages/syncing-server/src/Domain/GroupUser/Model/GroupUserPermission.ts b/packages/syncing-server/src/Domain/GroupUser/Model/GroupUserPermission.ts deleted file mode 100644 index 1ecd2a35a..000000000 --- a/packages/syncing-server/src/Domain/GroupUser/Model/GroupUserPermission.ts +++ /dev/null @@ -1 +0,0 @@ -export type GroupUserPermission = 'read' | 'write' | 'admin' diff --git a/packages/syncing-server/src/Domain/GroupUser/Repository/GroupUserRepositoryInterface.ts b/packages/syncing-server/src/Domain/GroupUser/Repository/GroupUserRepositoryInterface.ts deleted file mode 100644 index 7ec1a06c1..000000000 --- a/packages/syncing-server/src/Domain/GroupUser/Repository/GroupUserRepositoryInterface.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { GroupUser } from '../Model/GroupUser' - -export type GroupUserQuery = { - userUuid: string -} - -export type GroupUserFindAllForUserQuery = { - userUuid: string - lastSyncTime?: number -} - -export type GroupUserFindAllForGroup = { - groupUuid: string -} - -export interface GroupUserRepositoryInterface { - findByUuid(groupUserUuid: string): Promise - create(group: GroupUser): Promise - save(groupUser: GroupUser): Promise - remove(group: GroupUser): Promise - findAllForUser(query: GroupUserFindAllForUserQuery): Promise - findAllForGroup(query: GroupUserFindAllForGroup): Promise - findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise -} diff --git a/packages/syncing-server/src/Domain/GroupUser/Repository/TypeORMGroupUserRepository.ts b/packages/syncing-server/src/Domain/GroupUser/Repository/TypeORMGroupUserRepository.ts deleted file mode 100644 index a3aa2ff5b..000000000 --- a/packages/syncing-server/src/Domain/GroupUser/Repository/TypeORMGroupUserRepository.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { GroupUser } from '../Model/GroupUser' -import { Repository, SelectQueryBuilder } from 'typeorm' -import { - GroupUserFindAllForGroup, - GroupUserFindAllForUserQuery, - GroupUserRepositoryInterface, -} from './GroupUserRepositoryInterface' - -export class TypeORMGroupUserRepository implements GroupUserRepositoryInterface { - constructor(private ormRepository: Repository) {} - - async create(groupUser: GroupUser): Promise { - return this.ormRepository.save(groupUser) - } - - async save(groupUser: GroupUser): Promise { - return this.ormRepository.save(groupUser) - } - - findByUuid(uuid: string): Promise { - return this.ormRepository - .createQueryBuilder('group_user') - .where('group_user.uuid = :uuid', { - uuid, - }) - .getOne() - } - - findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise { - return this.ormRepository - .createQueryBuilder('group_user') - .where('group_user.user_uuid = :userUuid', { userUuid }) - .andWhere('group_user.group_uuid = :groupUuid', { groupUuid }) - .getOne() - } - - async remove(groupUser: GroupUser): Promise { - return this.ormRepository.remove(groupUser) - } - - findAllForGroup(query: GroupUserFindAllForGroup): Promise { - return this.ormRepository - .createQueryBuilder('group_user') - .where('group_user.group_uuid = :groupUuid', { groupUuid: query.groupUuid }) - .getMany() - } - - async findAllForUser(query: GroupUserFindAllForUserQuery): Promise { - return this.createFindAllQueryBuilder(query).getMany() - } - - private createFindAllQueryBuilder(query: GroupUserFindAllForUserQuery): SelectQueryBuilder { - const queryBuilder = this.ormRepository.createQueryBuilder('group_user') - - queryBuilder.where('group_user.user_uuid = :userUuid', { userUuid: query.userUuid }) - - if (query.lastSyncTime) { - queryBuilder.andWhere('group_user.updated_at_timestamp > :lastSyncTime', { - lastSyncTime: query.lastSyncTime, - }) - } - - return queryBuilder - } -} diff --git a/packages/syncing-server/src/Domain/GroupUser/Service/GroupUserService.ts b/packages/syncing-server/src/Domain/GroupUser/Service/GroupUserService.ts deleted file mode 100644 index a66539777..000000000 --- a/packages/syncing-server/src/Domain/GroupUser/Service/GroupUserService.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { TimerInterface } from '@standardnotes/time' -import { GroupUser } from '../Model/GroupUser' -import { GroupUserFactoryInterface } from '../Factory/GroupUserFactoryInterface' -import { v4 as uuidv4 } from 'uuid' -import { GroupUserRepositoryInterface } from '../Repository/GroupUserRepositoryInterface' -import { GroupUserServiceInterface } from './GroupUserServiceInterface' -import { GetGroupUsersDTO } from './GetGroupUsersDTO' -import { GroupsRepositoryInterface } from '../../Group/Repository/GroupRepositoryInterface' -import { GroupUserPermission } from '../Model/GroupUserPermission' - -export class GroupUserService implements GroupUserServiceInterface { - constructor( - private groupRepository: GroupsRepositoryInterface, - private groupUserRepository: GroupUserRepositoryInterface, - private groupUserFactory: GroupUserFactoryInterface, - private timer: TimerInterface, - ) {} - - async addGroupUser(dto: { - groupUuid: string - userUuid: string - permissions: GroupUserPermission - }): Promise { - const group = await this.groupRepository.findByUuid(dto.groupUuid) - if (!group) { - return null - } - - const timestamp = this.timer.getTimestampInMicroseconds() - const groupUser = this.groupUserFactory.create({ - uuid: uuidv4(), - user_uuid: dto.userUuid, - group_uuid: dto.groupUuid, - permissions: dto.permissions, - created_at_timestamp: timestamp, - updated_at_timestamp: timestamp, - }) - - return this.groupUserRepository.create(groupUser) - } - - getAllGroupUsersForUser(dto: GetGroupUsersDTO): Promise { - return this.groupUserRepository.findAllForUser(dto) - } - - getUserForGroup(dto: { userUuid: string; groupUuid: string }): Promise { - return this.groupUserRepository.findByUserUuidAndGroupUuid(dto.userUuid, dto.groupUuid) - } - - async getGroupUsersForGroup(dto: { - groupUuid: string - originatorUuid: string - }): Promise<{ users: GroupUser[]; isAdmin: boolean } | undefined> { - const group = await this.groupRepository.findByUuid(dto.groupUuid) - if (!group) { - return undefined - } - - const isUserGroupAdmin = group && group.userUuid === dto.originatorUuid - const doesUserBelongToGroup = await this.groupUserRepository.findByUserUuidAndGroupUuid( - dto.originatorUuid, - dto.groupUuid, - ) - - if (!isUserGroupAdmin && !doesUserBelongToGroup) { - return undefined - } - - const users = await this.groupUserRepository.findAllForGroup({ groupUuid: dto.groupUuid }) - - return { - users, - isAdmin: isUserGroupAdmin, - } - } - - async deleteGroupUser(dto: { originatorUuid: string; groupUuid: string; userUuid: string }): Promise { - const group = await this.groupRepository.findByUuid(dto.groupUuid) - if (!group) { - return false - } - - const isAuthorized = group.userUuid === dto.originatorUuid || dto.userUuid === dto.originatorUuid - if (!isAuthorized) { - return false - } - - const groupUser = await this.groupUserRepository.findByUserUuidAndGroupUuid(dto.userUuid, dto.groupUuid) - if (!groupUser) { - return false - } - - await this.groupUserRepository.remove(groupUser) - - return true - } - - async deleteAllGroupUsersForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise { - const group = await this.groupRepository.findByUuid(dto.groupUuid) - if (!group || group.userUuid !== dto.originatorUuid) { - return false - } - - const groupUsers = await this.groupUserRepository.findAllForGroup({ groupUuid: dto.groupUuid }) - for (const groupUser of groupUsers) { - await this.groupUserRepository.remove(groupUser) - } - - return true - } -} diff --git a/packages/syncing-server/src/Domain/GroupUser/Service/GroupUserServiceInterface.ts b/packages/syncing-server/src/Domain/GroupUser/Service/GroupUserServiceInterface.ts deleted file mode 100644 index cb2213b0a..000000000 --- a/packages/syncing-server/src/Domain/GroupUser/Service/GroupUserServiceInterface.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { GroupUser } from '../Model/GroupUser' -import { GroupUserPermission } from '../Model/GroupUserPermission' -import { GetGroupUsersDTO } from './GetGroupUsersDTO' - -export interface GroupUserServiceInterface { - addGroupUser(dto: { - groupUuid: string - userUuid: string - permissions: GroupUserPermission - }): Promise - - getAllGroupUsersForUser(dto: GetGroupUsersDTO): Promise - - getUserForGroup(dto: { userUuid: string; groupUuid: string }): Promise - - getGroupUsersForGroup(dto: { - groupUuid: string - originatorUuid: string - }): Promise<{ users: GroupUser[]; isAdmin: boolean } | undefined> - - deleteGroupUser(dto: { originatorUuid: string; groupUuid: string; userUuid: string }): Promise - - deleteAllGroupUsersForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise -} diff --git a/packages/syncing-server/src/Domain/Handler/UserCredentialsChangedEventHandler.ts b/packages/syncing-server/src/Domain/Handler/UserCredentialsChangedEventHandler.ts index 6fc6c69b5..6084c1989 100644 --- a/packages/syncing-server/src/Domain/Handler/UserCredentialsChangedEventHandler.ts +++ b/packages/syncing-server/src/Domain/Handler/UserCredentialsChangedEventHandler.ts @@ -5,18 +5,18 @@ import { } from '@standardnotes/domain-events' import { TimerInterface } from '@standardnotes/time' import { ContactsRepositoryInterface } from '../Contact/Repository/ContactRepositoryInterface' -import { GroupInviteRepositoryInterface } from '../GroupInvite/Repository/GroupInviteRepositoryInterface' +import { VaultInviteRepositoryInterface } from '../VaultInvite/Repository/VaultInviteRepositoryInterface' export class UserCredentialsChangedEventHandler implements DomainEventHandlerInterface { constructor( private contactRepository: ContactsRepositoryInterface, - private groupInviteRepository: GroupInviteRepositoryInterface, + private vaultInviteRepository: VaultInviteRepositoryInterface, private timer: TimerInterface, ) {} async handle(event: UserCredentialsChangedEvent): Promise { await this.updatePublicKeyOfAllContacts(event.payload) - await this.nullifyPendingInboundGroupInvites(event.payload) + await this.nullifyPendingInboundVaultInvites(event.payload) } private async updatePublicKeyOfAllContacts(payload: UserCredentialsChangedEventPayload): Promise { @@ -31,13 +31,13 @@ export class UserCredentialsChangedEventHandler implements DomainEventHandlerInt } } - private async nullifyPendingInboundGroupInvites(payload: UserCredentialsChangedEventPayload): Promise { - const inboundInvites = await this.groupInviteRepository.findAll({ + private async nullifyPendingInboundVaultInvites(payload: UserCredentialsChangedEventPayload): Promise { + const inboundInvites = await this.vaultInviteRepository.findAll({ userUuid: payload.userUuid, }) for (const invite of inboundInvites) { - await this.groupInviteRepository.remove(invite) + await this.vaultInviteRepository.remove(invite) } } } diff --git a/packages/syncing-server/src/Domain/Item/GetItemsDTO.ts b/packages/syncing-server/src/Domain/Item/GetItemsDTO.ts index 27516d119..ec38e8e1c 100644 --- a/packages/syncing-server/src/Domain/Item/GetItemsDTO.ts +++ b/packages/syncing-server/src/Domain/Item/GetItemsDTO.ts @@ -4,5 +4,5 @@ export type GetItemsDTO = { cursorToken?: string | null limit?: number contentType?: string - groupUuids?: string[] | null + vaultUuids?: string[] | null } diff --git a/packages/syncing-server/src/Domain/Item/Item.ts b/packages/syncing-server/src/Domain/Item/Item.ts index 52c090b8e..85116da88 100644 --- a/packages/syncing-server/src/Domain/Item/Item.ts +++ b/packages/syncing-server/src/Domain/Item/Item.ts @@ -15,11 +15,11 @@ export class Item { @Column({ type: 'varchar', - name: 'group_uuid', + name: 'vault_uuid', length: 255, nullable: true, }) - declare groupUuid: string | null + declare vaultUuid: string | null @Column({ type: 'varchar', @@ -77,9 +77,11 @@ export class Item { @Column({ name: 'user_uuid', length: 36, + type: 'varchar', + nullable: true, }) @Index('index_items_on_user_uuid') - declare userUuid: string + declare userUuid: string | null @Column({ name: 'last_edited_by_uuid', diff --git a/packages/syncing-server/src/Domain/Item/ItemFactory.ts b/packages/syncing-server/src/Domain/Item/ItemFactory.ts index ec43b1443..8b1da98d6 100644 --- a/packages/syncing-server/src/Domain/Item/ItemFactory.ts +++ b/packages/syncing-server/src/Domain/Item/ItemFactory.ts @@ -40,8 +40,8 @@ export class ItemFactory implements ItemFactoryInterface { if (dto.itemHash.last_edited_by_uuid) { newItem.lastEditedByUuid = dto.itemHash.last_edited_by_uuid } - if (dto.itemHash.group_uuid) { - newItem.groupUuid = dto.itemHash.group_uuid + if (dto.itemHash.vault_uuid) { + newItem.vaultUuid = dto.itemHash.vault_uuid } if (dto.itemHash.content_type) { newItem.contentType = dto.itemHash.content_type diff --git a/packages/syncing-server/src/Domain/Item/ItemHash.ts b/packages/syncing-server/src/Domain/Item/ItemHash.ts index 7aa71410e..f6b1fa2c6 100644 --- a/packages/syncing-server/src/Domain/Item/ItemHash.ts +++ b/packages/syncing-server/src/Domain/Item/ItemHash.ts @@ -10,7 +10,7 @@ export type ItemHash = { auth_hash?: string enc_item_key?: string items_key_id?: string - group_uuid?: string + vault_uuid?: string last_edited_by_uuid?: string | null created_at?: string created_at_timestamp?: number diff --git a/packages/syncing-server/src/Domain/Item/ItemQuery.ts b/packages/syncing-server/src/Domain/Item/ItemQuery.ts index ecfbf8ac8..c1a896ed8 100644 --- a/packages/syncing-server/src/Domain/Item/ItemQuery.ts +++ b/packages/syncing-server/src/Domain/Item/ItemQuery.ts @@ -6,8 +6,8 @@ export type ItemQuery = { lastSyncTime?: number syncTimeComparison?: '>' | '>=' contentType?: string | string[] - includeGroupUuids?: string[] - exclusiveGroupUuids?: string[] | null + includeVaultUuids?: string[] + exclusiveVaultUuids?: string[] | null deleted?: boolean offset?: number limit?: number diff --git a/packages/syncing-server/src/Domain/Item/ItemService.ts b/packages/syncing-server/src/Domain/Item/ItemService.ts index 4b590d685..866886d11 100644 --- a/packages/syncing-server/src/Domain/Item/ItemService.ts +++ b/packages/syncing-server/src/Domain/Item/ItemService.ts @@ -20,7 +20,7 @@ import { ConflictType } from '@standardnotes/responses' import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface' import { ProjectorInterface } from '../../Projection/ProjectorInterface' import { ItemProjection } from '../../Projection/ItemProjection' -import { GroupUserRepositoryInterface } from '../GroupUser/Repository/GroupUserRepositoryInterface' +import { VaultUserRepositoryInterface } from '../VaultUser/Repository/VaultUserRepositoryInterface' export class ItemService implements ItemServiceInterface { private readonly DEFAULT_ITEMS_LIMIT = 150 @@ -38,7 +38,7 @@ export class ItemService implements ItemServiceInterface { private timer: TimerInterface, private itemProjector: ProjectorInterface, private maxItemsSyncLimit: number, - private groupUsersRepository: GroupUserRepositoryInterface, + private vaultUsersRepository: VaultUserRepositoryInterface, private logger: Logger, ) {} @@ -48,16 +48,16 @@ export class ItemService implements ItemServiceInterface { const limit = dto.limit === undefined || dto.limit < 1 ? this.DEFAULT_ITEMS_LIMIT : dto.limit const upperBoundLimit = limit < this.maxItemsSyncLimit ? limit : this.maxItemsSyncLimit - const groupUsers = await this.groupUsersRepository.findAllForUser({ userUuid: dto.userUuid }) - const userGroupUuids = groupUsers.map((groupUser) => groupUser.groupUuid) - const exclusiveGroupUuids = dto.groupUuids - ? dto.groupUuids.filter((groupUuid) => userGroupUuids.includes(groupUuid)) + const vaultUsers = await this.vaultUsersRepository.findAllForUser({ userUuid: dto.userUuid }) + const userVaultUuids = vaultUsers.map((vaultUser) => vaultUser.vaultUuid) + const exclusiveVaultUuids = dto.vaultUuids + ? dto.vaultUuids.filter((vaultUuid) => userVaultUuids.includes(vaultUuid)) : undefined const itemQuery: ItemQuery = { userUuid: dto.userUuid, - includeGroupUuids: !dto.groupUuids ? userGroupUuids : undefined, - exclusiveGroupUuids: exclusiveGroupUuids, + includeVaultUuids: !dto.vaultUuids ? userVaultUuids : undefined, + exclusiveVaultUuids: exclusiveVaultUuids, lastSyncTime, syncTimeComparison, contentType: dto.contentType, @@ -199,6 +199,28 @@ export class ItemService implements ItemServiceInterface { ).toString('base64') } + async addItemToVault(dto: { + userUuid: string + itemUuid: string + vaultUuid: string + sessionUuid: string | null + }): Promise { + const item = await this.itemRepository.findByUuid(dto.itemUuid) + if (!item || item.userUuid !== dto.userUuid) { + return null + } + + // item.userUuid = null + item.vaultUuid = dto.vaultUuid + item.updatedWithSession = dto.sessionUuid + item.lastEditedByUuid = dto.userUuid + const updatedAt = this.timer.getTimestampInMicroseconds() + item.updatedAtTimestamp = updatedAt + item.updatedAt = this.timer.convertMicrosecondsToDate(updatedAt) + const savedItem = await this.itemRepository.save(item) + return savedItem + } + private async updateExistingItem(dto: { existingItem: Item itemHash: ItemHash @@ -207,7 +229,6 @@ export class ItemService implements ItemServiceInterface { }): Promise { dto.existingItem.updatedWithSession = dto.sessionUuid dto.existingItem.contentSize = 0 - dto.existingItem.groupUuid = dto.itemHash.group_uuid ?? null dto.existingItem.lastEditedByUuid = dto.userUuid if (dto.itemHash.content) { @@ -265,14 +286,14 @@ export class ItemService implements ItemServiceInterface { const savedItem = await this.itemRepository.save(dto.existingItem) if (secondsFromLastUpdate >= this.revisionFrequency) { - if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType)) { + if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType) && savedItem.userUuid) { await this.domainEventPublisher.publish( this.domainEventFactory.createItemRevisionCreationRequested(savedItem.uuid, savedItem.userUuid), ) } } - if (wasMarkedAsDuplicate) { + if (wasMarkedAsDuplicate && savedItem.userUuid) { await this.domainEventPublisher.publish( this.domainEventFactory.createDuplicateItemSyncedEvent(savedItem.uuid, savedItem.userUuid), ) @@ -286,13 +307,13 @@ export class ItemService implements ItemServiceInterface { const savedItem = await this.itemRepository.save(newItem) - if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType)) { + if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType) && savedItem.userUuid) { await this.domainEventPublisher.publish( this.domainEventFactory.createItemRevisionCreationRequested(savedItem.uuid, savedItem.userUuid), ) } - if (savedItem.duplicateOf) { + if (savedItem.duplicateOf && savedItem.userUuid) { await this.domainEventPublisher.publish( this.domainEventFactory.createDuplicateItemSyncedEvent(savedItem.uuid, savedItem.userUuid), ) diff --git a/packages/syncing-server/src/Domain/Item/SaveRule/OwnershipFilter.ts b/packages/syncing-server/src/Domain/Item/SaveRule/OwnershipFilter.ts index fccc8e356..62f157f0f 100644 --- a/packages/syncing-server/src/Domain/Item/SaveRule/OwnershipFilter.ts +++ b/packages/syncing-server/src/Domain/Item/SaveRule/OwnershipFilter.ts @@ -2,14 +2,14 @@ import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO' import { ItemSaveRuleResult } from './ItemSaveRuleResult' import { ItemSaveRuleInterface } from './ItemSaveRuleInterface' import { ConflictType } from '@standardnotes/responses' -import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface' +import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface' import { ItemHash } from '../ItemHash' -import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission' -import { GroupServiceInterface } from '../../Group/Service/GroupServiceInterface' +import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission' +import { VaultServiceInterface } from '../../Vault/Service/VaultServiceInterface' import { ContentType } from '@standardnotes/common' export class OwnershipFilter implements ItemSaveRuleInterface { - constructor(private groupService: GroupServiceInterface, private groupUserService: GroupUserServiceInterface) {} + constructor(private vaultService: VaultServiceInterface, private vaultUserService: VaultUserServiceInterface) {} async check(dto: ItemSaveValidationDTO): Promise { const itemBelongsToADifferentUser = dto.existingItem != null && dto.existingItem.userUuid !== dto.userUuid @@ -18,7 +18,7 @@ export class OwnershipFilter implements ItemSaveRuleInterface { passed: true, } - const groupReadonlyFail = { + const vaultReadonlyFail = { passed: false, conflict: { unsavedItem: dto.itemHash, @@ -34,39 +34,39 @@ export class OwnershipFilter implements ItemSaveRuleInterface { }, } - const groupUuidInvolved = dto.existingItem?.groupUuid || dto.itemHash.group_uuid - if (itemBelongsToADifferentUser && !groupUuidInvolved) { + const vaultUuidInvolved = dto.existingItem?.vaultUuid || dto.itemHash.vault_uuid + if (itemBelongsToADifferentUser && !vaultUuidInvolved) { return ownershipFail } - if (groupUuidInvolved) { - const groupAuthorization = await this.groupAuthorizationForItem(dto.userUuid, groupUuidInvolved) - if (!groupAuthorization) { + if (vaultUuidInvolved) { + const vaultAuthorization = await this.vaultAuthorizationForItem(dto.userUuid, vaultUuidInvolved) + if (!vaultAuthorization) { return ownershipFail } - if (groupAuthorization === 'read') { - return groupReadonlyFail + if (vaultAuthorization === 'read') { + return vaultReadonlyFail } - const isItemBeingRemovedFromGroup = - dto.existingItem != null && dto.existingItem.groupUuid != null && dto.itemHash.group_uuid == null + const isItemBeingRemovedFromVault = + dto.existingItem != null && dto.existingItem.vaultUuid != null && dto.itemHash.vault_uuid == null const isItemBeingDeleted = dto.itemHash.deleted === true - if (isItemBeingRemovedFromGroup || isItemBeingDeleted) { + if (isItemBeingRemovedFromVault || isItemBeingDeleted) { if (itemBelongsToADifferentUser) { - return groupAuthorization === 'admin' ? successValue : groupReadonlyFail + return vaultAuthorization === 'admin' ? successValue : vaultReadonlyFail } else { return successValue } } - if (dto.itemHash.content_type === ContentType.SharedItemsKey && groupAuthorization !== 'admin') { - return groupReadonlyFail + if (dto.itemHash.content_type === ContentType.SharedItemsKey && vaultAuthorization !== 'admin') { + return vaultReadonlyFail } - const usingValidKey = await this.groupItemIsBeingSavedWithValidItemsKey(dto.itemHash) + const usingValidKey = await this.vaultItemIsBeingSavedWithValidItemsKey(dto.itemHash) if (!usingValidKey) { return { @@ -82,32 +82,32 @@ export class OwnershipFilter implements ItemSaveRuleInterface { return successValue } - private async groupItemIsBeingSavedWithValidItemsKey(itemHash: ItemHash): Promise { + private async vaultItemIsBeingSavedWithValidItemsKey(itemHash: ItemHash): Promise { const isItemNotEncryptedByItemsKey = itemHash.content_type === ContentType.SharedItemsKey if (isItemNotEncryptedByItemsKey) { return true } - const group = await this.groupService.getGroup({ groupUuid: itemHash.group_uuid as string }) + const vault = await this.vaultService.getVault({ vaultUuid: itemHash.vault_uuid as string }) - if (!group) { + if (!vault) { return false } - return itemHash.items_key_id === group.specifiedItemsKeyUuid + return itemHash.items_key_id === vault.specifiedItemsKeyUuid } - private async groupAuthorizationForItem( + private async vaultAuthorizationForItem( userUuid: string, - groupUuid: string, - ): Promise { - const groupUser = await this.groupUserService.getUserForGroup({ + vaultUuid: string, + ): Promise { + const vaultUser = await this.vaultUserService.getUserForVault({ userUuid, - groupUuid: groupUuid, + vaultUuid: vaultUuid, }) - if (groupUser) { - return groupUser.permissions + if (vaultUser) { + return vaultUser.permissions } return undefined diff --git a/packages/syncing-server/src/Domain/Item/SaveRule/SnjsVersionFilter.ts b/packages/syncing-server/src/Domain/Item/SaveRule/SnjsVersionFilter.ts index f0cd218d8..2fe6483d1 100644 --- a/packages/syncing-server/src/Domain/Item/SaveRule/SnjsVersionFilter.ts +++ b/packages/syncing-server/src/Domain/Item/SaveRule/SnjsVersionFilter.ts @@ -4,7 +4,7 @@ import { ItemSaveRuleInterface } from './ItemSaveRuleInterface' import { ConflictType } from '@standardnotes/responses' import { lt } from 'semver' -const MINIMUM_SNJS_VERSION_FOR_GROUP_OPERATIONS = '2.200.0' +const MINIMUM_SNJS_VERSION_FOR_VAULT_OPERATIONS = '2.200.0' export class SnjsVersionFilter implements ItemSaveRuleInterface { async check(dto: ItemSaveValidationDTO): Promise { @@ -12,12 +12,12 @@ export class SnjsVersionFilter implements ItemSaveRuleInterface { passed: true, } - const groupUuidInvolved = dto.existingItem?.groupUuid || dto.itemHash.group_uuid - if (!groupUuidInvolved) { + const vaultUuidInvolved = dto.existingItem?.vaultUuid || dto.itemHash.vault_uuid + if (!vaultUuidInvolved) { return successValue } - const isClientSnjsVersionLessThanMinimum = lt(dto.snjsVersion, MINIMUM_SNJS_VERSION_FOR_GROUP_OPERATIONS) + const isClientSnjsVersionLessThanMinimum = lt(dto.snjsVersion, MINIMUM_SNJS_VERSION_FOR_VAULT_OPERATIONS) if (isClientSnjsVersionLessThanMinimum) { return { diff --git a/packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponse20200115.ts b/packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponse20200115.ts index b30f5ed3c..8ee2cc5c2 100644 --- a/packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponse20200115.ts +++ b/packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponse20200115.ts @@ -1,6 +1,6 @@ import { ContactProjection } from '../../../Projection/ContactProjection' -import { GroupInviteProjection } from '../../../Projection/GroupInviteProjection' -import { GroupProjection } from '../../../Projection/GroupProjection' +import { VaultInviteProjection } from '../../../Projection/VaultInviteProjection' +import { VaultProjection } from '../../../Projection/VaultProjection' import { ItemConflictProjection } from '../../../Projection/ItemConflictProjection' import { ItemProjection } from '../../../Projection/ItemProjection' import { SavedItemProjection } from '../../../Projection/SavedItemProjection' @@ -12,6 +12,6 @@ export type SyncResponse20200115 = { sync_token: string cursor_token?: string contacts: Array - groups: Array - group_invites: Array + vaults: Array + vault_invites: Array } diff --git a/packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20200115.ts b/packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20200115.ts index dbd00d39c..55c01cd7b 100644 --- a/packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20200115.ts +++ b/packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20200115.ts @@ -1,4 +1,4 @@ -import { GroupInvite } from './../../GroupInvite/Model/GroupInvite' +import { VaultInvite } from '../../VaultInvite/Model/VaultInvite' import { Contact } from './../../Contact/Model/Contact' import { ProjectorInterface } from '../../../Projection/ProjectorInterface' import { SyncItemsResponse } from '../../UseCase/SyncItemsResponse' @@ -10,18 +10,18 @@ import { SyncResponse20200115 } from './SyncResponse20200115' import { SyncResponseFactoryInterface } from './SyncResponseFactoryInterface' import { SavedItemProjection } from '../../../Projection/SavedItemProjection' import { ContactProjection } from '../../../Projection/ContactProjection' -import { GroupInviteProjection } from '../../../Projection/GroupInviteProjection' -import { GroupProjection } from '../../../Projection/GroupProjection' -import { Group } from '../../Group/Model/Group' +import { VaultInviteProjection } from '../../../Projection/VaultInviteProjection' +import { VaultProjection } from '../../../Projection/VaultProjection' +import { Vault } from '../../Vault/Model/Vault' export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface { constructor( private itemProjector: ProjectorInterface, private itemConflictProjector: ProjectorInterface, private savedItemProjector: ProjectorInterface, - private groupInviteProjector: ProjectorInterface, + private vaultInviteProjector: ProjectorInterface, private contactProjector: ProjectorInterface, - private groupProjector: ProjectorInterface, + private vaultProjector: ProjectorInterface, ) {} async createResponse(syncItemsResponse: SyncItemsResponse): Promise { @@ -40,14 +40,14 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface conflicts.push(this.itemConflictProjector.projectFull(itemConflict)) } - const groups = [] - for (const group of syncItemsResponse.groups) { - groups.push(this.groupProjector.projectFull(group)) + const vaults = [] + for (const vault of syncItemsResponse.vaults) { + vaults.push(this.vaultProjector.projectFull(vault)) } - const groupInvites = [] - for (const groupInvite of syncItemsResponse.groupInvites) { - groupInvites.push(this.groupInviteProjector.projectFull(groupInvite)) + const vaultInvites = [] + for (const vaultInvite of syncItemsResponse.vaultInvites) { + vaultInvites.push(this.vaultInviteProjector.projectFull(vaultInvite)) } const contacts = [] @@ -61,8 +61,8 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface conflicts, sync_token: syncItemsResponse.syncToken, cursor_token: syncItemsResponse.cursorToken, - groups, - group_invites: groupInvites, + vaults, + vault_invites: vaultInvites, contacts, } } diff --git a/packages/syncing-server/src/Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileReadValetTokenDTO.ts b/packages/syncing-server/src/Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileReadValetTokenDTO.ts deleted file mode 100644 index da67f2b69..000000000 --- a/packages/syncing-server/src/Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileReadValetTokenDTO.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type CreateGroupFileReadValetTokenDTO = { - userUuid: string - groupUuid: string - fileUuid: string - remoteIdentifier: string -} diff --git a/packages/syncing-server/src/Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileReadValetToken.ts b/packages/syncing-server/src/Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileReadValetToken.ts similarity index 60% rename from packages/syncing-server/src/Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileReadValetToken.ts rename to packages/syncing-server/src/Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileReadValetToken.ts index 9407fcd22..d358bc2b5 100644 --- a/packages/syncing-server/src/Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileReadValetToken.ts +++ b/packages/syncing-server/src/Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileReadValetToken.ts @@ -1,30 +1,30 @@ import { inject, injectable } from 'inversify' -import { GroupValetTokenData, TokenEncoderInterface, ValetTokenOperation } from '@standardnotes/security' +import { VaultValetTokenData, TokenEncoderInterface, ValetTokenOperation } from '@standardnotes/security' import { CreateValetTokenResponseData } from '@standardnotes/responses' import TYPES from '../../../Bootstrap/Types' import { UseCaseInterface } from '../UseCaseInterface' -import { CreateGroupFileReadValetTokenDTO } from './CreateGroupFileReadValetTokenDTO' +import { CreateVaultFileReadValetTokenDTO } from './CreateVaultFileReadValetTokenDTO' import { GetGlobalItem } from '../GetGlobalItem/GetGlobalItem' -import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface' +import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface' @injectable() -export class CreateGroupFileReadValetToken implements UseCaseInterface { +export class CreateVaultFileReadValetToken implements UseCaseInterface { constructor( @inject(TYPES.GetGlobalItem) private getGlobalItem: GetGlobalItem, - @inject(TYPES.GroupUserService) private groupUserService: GroupUserServiceInterface, - @inject(TYPES.ValetTokenEncoder) private tokenEncoder: TokenEncoderInterface, + @inject(TYPES.VaultUserService) private vaultUserService: VaultUserServiceInterface, + @inject(TYPES.ValetTokenEncoder) private tokenEncoder: TokenEncoderInterface, @inject(TYPES.VALET_TOKEN_TTL) private valetTokenTTL: number, ) {} - async execute(dto: CreateGroupFileReadValetTokenDTO): Promise { + async execute(dto: CreateVaultFileReadValetTokenDTO): Promise { const itemResponse = await this.getGlobalItem.execute({ itemUuid: dto.fileUuid, }) if ( itemResponse.success === false || - !itemResponse.item.groupUuid || - itemResponse.item.groupUuid !== dto.groupUuid + !itemResponse.item.vaultUuid || + itemResponse.item.vaultUuid !== dto.vaultUuid ) { return { success: false, @@ -32,21 +32,20 @@ export class CreateGroupFileReadValetToken implements UseCaseInterface { } } - const groupUser = await this.groupUserService.getUserForGroup({ + const vaultUser = await this.vaultUserService.getUserForVault({ userUuid: dto.userUuid, - groupUuid: dto.groupUuid, + vaultUuid: dto.vaultUuid, }) - if (!groupUser) { + if (!vaultUser) { return { success: false, reason: 'invalid-parameters', } } - const tokenData: GroupValetTokenData = { - groupUuid: dto.groupUuid, - fileOwnerUuid: itemResponse.item.userUuid, + const tokenData: VaultValetTokenData = { + vaultUuid: dto.vaultUuid, permittedOperation: ValetTokenOperation.Read, remoteIdentifier: dto.remoteIdentifier, } diff --git a/packages/syncing-server/src/Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileReadValetTokenDTO.ts b/packages/syncing-server/src/Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileReadValetTokenDTO.ts new file mode 100644 index 000000000..cf23e5a26 --- /dev/null +++ b/packages/syncing-server/src/Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileReadValetTokenDTO.ts @@ -0,0 +1,6 @@ +export type CreateVaultFileReadValetTokenDTO = { + userUuid: string + vaultUuid: string + fileUuid: string + remoteIdentifier: string +} diff --git a/packages/syncing-server/src/Domain/UseCase/SyncItems.ts b/packages/syncing-server/src/Domain/UseCase/SyncItems.ts index eee8ee566..ea2fa52c0 100644 --- a/packages/syncing-server/src/Domain/UseCase/SyncItems.ts +++ b/packages/syncing-server/src/Domain/UseCase/SyncItems.ts @@ -5,14 +5,14 @@ import { ItemServiceInterface } from '../Item/ItemServiceInterface' import { SyncItemsDTO } from './SyncItemsDTO' import { SyncItemsResponse } from './SyncItemsResponse' import { UseCaseInterface } from './UseCaseInterface' -import { GroupInviteServiceInterface } from '../GroupInvite/Service/GroupInviteServiceInterface' -import { GroupServiceInterface } from '../Group/Service/GroupServiceInterface' +import { VaultInviteServiceInterface } from '../VaultInvite/Service/VaultInviteServiceInterface' +import { VaultServiceInterface } from '../Vault/Service/VaultServiceInterface' export class SyncItems implements UseCaseInterface { constructor( private itemService: ItemServiceInterface, - private groupService: GroupServiceInterface, - private groupInviteService: GroupInviteServiceInterface, + private vaultService: VaultServiceInterface, + private vaultInviteService: VaultInviteServiceInterface, private contactService: ContactServiceInterface, ) {} @@ -20,7 +20,7 @@ export class SyncItems implements UseCaseInterface { const getItemsResult = await this.itemService.getItems({ userUuid: dto.userUuid, syncToken: dto.syncToken, - groupUuids: dto.groupUuids, + vaultUuids: dto.vaultUuids, cursorToken: dto.cursorToken, limit: dto.limit, contentType: dto.contentType, @@ -36,8 +36,8 @@ export class SyncItems implements UseCaseInterface { }) let retrievedItems = this.filterOutSyncConflictsForConsecutiveSyncs(getItemsResult.items, saveItemsResult.conflicts) - const isGroupExclusiveSync = dto.groupUuids && dto.groupUuids.length > 0 - if (this.isFirstSync(dto) && !isGroupExclusiveSync) { + const isVaultExclusiveSync = dto.vaultUuids && dto.vaultUuids.length > 0 + if (this.isFirstSync(dto) && !isVaultExclusiveSync) { retrievedItems = await this.itemService.frontLoadKeysItemsToTop(dto.userUuid, retrievedItems) } @@ -46,12 +46,12 @@ export class SyncItems implements UseCaseInterface { cursorToken: dto.cursorToken, }) - const groups = await this.groupService.getGroups({ + const vaults = await this.vaultService.getVaults({ userUuid: dto.userUuid, lastSyncTime, }) - const groupInvites = await this.groupInviteService.getInvitesForUser({ + const vaultInvites = await this.vaultInviteService.getInvitesForUser({ userUuid: dto.userUuid, lastSyncTime, }) @@ -67,8 +67,8 @@ export class SyncItems implements UseCaseInterface { savedItems: saveItemsResult.savedItems, conflicts: saveItemsResult.conflicts, cursorToken: getItemsResult.cursorToken, - groups, - groupInvites, + vaults, + vaultInvites, contacts, } diff --git a/packages/syncing-server/src/Domain/UseCase/SyncItemsDTO.ts b/packages/syncing-server/src/Domain/UseCase/SyncItemsDTO.ts index e3dd17d47..8bf37fd01 100644 --- a/packages/syncing-server/src/Domain/UseCase/SyncItemsDTO.ts +++ b/packages/syncing-server/src/Domain/UseCase/SyncItemsDTO.ts @@ -5,7 +5,7 @@ export type SyncItemsDTO = { itemHashes: Array computeIntegrityHash: boolean limit: number - groupUuids?: string[] | null + vaultUuids?: string[] | null syncToken?: string | null cursorToken?: string | null contentType?: string diff --git a/packages/syncing-server/src/Domain/UseCase/SyncItemsResponse.ts b/packages/syncing-server/src/Domain/UseCase/SyncItemsResponse.ts index 6e1fd85c2..eb1dbb73c 100644 --- a/packages/syncing-server/src/Domain/UseCase/SyncItemsResponse.ts +++ b/packages/syncing-server/src/Domain/UseCase/SyncItemsResponse.ts @@ -1,15 +1,15 @@ -import { GroupInvite } from './../GroupInvite/Model/GroupInvite' +import { VaultInvite } from '../VaultInvite/Model/VaultInvite' import { Contact } from '../Contact/Model/Contact' import { Item } from '../Item/Item' import { ItemConflict } from '../Item/ItemConflict' -import { Group } from '../Group/Model/Group' +import { Vault } from '../Vault/Model/Vault' export type SyncItemsResponse = { retrievedItems: Array savedItems: Array conflicts: Array - groups: Array - groupInvites: Array + vaults: Array + vaultInvites: Array contacts: Array syncToken: string cursorToken?: string diff --git a/packages/syncing-server/src/Domain/Vault/Factory/VaultFactory.ts b/packages/syncing-server/src/Domain/Vault/Factory/VaultFactory.ts new file mode 100644 index 000000000..f62b16f1d --- /dev/null +++ b/packages/syncing-server/src/Domain/Vault/Factory/VaultFactory.ts @@ -0,0 +1,31 @@ +import { TimerInterface } from '@standardnotes/time' +import { VaultFactoryInterface } from './VaultFactoryInterface' +import { VaultHash } from './VaultHash' +import { Vault } from '../Model/Vault' + +export class VaultFactory implements VaultFactoryInterface { + constructor(private timer: TimerInterface) {} + + create(dto: { userUuid: string; vaultHash: VaultHash }): Vault { + const newVault = new Vault() + newVault.uuid = dto.vaultHash.uuid + newVault.userUuid = dto.userUuid + newVault.specifiedItemsKeyUuid = dto.vaultHash.specified_items_key_uuid + newVault.vaultKeyTimestamp = dto.vaultHash.vault_key_timestamp + + const now = this.timer.getTimestampInMicroseconds() + newVault.updatedAtTimestamp = now + newVault.createdAtTimestamp = now + + if (dto.vaultHash.created_at_timestamp) { + newVault.createdAtTimestamp = dto.vaultHash.created_at_timestamp + } + + return newVault + } + + createStub(dto: { userUuid: string; vaultHash: VaultHash }): Vault { + const item = this.create(dto) + return item + } +} diff --git a/packages/syncing-server/src/Domain/Vault/Factory/VaultFactoryInterface.ts b/packages/syncing-server/src/Domain/Vault/Factory/VaultFactoryInterface.ts new file mode 100644 index 000000000..06eeca20e --- /dev/null +++ b/packages/syncing-server/src/Domain/Vault/Factory/VaultFactoryInterface.ts @@ -0,0 +1,7 @@ +import { Vault } from '../Model/Vault' +import { VaultHash } from './VaultHash' + +export interface VaultFactoryInterface { + create(dto: { userUuid: string; vaultHash: VaultHash }): Vault + createStub(dto: { userUuid: string; vaultHash: VaultHash }): Vault +} diff --git a/packages/syncing-server/src/Domain/Group/Factory/GroupHash.ts b/packages/syncing-server/src/Domain/Vault/Factory/VaultHash.ts similarity index 70% rename from packages/syncing-server/src/Domain/Group/Factory/GroupHash.ts rename to packages/syncing-server/src/Domain/Vault/Factory/VaultHash.ts index 48e618aca..11e0289b2 100644 --- a/packages/syncing-server/src/Domain/Group/Factory/GroupHash.ts +++ b/packages/syncing-server/src/Domain/Vault/Factory/VaultHash.ts @@ -1,8 +1,8 @@ -export type GroupHash = { +export type VaultHash = { uuid: string user_uuid: string specified_items_key_uuid: string - group_key_timestamp: number + vault_key_timestamp: number created_at_timestamp?: number updated_at_timestamp?: number } diff --git a/packages/syncing-server/src/Domain/Group/Model/Group.ts b/packages/syncing-server/src/Domain/Vault/Model/Vault.ts similarity index 78% rename from packages/syncing-server/src/Domain/Group/Model/Group.ts rename to packages/syncing-server/src/Domain/Vault/Model/Vault.ts index 87dc24c96..9ff267f8f 100644 --- a/packages/syncing-server/src/Domain/Group/Model/Group.ts +++ b/packages/syncing-server/src/Domain/Vault/Model/Vault.ts @@ -1,7 +1,7 @@ import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm' -@Entity({ name: 'groups' }) -export class Group { +@Entity({ name: 'vaults' }) +export class Vault { @PrimaryGeneratedColumn('uuid') declare uuid: string @@ -9,7 +9,7 @@ export class Group { name: 'user_uuid', length: 36, }) - @Index('index_groups_on_user_uuid') + @Index('index_vaults_on_user_uuid') declare userUuid: string @Column({ @@ -19,10 +19,10 @@ export class Group { declare specifiedItemsKeyUuid: string @Column({ - name: 'group_key_timestamp', + name: 'vault_key_timestamp', type: 'bigint', }) - declare groupKeyTimestamp: number + declare vaultKeyTimestamp: number @Column({ name: 'created_at_timestamp', diff --git a/packages/syncing-server/src/Domain/Vault/Repository/TypeORMVaultRepository.ts b/packages/syncing-server/src/Domain/Vault/Repository/TypeORMVaultRepository.ts new file mode 100644 index 000000000..c0ef9c36c --- /dev/null +++ b/packages/syncing-server/src/Domain/Vault/Repository/TypeORMVaultRepository.ts @@ -0,0 +1,56 @@ +import { Repository, SelectQueryBuilder } from 'typeorm' +import { VaultsRepositoryInterface, UserVaultsQuery } from './VaultRepositoryInterface' +import { Vault } from '../Model/Vault' + +export class TypeORMVaultRepository implements VaultsRepositoryInterface { + constructor(private ormRepository: Repository) {} + + async create(vault: Vault): Promise { + return this.ormRepository.save(vault) + } + + async save(vault: Vault): Promise { + return this.ormRepository.save(vault) + } + + findByUuid(uuid: string): Promise { + return this.ormRepository + .createQueryBuilder('vault') + .where('vault.uuid = :uuid', { + uuid, + }) + .getOne() + } + + async remove(vault: Vault): Promise { + return this.ormRepository.remove(vault) + } + + async findAll(query: UserVaultsQuery): Promise { + return this.createFindAllQueryBuilder(query).getMany() + } + + private createFindAllQueryBuilder(query: UserVaultsQuery): SelectQueryBuilder { + const queryBuilder = this.ormRepository.createQueryBuilder('vault') + + if (!query.userUuid && !query.vaultUuids) { + throw new Error('Either userUuid or vaultUuids must be provided') + } + + if (query.userUuid) { + queryBuilder.where('vault.user_uuid = :userUuid', { userUuid: query.userUuid }) + } + + if (query.vaultUuids) { + queryBuilder.where('vault.uuid IN (:...vaultUuids)', { vaultUuids: query.vaultUuids }) + } + + if (query.lastSyncTime) { + queryBuilder.andWhere('vault.updated_at_timestamp > :lastSyncTime', { + lastSyncTime: query.lastSyncTime, + }) + } + + return queryBuilder + } +} diff --git a/packages/syncing-server/src/Domain/Vault/Repository/VaultRepositoryInterface.ts b/packages/syncing-server/src/Domain/Vault/Repository/VaultRepositoryInterface.ts new file mode 100644 index 000000000..7cc28973b --- /dev/null +++ b/packages/syncing-server/src/Domain/Vault/Repository/VaultRepositoryInterface.ts @@ -0,0 +1,19 @@ +import { Vault } from '../Model/Vault' + +export type VaultQuery = { + userUuid: string +} + +export type UserVaultsQuery = { + userUuid?: string + vaultUuids?: string[] + lastSyncTime?: number +} + +export interface VaultsRepositoryInterface { + findByUuid(uuid: string): Promise + create(vault: Vault): Promise + save(vault: Vault): Promise + remove(vault: Vault): Promise + findAll(query: UserVaultsQuery): Promise +} diff --git a/packages/syncing-server/src/Domain/Vault/Service/VaultService.ts b/packages/syncing-server/src/Domain/Vault/Service/VaultService.ts new file mode 100644 index 000000000..455f5bf24 --- /dev/null +++ b/packages/syncing-server/src/Domain/Vault/Service/VaultService.ts @@ -0,0 +1,99 @@ +import { TimerInterface } from '@standardnotes/time' +import { Vault } from '../Model/Vault' +import { VaultsRepositoryInterface } from '../Repository/VaultRepositoryInterface' +import { CreateVaultDTO, VaultServiceInterface, UpdateVaultDTO } from './VaultServiceInterface' +import { VaultFactoryInterface } from '../Factory/VaultFactoryInterface' +import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface' +import { VaultInviteServiceInterface } from '../../VaultInvite/Service/VaultInviteServiceInterface' + +export class VaultService implements VaultServiceInterface { + constructor( + private vaultRepository: VaultsRepositoryInterface, + private vaultFactory: VaultFactoryInterface, + private vaultUserService: VaultUserServiceInterface, + private vaultInviteService: VaultInviteServiceInterface, + private timer: TimerInterface, + ) {} + + async createVault(dto: CreateVaultDTO): Promise { + const existingVault = await this.vaultRepository.findByUuid(dto.vaultUuid) + if (existingVault) { + return null + } + + const timestamp = this.timer.getTimestampInMicroseconds() + const vault = this.vaultFactory.create({ + userUuid: dto.userUuid, + vaultHash: { + uuid: dto.vaultUuid, + user_uuid: dto.userUuid, + specified_items_key_uuid: dto.specifiedItemsKeyUuid, + vault_key_timestamp: dto.vaultKeyTimestamp, + created_at_timestamp: timestamp, + updated_at_timestamp: timestamp, + }, + }) + + const savedVault = await this.vaultRepository.create(vault) + + return savedVault + } + + async getVault(dto: { vaultUuid: string }): Promise { + const vault = await this.vaultRepository.findByUuid(dto.vaultUuid) + + return vault + } + + async getVaults(dto: { userUuid: string; lastSyncTime?: number }): Promise { + const vaultUsers = await this.vaultUserService.getAllVaultUsersForUser({ + userUuid: dto.userUuid, + }) + + const vaultUuids = vaultUsers.map((vaultUser) => vaultUser.vaultUuid) + + if (vaultUuids.length === 0) { + return [] + } + + return this.vaultRepository.findAll({ vaultUuids, lastSyncTime: dto.lastSyncTime }) + } + + async updateVault(dto: UpdateVaultDTO): Promise { + const vault = await this.vaultRepository.findByUuid(dto.vaultUuid) + if (!vault || vault.userUuid !== dto.originatorUuid) { + return null + } + + if (dto.vaultKeyTimestamp < vault.vaultKeyTimestamp) { + return null + } + + vault.specifiedItemsKeyUuid = dto.specifiedItemsKeyUuid + vault.vaultKeyTimestamp = dto.vaultKeyTimestamp + vault.updatedAtTimestamp = this.timer.getTimestampInMicroseconds() + + const savedVault = await this.vaultRepository.save(vault) + + return savedVault + } + + async deleteVault(dto: { vaultUuid: string; originatorUuid: string }): Promise { + const vault = await this.vaultRepository.findByUuid(dto.vaultUuid) + if (!vault || vault.userUuid !== dto.originatorUuid) { + return false + } + + await this.vaultRepository.remove(vault) + await this.vaultUserService.deleteAllVaultUsersForVault({ + vaultUuid: dto.vaultUuid, + originatorUuid: dto.originatorUuid, + }) + await this.vaultInviteService.deleteAllInvitesForVault({ + vaultUuid: dto.vaultUuid, + originatorUuid: dto.originatorUuid, + }) + + return true + } +} diff --git a/packages/syncing-server/src/Domain/Vault/Service/VaultServiceInterface.ts b/packages/syncing-server/src/Domain/Vault/Service/VaultServiceInterface.ts new file mode 100644 index 000000000..c696781cb --- /dev/null +++ b/packages/syncing-server/src/Domain/Vault/Service/VaultServiceInterface.ts @@ -0,0 +1,27 @@ +import { Vault } from '../Model/Vault' + +export type CreateVaultDTO = { + userUuid: string + vaultUuid: string + vaultKeyTimestamp: number + specifiedItemsKeyUuid: string +} + +export type UpdateVaultDTO = { + originatorUuid: string + vaultUuid: string + vaultKeyTimestamp: number + specifiedItemsKeyUuid: string +} + +export interface VaultServiceInterface { + createVault(dto: CreateVaultDTO): Promise + + updateVault(dto: UpdateVaultDTO): Promise + + deleteVault(dto: { vaultUuid: string; originatorUuid: string }): Promise + + getVault(dto: { vaultUuid: string }): Promise + + getVaults(dto: { userUuid: string; lastSyncTime?: number }): Promise +} diff --git a/packages/syncing-server/src/Domain/VaultInvite/Factory/VaultInviteFactory.ts b/packages/syncing-server/src/Domain/VaultInvite/Factory/VaultInviteFactory.ts new file mode 100644 index 000000000..f73685967 --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultInvite/Factory/VaultInviteFactory.ts @@ -0,0 +1,35 @@ +import { TimerInterface } from '@standardnotes/time' +import { VaultInviteFactoryInterface } from './VaultInviteFactoryInterface' +import { VaultInviteHash } from './VaultInviteHash' +import { VaultInvite } from '../Model/VaultInvite' + +export class VaultInviteFactory implements VaultInviteFactoryInterface { + constructor(private timer: TimerInterface) {} + + create(dto: VaultInviteHash): VaultInvite { + const newVaultInvite = new VaultInvite() + newVaultInvite.uuid = dto.uuid + newVaultInvite.userUuid = dto.user_uuid + newVaultInvite.vaultUuid = dto.vault_uuid + newVaultInvite.inviterUuid = dto.inviter_uuid + newVaultInvite.inviterPublicKey = dto.inviter_public_key + newVaultInvite.encryptedVaultData = dto.encrypted_vault_data + newVaultInvite.inviteType = dto.invite_type + newVaultInvite.permissions = dto.permissions + + const now = this.timer.getTimestampInMicroseconds() + newVaultInvite.updatedAtTimestamp = now + newVaultInvite.createdAtTimestamp = now + + if (dto.created_at_timestamp) { + newVaultInvite.createdAtTimestamp = dto.created_at_timestamp + } + + return newVaultInvite + } + + createStub(dto: VaultInviteHash): VaultInvite { + const item = this.create(dto) + return item + } +} diff --git a/packages/syncing-server/src/Domain/VaultInvite/Factory/VaultInviteFactoryInterface.ts b/packages/syncing-server/src/Domain/VaultInvite/Factory/VaultInviteFactoryInterface.ts new file mode 100644 index 000000000..c160b752d --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultInvite/Factory/VaultInviteFactoryInterface.ts @@ -0,0 +1,7 @@ +import { VaultInvite } from '../Model/VaultInvite' +import { VaultInviteHash } from './VaultInviteHash' + +export interface VaultInviteFactoryInterface { + create(dto: VaultInviteHash): VaultInvite + createStub(dto: VaultInviteHash): VaultInvite +} diff --git a/packages/syncing-server/src/Domain/VaultInvite/Factory/VaultInviteHash.ts b/packages/syncing-server/src/Domain/VaultInvite/Factory/VaultInviteHash.ts new file mode 100644 index 000000000..27e3cec23 --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultInvite/Factory/VaultInviteHash.ts @@ -0,0 +1,15 @@ +import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission' +import { VaultInviteType } from '../Model/VaultInviteType' + +export type VaultInviteHash = { + uuid: string + user_uuid: string + vault_uuid: string + inviter_uuid: string + inviter_public_key: string + encrypted_vault_data: string + invite_type: VaultInviteType + permissions: VaultUserPermission + created_at_timestamp?: number + updated_at_timestamp?: number +} diff --git a/packages/syncing-server/src/Domain/GroupInvite/Model/GroupInvite.ts b/packages/syncing-server/src/Domain/VaultInvite/Model/VaultInvite.ts similarity index 65% rename from packages/syncing-server/src/Domain/GroupInvite/Model/GroupInvite.ts rename to packages/syncing-server/src/Domain/VaultInvite/Model/VaultInvite.ts index f10262bb4..005ed7729 100644 --- a/packages/syncing-server/src/Domain/GroupInvite/Model/GroupInvite.ts +++ b/packages/syncing-server/src/Domain/VaultInvite/Model/VaultInvite.ts @@ -1,17 +1,17 @@ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm' -import { GroupInviteType } from './GroupInviteType' -import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission' +import { VaultInviteType } from './VaultInviteType' +import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission' -@Entity({ name: 'group_invites' }) -export class GroupInvite { +@Entity({ name: 'vault_invites' }) +export class VaultInvite { @PrimaryGeneratedColumn('uuid') declare uuid: string @Column({ - name: 'group_uuid', + name: 'vault_uuid', length: 36, }) - declare groupUuid: string + declare vaultUuid: string @Column({ name: 'user_uuid', @@ -27,7 +27,7 @@ export class GroupInvite { @Column({ name: 'invite_type', }) - declare inviteType: GroupInviteType + declare inviteType: VaultInviteType @Column({ name: 'inviter_public_key', @@ -35,14 +35,14 @@ export class GroupInvite { declare inviterPublicKey: string @Column({ - name: 'encrypted_group_data', + name: 'encrypted_vault_data', }) - declare encryptedGroupData: string + declare encryptedVaultData: string @Column({ name: 'permissions', }) - declare permissions: GroupUserPermission + declare permissions: VaultUserPermission @Column({ name: 'created_at_timestamp', diff --git a/packages/syncing-server/src/Domain/GroupInvite/Model/GroupInviteType.ts b/packages/syncing-server/src/Domain/VaultInvite/Model/VaultInviteType.ts similarity index 61% rename from packages/syncing-server/src/Domain/GroupInvite/Model/GroupInviteType.ts rename to packages/syncing-server/src/Domain/VaultInvite/Model/VaultInviteType.ts index 17924faa6..23d756c89 100644 --- a/packages/syncing-server/src/Domain/GroupInvite/Model/GroupInviteType.ts +++ b/packages/syncing-server/src/Domain/VaultInvite/Model/VaultInviteType.ts @@ -1,4 +1,4 @@ -export enum GroupInviteType { +export enum VaultInviteType { Join = 'join', KeyChange = 'key-change', } diff --git a/packages/syncing-server/src/Domain/VaultInvite/Repository/TypeORMVaultInviteRepository.ts b/packages/syncing-server/src/Domain/VaultInvite/Repository/TypeORMVaultInviteRepository.ts new file mode 100644 index 000000000..2d231aea7 --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultInvite/Repository/TypeORMVaultInviteRepository.ts @@ -0,0 +1,71 @@ +import { VaultInvite } from '../Model/VaultInvite' +import { Repository, SelectQueryBuilder } from 'typeorm' +import { + VaultInviteFindAllForVault, + VaultInviteFindAllForUserQuery, + VaultInviteRepositoryInterface, +} from './VaultInviteRepositoryInterface' + +export class TypeORMVaultInviteRepository implements VaultInviteRepositoryInterface { + constructor(private ormRepository: Repository) {} + + async create(vaultInvite: VaultInvite): Promise { + return this.ormRepository.save(vaultInvite) + } + + async save(vaultInvite: VaultInvite): Promise { + return this.ormRepository.save(vaultInvite) + } + + findByUuid(uuid: string): Promise { + return this.ormRepository + .createQueryBuilder('vault_invite') + .where('vault_invite.uuid = :uuid', { + uuid, + }) + .getOne() + } + + findByUserUuidAndVaultUuid(userUuid: string, vaultUuid: string): Promise { + return this.ormRepository + .createQueryBuilder('vault_invite') + .where('vault_invite.user_uuid = :userUuid', { userUuid }) + .andWhere('vault_invite.vault_uuid = :vaultUuid', { vaultUuid }) + .getOne() + } + + async remove(vaultInvite: VaultInvite): Promise { + return this.ormRepository.remove(vaultInvite) + } + + findAllForVault(query: VaultInviteFindAllForVault): Promise { + return this.ormRepository + .createQueryBuilder('vault_invite') + .where('vault_invite.vault_uuid = :vaultUuid', { vaultUuid: query.vaultUuid }) + .getMany() + } + + async findAll(query: VaultInviteFindAllForUserQuery): Promise { + return this.createFindAllQueryBuilder(query).getMany() + } + + private createFindAllQueryBuilder(query: VaultInviteFindAllForUserQuery): SelectQueryBuilder { + const queryBuilder = this.ormRepository.createQueryBuilder('vault_invite') + + if (query.userUuid) { + queryBuilder.where('vault_invite.user_uuid = :userUuid', { userUuid: query.userUuid }) + } else if (query.inviterUuid) { + queryBuilder.where('vault_invite.inviter_uuid = :inviterUuid', { + inviterUuid: query.inviterUuid, + }) + } + + if (query.lastSyncTime) { + queryBuilder.andWhere('vault_invite.updated_at_timestamp > :lastSyncTime', { + lastSyncTime: query.lastSyncTime, + }) + } + + return queryBuilder + } +} diff --git a/packages/syncing-server/src/Domain/VaultInvite/Repository/VaultInviteRepositoryInterface.ts b/packages/syncing-server/src/Domain/VaultInvite/Repository/VaultInviteRepositoryInterface.ts new file mode 100644 index 000000000..d96f5c9b1 --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultInvite/Repository/VaultInviteRepositoryInterface.ts @@ -0,0 +1,25 @@ +import { VaultInvite } from '../Model/VaultInvite' + +export type VaultInviteQuery = { + userUuid: string +} + +export type VaultInviteFindAllForUserQuery = { + userUuid?: string + lastSyncTime?: number + inviterUuid?: string +} + +export type VaultInviteFindAllForVault = { + vaultUuid: string +} + +export interface VaultInviteRepositoryInterface { + findByUuid(vaultInviteUuid: string): Promise + create(vault: VaultInvite): Promise + save(vaultInvite: VaultInvite): Promise + remove(vault: VaultInvite): Promise + findAll(query: VaultInviteFindAllForUserQuery): Promise + findAllForVault(query: VaultInviteFindAllForVault): Promise + findByUserUuidAndVaultUuid(userUuid: string, vaultUuid: string): Promise +} diff --git a/packages/syncing-server/src/Domain/VaultInvite/Service/CreateInviteDTO.ts b/packages/syncing-server/src/Domain/VaultInvite/Service/CreateInviteDTO.ts new file mode 100644 index 000000000..5515e31b3 --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultInvite/Service/CreateInviteDTO.ts @@ -0,0 +1,12 @@ +import { VaultInviteType } from '../Model/VaultInviteType' +import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission' + +export type CreateInviteDTO = { + originatorUuid: string + vaultUuid: string + userUuid: string + inviterPublicKey: string + encryptedVaultData: string + inviteType: VaultInviteType + permissions: VaultUserPermission +} diff --git a/packages/syncing-server/src/Domain/GroupInvite/Service/GetUserGroupInvitesDTO.ts b/packages/syncing-server/src/Domain/VaultInvite/Service/GetUserVaultInvitesDTO.ts similarity index 51% rename from packages/syncing-server/src/Domain/GroupInvite/Service/GetUserGroupInvitesDTO.ts rename to packages/syncing-server/src/Domain/VaultInvite/Service/GetUserVaultInvitesDTO.ts index 2b6223381..b785d0706 100644 --- a/packages/syncing-server/src/Domain/GroupInvite/Service/GetUserGroupInvitesDTO.ts +++ b/packages/syncing-server/src/Domain/VaultInvite/Service/GetUserVaultInvitesDTO.ts @@ -1,4 +1,4 @@ -export interface GetUserGroupInvitesDTO { +export interface GetUserVaultInvitesDTO { userUuid: string lastSyncTime?: number } diff --git a/packages/syncing-server/src/Domain/VaultInvite/Service/UpdateInviteDTO.ts b/packages/syncing-server/src/Domain/VaultInvite/Service/UpdateInviteDTO.ts new file mode 100644 index 000000000..bf65db8f2 --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultInvite/Service/UpdateInviteDTO.ts @@ -0,0 +1,9 @@ +import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission' + +export type UpdateInviteDTO = { + originatorUuid: string + inviteUuid: string + inviterPublicKey: string + encryptedVaultData: string + permissions?: VaultUserPermission +} diff --git a/packages/syncing-server/src/Domain/VaultInvite/Service/VaultInviteService.ts b/packages/syncing-server/src/Domain/VaultInvite/Service/VaultInviteService.ts new file mode 100644 index 000000000..b194d99e6 --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultInvite/Service/VaultInviteService.ts @@ -0,0 +1,172 @@ +import { TimerInterface } from '@standardnotes/time' +import { VaultInvite } from '../Model/VaultInvite' +import { VaultInviteFactoryInterface } from '../Factory/VaultInviteFactoryInterface' +import { v4 as uuidv4 } from 'uuid' +import { VaultInviteRepositoryInterface } from '../Repository/VaultInviteRepositoryInterface' +import { VaultInviteServiceInterface } from './VaultInviteServiceInterface' +import { GetUserVaultInvitesDTO } from './GetUserVaultInvitesDTO' +import { VaultsRepositoryInterface } from '../../Vault/Repository/VaultRepositoryInterface' +import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface' +import { VaultInviteType } from '../Model/VaultInviteType' +import { CreateInviteDTO } from './CreateInviteDTO' +import { UpdateInviteDTO } from './UpdateInviteDTO' + +export class VaultInviteService implements VaultInviteServiceInterface { + constructor( + private vaultRepository: VaultsRepositoryInterface, + private vaultInviteRepository: VaultInviteRepositoryInterface, + private vaultInviteFactory: VaultInviteFactoryInterface, + private vaultUserService: VaultUserServiceInterface, + private timer: TimerInterface, + ) {} + + async createInvite(dto: CreateInviteDTO): Promise { + const vault = await this.vaultRepository.findByUuid(dto.vaultUuid) + if (!vault || vault.userUuid !== dto.originatorUuid) { + return null + } + + const timestamp = this.timer.getTimestampInMicroseconds() + + const vaultInvite = this.vaultInviteFactory.create({ + uuid: uuidv4(), + user_uuid: dto.userUuid, + vault_uuid: dto.vaultUuid, + inviter_uuid: dto.originatorUuid, + inviter_public_key: dto.inviterPublicKey, + encrypted_vault_data: dto.encryptedVaultData, + invite_type: dto.inviteType, + permissions: dto.permissions, + created_at_timestamp: timestamp, + updated_at_timestamp: timestamp, + }) + + return this.vaultInviteRepository.create(vaultInvite) + } + + async updateInvite(dto: UpdateInviteDTO): Promise { + const vaultInvite = await this.vaultInviteRepository.findByUuid(dto.inviteUuid) + if (!vaultInvite || vaultInvite.inviterUuid !== dto.originatorUuid) { + return null + } + + vaultInvite.inviterPublicKey = dto.inviterPublicKey + vaultInvite.encryptedVaultData = dto.encryptedVaultData + if (dto.permissions) { + vaultInvite.permissions = dto.permissions + } + vaultInvite.updatedAtTimestamp = this.timer.getTimestampInMicroseconds() + + return this.vaultInviteRepository.save(vaultInvite) + } + + getInvitesForUser(dto: GetUserVaultInvitesDTO): Promise { + return this.vaultInviteRepository.findAll(dto) + } + + getOutboundInvitesForUser(dto: { userUuid: string }): Promise { + return this.vaultInviteRepository.findAll({ + inviterUuid: dto.userUuid, + }) + } + + async deleteAllInboundInvites(dto: { userUuid: string }): Promise { + const inboundInvites = await this.vaultInviteRepository.findAll({ + userUuid: dto.userUuid, + }) + + for (const invite of inboundInvites) { + await this.vaultInviteRepository.remove(invite) + } + } + + async getInvitesForVault(dto: { vaultUuid: string; originatorUuid: string }): Promise { + const vault = await this.vaultRepository.findByUuid(dto.vaultUuid) + if (!vault) { + return undefined + } + + const isUserVaultAdmin = vault && vault.userUuid === dto.originatorUuid + if (!isUserVaultAdmin) { + return undefined + } + + const users = await this.vaultInviteRepository.findAllForVault({ vaultUuid: dto.vaultUuid }) + + return users + } + + async acceptInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise { + const invite = await this.vaultInviteRepository.findByUuid(dto.inviteUuid) + if (!invite) { + return false + } + + const isAuthorized = invite.userUuid === dto.originatorUuid + if (!isAuthorized) { + return false + } + + if (invite.inviteType === VaultInviteType.Join) { + const addedUser = await this.vaultUserService.addVaultUser({ + userUuid: dto.originatorUuid, + vaultUuid: invite.vaultUuid, + permissions: invite.permissions, + }) + + if (!addedUser) { + return false + } + } + + await this.vaultInviteRepository.remove(invite) + + return true + } + + async declineInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise { + const invite = await this.vaultInviteRepository.findByUuid(dto.inviteUuid) + if (!invite) { + return false + } + + const isAuthorized = invite.userUuid === dto.originatorUuid + if (!isAuthorized) { + return false + } + + await this.vaultInviteRepository.remove(invite) + + return true + } + + async deleteInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise { + const invite = await this.vaultInviteRepository.findByUuid(dto.inviteUuid) + if (!invite) { + return false + } + + const isAuthorized = invite.inviterUuid === dto.originatorUuid || invite.userUuid === dto.originatorUuid + if (!isAuthorized) { + return false + } + + await this.vaultInviteRepository.remove(invite) + + return true + } + + async deleteAllInvitesForVault(dto: { originatorUuid: string; vaultUuid: string }): Promise { + const vault = await this.vaultRepository.findByUuid(dto.vaultUuid) + if (!vault || vault.userUuid !== dto.originatorUuid) { + return false + } + + const vaultInvites = await this.vaultInviteRepository.findAllForVault({ vaultUuid: dto.vaultUuid }) + for (const vaultInvite of vaultInvites) { + await this.vaultInviteRepository.remove(vaultInvite) + } + + return true + } +} diff --git a/packages/syncing-server/src/Domain/VaultInvite/Service/VaultInviteServiceInterface.ts b/packages/syncing-server/src/Domain/VaultInvite/Service/VaultInviteServiceInterface.ts new file mode 100644 index 000000000..f44a56a7b --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultInvite/Service/VaultInviteServiceInterface.ts @@ -0,0 +1,26 @@ +import { VaultInvite } from '../Model/VaultInvite' +import { CreateInviteDTO } from './CreateInviteDTO' +import { GetUserVaultInvitesDTO } from './GetUserVaultInvitesDTO' +import { UpdateInviteDTO } from './UpdateInviteDTO' + +export interface VaultInviteServiceInterface { + createInvite(dto: CreateInviteDTO): Promise + + updateInvite(dto: UpdateInviteDTO): Promise + + getInvitesForUser(dto: GetUserVaultInvitesDTO): Promise + + getInvitesForVault(dto: { vaultUuid: string; originatorUuid: string }): Promise + + getOutboundInvitesForUser(dto: { userUuid: string }): Promise + + acceptInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise + + declineInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise + + deleteInvite(dto: { originatorUuid: string; vaultUuid: string; inviteUuid: string }): Promise + + deleteAllInvitesForVault(dto: { originatorUuid: string; vaultUuid: string }): Promise + + deleteAllInboundInvites(dto: { userUuid: string }): Promise +} diff --git a/packages/syncing-server/src/Domain/VaultUser/Factory/VaultUserFactory.ts b/packages/syncing-server/src/Domain/VaultUser/Factory/VaultUserFactory.ts new file mode 100644 index 000000000..226e962ba --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultUser/Factory/VaultUserFactory.ts @@ -0,0 +1,31 @@ +import { TimerInterface } from '@standardnotes/time' +import { VaultUserFactoryInterface } from './VaultUserFactoryInterface' +import { VaultUserHash } from './VaultUserHash' +import { VaultUser } from '../Model/VaultUser' + +export class VaultUserFactory implements VaultUserFactoryInterface { + constructor(private timer: TimerInterface) {} + + create(dto: VaultUserHash): VaultUser { + const newVaultUser = new VaultUser() + newVaultUser.uuid = dto.uuid + newVaultUser.userUuid = dto.user_uuid + newVaultUser.vaultUuid = dto.vault_uuid + newVaultUser.permissions = dto.permissions + + const now = this.timer.getTimestampInMicroseconds() + newVaultUser.updatedAtTimestamp = now + newVaultUser.createdAtTimestamp = now + + if (dto.created_at_timestamp) { + newVaultUser.createdAtTimestamp = dto.created_at_timestamp + } + + return newVaultUser + } + + createStub(dto: VaultUserHash): VaultUser { + const item = this.create(dto) + return item + } +} diff --git a/packages/syncing-server/src/Domain/VaultUser/Factory/VaultUserFactoryInterface.ts b/packages/syncing-server/src/Domain/VaultUser/Factory/VaultUserFactoryInterface.ts new file mode 100644 index 000000000..823830403 --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultUser/Factory/VaultUserFactoryInterface.ts @@ -0,0 +1,7 @@ +import { VaultUser } from '../Model/VaultUser' +import { VaultUserHash } from './VaultUserHash' + +export interface VaultUserFactoryInterface { + create(dto: VaultUserHash): VaultUser + createStub(dto: VaultUserHash): VaultUser +} diff --git a/packages/syncing-server/src/Domain/VaultUser/Factory/VaultUserHash.ts b/packages/syncing-server/src/Domain/VaultUser/Factory/VaultUserHash.ts new file mode 100644 index 000000000..d7988b332 --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultUser/Factory/VaultUserHash.ts @@ -0,0 +1,10 @@ +import { VaultUserPermission } from '../Model/VaultUserPermission' + +export type VaultUserHash = { + uuid: string + user_uuid: string + vault_uuid: string + permissions: VaultUserPermission + created_at_timestamp?: number + updated_at_timestamp?: number +} diff --git a/packages/syncing-server/src/Domain/GroupUser/Model/GroupUser.ts b/packages/syncing-server/src/Domain/VaultUser/Model/VaultUser.ts similarity index 70% rename from packages/syncing-server/src/Domain/GroupUser/Model/GroupUser.ts rename to packages/syncing-server/src/Domain/VaultUser/Model/VaultUser.ts index 9b384c059..17f894da8 100644 --- a/packages/syncing-server/src/Domain/GroupUser/Model/GroupUser.ts +++ b/packages/syncing-server/src/Domain/VaultUser/Model/VaultUser.ts @@ -1,16 +1,16 @@ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm' -import { GroupUserPermission } from './GroupUserPermission' +import { VaultUserPermission } from './VaultUserPermission' -@Entity({ name: 'group_users' }) -export class GroupUser { +@Entity({ name: 'vault_users' }) +export class VaultUser { @PrimaryGeneratedColumn('uuid') declare uuid: string @Column({ - name: 'group_uuid', + name: 'vault_uuid', length: 36, }) - declare groupUuid: string + declare vaultUuid: string @Column({ name: 'user_uuid', @@ -21,7 +21,7 @@ export class GroupUser { @Column({ name: 'permissions', }) - declare permissions: GroupUserPermission + declare permissions: VaultUserPermission @Column({ name: 'created_at_timestamp', diff --git a/packages/syncing-server/src/Domain/VaultUser/Model/VaultUserPermission.ts b/packages/syncing-server/src/Domain/VaultUser/Model/VaultUserPermission.ts new file mode 100644 index 000000000..9d75368cd --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultUser/Model/VaultUserPermission.ts @@ -0,0 +1 @@ +export type VaultUserPermission = 'read' | 'write' | 'admin' diff --git a/packages/syncing-server/src/Domain/VaultUser/Repository/TypeORMVaultUserRepository.ts b/packages/syncing-server/src/Domain/VaultUser/Repository/TypeORMVaultUserRepository.ts new file mode 100644 index 000000000..3cb3c44a1 --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultUser/Repository/TypeORMVaultUserRepository.ts @@ -0,0 +1,65 @@ +import { VaultUser } from '../Model/VaultUser' +import { Repository, SelectQueryBuilder } from 'typeorm' +import { + VaultUserFindAllForVault, + VaultUserFindAllForUserQuery, + VaultUserRepositoryInterface, +} from './VaultUserRepositoryInterface' + +export class TypeORMVaultUserRepository implements VaultUserRepositoryInterface { + constructor(private ormRepository: Repository) {} + + async create(vaultUser: VaultUser): Promise { + return this.ormRepository.save(vaultUser) + } + + async save(vaultUser: VaultUser): Promise { + return this.ormRepository.save(vaultUser) + } + + findByUuid(uuid: string): Promise { + return this.ormRepository + .createQueryBuilder('vault_user') + .where('vault_user.uuid = :uuid', { + uuid, + }) + .getOne() + } + + findByUserUuidAndVaultUuid(userUuid: string, vaultUuid: string): Promise { + return this.ormRepository + .createQueryBuilder('vault_user') + .where('vault_user.user_uuid = :userUuid', { userUuid }) + .andWhere('vault_user.vault_uuid = :vaultUuid', { vaultUuid }) + .getOne() + } + + async remove(vaultUser: VaultUser): Promise { + return this.ormRepository.remove(vaultUser) + } + + findAllForVault(query: VaultUserFindAllForVault): Promise { + return this.ormRepository + .createQueryBuilder('vault_user') + .where('vault_user.vault_uuid = :vaultUuid', { vaultUuid: query.vaultUuid }) + .getMany() + } + + async findAllForUser(query: VaultUserFindAllForUserQuery): Promise { + return this.createFindAllQueryBuilder(query).getMany() + } + + private createFindAllQueryBuilder(query: VaultUserFindAllForUserQuery): SelectQueryBuilder { + const queryBuilder = this.ormRepository.createQueryBuilder('vault_user') + + queryBuilder.where('vault_user.user_uuid = :userUuid', { userUuid: query.userUuid }) + + if (query.lastSyncTime) { + queryBuilder.andWhere('vault_user.updated_at_timestamp > :lastSyncTime', { + lastSyncTime: query.lastSyncTime, + }) + } + + return queryBuilder + } +} diff --git a/packages/syncing-server/src/Domain/VaultUser/Repository/VaultUserRepositoryInterface.ts b/packages/syncing-server/src/Domain/VaultUser/Repository/VaultUserRepositoryInterface.ts new file mode 100644 index 000000000..7e60e16e9 --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultUser/Repository/VaultUserRepositoryInterface.ts @@ -0,0 +1,24 @@ +import { VaultUser } from '../Model/VaultUser' + +export type VaultUserQuery = { + userUuid: string +} + +export type VaultUserFindAllForUserQuery = { + userUuid: string + lastSyncTime?: number +} + +export type VaultUserFindAllForVault = { + vaultUuid: string +} + +export interface VaultUserRepositoryInterface { + findByUuid(vaultUserUuid: string): Promise + create(vault: VaultUser): Promise + save(vaultUser: VaultUser): Promise + remove(vault: VaultUser): Promise + findAllForUser(query: VaultUserFindAllForUserQuery): Promise + findAllForVault(query: VaultUserFindAllForVault): Promise + findByUserUuidAndVaultUuid(userUuid: string, vaultUuid: string): Promise +} diff --git a/packages/syncing-server/src/Domain/GroupUser/Service/GetGroupUsersDTO.ts b/packages/syncing-server/src/Domain/VaultUser/Service/GetVaultUsersDTO.ts similarity index 55% rename from packages/syncing-server/src/Domain/GroupUser/Service/GetGroupUsersDTO.ts rename to packages/syncing-server/src/Domain/VaultUser/Service/GetVaultUsersDTO.ts index 59462c7c0..078b4f494 100644 --- a/packages/syncing-server/src/Domain/GroupUser/Service/GetGroupUsersDTO.ts +++ b/packages/syncing-server/src/Domain/VaultUser/Service/GetVaultUsersDTO.ts @@ -1,4 +1,4 @@ -export interface GetGroupUsersDTO { +export interface GetVaultUsersDTO { userUuid: string lastSyncTime?: number } diff --git a/packages/syncing-server/src/Domain/VaultUser/Service/VaultUserService.ts b/packages/syncing-server/src/Domain/VaultUser/Service/VaultUserService.ts new file mode 100644 index 000000000..0bad0c7d0 --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultUser/Service/VaultUserService.ts @@ -0,0 +1,111 @@ +import { TimerInterface } from '@standardnotes/time' +import { VaultUser } from '../Model/VaultUser' +import { VaultUserFactoryInterface } from '../Factory/VaultUserFactoryInterface' +import { v4 as uuidv4 } from 'uuid' +import { VaultUserRepositoryInterface } from '../Repository/VaultUserRepositoryInterface' +import { VaultUserServiceInterface } from './VaultUserServiceInterface' +import { GetVaultUsersDTO } from './GetVaultUsersDTO' +import { VaultsRepositoryInterface } from '../../Vault/Repository/VaultRepositoryInterface' +import { VaultUserPermission } from '../Model/VaultUserPermission' + +export class VaultUserService implements VaultUserServiceInterface { + constructor( + private vaultRepository: VaultsRepositoryInterface, + private vaultUserRepository: VaultUserRepositoryInterface, + private vaultUserFactory: VaultUserFactoryInterface, + private timer: TimerInterface, + ) {} + + async addVaultUser(dto: { + vaultUuid: string + userUuid: string + permissions: VaultUserPermission + }): Promise { + const vault = await this.vaultRepository.findByUuid(dto.vaultUuid) + if (!vault) { + return null + } + + const timestamp = this.timer.getTimestampInMicroseconds() + const vaultUser = this.vaultUserFactory.create({ + uuid: uuidv4(), + user_uuid: dto.userUuid, + vault_uuid: dto.vaultUuid, + permissions: dto.permissions, + created_at_timestamp: timestamp, + updated_at_timestamp: timestamp, + }) + + return this.vaultUserRepository.create(vaultUser) + } + + getAllVaultUsersForUser(dto: GetVaultUsersDTO): Promise { + return this.vaultUserRepository.findAllForUser(dto) + } + + getUserForVault(dto: { userUuid: string; vaultUuid: string }): Promise { + return this.vaultUserRepository.findByUserUuidAndVaultUuid(dto.userUuid, dto.vaultUuid) + } + + async getVaultUsersForVault(dto: { + vaultUuid: string + originatorUuid: string + }): Promise<{ users: VaultUser[]; isAdmin: boolean } | undefined> { + const vault = await this.vaultRepository.findByUuid(dto.vaultUuid) + if (!vault) { + return undefined + } + + const isUserVaultAdmin = vault && vault.userUuid === dto.originatorUuid + const doesUserBelongToVault = await this.vaultUserRepository.findByUserUuidAndVaultUuid( + dto.originatorUuid, + dto.vaultUuid, + ) + + if (!isUserVaultAdmin && !doesUserBelongToVault) { + return undefined + } + + const users = await this.vaultUserRepository.findAllForVault({ vaultUuid: dto.vaultUuid }) + + return { + users, + isAdmin: isUserVaultAdmin, + } + } + + async deleteVaultUser(dto: { originatorUuid: string; vaultUuid: string; userUuid: string }): Promise { + const vault = await this.vaultRepository.findByUuid(dto.vaultUuid) + if (!vault) { + return false + } + + const isAuthorized = vault.userUuid === dto.originatorUuid || dto.userUuid === dto.originatorUuid + if (!isAuthorized) { + return false + } + + const vaultUser = await this.vaultUserRepository.findByUserUuidAndVaultUuid(dto.userUuid, dto.vaultUuid) + if (!vaultUser) { + return false + } + + await this.vaultUserRepository.remove(vaultUser) + + return true + } + + async deleteAllVaultUsersForVault(dto: { originatorUuid: string; vaultUuid: string }): Promise { + const vault = await this.vaultRepository.findByUuid(dto.vaultUuid) + if (!vault || vault.userUuid !== dto.originatorUuid) { + return false + } + + const vaultUsers = await this.vaultUserRepository.findAllForVault({ vaultUuid: dto.vaultUuid }) + for (const vaultUser of vaultUsers) { + await this.vaultUserRepository.remove(vaultUser) + } + + return true + } +} diff --git a/packages/syncing-server/src/Domain/VaultUser/Service/VaultUserServiceInterface.ts b/packages/syncing-server/src/Domain/VaultUser/Service/VaultUserServiceInterface.ts new file mode 100644 index 000000000..b7993d825 --- /dev/null +++ b/packages/syncing-server/src/Domain/VaultUser/Service/VaultUserServiceInterface.ts @@ -0,0 +1,24 @@ +import { VaultUser } from '../Model/VaultUser' +import { VaultUserPermission } from '../Model/VaultUserPermission' +import { GetVaultUsersDTO } from './GetVaultUsersDTO' + +export interface VaultUserServiceInterface { + addVaultUser(dto: { + vaultUuid: string + userUuid: string + permissions: VaultUserPermission + }): Promise + + getAllVaultUsersForUser(dto: GetVaultUsersDTO): Promise + + getUserForVault(dto: { userUuid: string; vaultUuid: string }): Promise + + getVaultUsersForVault(dto: { + vaultUuid: string + originatorUuid: string + }): Promise<{ users: VaultUser[]; isAdmin: boolean } | undefined> + + deleteVaultUser(dto: { originatorUuid: string; vaultUuid: string; userUuid: string }): Promise + + deleteAllVaultUsersForVault(dto: { originatorUuid: string; vaultUuid: string }): Promise +} diff --git a/packages/syncing-server/src/Infra/TypeORM/TypeORMItemRepository.ts b/packages/syncing-server/src/Infra/TypeORM/TypeORMItemRepository.ts index 14604a11e..4c516b48a 100644 --- a/packages/syncing-server/src/Infra/TypeORM/TypeORMItemRepository.ts +++ b/packages/syncing-server/src/Infra/TypeORM/TypeORMItemRepository.ts @@ -131,17 +131,17 @@ export class TypeORMItemRepository implements ItemRepositoryInterface { queryBuilder.orderBy(`item.${query.sortBy}`, query.sortOrder) } - if (query.includeGroupUuids != undefined && query.includeGroupUuids.length > 0) { + if (query.includeVaultUuids != undefined && query.includeVaultUuids.length > 0) { queryBuilder.where( new Brackets((qb) => { - qb.where('item.group_uuid IN (:...groupUuids)', { groupUuids: query.includeGroupUuids }) + qb.where('item.vault_uuid IN (:...vaultUuids)', { vaultUuids: query.includeVaultUuids }) if (query.userUuid) { qb.orWhere('item.user_uuid = :userUuid', { userUuid: query.userUuid }) } }), ) - } else if (query.exclusiveGroupUuids != undefined && query.exclusiveGroupUuids.length > 0) { - queryBuilder.andWhere('item.group_uuid IN (:...groupUuids)', { groupUuids: query.exclusiveGroupUuids }) + } else if (query.exclusiveVaultUuids != undefined && query.exclusiveVaultUuids.length > 0) { + queryBuilder.andWhere('item.vault_uuid IN (:...vaultUuids)', { vaultUuids: query.exclusiveVaultUuids }) } else if (query.userUuid !== undefined) { queryBuilder.where('item.user_uuid = :userUuid', { userUuid: query.userUuid }) } diff --git a/packages/syncing-server/src/Projection/GroupInviteProjection.ts b/packages/syncing-server/src/Projection/GroupInviteProjection.ts deleted file mode 100644 index d7260185e..000000000 --- a/packages/syncing-server/src/Projection/GroupInviteProjection.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { GroupInviteType } from '../Domain/GroupInvite/Model/GroupInviteType' - -export type GroupInviteProjection = { - uuid: string - group_uuid: string - user_uuid: string - inviter_uuid: string - inviter_public_key: string - encrypted_group_data: string - invite_type: GroupInviteType - permissions: string - created_at_timestamp: number - updated_at_timestamp: number -} diff --git a/packages/syncing-server/src/Projection/GroupInviteProjector.ts b/packages/syncing-server/src/Projection/GroupInviteProjector.ts deleted file mode 100644 index 1d4d4e69d..000000000 --- a/packages/syncing-server/src/Projection/GroupInviteProjector.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { GroupInvite } from '../Domain/GroupInvite/Model/GroupInvite' -import { ProjectorInterface } from './ProjectorInterface' -import { GroupInviteProjection } from './GroupInviteProjection' - -export class GroupInviteProjector implements ProjectorInterface { - projectSimple(_groupInvite: GroupInvite): GroupInviteProjection { - throw Error('not implemented') - } - - projectCustom(_projectionType: string, groupInvite: GroupInvite): GroupInviteProjection { - const fullProjection = this.projectFull(groupInvite) - - return { - ...fullProjection, - user_uuid: groupInvite.userUuid, - } - } - - projectFull(groupInvite: GroupInvite): GroupInviteProjection { - return { - uuid: groupInvite.uuid, - group_uuid: groupInvite.groupUuid, - user_uuid: groupInvite.userUuid, - inviter_uuid: groupInvite.inviterUuid, - inviter_public_key: groupInvite.inviterPublicKey, - encrypted_group_data: groupInvite.encryptedGroupData, - invite_type: groupInvite.inviteType, - permissions: groupInvite.permissions, - created_at_timestamp: groupInvite.createdAtTimestamp, - updated_at_timestamp: groupInvite.updatedAtTimestamp, - } - } -} diff --git a/packages/syncing-server/src/Projection/GroupProjector.ts b/packages/syncing-server/src/Projection/GroupProjector.ts deleted file mode 100644 index b7c7adb63..000000000 --- a/packages/syncing-server/src/Projection/GroupProjector.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Group } from './../Domain/Group/Model/Group' -import { ProjectorInterface } from './ProjectorInterface' -import { GroupProjection } from './GroupProjection' - -export class GroupProjector implements ProjectorInterface { - projectSimple(_group: Group): GroupProjection { - throw Error('not implemented') - } - - projectCustom(_projectionType: string, group: Group): GroupProjection { - const fullProjection = this.projectFull(group) - - return { - ...fullProjection, - user_uuid: group.userUuid, - } - } - - projectFull(group: Group): GroupProjection { - return { - uuid: group.uuid, - user_uuid: group.userUuid, - specified_items_key_uuid: group.specifiedItemsKeyUuid, - group_key_timestamp: group.groupKeyTimestamp, - created_at_timestamp: group.createdAtTimestamp, - updated_at_timestamp: group.updatedAtTimestamp, - } - } -} diff --git a/packages/syncing-server/src/Projection/GroupUserProjector.ts b/packages/syncing-server/src/Projection/GroupUserProjector.ts deleted file mode 100644 index 7f557cff4..000000000 --- a/packages/syncing-server/src/Projection/GroupUserProjector.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { GroupUser } from '../Domain/GroupUser/Model/GroupUser' -import { ProjectorInterface } from './ProjectorInterface' -import { GroupUserProjection, GroupUserListingProjection } from './GroupUserProjection' - -export class GroupUserProjector implements ProjectorInterface { - projectSimple(_groupUser: GroupUser): GroupUserProjection { - throw Error('not implemented') - } - - projectCustom(_projectionType: string, groupUser: GroupUser): GroupUserProjection { - const fullProjection = this.projectFull(groupUser) - - return { - ...fullProjection, - user_uuid: groupUser.userUuid, - } - } - - projectAsDisplayableUserForOtherGroupMembers( - groupUser: GroupUser, - isRequesterGroupAdmin: boolean, - ): GroupUserListingProjection { - return { - uuid: groupUser.uuid, - group_uuid: groupUser.groupUuid, - user_uuid: groupUser.userUuid, - permissions: isRequesterGroupAdmin ? groupUser.permissions : undefined, - created_at_timestamp: groupUser.createdAtTimestamp, - updated_at_timestamp: groupUser.updatedAtTimestamp, - } - } - - projectFull(groupUser: GroupUser): GroupUserProjection { - return { - uuid: groupUser.uuid, - group_uuid: groupUser.groupUuid, - user_uuid: groupUser.userUuid, - permissions: groupUser.permissions, - created_at_timestamp: groupUser.createdAtTimestamp, - updated_at_timestamp: groupUser.updatedAtTimestamp, - } - } -} diff --git a/packages/syncing-server/src/Projection/ItemProjection.ts b/packages/syncing-server/src/Projection/ItemProjection.ts index 11e491af1..ef4433a23 100644 --- a/packages/syncing-server/src/Projection/ItemProjection.ts +++ b/packages/syncing-server/src/Projection/ItemProjection.ts @@ -3,12 +3,12 @@ export type ItemProjection = { items_key_id: string | null duplicate_of: string | null enc_item_key: string | null - group_uuid: string | null + vault_uuid: string | null content: string | null content_type: string auth_hash: string | null deleted: boolean - user_uuid: string + user_uuid: string | null last_edited_by_uuid: string | null created_at: string created_at_timestamp: number diff --git a/packages/syncing-server/src/Projection/ItemProjectionWithUser.ts b/packages/syncing-server/src/Projection/ItemProjectionWithUser.ts index 66ba0095e..3ec3e44c7 100644 --- a/packages/syncing-server/src/Projection/ItemProjectionWithUser.ts +++ b/packages/syncing-server/src/Projection/ItemProjectionWithUser.ts @@ -1,5 +1,5 @@ import { ItemProjection } from './ItemProjection' export type ItemProjectionWithUser = ItemProjection & { - user_uuid: string + user_uuid: string | null } diff --git a/packages/syncing-server/src/Projection/ItemProjector.ts b/packages/syncing-server/src/Projection/ItemProjector.ts index d5856b2f7..9c4e899a1 100644 --- a/packages/syncing-server/src/Projection/ItemProjector.ts +++ b/packages/syncing-server/src/Projection/ItemProjector.ts @@ -33,7 +33,7 @@ export class ItemProjector implements ProjectorInterface { updated_at: this.timer.convertMicrosecondsToStringDate(item.updatedAtTimestamp), updated_at_timestamp: item.updatedAtTimestamp, updated_with_session: item.updatedWithSession, - group_uuid: item.groupUuid, + vault_uuid: item.vaultUuid, user_uuid: item.userUuid, last_edited_by_uuid: item.lastEditedByUuid, } diff --git a/packages/syncing-server/src/Projection/SavedItemProjection.ts b/packages/syncing-server/src/Projection/SavedItemProjection.ts index 1e2f5f098..164ecda04 100644 --- a/packages/syncing-server/src/Projection/SavedItemProjection.ts +++ b/packages/syncing-server/src/Projection/SavedItemProjection.ts @@ -8,6 +8,6 @@ export type SavedItemProjection = { created_at_timestamp: number updated_at: string updated_at_timestamp: number - group_uuid: string | null + vault_uuid: string | null last_edited_by_uuid: string | null } diff --git a/packages/syncing-server/src/Projection/SavedItemProjector.ts b/packages/syncing-server/src/Projection/SavedItemProjector.ts index ca3d52f6a..3edd9d861 100644 --- a/packages/syncing-server/src/Projection/SavedItemProjector.ts +++ b/packages/syncing-server/src/Projection/SavedItemProjector.ts @@ -27,7 +27,7 @@ export class SavedItemProjector implements ProjectorInterface { + projectSimple(_vaultInvite: VaultInvite): VaultInviteProjection { + throw Error('not implemented') + } + + projectCustom(_projectionType: string, vaultInvite: VaultInvite): VaultInviteProjection { + const fullProjection = this.projectFull(vaultInvite) + + return { + ...fullProjection, + user_uuid: vaultInvite.userUuid, + } + } + + projectFull(vaultInvite: VaultInvite): VaultInviteProjection { + return { + uuid: vaultInvite.uuid, + vault_uuid: vaultInvite.vaultUuid, + user_uuid: vaultInvite.userUuid, + inviter_uuid: vaultInvite.inviterUuid, + inviter_public_key: vaultInvite.inviterPublicKey, + encrypted_vault_data: vaultInvite.encryptedVaultData, + invite_type: vaultInvite.inviteType, + permissions: vaultInvite.permissions, + created_at_timestamp: vaultInvite.createdAtTimestamp, + updated_at_timestamp: vaultInvite.updatedAtTimestamp, + } + } +} diff --git a/packages/syncing-server/src/Projection/GroupProjection.ts b/packages/syncing-server/src/Projection/VaultProjection.ts similarity index 68% rename from packages/syncing-server/src/Projection/GroupProjection.ts rename to packages/syncing-server/src/Projection/VaultProjection.ts index 1cbc2c92e..7988ae418 100644 --- a/packages/syncing-server/src/Projection/GroupProjection.ts +++ b/packages/syncing-server/src/Projection/VaultProjection.ts @@ -1,8 +1,8 @@ -export type GroupProjection = { +export type VaultProjection = { uuid: string user_uuid: string specified_items_key_uuid: string - group_key_timestamp: number + vault_key_timestamp: number created_at_timestamp: number updated_at_timestamp: number } diff --git a/packages/syncing-server/src/Projection/VaultProjector.ts b/packages/syncing-server/src/Projection/VaultProjector.ts new file mode 100644 index 000000000..c692eab61 --- /dev/null +++ b/packages/syncing-server/src/Projection/VaultProjector.ts @@ -0,0 +1,29 @@ +import { Vault } from '../Domain/Vault/Model/Vault' +import { ProjectorInterface } from './ProjectorInterface' +import { VaultProjection } from './VaultProjection' + +export class VaultProjector implements ProjectorInterface { + projectSimple(_vault: Vault): VaultProjection { + throw Error('not implemented') + } + + projectCustom(_projectionType: string, vault: Vault): VaultProjection { + const fullProjection = this.projectFull(vault) + + return { + ...fullProjection, + user_uuid: vault.userUuid, + } + } + + projectFull(vault: Vault): VaultProjection { + return { + uuid: vault.uuid, + user_uuid: vault.userUuid, + specified_items_key_uuid: vault.specifiedItemsKeyUuid, + vault_key_timestamp: vault.vaultKeyTimestamp, + created_at_timestamp: vault.createdAtTimestamp, + updated_at_timestamp: vault.updatedAtTimestamp, + } + } +} diff --git a/packages/syncing-server/src/Projection/GroupUserProjection.ts b/packages/syncing-server/src/Projection/VaultUserProjection.ts similarity index 66% rename from packages/syncing-server/src/Projection/GroupUserProjection.ts rename to packages/syncing-server/src/Projection/VaultUserProjection.ts index 70f3d6c77..6a9479c58 100644 --- a/packages/syncing-server/src/Projection/GroupUserProjection.ts +++ b/packages/syncing-server/src/Projection/VaultUserProjection.ts @@ -1,15 +1,15 @@ -export type GroupUserProjection = { +export type VaultUserProjection = { uuid: string - group_uuid: string + vault_uuid: string user_uuid: string permissions: string created_at_timestamp: number updated_at_timestamp: number } -export type GroupUserListingProjection = { +export type VaultUserListingProjection = { uuid: string - group_uuid: string + vault_uuid: string user_uuid: string permissions?: string created_at_timestamp: number diff --git a/packages/syncing-server/src/Projection/VaultUserProjector.ts b/packages/syncing-server/src/Projection/VaultUserProjector.ts new file mode 100644 index 000000000..23456bfee --- /dev/null +++ b/packages/syncing-server/src/Projection/VaultUserProjector.ts @@ -0,0 +1,43 @@ +import { VaultUser } from '../Domain/VaultUser/Model/VaultUser' +import { ProjectorInterface } from './ProjectorInterface' +import { VaultUserProjection, VaultUserListingProjection } from './VaultUserProjection' + +export class VaultUserProjector implements ProjectorInterface { + projectSimple(_vaultUser: VaultUser): VaultUserProjection { + throw Error('not implemented') + } + + projectCustom(_projectionType: string, vaultUser: VaultUser): VaultUserProjection { + const fullProjection = this.projectFull(vaultUser) + + return { + ...fullProjection, + user_uuid: vaultUser.userUuid, + } + } + + projectAsDisplayableUserForOtherVaultMembers( + vaultUser: VaultUser, + isRequesterVaultAdmin: boolean, + ): VaultUserListingProjection { + return { + uuid: vaultUser.uuid, + vault_uuid: vaultUser.vaultUuid, + user_uuid: vaultUser.userUuid, + permissions: isRequesterVaultAdmin ? vaultUser.permissions : undefined, + created_at_timestamp: vaultUser.createdAtTimestamp, + updated_at_timestamp: vaultUser.updatedAtTimestamp, + } + } + + projectFull(vaultUser: VaultUser): VaultUserProjection { + return { + uuid: vaultUser.uuid, + vault_uuid: vaultUser.vaultUuid, + user_uuid: vaultUser.userUuid, + permissions: vaultUser.permissions, + created_at_timestamp: vaultUser.createdAtTimestamp, + updated_at_timestamp: vaultUser.updatedAtTimestamp, + } + } +}