refactor: rename vaults to groups

This commit is contained in:
Mo
2023-05-31 11:42:36 -05:00
parent ed48350d3e
commit 9323ca79c6
161 changed files with 2327 additions and 2317 deletions
@@ -1,9 +1,9 @@
import { TypeORMVaultInviteRepository } from '../Domain/VaultInvite/Repository/TypeORMVaultInviteRepository'
import { TypeORMGroupInviteRepository } from '../Domain/GroupInvite/Repository/TypeORMGroupInviteRepository'
import { Contact } from './../Domain/Contact/Model/Contact'
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 { 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 * as winston from 'winston'
import { Container, interfaces } from 'inversify'
@@ -24,23 +24,23 @@ 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 { 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 { 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 { ContactProjection } from '../Projection/ContactProjection'
import { ContactProjector } from '../Projection/ContactProjector'
import { VaultInviteRepositoryInterface } from '../Domain/VaultInvite/Repository/VaultInviteRepositoryInterface'
import { GroupInviteRepositoryInterface } from '../Domain/GroupInvite/Repository/GroupInviteRepositoryInterface'
import { ContactsRepositoryInterface } from '../Domain/Contact/Repository/ContactRepositoryInterface'
import { TypeORMContactRepository } from '../Domain/Contact/Repository/TypeORMContactRepository'
import { RemovedVaultUserRepositoryInterface } from '../Domain/RemovedVaultUser/Repository/RemovedVaultUserRepositoryInterface'
import { TypeORMRemovedVaultUserRepository } from '../Domain/RemovedVaultUser/Repository/TypeORMRemovedVaultUserRepository'
import { RemovedVaultUser } from '../Domain/RemovedVaultUser/Model/RemovedVaultUser'
import { RemovedGroupUserRepositoryInterface } from '../Domain/RemovedGroupUser/Repository/RemovedGroupUserRepositoryInterface'
import { TypeORMRemovedGroupUserRepository } from '../Domain/RemovedGroupUser/Repository/TypeORMRemovedGroupUserRepository'
import { RemovedGroupUser } from '../Domain/RemovedGroupUser/Model/RemovedGroupUser'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -99,23 +99,23 @@ export class CommonContainerConfigLoader {
container.bind<ItemRepositoryInterface>(TYPES.ItemRepository).toDynamicValue((context: interfaces.Context) => {
return new TypeORMItemRepository(context.container.get(TYPES.ORMItemRepository))
})
container.bind<VaultsRepositoryInterface>(TYPES.VaultRepository).toDynamicValue((context: interfaces.Context) => {
return new TypeORMVaultRepository(context.container.get(TYPES.ORMVaultRepository))
container.bind<GroupsRepositoryInterface>(TYPES.GroupRepository).toDynamicValue((context: interfaces.Context) => {
return new TypeORMGroupRepository(context.container.get(TYPES.ORMGroupRepository))
})
container
.bind<VaultUserRepositoryInterface>(TYPES.VaultUserRepository)
.bind<GroupUserRepositoryInterface>(TYPES.GroupUserRepository)
.toDynamicValue((context: interfaces.Context) => {
return new TypeORMVaultUserRepository(context.container.get(TYPES.ORMVaultUserRepository))
return new TypeORMGroupUserRepository(context.container.get(TYPES.ORMGroupUserRepository))
})
container
.bind<RemovedVaultUserRepositoryInterface>(TYPES.RemovedVaultUserRepository)
.bind<RemovedGroupUserRepositoryInterface>(TYPES.RemovedGroupUserRepository)
.toDynamicValue((context: interfaces.Context) => {
return new TypeORMRemovedVaultUserRepository(context.container.get(TYPES.ORMRemovedVaultUserRepository))
return new TypeORMRemovedGroupUserRepository(context.container.get(TYPES.ORMRemovedGroupUserRepository))
})
container
.bind<VaultInviteRepositoryInterface>(TYPES.VaultInviteRepository)
.bind<GroupInviteRepositoryInterface>(TYPES.GroupInviteRepository)
.toDynamicValue((context: interfaces.Context) => {
return new TypeORMVaultInviteRepository(context.container.get(TYPES.ORMVaultInviteRepository))
return new TypeORMGroupInviteRepository(context.container.get(TYPES.ORMGroupInviteRepository))
})
container
.bind<ContactsRepositoryInterface>(TYPES.ContactRepository)
@@ -125,16 +125,16 @@ export class CommonContainerConfigLoader {
// ORM
container.bind<Repository<Item>>(TYPES.ORMItemRepository).toDynamicValue(() => AppDataSource.getRepository(Item))
container.bind<Repository<Vault>>(TYPES.ORMVaultRepository).toDynamicValue(() => AppDataSource.getRepository(Vault))
container.bind<Repository<Group>>(TYPES.ORMGroupRepository).toDynamicValue(() => AppDataSource.getRepository(Group))
container
.bind<Repository<VaultUser>>(TYPES.ORMVaultUserRepository)
.toDynamicValue(() => AppDataSource.getRepository(VaultUser))
.bind<Repository<GroupUser>>(TYPES.ORMGroupUserRepository)
.toDynamicValue(() => AppDataSource.getRepository(GroupUser))
container
.bind<Repository<RemovedVaultUser>>(TYPES.ORMRemovedVaultUserRepository)
.toDynamicValue(() => AppDataSource.getRepository(RemovedVaultUser))
.bind<Repository<RemovedGroupUser>>(TYPES.ORMRemovedGroupUserRepository)
.toDynamicValue(() => AppDataSource.getRepository(RemovedGroupUser))
container
.bind<Repository<VaultInvite>>(TYPES.ORMVaultInviteRepository)
.toDynamicValue(() => AppDataSource.getRepository(VaultInvite))
.bind<Repository<GroupInvite>>(TYPES.ORMGroupInviteRepository)
.toDynamicValue(() => AppDataSource.getRepository(GroupInvite))
container
.bind<Repository<Contact>>(TYPES.ORMContactRepository)
.toDynamicValue(() => AppDataSource.getRepository(Contact))
@@ -145,16 +145,16 @@ export class CommonContainerConfigLoader {
.toDynamicValue((context: interfaces.Context) => {
return new ItemProjector(context.container.get(TYPES.Timer))
})
container.bind<ProjectorInterface<Vault, VaultProjection>>(TYPES.VaultProjector).toDynamicValue(() => {
return new VaultProjector()
container.bind<ProjectorInterface<Group, GroupProjection>>(TYPES.GroupProjector).toDynamicValue(() => {
return new GroupProjector()
})
container.bind<ProjectorInterface<VaultUser, VaultUserProjection>>(TYPES.VaultUserProjector).toDynamicValue(() => {
return new VaultUserProjector()
container.bind<ProjectorInterface<GroupUser, GroupUserProjection>>(TYPES.GroupUserProjector).toDynamicValue(() => {
return new GroupUserProjector()
})
container
.bind<ProjectorInterface<VaultInvite, VaultInviteProjection>>(TYPES.VaultInviteProjector)
.bind<ProjectorInterface<GroupInvite, GroupInviteProjection>>(TYPES.GroupInviteProjector)
.toDynamicValue(() => {
return new VaultInviteProjector()
return new GroupInviteProjector()
})
container.bind<ProjectorInterface<Contact, ContactProjection>>(TYPES.ContactProjector).toDynamicValue(() => {
return new ContactProjector()
@@ -1,13 +1,13 @@
import { RemovedVaultUser } from './../Domain/RemovedVaultUser/Model/RemovedVaultUser'
import { RemovedGroupUser } from './../Domain/RemovedGroupUser/Model/RemovedGroupUser'
import { Contact } from './../Domain/Contact/Model/Contact'
import { VaultInvite } from '../Domain/VaultInvite/Model/VaultInvite'
import { VaultUser } from '../Domain/VaultUser/Model/VaultUser'
import { GroupInvite } from '../Domain/GroupInvite/Model/GroupInvite'
import { GroupUser } from '../Domain/GroupUser/Model/GroupUser'
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 { Vault } from '../Domain/Vault/Model/Vault'
import { Group } from '../Domain/Group/Model/Group'
const env: Env = new Env()
env.load()
@@ -43,7 +43,7 @@ const replicationConfig = {
const commonDataSourceOptions = {
maxQueryExecutionTime,
entities: [Item, Vault, VaultUser, RemovedVaultUser, VaultInvite, Contact],
entities: [Item, Group, GroupUser, RemovedGroupUser, GroupInvite, Contact],
migrations: [`dist/migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
migrationsRun: true,
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
@@ -1,9 +1,9 @@
import { GetGlobalItem } from './../Domain/UseCase/GetGlobalItem/GetGlobalItem'
import { ContactFactory } from './../Domain/Contact/Factory/ContactFactory'
import { VaultServiceInterface } from '../Domain/Vault/Service/VaultServiceInterface'
import { VaultUserFactory } from '../Domain/VaultUser/Factory/VaultUserFactory'
import { VaultFactoryInterface } from '../Domain/Vault/Factory/VaultFactoryInterface'
import { GroupServiceInterface } from '../Domain/Group/Service/GroupServiceInterface'
import { GroupUserFactory } from '../Domain/GroupUser/Factory/GroupUserFactory'
import { GroupFactoryInterface } from '../Domain/Group/Factory/GroupFactoryInterface'
import { Container, interfaces } from 'inversify'
import { Env } from './Env'
@@ -20,7 +20,7 @@ import { SyncItems } from '../Domain/UseCase/SyncItems'
import { ItemConflictProjector } from '../Projection/ItemConflictProjector'
import { ItemSaveValidatorInterface } from '../Domain/Item/SaveValidator/ItemSaveValidatorInterface'
import { ItemSaveValidator } from '../Domain/Item/SaveValidator/ItemSaveValidator'
import { VaultFilter } from '../Domain/Item/SaveRule/VaultFilter'
import { GroupFilter } from '../Domain/Item/SaveRule/GroupFilter'
import { OwnershipFilter } from '../Domain/Item/SaveRule/OwnershipFilter'
import { TimeDifferenceFilter } from '../Domain/Item/SaveRule/TimeDifferenceFilter'
import { ItemFactoryInterface } from '../Domain/Item/ItemFactoryInterface'
@@ -37,21 +37,21 @@ import { ItemConflict } from '../Domain/Item/ItemConflict'
import { ItemConflictProjection } from '../Projection/ItemConflictProjection'
import { CommonContainerConfigLoader } from './CommonContainerConfigLoader'
import { TokenEncoder, TokenEncoderInterface, ValetTokenData } from '@standardnotes/security'
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 { 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 { ContactServiceInterface } from '../Domain/Contact/Service/ContactServiceInterface'
import { ContactService } from '../Domain/Contact/Service/ContactService'
import { ContactFactoryInterface } from '../Domain/Contact/Factory/ContactFactoryInterface'
import { VaultSnjsVersionFilter } from '../Domain/Item/SaveRule/VaultSnjsVersionFilter'
import { CreateVaultFileValetToken } from '../Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileValetToken'
import { RemovedVaultUserService } from '../Domain/RemovedVaultUser/Service/RemovedVaultUserService'
import { RemovedVaultUserServiceInterface } from '../Domain/RemovedVaultUser/Service/RemovedVaultUserServiceInterface'
import { RemovedVaultUserFactory } from '../Domain/RemovedVaultUser/Factory/RemovedVaultUserFactory'
import { GroupSnjsVersionFilter } from '../Domain/Item/SaveRule/GroupSnjsVersionFilter'
import { CreateGroupFileValetToken } from '../Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileValetToken'
import { RemovedGroupUserService } from '../Domain/RemovedGroupUser/Service/RemovedGroupUserService'
import { RemovedGroupUserServiceInterface } from '../Domain/RemovedGroupUser/Service/RemovedGroupUserServiceInterface'
import { RemovedGroupUserFactory } from '../Domain/RemovedGroupUser/Factory/RemovedGroupUserFactory'
export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -103,8 +103,8 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
container.bind<SyncItems>(TYPES.SyncItems).toDynamicValue((context: interfaces.Context) => {
return new SyncItems(
context.container.get(TYPES.ItemService),
context.container.get(TYPES.VaultService),
context.container.get(TYPES.VaultInviteService),
context.container.get(TYPES.GroupService),
context.container.get(TYPES.GroupInviteService),
context.container.get(TYPES.ContactService),
)
})
@@ -119,11 +119,11 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
})
container
.bind<CreateVaultFileValetToken>(TYPES.CreateVaultFileReadValetToken)
.bind<CreateGroupFileValetToken>(TYPES.CreateGroupFileReadValetToken)
.toDynamicValue((context: interfaces.Context) => {
return new CreateVaultFileValetToken(
context.container.get(TYPES.VaultService),
context.container.get(TYPES.VaultUserService),
return new CreateGroupFileValetToken(
context.container.get(TYPES.GroupService),
context.container.get(TYPES.GroupUserService),
context.container.get(TYPES.ValetTokenEncoder),
context.container.get(TYPES.VALET_TOKEN_TTL),
)
@@ -147,48 +147,48 @@ 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.VaultUserRepository),
context.container.get(TYPES.VaultService),
context.container.get(TYPES.GroupUserRepository),
context.container.get(TYPES.GroupService),
context.container.get(TYPES.Logger),
)
})
// Services
container.bind<VaultServiceInterface>(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),
container.bind<GroupServiceInterface>(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),
context.container.get(TYPES.Timer),
)
})
container.bind<VaultUserServiceInterface>(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.RemovedVaultUserService),
container.bind<GroupUserServiceInterface>(TYPES.GroupUserService).toDynamicValue((context: interfaces.Context) => {
return new GroupUserService(
context.container.get(TYPES.GroupRepository),
context.container.get(TYPES.GroupUserRepository),
context.container.get(TYPES.GroupUserFactory),
context.container.get(TYPES.RemovedGroupUserService),
context.container.get(TYPES.Timer),
)
})
container
.bind<RemovedVaultUserServiceInterface>(TYPES.RemovedVaultUserService)
.bind<RemovedGroupUserServiceInterface>(TYPES.RemovedGroupUserService)
.toDynamicValue((context: interfaces.Context) => {
return new RemovedVaultUserService(
context.container.get(TYPES.VaultRepository),
context.container.get(TYPES.RemovedVaultUserRepository),
context.container.get(TYPES.RemovedVaultUserFactory),
return new RemovedGroupUserService(
context.container.get(TYPES.GroupRepository),
context.container.get(TYPES.RemovedGroupUserRepository),
context.container.get(TYPES.RemovedGroupUserFactory),
context.container.get(TYPES.Timer),
)
})
container
.bind<VaultInviteServiceInterface>(TYPES.VaultInviteService)
.bind<GroupInviteServiceInterface>(TYPES.GroupInviteService)
.toDynamicValue((context: interfaces.Context) => {
return new VaultInviteService(
context.container.get(TYPES.VaultRepository),
context.container.get(TYPES.VaultInviteRepository),
context.container.get(TYPES.VaultInviteFactory),
context.container.get(TYPES.VaultUserService),
return new GroupInviteService(
context.container.get(TYPES.GroupRepository),
context.container.get(TYPES.GroupInviteRepository),
context.container.get(TYPES.GroupInviteFactory),
context.container.get(TYPES.GroupUserService),
context.container.get(TYPES.Timer),
)
})
@@ -212,9 +212,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.VaultInviteProjector),
context.container.get(TYPES.GroupInviteProjector),
context.container.get(TYPES.ContactProjector),
context.container.get(TYPES.VaultProjector),
context.container.get(TYPES.GroupProjector),
)
})
container
@@ -229,19 +229,19 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
container.bind<ItemFactoryInterface>(TYPES.ItemFactory).toDynamicValue((context: interfaces.Context) => {
return new ItemFactory(context.container.get(TYPES.Timer), context.container.get(TYPES.ItemProjector))
})
container.bind<VaultFactoryInterface>(TYPES.VaultFactory).toDynamicValue((context: interfaces.Context) => {
return new VaultFactory(context.container.get(TYPES.Timer))
container.bind<GroupFactoryInterface>(TYPES.GroupFactory).toDynamicValue((context: interfaces.Context) => {
return new GroupFactory(context.container.get(TYPES.Timer))
})
container.bind<VaultUserFactory>(TYPES.VaultUserFactory).toDynamicValue((context: interfaces.Context) => {
return new VaultUserFactory(context.container.get(TYPES.Timer))
container.bind<GroupUserFactory>(TYPES.GroupUserFactory).toDynamicValue((context: interfaces.Context) => {
return new GroupUserFactory(context.container.get(TYPES.Timer))
})
container
.bind<RemovedVaultUserFactory>(TYPES.RemovedVaultUserFactory)
.bind<RemovedGroupUserFactory>(TYPES.RemovedGroupUserFactory)
.toDynamicValue((context: interfaces.Context) => {
return new RemovedVaultUserFactory(context.container.get(TYPES.Timer))
return new RemovedGroupUserFactory(context.container.get(TYPES.Timer))
})
container.bind<VaultInviteFactory>(TYPES.VaultInviteFactory).toDynamicValue((context: interfaces.Context) => {
return new VaultInviteFactory(context.container.get(TYPES.Timer))
container.bind<GroupInviteFactory>(TYPES.GroupInviteFactory).toDynamicValue((context: interfaces.Context) => {
return new GroupInviteFactory(context.container.get(TYPES.Timer))
})
container.bind<ContactFactoryInterface>(TYPES.ContactFactory).toDynamicValue((context: interfaces.Context) => {
return new ContactFactory(context.container.get(TYPES.Timer))
@@ -249,10 +249,10 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
container.bind<OwnershipFilter>(TYPES.OwnershipFilter).toDynamicValue(() => new OwnershipFilter())
container
.bind<VaultFilter>(TYPES.VaultFilter)
.bind<GroupFilter>(TYPES.GroupFilter)
.toDynamicValue(
(context: interfaces.Context) =>
new VaultFilter(context.container.get(TYPES.VaultService), context.container.get(TYPES.VaultUserService)),
new GroupFilter(context.container.get(TYPES.GroupService), context.container.get(TYPES.GroupUserService)),
)
container
.bind<TimeDifferenceFilter>(TYPES.TimeDifferenceFilter)
@@ -261,16 +261,16 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
container.bind<ContentTypeFilter>(TYPES.ContentTypeFilter).toDynamicValue(() => new ContentTypeFilter())
container.bind<ContentFilter>(TYPES.ContentFilter).toDynamicValue(() => new ContentFilter())
container
.bind<VaultSnjsVersionFilter>(TYPES.VaultSnjsVersionFilter)
.toDynamicValue(() => new VaultSnjsVersionFilter())
.bind<GroupSnjsVersionFilter>(TYPES.GroupSnjsVersionFilter)
.toDynamicValue(() => new GroupSnjsVersionFilter())
container
.bind<ItemSaveValidatorInterface>(TYPES.ItemSaveValidator)
.toDynamicValue((context: interfaces.Context) => {
return new ItemSaveValidator([
context.container.get(TYPES.OwnershipFilter),
context.container.get(TYPES.VaultFilter),
context.container.get(TYPES.VaultSnjsVersionFilter),
context.container.get(TYPES.GroupFilter),
context.container.get(TYPES.GroupSnjsVersionFilter),
context.container.get(TYPES.TimeDifferenceFilter),
context.container.get(TYPES.UuidFilter),
context.container.get(TYPES.ContentTypeFilter),
+24 -24
View File
@@ -9,19 +9,19 @@ const TYPES = {
// Repositories
ItemRepository: Symbol.for('ItemRepository'),
VaultRepository: Symbol.for('VaultRepository'),
VaultUserRepository: Symbol.for('VaultUserRepository'),
RemovedVaultUserRepository: Symbol.for('RemovedVaultUserRepository'),
GroupRepository: Symbol.for('GroupRepository'),
GroupUserRepository: Symbol.for('GroupUserRepository'),
RemovedGroupUserRepository: Symbol.for('RemovedGroupUserRepository'),
ContactRepository: Symbol.for('ContactRepository'),
VaultInviteRepository: Symbol.for('VaultInviteRepository'),
GroupInviteRepository: Symbol.for('GroupInviteRepository'),
// ORM
ORMItemRepository: Symbol.for('ORMItemRepository'),
ORMVaultRepository: Symbol.for('ORMVaultRepository'),
ORMVaultUserRepository: Symbol.for('ORMVaultUserRepository'),
ORMRemovedVaultUserRepository: Symbol.for('ORMRemovedVaultUserRepository'),
ORMGroupRepository: Symbol.for('ORMGroupRepository'),
ORMGroupUserRepository: Symbol.for('ORMGroupUserRepository'),
ORMRemovedGroupUserRepository: Symbol.for('ORMRemovedGroupUserRepository'),
ORMContactRepository: Symbol.for('ORMContactRepository'),
ORMVaultInviteRepository: Symbol.for('ORMVaultInviteRepository'),
ORMGroupInviteRepository: Symbol.for('ORMGroupInviteRepository'),
// Middleware
AuthMiddleware: Symbol.for('AuthMiddleware'),
@@ -30,25 +30,25 @@ const TYPES = {
ItemProjector: Symbol.for('ItemProjector'),
SavedItemProjector: Symbol.for('SavedItemProjector'),
ItemConflictProjector: Symbol.for('ItemConflictProjector'),
VaultProjector: Symbol.for('VaultProjector'),
VaultUserProjector: Symbol.for('VaultUserProjector'),
GroupProjector: Symbol.for('GroupProjector'),
GroupUserProjector: Symbol.for('GroupUserProjector'),
ContactProjector: Symbol.for('ContactProjector'),
VaultInviteProjector: Symbol.for('VaultInviteProjector'),
GroupInviteProjector: Symbol.for('GroupInviteProjector'),
// Services
VaultService: Symbol.for('VaultService'),
VaultUserService: Symbol.for('VaultUserService'),
RemovedVaultUserService: Symbol.for('RemovedVaultUserService'),
GroupService: Symbol.for('GroupService'),
GroupUserService: Symbol.for('GroupUserService'),
RemovedGroupUserService: Symbol.for('RemovedGroupUserService'),
ContactService: Symbol.for('ContactService'),
VaultInviteService: Symbol.for('VaultInviteService'),
GroupInviteService: Symbol.for('GroupInviteService'),
// Factories
ItemFactory: Symbol.for('ItemFactory'),
VaultFactory: Symbol.for('VaultFactory'),
VaultUserFactory: Symbol.for('VaultUserFactory'),
RemovedVaultUserFactory: Symbol.for('RemovedVaultUserFactory'),
GroupFactory: Symbol.for('GroupFactory'),
GroupUserFactory: Symbol.for('GroupUserFactory'),
RemovedGroupUserFactory: Symbol.for('RemovedGroupUserFactory'),
ContactFactory: Symbol.for('ContactFactory'),
VaultInviteFactory: Symbol.for('VaultInviteFactory'),
GroupInviteFactory: Symbol.for('GroupInviteFactory'),
// env vars
REDIS_URL: Symbol.for('REDIS_URL'),
@@ -75,15 +75,15 @@ const TYPES = {
CheckIntegrity: Symbol.for('CheckIntegrity'),
GetItem: Symbol.for('GetItem'),
GetGlobalItem: Symbol.for('GetGlobalItem'),
CreateVaultFileReadValetToken: Symbol.for('CreateVaultFileReadValetToken'),
CreateGroupFileReadValetToken: Symbol.for('CreateGroupFileReadValetToken'),
// Handlers
UserCredentialsChangedEventHandler: Symbol.for('UserCredentialsChangedEventHandler'),
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
DuplicateItemSyncedEventHandler: Symbol.for('DuplicateItemSyncedEventHandler'),
EmailBackupRequestedEventHandler: Symbol.for('EmailBackupRequestedEventHandler'),
ItemRevisionCreationRequestedEventHandler: Symbol.for('ItemRevisionCreationRequestedEventHandler'),
VaultFileUploadedEventHandler: Symbol.for('VaultFileUploadedEventHandler'),
VaultFileRemovedEventHandler: Symbol.for('VaultFileRemovedEventHandler'),
GroupFileUploadedEventHandler: Symbol.for('GroupFileUploadedEventHandler'),
GroupFileRemovedEventHandler: Symbol.for('GroupFileRemovedEventHandler'),
ContentDecoder: Symbol.for('ContentDecoder'),
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
@@ -101,8 +101,8 @@ const TYPES = {
ItemBackupService: Symbol.for('ItemBackupService'),
ItemSaveValidator: Symbol.for('ItemSaveValidator'),
OwnershipFilter: Symbol.for('OwnershipFilter'),
VaultFilter: Symbol.for('VaultFilter'),
VaultSnjsVersionFilter: Symbol.for('VaultSnjsVersionFilter'),
GroupFilter: Symbol.for('GroupFilter'),
GroupSnjsVersionFilter: Symbol.for('GroupSnjsVersionFilter'),
TimeDifferenceFilter: Symbol.for('TimeDifferenceFilter'),
UuidFilter: Symbol.for('UuidFilter'),
ContentTypeFilter: Symbol.for('ContentTypeFilter'),
@@ -31,8 +31,8 @@ import { ItemRevisionCreationRequestedEventHandler } from '../Domain/Handler/Ite
import { FSItemBackupService } from '../Infra/FS/FSItemBackupService'
import { CommonContainerConfigLoader } from './CommonContainerConfigLoader'
import { UserCredentialsChangedEventHandler } from '../Domain/Handler/UserCredentialsChangedEventHandler'
import { VaultFileUploadedEventHandler } from '../Domain/Handler/VaultFileUploadedEventHandler'
import { VaultFileRemovedEventHandler } from '../Domain/Handler/VaultFileRemovedEventHandler'
import { GroupFileUploadedEventHandler } from '../Domain/Handler/GroupFileUploadedEventHandler'
import { GroupFileRemovedEventHandler } from '../Domain/Handler/GroupFileRemovedEventHandler'
export class WorkerContainerConfigLoader extends CommonContainerConfigLoader {
private readonly DEFAULT_FILE_UPLOAD_PATH = `${__dirname}/../../uploads`
@@ -140,13 +140,13 @@ export class WorkerContainerConfigLoader extends CommonContainerConfigLoader {
.toDynamicValue((context: interfaces.Context) => {
return new UserCredentialsChangedEventHandler(
context.container.get(TYPES.ContactRepository),
context.container.get(TYPES.VaultInviteRepository),
context.container.get(TYPES.GroupInviteRepository),
context.container.get(TYPES.Timer),
)
})
container.bind<VaultFileUploadedEventHandler>(TYPES.VaultFileUploadedEventHandler).to(VaultFileUploadedEventHandler)
container.bind<VaultFileRemovedEventHandler>(TYPES.VaultFileRemovedEventHandler).to(VaultFileRemovedEventHandler)
container.bind<GroupFileUploadedEventHandler>(TYPES.GroupFileUploadedEventHandler).to(GroupFileUploadedEventHandler)
container.bind<GroupFileRemovedEventHandler>(TYPES.GroupFileRemovedEventHandler).to(GroupFileRemovedEventHandler)
// Services
container.bind<ContentDecoder>(TYPES.ContentDecoder).toDynamicValue(() => new ContentDecoder())
@@ -199,8 +199,8 @@ export class WorkerContainerConfigLoader extends CommonContainerConfigLoader {
['EMAIL_BACKUP_REQUESTED', context.container.get(TYPES.EmailBackupRequestedEventHandler)],
['ITEM_REVISION_CREATION_REQUESTED', context.container.get(TYPES.ItemRevisionCreationRequestedEventHandler)],
['USER_CREDENTIALS_CHANGED', container.get(TYPES.UserCredentialsChangedEventHandler)],
['VAULT_FILE_REMOVED', container.get(TYPES.VaultFileRemovedEventHandler)],
['VAULT_FILE_UPLOADED', container.get(TYPES.VaultFileUploadedEventHandler)],
['GROUP_FILE_REMOVED', container.get(TYPES.GroupFileRemovedEventHandler)],
['GROUP_FILE_UPLOADED', container.get(TYPES.GroupFileUploadedEventHandler)],
])
const handler =
@@ -10,29 +10,29 @@ import {
} from 'inversify-express-utils'
import TYPES from '../Bootstrap/Types'
import { inject } from 'inversify'
import { VaultInviteServiceInterface } from '../Domain/VaultInvite/Service/VaultInviteServiceInterface'
import { VaultInviteProjector } from '../Projection/VaultInviteProjector'
import { GroupInviteServiceInterface } from '../Domain/GroupInvite/Service/GroupInviteServiceInterface'
import { GroupInviteProjector } from '../Projection/GroupInviteProjector'
@controller('/vaults')
export class VaultInvitesController extends BaseHttpController {
@controller('/groups')
export class GroupInvitesController extends BaseHttpController {
constructor(
@inject(TYPES.VaultInviteService) private vaultInviteService: VaultInviteServiceInterface,
@inject(TYPES.VaultInviteProjector) private vaultInviteProjector: VaultInviteProjector,
@inject(TYPES.GroupInviteService) private groupInviteService: GroupInviteServiceInterface,
@inject(TYPES.GroupInviteProjector) private groupInviteProjector: GroupInviteProjector,
) {
super()
}
@httpPost('/:vaultUuid/invites', TYPES.AuthMiddleware)
public async createVaultInvite(
@httpPost('/:groupUuid/invites', TYPES.AuthMiddleware)
public async createGroupInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.createInvite({
const result = await this.groupInviteService.createInvite({
originatorUuid: response.locals.user.uuid,
vaultUuid: request.params.vaultUuid,
groupUuid: request.params.groupUuid,
userUuid: request.body.invitee_uuid,
inviterPublicKey: request.body.inviter_public_key,
encryptedVaultData: request.body.encrypted_vault_data,
encryptedGroupData: request.body.encrypted_group_data,
inviteType: request.body.invite_type,
permissions: request.body.permissions,
})
@@ -41,19 +41,19 @@ export class VaultInvitesController extends BaseHttpController {
return this.errorResponse(400, 'Could not create invite')
}
return this.json({ invite: this.vaultInviteProjector.projectFull(result) })
return this.json({ invite: this.groupInviteProjector.projectFull(result) })
}
@httpPatch('/:vaultUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
public async updateVaultInvite(
@httpPatch('/:groupUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
public async updateGroupInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.updateInvite({
const result = await this.groupInviteService.updateInvite({
originatorUuid: response.locals.user.uuid,
inviteUuid: request.params.inviteUuid,
inviterPublicKey: request.body.inviter_public_key,
encryptedVaultData: request.body.encrypted_vault_data,
encryptedGroupData: request.body.encrypted_group_data,
permissions: request.body.permissions,
})
@@ -61,15 +61,15 @@ export class VaultInvitesController extends BaseHttpController {
return this.errorResponse(400, 'Could not update invite')
}
return this.json({ invite: this.vaultInviteProjector.projectFull(result) })
return this.json({ invite: this.groupInviteProjector.projectFull(result) })
}
@httpPost('/:vaultUuid/invites/:inviteUuid/accept', TYPES.AuthMiddleware)
public async acceptVaultInvite(
@httpPost('/:groupUuid/invites/:inviteUuid/accept', TYPES.AuthMiddleware)
public async acceptGroupInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.acceptInvite({
const result = await this.groupInviteService.acceptInvite({
originatorUuid: response.locals.user.uuid,
inviteUuid: request.params.inviteUuid,
})
@@ -81,12 +81,12 @@ export class VaultInvitesController extends BaseHttpController {
return this.json({ success: true })
}
@httpPost('/:vaultUuid/invites/:inviteUuid/decline', TYPES.AuthMiddleware)
public async declineVaultInvite(
@httpPost('/:groupUuid/invites/:inviteUuid/decline', TYPES.AuthMiddleware)
public async declineGroupInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.declineInvite({
const result = await this.groupInviteService.declineInvite({
originatorUuid: response.locals.user.uuid,
inviteUuid: request.params.inviteUuid,
})
@@ -103,7 +103,7 @@ export class VaultInvitesController extends BaseHttpController {
_request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
await this.vaultInviteService.deleteAllInboundInvites({
await this.groupInviteService.deleteAllInboundInvites({
userUuid: response.locals.user.uuid,
})
@@ -115,15 +115,15 @@ export class VaultInvitesController extends BaseHttpController {
_request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.getOutboundInvitesForUser({
const result = await this.groupInviteService.getOutboundInvitesForUser({
userUuid: response.locals.user.uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get vault invites')
return this.errorResponse(400, 'Could not get group invites')
}
const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite))
const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite))
return this.json({ invites: projected })
}
@@ -133,46 +133,46 @@ export class VaultInvitesController extends BaseHttpController {
_request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.getInvitesForUser({
const result = await this.groupInviteService.getInvitesForUser({
userUuid: response.locals.user.uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get vault invites')
return this.errorResponse(400, 'Could not get group invites')
}
const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite))
const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite))
return this.json({ invites: projected })
}
@httpGet('/:vaultUuid/invites', TYPES.AuthMiddleware)
public async getVaultInvites(
@httpGet('/:groupUuid/invites', TYPES.AuthMiddleware)
public async getGroupInvites(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.getInvitesForVault({
const result = await this.groupInviteService.getInvitesForGroup({
originatorUuid: response.locals.user.uuid,
vaultUuid: request.params.vaultUuid,
groupUuid: request.params.groupUuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get vault invites')
return this.errorResponse(400, 'Could not get group invites')
}
const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite))
const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite))
return this.json({ invites: projected })
}
@httpDelete('/:vaultUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
public async deleteVaultInvite(
@httpDelete('/:groupUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
public async deleteGroupInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.deleteInvite({
const result = await this.groupInviteService.deleteInvite({
originatorUuid: response.locals.user.uuid,
vaultUuid: request.params.vaultUuid,
groupUuid: request.params.groupUuid,
inviteUuid: request.params.inviteUuid,
})
@@ -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 { VaultUserServiceInterface } from '../Domain/VaultUser/Service/VaultUserServiceInterface'
import { VaultUserProjector } from '../Projection/VaultUserProjector'
import { GroupUserServiceInterface } from '../Domain/GroupUser/Service/GroupUserServiceInterface'
import { GroupUserProjector } from '../Projection/GroupUserProjector'
@controller('/vaults/:vaultUuid/users')
export class VaultUsersController extends BaseHttpController {
@controller('/groups/:groupUuid/users')
export class GroupUsersController extends BaseHttpController {
constructor(
@inject(TYPES.VaultUserService) private vaultUserService: VaultUserServiceInterface,
@inject(TYPES.VaultUserProjector) private vaultUserProjector: VaultUserProjector,
@inject(TYPES.GroupUserService) private groupUserService: GroupUserServiceInterface,
@inject(TYPES.GroupUserProjector) private groupUserProjector: GroupUserProjector,
) {
super()
}
@httpGet('/', TYPES.AuthMiddleware)
public async getVaultUsers(
public async getGroupUsers(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultUserService.getVaultUsersForVault({
const result = await this.groupUserService.getGroupUsersForGroup({
originatorUuid: response.locals.user.uuid,
vaultUuid: request.params.vaultUuid,
groupUuid: request.params.groupUuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get vault users')
return this.errorResponse(400, 'Could not get group users')
}
const { users, isAdmin } = result
const projected = users.map((vaultUser) =>
this.vaultUserProjector.projectAsDisplayableUserForOtherVaultMembers(vaultUser, isAdmin),
const projected = users.map((groupUser) =>
this.groupUserProjector.projectAsDisplayableUserForOtherGroupMembers(groupUser, isAdmin),
)
return this.json({ users: projected })
}
@httpDelete('/:userUuid', TYPES.AuthMiddleware)
public async deleteVaultUser(
public async deleteGroupUser(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultUserService.deleteVaultUser({
vaultUuid: request.params.vaultUuid,
const result = await this.groupUserService.deleteGroupUser({
groupUuid: request.params.groupUuid,
userUuid: request.params.userUuid,
originatorUuid: response.locals.user.uuid,
})
@@ -0,0 +1,150 @@
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 { CreateGroupFileValetToken } from '../Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileValetToken'
import { RemovedGroupUserServiceInterface } from '../Domain/RemovedGroupUser/Service/RemovedGroupUserServiceInterface'
@controller('/groups')
export class GroupsController extends BaseHttpController {
constructor(
@inject(TYPES.GroupService) private groupService: GroupServiceInterface,
@inject(TYPES.RemovedGroupUserService) private removedGroupUserService: RemovedGroupUserServiceInterface,
@inject(TYPES.GroupProjector) private groupProjector: ProjectorInterface<Group, GroupProjection>,
@inject(TYPES.GroupUserProjector) private groupUserProjector: ProjectorInterface<GroupUser, GroupUserProjection>,
@inject(TYPES.CreateGroupFileReadValetToken) private createGroupFileReadValetToken: CreateGroupFileValetToken,
) {
super()
}
@httpGet('/', TYPES.AuthMiddleware)
public async getGroups(_request: Request, response: Response): Promise<results.JsonResult> {
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<results.NotFoundResult | results.JsonResult> {
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,
operation: request.body.operation,
unencryptedFileSize: request.body.unencrypted_file_size,
moveOperationType: request.body.move_operation_type,
groupToGroupMoveTargetUuid: request.body.group_to_group_move_target_uuid,
})
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<results.NotFoundResult | results.JsonResult> {
const result = await this.groupService.createGroup({
userUuid: response.locals.user.uuid,
groupUuid: request.body.group_uuid,
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not create group')
}
return this.json({
group: this.groupProjector.projectFull(result.group),
groupUser: this.groupUserProjector.projectFull(result.groupUser),
})
}
@httpPatch('/:groupUuid', TYPES.AuthMiddleware)
public async updateGroup(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupService.updateGroup({
groupUuid: request.params.groupUuid,
originatorUuid: response.locals.user.uuid,
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<results.NotFoundResult | results.JsonResult> {
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 })
}
@httpGet('/removed', TYPES.AuthMiddleware)
public async getAllRemovedFromGroupsForCurrentUser(
_request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.removedGroupUserService.getAllRemovedGroupUsersForUser({
userUuid: response.locals.user.uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get group users')
}
return this.json({
removedGroups: result.map((removedUser) => {
return {
groupUuid: removedUser.groupUuid,
removedAt: removedUser.createdAtTimestamp,
}
}),
})
}
private errorResponse(status: number, message?: string, tag?: string) {
return this.json(
{
error: { message, tag },
},
status,
)
}
}
@@ -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,
vaultUuids: request.body.vault_uuids,
groupUuids: request.body.group_uuids,
limit: request.body.limit,
contentType: request.body.content_type,
apiVersion: request.body.api ?? ApiVersion.v20161215,
@@ -1,150 +0,0 @@
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 { CreateVaultFileValetToken } from '../Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileValetToken'
import { RemovedVaultUserServiceInterface } from '../Domain/RemovedVaultUser/Service/RemovedVaultUserServiceInterface'
@controller('/vaults')
export class VaultsController extends BaseHttpController {
constructor(
@inject(TYPES.VaultService) private vaultService: VaultServiceInterface,
@inject(TYPES.RemovedVaultUserService) private removedVaultUserService: RemovedVaultUserServiceInterface,
@inject(TYPES.VaultProjector) private vaultProjector: ProjectorInterface<Vault, VaultProjection>,
@inject(TYPES.VaultUserProjector) private vaultUserProjector: ProjectorInterface<VaultUser, VaultUserProjection>,
@inject(TYPES.CreateVaultFileReadValetToken) private createVaultFileReadValetToken: CreateVaultFileValetToken,
) {
super()
}
@httpGet('/', TYPES.AuthMiddleware)
public async getVaults(_request: Request, response: Response): Promise<results.JsonResult> {
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<results.NotFoundResult | results.JsonResult> {
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,
operation: request.body.operation,
unencryptedFileSize: request.body.unencrypted_file_size,
moveOperationType: request.body.move_operation_type,
vaultToVaultMoveTargetUuid: request.body.vault_to_vault_move_target_uuid,
})
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<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultService.createVault({
userUuid: response.locals.user.uuid,
vaultUuid: request.body.vault_uuid,
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not create vault')
}
return this.json({
vault: this.vaultProjector.projectFull(result.vault),
vaultUser: this.vaultUserProjector.projectFull(result.vaultUser),
})
}
@httpPatch('/:vaultUuid', TYPES.AuthMiddleware)
public async updateVault(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultService.updateVault({
vaultUuid: request.params.vaultUuid,
originatorUuid: response.locals.user.uuid,
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<results.NotFoundResult | results.JsonResult> {
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 })
}
@httpGet('/removed', TYPES.AuthMiddleware)
public async getAllRemovedFromVaultsForCurrentUser(
_request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.removedVaultUserService.getAllRemovedVaultUsersForUser({
userUuid: response.locals.user.uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get vault users')
}
return this.json({
removedVaults: result.map((removedUser) => {
return {
vaultUuid: removedUser.vaultUuid,
removedAt: removedUser.createdAtTimestamp,
}
}),
})
}
private errorResponse(status: number, message?: string, tag?: string) {
return this.json(
{
error: { message, tag },
},
status,
)
}
}
@@ -0,0 +1,32 @@
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.fileUploadBytesUsed = dto.groupHash.file_upload_bytes_used
newGroup.fileUploadBytesLimit = dto.groupHash.file_upload_bytes_limit
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
}
}
@@ -0,0 +1,7 @@
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
}
@@ -1,4 +1,4 @@
export type VaultHash = {
export type GroupHash = {
uuid: string
user_uuid: string
specified_items_key_uuid: string
@@ -1,7 +1,7 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'vaults' })
export class Vault {
@Entity({ name: 'groups' })
export class Group {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@@ -9,7 +9,7 @@ export class Vault {
name: 'user_uuid',
length: 36,
})
@Index('index_vaults_on_user_uuid')
@Index('index_groups_on_user_uuid')
declare userUuid: string
@Column({
@@ -0,0 +1,19 @@
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<Group | null>
create(group: Group): Promise<Group>
save(group: Group): Promise<Group>
remove(group: Group): Promise<Group>
findAll(query: UserGroupsQuery): Promise<Group[]>
}
@@ -0,0 +1,56 @@
import { Repository, SelectQueryBuilder } from 'typeorm'
import { GroupsRepositoryInterface, UserGroupsQuery } from './GroupRepositoryInterface'
import { Group } from '../Model/Group'
export class TypeORMGroupRepository implements GroupsRepositoryInterface {
constructor(private ormRepository: Repository<Group>) {}
async create(group: Group): Promise<Group> {
return this.ormRepository.save(group)
}
async save(group: Group): Promise<Group> {
return this.ormRepository.save(group)
}
findByUuid(uuid: string): Promise<Group | null> {
return this.ormRepository
.createQueryBuilder('group')
.where('group.uuid = :uuid', {
uuid,
})
.getOne()
}
async remove(group: Group): Promise<Group> {
return this.ormRepository.remove(group)
}
async findAll(query: UserGroupsQuery): Promise<Group[]> {
return this.createFindAllQueryBuilder(query).getMany()
}
private createFindAllQueryBuilder(query: UserGroupsQuery): SelectQueryBuilder<Group> {
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
}
}
@@ -0,0 +1,101 @@
import { TimerInterface } from '@standardnotes/time'
import { Group } from '../Model/Group'
import { GroupsRepositoryInterface } from '../Repository/GroupRepositoryInterface'
import { CreateGroupDTO, GroupServiceInterface, UpdateGroupDTO, CreateGroupResult } 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<CreateGroupResult | null> {
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,
file_upload_bytes_limit: 1_000_000,
file_upload_bytes_used: 0,
created_at_timestamp: timestamp,
updated_at_timestamp: timestamp,
},
})
const savedGroup = await this.groupRepository.create(group)
const groupUser = await this.groupUserService.addGroupUser({
groupUuid: savedGroup.uuid,
userUuid: dto.userUuid,
permissions: 'admin',
})
return { group, groupUser }
}
async getGroup(dto: { groupUuid: string }): Promise<Group | null> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
return group
}
async getGroups(dto: { userUuid: string; lastSyncTime?: number }): Promise<Group[]> {
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<Group | null> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group || group.userUuid !== dto.originatorUuid) {
return null
}
group.specifiedItemsKeyUuid = dto.specifiedItemsKeyUuid
group.updatedAtTimestamp = this.timer.getTimestampInMicroseconds()
const savedGroup = await this.groupRepository.save(group)
return savedGroup
}
async deleteGroup(dto: { groupUuid: string; originatorUuid: string }): Promise<boolean> {
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
}
}
@@ -0,0 +1,31 @@
import { GroupUser } from './../../GroupUser/Model/GroupUser'
import { Group } from '../Model/Group'
export type CreateGroupDTO = {
userUuid: string
groupUuid: string
specifiedItemsKeyUuid: string
}
export type CreateGroupResult = {
group: Group
groupUser: GroupUser
}
export type UpdateGroupDTO = {
originatorUuid: string
groupUuid: string
specifiedItemsKeyUuid: string
}
export interface GroupServiceInterface {
createGroup(dto: CreateGroupDTO): Promise<CreateGroupResult | null>
updateGroup(dto: UpdateGroupDTO): Promise<Group | null>
deleteGroup(dto: { groupUuid: string; originatorUuid: string }): Promise<boolean>
getGroup(dto: { groupUuid: string }): Promise<Group | null>
getGroups(dto: { userUuid: string; lastSyncTime?: number }): Promise<Group[]>
}
@@ -0,0 +1,35 @@
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
}
}
@@ -0,0 +1,7 @@
import { GroupInvite } from '../Model/GroupInvite'
import { GroupInviteHash } from './GroupInviteHash'
export interface GroupInviteFactoryInterface {
create(dto: GroupInviteHash): GroupInvite
createStub(dto: GroupInviteHash): GroupInvite
}
@@ -0,0 +1,15 @@
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
}
@@ -1,17 +1,17 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import { VaultInviteType } from './VaultInviteType'
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
import { GroupInviteType } from './GroupInviteType'
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
@Entity({ name: 'vault_invites' })
export class VaultInvite {
@Entity({ name: 'group_invites' })
export class GroupInvite {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'vault_uuid',
name: 'group_uuid',
length: 36,
})
declare vaultUuid: string
declare groupUuid: string
@Column({
name: 'user_uuid',
@@ -27,7 +27,7 @@ export class VaultInvite {
@Column({
name: 'invite_type',
})
declare inviteType: VaultInviteType
declare inviteType: GroupInviteType
@Column({
name: 'inviter_public_key',
@@ -35,14 +35,14 @@ export class VaultInvite {
declare inviterPublicKey: string
@Column({
name: 'encrypted_vault_data',
name: 'encrypted_group_data',
})
declare encryptedVaultData: string
declare encryptedGroupData: string
@Column({
name: 'permissions',
})
declare permissions: VaultUserPermission
declare permissions: GroupUserPermission
@Column({
name: 'created_at_timestamp',
@@ -1,4 +1,4 @@
export enum VaultInviteType {
export enum GroupInviteType {
Join = 'join',
KeyChange = 'key-change',
}
@@ -0,0 +1,25 @@
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<GroupInvite | null>
create(group: GroupInvite): Promise<GroupInvite>
save(groupInvite: GroupInvite): Promise<GroupInvite>
remove(group: GroupInvite): Promise<GroupInvite>
findAll(query: GroupInviteFindAllForUserQuery): Promise<GroupInvite[]>
findAllForGroup(query: GroupInviteFindAllForGroup): Promise<GroupInvite[]>
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupInvite | null>
}
@@ -0,0 +1,71 @@
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<GroupInvite>) {}
async create(groupInvite: GroupInvite): Promise<GroupInvite> {
return this.ormRepository.save(groupInvite)
}
async save(groupInvite: GroupInvite): Promise<GroupInvite> {
return this.ormRepository.save(groupInvite)
}
findByUuid(uuid: string): Promise<GroupInvite | null> {
return this.ormRepository
.createQueryBuilder('group_invite')
.where('group_invite.uuid = :uuid', {
uuid,
})
.getOne()
}
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupInvite | null> {
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<GroupInvite> {
return this.ormRepository.remove(groupInvite)
}
findAllForGroup(query: GroupInviteFindAllForGroup): Promise<GroupInvite[]> {
return this.ormRepository
.createQueryBuilder('group_invite')
.where('group_invite.group_uuid = :groupUuid', { groupUuid: query.groupUuid })
.getMany()
}
async findAll(query: GroupInviteFindAllForUserQuery): Promise<GroupInvite[]> {
return this.createFindAllQueryBuilder(query).getMany()
}
private createFindAllQueryBuilder(query: GroupInviteFindAllForUserQuery): SelectQueryBuilder<GroupInvite> {
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
}
}
@@ -0,0 +1,12 @@
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
}
@@ -1,4 +1,4 @@
export interface GetUserVaultInvitesDTO {
export interface GetUserGroupInvitesDTO {
userUuid: string
lastSyncTime?: number
}
@@ -0,0 +1,172 @@
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<GroupInvite | null> {
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<GroupInvite | null> {
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<GroupInvite[]> {
return this.groupInviteRepository.findAll(dto)
}
getOutboundInvitesForUser(dto: { userUuid: string }): Promise<GroupInvite[]> {
return this.groupInviteRepository.findAll({
inviterUuid: dto.userUuid,
})
}
async deleteAllInboundInvites(dto: { userUuid: string }): Promise<void> {
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<GroupInvite[] | undefined> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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
}
}
@@ -0,0 +1,26 @@
import { GroupInvite } from '../Model/GroupInvite'
import { CreateInviteDTO } from './CreateInviteDTO'
import { GetUserGroupInvitesDTO } from './GetUserGroupInvitesDTO'
import { UpdateInviteDTO } from './UpdateInviteDTO'
export interface GroupInviteServiceInterface {
createInvite(dto: CreateInviteDTO): Promise<GroupInvite | null>
updateInvite(dto: UpdateInviteDTO): Promise<GroupInvite | null>
getInvitesForUser(dto: GetUserGroupInvitesDTO): Promise<GroupInvite[]>
getInvitesForGroup(dto: { groupUuid: string; originatorUuid: string }): Promise<GroupInvite[] | undefined>
getOutboundInvitesForUser(dto: { userUuid: string }): Promise<GroupInvite[]>
acceptInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean>
declineInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean>
deleteInvite(dto: { originatorUuid: string; groupUuid: string; inviteUuid: string }): Promise<boolean>
deleteAllInvitesForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<boolean>
deleteAllInboundInvites(dto: { userUuid: string }): Promise<void>
}
@@ -0,0 +1,9 @@
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
export type UpdateInviteDTO = {
originatorUuid: string
inviteUuid: string
inviterPublicKey: string
encryptedGroupData: string
permissions?: GroupUserPermission
}
@@ -0,0 +1,31 @@
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
}
}
@@ -0,0 +1,7 @@
import { GroupUser } from '../Model/GroupUser'
import { GroupUserHash } from './GroupUserHash'
export interface GroupUserFactoryInterface {
create(dto: GroupUserHash): GroupUser
createStub(dto: GroupUserHash): GroupUser
}
@@ -0,0 +1,10 @@
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
}
@@ -1,16 +1,16 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import { VaultUserPermission } from './VaultUserPermission'
import { GroupUserPermission } from './GroupUserPermission'
@Entity({ name: 'vault_users' })
export class VaultUser {
@Entity({ name: 'group_users' })
export class GroupUser {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'vault_uuid',
name: 'group_uuid',
length: 36,
})
declare vaultUuid: string
declare groupUuid: string
@Column({
name: 'user_uuid',
@@ -21,7 +21,7 @@ export class VaultUser {
@Column({
name: 'permissions',
})
declare permissions: VaultUserPermission
declare permissions: GroupUserPermission
@Column({
name: 'created_at_timestamp',
@@ -0,0 +1 @@
export type GroupUserPermission = 'read' | 'write' | 'admin'
@@ -0,0 +1,20 @@
import { GroupUser } from '../Model/GroupUser'
export type GroupUserFindAllForUserQuery = {
userUuid: string
lastSyncTime?: number
}
export type GroupUserFindAllForGroup = {
groupUuid: string
}
export interface GroupUserRepositoryInterface {
findByUuid(groupUserUuid: string): Promise<GroupUser | null>
create(group: GroupUser): Promise<GroupUser>
save(groupUser: GroupUser): Promise<GroupUser>
remove(group: GroupUser): Promise<GroupUser>
findAllForUser(query: GroupUserFindAllForUserQuery): Promise<GroupUser[]>
findAllForGroup(query: GroupUserFindAllForGroup): Promise<GroupUser[]>
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupUser | null>
}
@@ -0,0 +1,65 @@
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<GroupUser>) {}
async create(groupUser: GroupUser): Promise<GroupUser> {
return this.ormRepository.save(groupUser)
}
async save(groupUser: GroupUser): Promise<GroupUser> {
return this.ormRepository.save(groupUser)
}
findByUuid(uuid: string): Promise<GroupUser | null> {
return this.ormRepository
.createQueryBuilder('group_user')
.where('group_user.uuid = :uuid', {
uuid,
})
.getOne()
}
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupUser | null> {
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<GroupUser> {
return this.ormRepository.remove(groupUser)
}
findAllForGroup(query: GroupUserFindAllForGroup): Promise<GroupUser[]> {
return this.ormRepository
.createQueryBuilder('group_user')
.where('group_user.group_uuid = :groupUuid', { groupUuid: query.groupUuid })
.getMany()
}
async findAllForUser(query: GroupUserFindAllForUserQuery): Promise<GroupUser[]> {
return this.createFindAllQueryBuilder(query).getMany()
}
private createFindAllQueryBuilder(query: GroupUserFindAllForUserQuery): SelectQueryBuilder<GroupUser> {
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
}
}
@@ -1,4 +1,4 @@
export interface GetVaultUsersDTO {
export interface GetGroupUsersDTO {
userUuid: string
lastSyncTime?: number
}
@@ -0,0 +1,132 @@
import { RemovedGroupUserServiceInterface } from './../../RemovedGroupUser/Service/RemovedGroupUserServiceInterface'
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 removedGroupUserService: RemovedGroupUserServiceInterface,
private timer: TimerInterface,
) {}
async addGroupUser(dto: {
groupUuid: string
userUuid: string
permissions: GroupUserPermission
}): Promise<GroupUser> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group) {
throw new Error('Attempting to add a group user to a non-existent group')
}
await this.removedGroupUserService.deleteRemovedGroupUser({
groupUuid: dto.groupUuid,
userUuid: dto.userUuid,
})
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<GroupUser[]> {
return this.groupUserRepository.findAllForUser(dto)
}
getUserForGroup(dto: { userUuid: string; groupUuid: string }): Promise<GroupUser | null> {
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<boolean> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group) {
return false
}
if (group.userUuid === dto.userUuid) {
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)
await this.removedGroupUserService.addRemovedGroupUser({
groupUuid: dto.groupUuid,
userUuid: dto.userUuid,
removedBy: dto.originatorUuid,
})
return true
}
async deleteAllGroupUsersForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<boolean> {
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)
await this.removedGroupUserService.addRemovedGroupUser({
groupUuid: dto.groupUuid,
userUuid: groupUser.userUuid,
removedBy: dto.originatorUuid,
})
}
return true
}
}
@@ -0,0 +1,20 @@
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<GroupUser>
getAllGroupUsersForUser(dto: GetGroupUsersDTO): Promise<GroupUser[]>
getUserForGroup(dto: { userUuid: string; groupUuid: string }): Promise<GroupUser | null>
getGroupUsersForGroup(dto: {
groupUuid: string
originatorUuid: string
}): Promise<{ users: GroupUser[]; isAdmin: boolean } | undefined>
deleteGroupUser(dto: { originatorUuid: string; groupUuid: string; userUuid: string }): Promise<boolean>
deleteAllGroupUsersForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<boolean>
}
@@ -0,0 +1,33 @@
import { DomainEventHandlerInterface, GroupFileRemovedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { GroupServiceInterface } from '../Group/Service/GroupServiceInterface'
import { GroupsRepositoryInterface } from '../Group/Repository/GroupRepositoryInterface'
@injectable()
export class GroupFileRemovedEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.GroupService) private groupService: GroupServiceInterface,
@inject(TYPES.GroupRepository) private groupRepository: GroupsRepositoryInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
async handle(event: GroupFileRemovedEvent): Promise<void> {
const group = await this.groupService.getGroup({
groupUuid: event.payload.groupUuid,
})
if (group === null) {
this.logger.warn(`Could not find group with uuid: ${event.payload.groupUuid}`)
return
}
group.fileUploadBytesUsed -= event.payload.fileByteSize
await this.groupRepository.save(group)
}
}
@@ -0,0 +1,33 @@
import { DomainEventHandlerInterface, GroupFileUploadedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { GroupServiceInterface } from '../Group/Service/GroupServiceInterface'
import { GroupsRepositoryInterface } from '../Group/Repository/GroupRepositoryInterface'
@injectable()
export class GroupFileUploadedEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.GroupService) private groupService: GroupServiceInterface,
@inject(TYPES.GroupRepository) private groupRepository: GroupsRepositoryInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
async handle(event: GroupFileUploadedEvent): Promise<void> {
const group = await this.groupService.getGroup({
groupUuid: event.payload.groupUuid,
})
if (group === null) {
this.logger.warn(`Could not find group with uuid: ${event.payload.groupUuid}`)
return
}
group.fileUploadBytesUsed += event.payload.fileByteSize
await this.groupRepository.save(group)
}
}
@@ -5,18 +5,18 @@ import {
} from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
import { ContactsRepositoryInterface } from '../Contact/Repository/ContactRepositoryInterface'
import { VaultInviteRepositoryInterface } from '../VaultInvite/Repository/VaultInviteRepositoryInterface'
import { GroupInviteRepositoryInterface } from '../GroupInvite/Repository/GroupInviteRepositoryInterface'
export class UserCredentialsChangedEventHandler implements DomainEventHandlerInterface {
constructor(
private contactRepository: ContactsRepositoryInterface,
private vaultInviteRepository: VaultInviteRepositoryInterface,
private groupInviteRepository: GroupInviteRepositoryInterface,
private timer: TimerInterface,
) {}
async handle(event: UserCredentialsChangedEvent): Promise<void> {
await this.updatePublicKeyOfAllContacts(event.payload)
await this.nullifyPendingInboundVaultInvites(event.payload)
await this.nullifyPendingInboundGroupInvites(event.payload)
}
private async updatePublicKeyOfAllContacts(payload: UserCredentialsChangedEventPayload): Promise<void> {
@@ -31,13 +31,13 @@ export class UserCredentialsChangedEventHandler implements DomainEventHandlerInt
}
}
private async nullifyPendingInboundVaultInvites(payload: UserCredentialsChangedEventPayload): Promise<void> {
const inboundInvites = await this.vaultInviteRepository.findAll({
private async nullifyPendingInboundGroupInvites(payload: UserCredentialsChangedEventPayload): Promise<void> {
const inboundInvites = await this.groupInviteRepository.findAll({
userUuid: payload.userUuid,
})
for (const invite of inboundInvites) {
await this.vaultInviteRepository.remove(invite)
await this.groupInviteRepository.remove(invite)
}
}
}
@@ -1,33 +0,0 @@
import { DomainEventHandlerInterface, VaultFileRemovedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { VaultServiceInterface } from '../Vault/Service/VaultServiceInterface'
import { VaultsRepositoryInterface } from '../Vault/Repository/VaultRepositoryInterface'
@injectable()
export class VaultFileRemovedEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.VaultService) private vaultService: VaultServiceInterface,
@inject(TYPES.VaultRepository) private vaultRepository: VaultsRepositoryInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
async handle(event: VaultFileRemovedEvent): Promise<void> {
const vault = await this.vaultService.getVault({
vaultUuid: event.payload.vaultUuid,
})
if (vault === null) {
this.logger.warn(`Could not find vault with uuid: ${event.payload.vaultUuid}`)
return
}
vault.fileUploadBytesUsed -= event.payload.fileByteSize
await this.vaultRepository.save(vault)
}
}
@@ -1,33 +0,0 @@
import { DomainEventHandlerInterface, VaultFileUploadedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { VaultServiceInterface } from '../Vault/Service/VaultServiceInterface'
import { VaultsRepositoryInterface } from '../Vault/Repository/VaultRepositoryInterface'
@injectable()
export class VaultFileUploadedEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.VaultService) private vaultService: VaultServiceInterface,
@inject(TYPES.VaultRepository) private vaultRepository: VaultsRepositoryInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
async handle(event: VaultFileUploadedEvent): Promise<void> {
const vault = await this.vaultService.getVault({
vaultUuid: event.payload.vaultUuid,
})
if (vault === null) {
this.logger.warn(`Could not find vault with uuid: ${event.payload.vaultUuid}`)
return
}
vault.fileUploadBytesUsed += event.payload.fileByteSize
await this.vaultRepository.save(vault)
}
}
@@ -4,5 +4,5 @@ export type GetItemsDTO = {
cursorToken?: string | null
limit?: number
contentType?: string
vaultUuids?: string[] | null
groupUuids?: string[] | null
}
@@ -15,11 +15,19 @@ export class Item {
@Column({
type: 'varchar',
name: 'vault_uuid',
name: 'group_uuid',
length: 255,
nullable: true,
})
declare vaultUuid: string | null
declare groupUuid: string | null
@Column({
type: 'varchar',
name: 'vault_identifier',
length: 255,
nullable: true,
})
declare vault_identifier: string | null
@Column({
type: 'varchar',
@@ -34,8 +34,8 @@ export class ItemFactory implements ItemFactoryInterface {
newItem.contentSize = 0
newItem.createdByUuid = dto.userUuid
if (dto.itemHash.vault_uuid) {
newItem.vaultUuid = dto.itemHash.vault_uuid
if (dto.itemHash.group_uuid) {
newItem.groupUuid = dto.itemHash.group_uuid
} else {
newItem.userUuid = dto.userUuid
}
@@ -10,7 +10,7 @@ export type ItemHash = {
auth_hash?: string
enc_item_key?: string
items_key_id?: string
vault_uuid?: string
group_uuid?: string
last_edited_by_uuid?: string | null
created_at?: string
created_at_timestamp?: number
@@ -6,8 +6,8 @@ export type ItemQuery = {
lastSyncTime?: number
syncTimeComparison?: '>' | '>='
contentType?: string | string[]
includeVaultUuids?: string[]
exclusiveVaultUuids?: string[] | null
includeGroupUuids?: string[]
exclusiveGroupUuids?: string[] | null
deleted?: boolean
offset?: number
limit?: number
@@ -19,9 +19,9 @@ import { ItemSaveValidatorInterface } from './SaveValidator/ItemSaveValidatorInt
import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { ItemProjection } from '../../Projection/ItemProjection'
import { VaultUserRepositoryInterface } from '../VaultUser/Repository/VaultUserRepositoryInterface'
import { GroupUserRepositoryInterface } from '../GroupUser/Repository/GroupUserRepositoryInterface'
import { ConflictType } from '../../Tmp/ConflictType'
import { VaultServiceInterface } from '../Vault/Service/VaultServiceInterface'
import { GroupServiceInterface } from '../Group/Service/GroupServiceInterface'
export class ItemService implements ItemServiceInterface {
private readonly DEFAULT_ITEMS_LIMIT = 150
@@ -39,8 +39,8 @@ export class ItemService implements ItemServiceInterface {
private timer: TimerInterface,
private itemProjector: ProjectorInterface<Item, ItemProjection>,
private maxItemsSyncLimit: number,
private vaultUsersRepository: VaultUserRepositoryInterface,
private vaultService: VaultServiceInterface,
private groupUsersRepository: GroupUserRepositoryInterface,
private groupService: GroupServiceInterface,
private logger: Logger,
) {}
@@ -50,16 +50,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 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))
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))
: undefined
const itemQuery: ItemQuery = {
userUuid: dto.userUuid,
includeVaultUuids: !dto.vaultUuids ? userVaultUuids : undefined,
exclusiveVaultUuids: exclusiveVaultUuids,
includeGroupUuids: !dto.groupUuids ? userGroupUuids : undefined,
exclusiveGroupUuids: exclusiveGroupUuids,
lastSyncTime,
syncTimeComparison,
contentType: dto.contentType,
@@ -99,7 +99,7 @@ export class ItemService implements ItemServiceInterface {
const savedItems: Array<Item> = []
const conflicts: Array<ItemConflict> = []
await this.dynamicallyCreateVaultsBasedOnIncomingItems(dto)
await this.dynamicallyCreateGroupsBasedOnIncomingItems(dto)
const lastUpdatedTimestamp = this.timer.getTimestampInMicroseconds()
@@ -187,15 +187,15 @@ export class ItemService implements ItemServiceInterface {
return retrievedItems
}
private async dynamicallyCreateVaultsBasedOnIncomingItems(dto: SaveItemsDTO): Promise<void> {
const processedVaultUuids = new Set()
private async dynamicallyCreateGroupsBasedOnIncomingItems(dto: SaveItemsDTO): Promise<void> {
const processedGroupUuids = new Set()
for (const itemHash of dto.itemHashes) {
if (!itemHash.vault_uuid || processedVaultUuids.has(itemHash.vault_uuid)) {
if (!itemHash.group_uuid || processedGroupUuids.has(itemHash.group_uuid)) {
continue
}
processedVaultUuids.add(itemHash.vault_uuid)
processedGroupUuids.add(itemHash.group_uuid)
const vaultItemsKeyUuid =
itemHash.content_type === ContentType.VaultItemsKey ? itemHash.uuid : itemHash.items_key_id
@@ -203,9 +203,9 @@ export class ItemService implements ItemServiceInterface {
continue
}
await this.vaultService.createVault({
await this.groupService.createGroup({
userUuid: dto.userUuid,
vaultUuid: itemHash.vault_uuid,
groupUuid: itemHash.group_uuid,
specifiedItemsKeyUuid: vaultItemsKeyUuid,
})
}
@@ -239,10 +239,10 @@ export class ItemService implements ItemServiceInterface {
dto.existingItem.contentSize = 0
dto.existingItem.lastEditedByUuid = dto.userUuid
if (dto.itemHash.vault_uuid) {
dto.existingItem.vaultUuid = dto.itemHash.vault_uuid
if (dto.itemHash.group_uuid) {
dto.existingItem.groupUuid = dto.itemHash.group_uuid
} else {
dto.existingItem.vaultUuid = null
dto.existingItem.groupUuid = null
}
if (dto.itemHash.content) {
@@ -0,0 +1,301 @@
import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
import { ItemHash } from '../ItemHash'
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
import { GroupServiceInterface } from '../../Group/Service/GroupServiceInterface'
import { ContentType } from '@standardnotes/common'
import { ConflictType } from '../../../Tmp/ConflictType'
import {
GroupSaveOperation,
AddToGroupSaveOperation,
RemoveFromGroupSaveOperation,
MoveToOtherGroupSaveOperation,
SaveToGroupSaveOperation,
CreateToGroupSaveOperation,
} from './GroupSaveOperation'
import { Item } from '../Item'
export class GroupFilter implements ItemSaveRuleInterface {
constructor(private groupService: GroupServiceInterface, private groupUserService: GroupUserServiceInterface) {}
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
if (!dto.existingItem?.groupUuid && !dto.itemHash.group_uuid) {
return {
passed: true,
}
}
const operation = this.getSaveOperationType(dto)
return this.getResultForOperation(operation)
}
private async getResultForOperation(operation: GroupSaveOperation): Promise<ItemSaveRuleResult> {
if (operation.type === 'add-to-group') {
return this.handleAddToGroupOperation(operation)
} else if (operation.type === 'remove-from-group') {
return this.handleRemoveFromGroupOperation(operation)
} else if (operation.type === 'move-to-other-group') {
return this.handleMoveToOtherGroupOperation(operation)
} else if (operation.type === 'save-to-group') {
return this.handleSaveToGroupOperation(operation)
} else if (operation.type === 'create-to-group') {
return this.handleCreateToGroupOperation(operation)
}
throw new Error(`Unsupported group operation: ${operation}`)
}
private getSaveOperationType(dto: ItemSaveValidationDTO): GroupSaveOperation {
const existingItemGroupUuid = dto.existingItem?.groupUuid
const targetItemGroupUuid = dto.itemHash.group_uuid
const common = {
incomingItem: dto.itemHash,
userUuid: dto.userUuid,
}
if (
dto.existingItem &&
existingItemGroupUuid &&
targetItemGroupUuid &&
existingItemGroupUuid !== targetItemGroupUuid
) {
return {
type: 'move-to-other-group',
groupUuid: existingItemGroupUuid,
targetGroupUuid: targetItemGroupUuid,
existingItem: dto.existingItem,
...common,
}
}
if (dto.existingItem && existingItemGroupUuid && !targetItemGroupUuid) {
return {
type: 'remove-from-group',
groupUuid: existingItemGroupUuid,
existingItem: dto.existingItem,
...common,
}
}
if (dto.existingItem && !existingItemGroupUuid && targetItemGroupUuid) {
return {
type: 'add-to-group',
groupUuid: targetItemGroupUuid,
existingItem: dto.existingItem,
...common,
}
}
if (dto.existingItem && existingItemGroupUuid && existingItemGroupUuid === targetItemGroupUuid) {
return {
type: 'save-to-group',
groupUuid: existingItemGroupUuid,
existingItem: dto.existingItem,
...common,
}
}
if (!dto.existingItem && targetItemGroupUuid) {
return {
type: 'create-to-group',
groupUuid: targetItemGroupUuid,
...common,
}
}
throw new Error('Invalid save operation')
}
private isAuthorizedToSaveContentType(contentType: ContentType, permissions: GroupUserPermission): boolean {
if (contentType === ContentType.VaultItemsKey) {
return permissions === 'admin'
}
return true
}
private buildFailResult(operation: GroupSaveOperation, type: ConflictType, serverItem?: Item) {
return {
passed: false,
conflict: {
unsavedItem: operation.incomingItem,
serverItem,
type,
},
}
}
private buildSuccessValue(): ItemSaveRuleResult {
return {
passed: true,
}
}
private async handleAddToGroupOperation(operation: AddToGroupSaveOperation): Promise<ItemSaveRuleResult> {
const groupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
if (!groupPermissions) {
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
}
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
return this.buildFailResult(operation, ConflictType.GroupInvalidState)
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, groupPermissions)) {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
if (groupPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
if (operation.existingItem.userUuid !== operation.userUuid) {
return this.buildFailResult(operation, ConflictType.UuidConflict)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleRemoveFromGroupOperation(operation: RemoveFromGroupSaveOperation): Promise<ItemSaveRuleResult> {
const groupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
if (!groupPermissions) {
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
}
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
return this.buildFailResult(operation, ConflictType.GroupInvalidState)
}
if (operation.existingItem.userUuid === operation.userUuid) {
return this.buildSuccessValue()
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, groupPermissions)) {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
if (groupPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleMoveToOtherGroupOperation(operation: MoveToOtherGroupSaveOperation): Promise<ItemSaveRuleResult> {
const sourceGroupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
const targetGroupPermissions = await this.getGroupPermissions(operation.userUuid, operation.targetGroupUuid)
if (!sourceGroupPermissions || !targetGroupPermissions) {
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
}
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
return this.buildFailResult(operation, ConflictType.GroupInvalidState)
}
if (sourceGroupPermissions === 'read' || targetGroupPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
if (
!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, sourceGroupPermissions) ||
!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, targetGroupPermissions)
) {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleSaveToGroupOperation(operation: SaveToGroupSaveOperation): Promise<ItemSaveRuleResult> {
const groupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
if (!groupPermissions) {
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, groupPermissions)) {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
if (groupPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleCreateToGroupOperation(operation: CreateToGroupSaveOperation): Promise<ItemSaveRuleResult> {
const groupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
if (!groupPermissions) {
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, groupPermissions)) {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
if (groupPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey)
}
return this.buildSuccessValue()
}
private async incomingItemUsesValidItemsKey(itemHash: ItemHash): Promise<boolean> {
const isItemNotEncryptedByItemsKey = itemHash.content_type === ContentType.VaultItemsKey
if (isItemNotEncryptedByItemsKey) {
return true
}
const group = await this.groupService.getGroup({ groupUuid: itemHash.group_uuid as string })
if (!group) {
return false
}
return itemHash.items_key_id === group.specifiedItemsKeyUuid
}
private async getGroupPermissions(userUuid: string, groupUuid: string): Promise<GroupUserPermission | undefined> {
const groupUser = await this.groupUserService.getUserForGroup({
userUuid,
groupUuid: groupUuid,
})
if (groupUser) {
return groupUser.permissions
}
return undefined
}
}
@@ -0,0 +1,44 @@
import { ItemHash } from '../ItemHash'
import { Item } from '../Item'
type SaveOperationCommon = {
incomingItem: ItemHash
userUuid: string
}
export type AddToGroupSaveOperation = SaveOperationCommon & {
type: 'add-to-group'
groupUuid: string
existingItem: Item
}
export type RemoveFromGroupSaveOperation = SaveOperationCommon & {
type: 'remove-from-group'
groupUuid: string
existingItem: Item
}
export type MoveToOtherGroupSaveOperation = SaveOperationCommon & {
type: 'move-to-other-group'
groupUuid: string
targetGroupUuid: string
existingItem: Item
}
export type SaveToGroupSaveOperation = SaveOperationCommon & {
type: 'save-to-group'
groupUuid: string
existingItem: Item
}
export type CreateToGroupSaveOperation = SaveOperationCommon & {
type: 'create-to-group'
groupUuid: string
}
export type GroupSaveOperation =
| AddToGroupSaveOperation
| RemoveFromGroupSaveOperation
| MoveToOtherGroupSaveOperation
| SaveToGroupSaveOperation
| CreateToGroupSaveOperation
@@ -4,20 +4,20 @@ import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
import { lt } from 'semver'
import { ConflictType } from '../../../Tmp/ConflictType'
const MINIMUM_SNJS_VERSION_FOR_VAULT_OPERATIONS = '2.200.0'
const MINIMUM_SNJS_VERSION_FOR_GROUP_OPERATIONS = '2.200.0'
export class VaultSnjsVersionFilter implements ItemSaveRuleInterface {
export class GroupSnjsVersionFilter implements ItemSaveRuleInterface {
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
const successValue = {
passed: true,
}
const vaultUuidInvolved = dto.existingItem?.vaultUuid || dto.itemHash.vault_uuid
if (!vaultUuidInvolved) {
const groupUuidInvolved = dto.existingItem?.groupUuid || dto.itemHash.group_uuid
if (!groupUuidInvolved) {
return successValue
}
const isClientSnjsVersionLessThanMinimum = lt(dto.snjsVersion, MINIMUM_SNJS_VERSION_FOR_VAULT_OPERATIONS)
const isClientSnjsVersionLessThanMinimum = lt(dto.snjsVersion, MINIMUM_SNJS_VERSION_FOR_GROUP_OPERATIONS)
if (isClientSnjsVersionLessThanMinimum) {
return {
passed: false,
@@ -5,8 +5,8 @@ import { ConflictType } from '../../../Tmp/ConflictType'
export class OwnershipFilter implements ItemSaveRuleInterface {
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
const deferToVaultFilter = dto.existingItem?.vaultUuid || dto.itemHash.vault_uuid
if (deferToVaultFilter) {
const deferToGroupFilter = dto.existingItem?.groupUuid || dto.itemHash.group_uuid
if (deferToGroupFilter) {
return {
passed: true,
}
@@ -1,301 +0,0 @@
import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface'
import { ItemHash } from '../ItemHash'
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
import { VaultServiceInterface } from '../../Vault/Service/VaultServiceInterface'
import { ContentType } from '@standardnotes/common'
import { ConflictType } from '../../../Tmp/ConflictType'
import {
VaultSaveOperation,
AddToVaultSaveOperation,
RemoveFromVaultSaveOperation,
MoveToOtherVaultSaveOperation,
SaveToVaultSaveOperation,
CreateToVaultSaveOperation,
} from './VaultSaveOperation'
import { Item } from '../Item'
export class VaultFilter implements ItemSaveRuleInterface {
constructor(private vaultService: VaultServiceInterface, private vaultUserService: VaultUserServiceInterface) {}
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
if (!dto.existingItem?.vaultUuid && !dto.itemHash.vault_uuid) {
return {
passed: true,
}
}
const operation = this.getSaveOperationType(dto)
return this.getResultForOperation(operation)
}
private async getResultForOperation(operation: VaultSaveOperation): Promise<ItemSaveRuleResult> {
if (operation.type === 'add-to-vault') {
return this.handleAddToVaultOperation(operation)
} else if (operation.type === 'remove-from-vault') {
return this.handleRemoveFromVaultOperation(operation)
} else if (operation.type === 'move-to-other-vault') {
return this.handleMoveToOtherVaultOperation(operation)
} else if (operation.type === 'save-to-vault') {
return this.handleSaveToVaultOperation(operation)
} else if (operation.type === 'create-to-vault') {
return this.handleCreateToVaultOperation(operation)
}
throw new Error(`Unsupported vault operation: ${operation}`)
}
private getSaveOperationType(dto: ItemSaveValidationDTO): VaultSaveOperation {
const existingItemVaultUuid = dto.existingItem?.vaultUuid
const targetItemVaultUuid = dto.itemHash.vault_uuid
const common = {
incomingItem: dto.itemHash,
userUuid: dto.userUuid,
}
if (
dto.existingItem &&
existingItemVaultUuid &&
targetItemVaultUuid &&
existingItemVaultUuid !== targetItemVaultUuid
) {
return {
type: 'move-to-other-vault',
vaultUuid: existingItemVaultUuid,
targetVaultUuid: targetItemVaultUuid,
existingItem: dto.existingItem,
...common,
}
}
if (dto.existingItem && existingItemVaultUuid && !targetItemVaultUuid) {
return {
type: 'remove-from-vault',
vaultUuid: existingItemVaultUuid,
existingItem: dto.existingItem,
...common,
}
}
if (dto.existingItem && !existingItemVaultUuid && targetItemVaultUuid) {
return {
type: 'add-to-vault',
vaultUuid: targetItemVaultUuid,
existingItem: dto.existingItem,
...common,
}
}
if (dto.existingItem && existingItemVaultUuid && existingItemVaultUuid === targetItemVaultUuid) {
return {
type: 'save-to-vault',
vaultUuid: existingItemVaultUuid,
existingItem: dto.existingItem,
...common,
}
}
if (!dto.existingItem && targetItemVaultUuid) {
return {
type: 'create-to-vault',
vaultUuid: targetItemVaultUuid,
...common,
}
}
throw new Error('Invalid save operation')
}
private isAuthorizedToSaveContentType(contentType: ContentType, permissions: VaultUserPermission): boolean {
if (contentType === ContentType.VaultItemsKey) {
return permissions === 'admin'
}
return true
}
private buildFailResult(operation: VaultSaveOperation, type: ConflictType, serverItem?: Item) {
return {
passed: false,
conflict: {
unsavedItem: operation.incomingItem,
serverItem,
type,
},
}
}
private buildSuccessValue(): ItemSaveRuleResult {
return {
passed: true,
}
}
private async handleAddToVaultOperation(operation: AddToVaultSaveOperation): Promise<ItemSaveRuleResult> {
const vaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.vaultUuid)
if (!vaultPermissions) {
return this.buildFailResult(operation, ConflictType.VaultNotMemberError)
}
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
return this.buildFailResult(operation, ConflictType.VaultInvalidState)
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, vaultPermissions)) {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
if (vaultPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
if (operation.existingItem.userUuid !== operation.userUuid) {
return this.buildFailResult(operation, ConflictType.UuidConflict)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.VaultInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleRemoveFromVaultOperation(operation: RemoveFromVaultSaveOperation): Promise<ItemSaveRuleResult> {
const vaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.vaultUuid)
if (!vaultPermissions) {
return this.buildFailResult(operation, ConflictType.VaultNotMemberError)
}
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
return this.buildFailResult(operation, ConflictType.VaultInvalidState)
}
if (operation.existingItem.userUuid === operation.userUuid) {
return this.buildSuccessValue()
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, vaultPermissions)) {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
if (vaultPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.VaultInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleMoveToOtherVaultOperation(operation: MoveToOtherVaultSaveOperation): Promise<ItemSaveRuleResult> {
const sourceVaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.vaultUuid)
const targetVaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.targetVaultUuid)
if (!sourceVaultPermissions || !targetVaultPermissions) {
return this.buildFailResult(operation, ConflictType.VaultNotMemberError)
}
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
return this.buildFailResult(operation, ConflictType.VaultInvalidState)
}
if (sourceVaultPermissions === 'read' || targetVaultPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
if (
!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, sourceVaultPermissions) ||
!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, targetVaultPermissions)
) {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.VaultInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleSaveToVaultOperation(operation: SaveToVaultSaveOperation): Promise<ItemSaveRuleResult> {
const vaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.vaultUuid)
if (!vaultPermissions) {
return this.buildFailResult(operation, ConflictType.VaultNotMemberError)
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, vaultPermissions)) {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
if (vaultPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.VaultInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleCreateToVaultOperation(operation: CreateToVaultSaveOperation): Promise<ItemSaveRuleResult> {
const vaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.vaultUuid)
if (!vaultPermissions) {
return this.buildFailResult(operation, ConflictType.VaultNotMemberError)
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, vaultPermissions)) {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
if (vaultPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.VaultInvalidItemsKey)
}
return this.buildSuccessValue()
}
private async incomingItemUsesValidItemsKey(itemHash: ItemHash): Promise<boolean> {
const isItemNotEncryptedByItemsKey = itemHash.content_type === ContentType.VaultItemsKey
if (isItemNotEncryptedByItemsKey) {
return true
}
const vault = await this.vaultService.getVault({ vaultUuid: itemHash.vault_uuid as string })
if (!vault) {
return false
}
return itemHash.items_key_id === vault.specifiedItemsKeyUuid
}
private async getVaultPermissions(userUuid: string, vaultUuid: string): Promise<VaultUserPermission | undefined> {
const vaultUser = await this.vaultUserService.getUserForVault({
userUuid,
vaultUuid: vaultUuid,
})
if (vaultUser) {
return vaultUser.permissions
}
return undefined
}
}
@@ -1,44 +0,0 @@
import { ItemHash } from '../ItemHash'
import { Item } from '../Item'
type SaveOperationCommon = {
incomingItem: ItemHash
userUuid: string
}
export type AddToVaultSaveOperation = SaveOperationCommon & {
type: 'add-to-vault'
vaultUuid: string
existingItem: Item
}
export type RemoveFromVaultSaveOperation = SaveOperationCommon & {
type: 'remove-from-vault'
vaultUuid: string
existingItem: Item
}
export type MoveToOtherVaultSaveOperation = SaveOperationCommon & {
type: 'move-to-other-vault'
vaultUuid: string
targetVaultUuid: string
existingItem: Item
}
export type SaveToVaultSaveOperation = SaveOperationCommon & {
type: 'save-to-vault'
vaultUuid: string
existingItem: Item
}
export type CreateToVaultSaveOperation = SaveOperationCommon & {
type: 'create-to-vault'
vaultUuid: string
}
export type VaultSaveOperation =
| AddToVaultSaveOperation
| RemoveFromVaultSaveOperation
| MoveToOtherVaultSaveOperation
| SaveToVaultSaveOperation
| CreateToVaultSaveOperation
@@ -1,6 +1,6 @@
import { ContactProjection } from '../../../Projection/ContactProjection'
import { VaultInviteProjection } from '../../../Projection/VaultInviteProjection'
import { VaultProjection } from '../../../Projection/VaultProjection'
import { GroupInviteProjection } from '../../../Projection/GroupInviteProjection'
import { GroupProjection } from '../../../Projection/GroupProjection'
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<ContactProjection>
vaults: Array<VaultProjection>
vault_invites: Array<VaultInviteProjection>
groups: Array<GroupProjection>
group_invites: Array<GroupInviteProjection>
}
@@ -1,4 +1,4 @@
import { VaultInvite } from '../../VaultInvite/Model/VaultInvite'
import { GroupInvite } from '../../GroupInvite/Model/GroupInvite'
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 { VaultInviteProjection } from '../../../Projection/VaultInviteProjection'
import { VaultProjection } from '../../../Projection/VaultProjection'
import { Vault } from '../../Vault/Model/Vault'
import { GroupInviteProjection } from '../../../Projection/GroupInviteProjection'
import { GroupProjection } from '../../../Projection/GroupProjection'
import { Group } from '../../Group/Model/Group'
export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface {
constructor(
private itemProjector: ProjectorInterface<Item, ItemProjection>,
private itemConflictProjector: ProjectorInterface<ItemConflict, ItemConflictProjection>,
private savedItemProjector: ProjectorInterface<Item, SavedItemProjection>,
private vaultInviteProjector: ProjectorInterface<VaultInvite, VaultInviteProjection>,
private groupInviteProjector: ProjectorInterface<GroupInvite, GroupInviteProjection>,
private contactProjector: ProjectorInterface<Contact, ContactProjection>,
private vaultProjector: ProjectorInterface<Vault, VaultProjection>,
private groupProjector: ProjectorInterface<Group, GroupProjection>,
) {}
async createResponse(syncItemsResponse: SyncItemsResponse): Promise<SyncResponse20200115> {
@@ -40,14 +40,14 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface
conflicts.push(this.itemConflictProjector.projectFull(itemConflict))
}
const vaults = []
for (const vault of syncItemsResponse.vaults) {
vaults.push(this.vaultProjector.projectFull(vault))
const groups = []
for (const group of syncItemsResponse.groups) {
groups.push(this.groupProjector.projectFull(group))
}
const vaultInvites = []
for (const vaultInvite of syncItemsResponse.vaultInvites) {
vaultInvites.push(this.vaultInviteProjector.projectFull(vaultInvite))
const groupInvites = []
for (const groupInvite of syncItemsResponse.groupInvites) {
groupInvites.push(this.groupInviteProjector.projectFull(groupInvite))
}
const contacts = []
@@ -61,8 +61,8 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface
conflicts,
sync_token: syncItemsResponse.syncToken,
cursor_token: syncItemsResponse.cursorToken,
vaults,
vault_invites: vaultInvites,
groups,
group_invites: groupInvites,
contacts,
}
}
@@ -0,0 +1,31 @@
import { TimerInterface } from '@standardnotes/time'
import { RemovedGroupUserFactoryInterface } from './RemovedGroupUserFactoryInterface'
import { RemovedGroupUserHash } from './RemovedGroupUserHash'
import { RemovedGroupUser } from '../Model/RemovedGroupUser'
export class RemovedGroupUserFactory implements RemovedGroupUserFactoryInterface {
constructor(private timer: TimerInterface) {}
create(dto: RemovedGroupUserHash): RemovedGroupUser {
const newRemovedGroupUser = new RemovedGroupUser()
newRemovedGroupUser.uuid = dto.uuid
newRemovedGroupUser.userUuid = dto.user_uuid
newRemovedGroupUser.groupUuid = dto.group_uuid
newRemovedGroupUser.removedBy = dto.removed_by
const now = this.timer.getTimestampInMicroseconds()
newRemovedGroupUser.updatedAtTimestamp = now
newRemovedGroupUser.createdAtTimestamp = now
if (dto.created_at_timestamp) {
newRemovedGroupUser.createdAtTimestamp = dto.created_at_timestamp
}
return newRemovedGroupUser
}
createStub(dto: RemovedGroupUserHash): RemovedGroupUser {
const item = this.create(dto)
return item
}
}
@@ -0,0 +1,7 @@
import { RemovedGroupUser } from '../Model/RemovedGroupUser'
import { RemovedGroupUserHash } from './RemovedGroupUserHash'
export interface RemovedGroupUserFactoryInterface {
create(dto: RemovedGroupUserHash): RemovedGroupUser
createStub(dto: RemovedGroupUserHash): RemovedGroupUser
}
@@ -1,8 +1,8 @@
export type RemovedVaultUserHash = {
export type RemovedGroupUserHash = {
uuid: string
user_uuid: string
removed_by: string
vault_uuid: string
group_uuid: string
created_at_timestamp?: number
updated_at_timestamp?: number
}
@@ -1,15 +1,15 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'removed_vault_users' })
export class RemovedVaultUser {
@Entity({ name: 'removed_group_users' })
export class RemovedGroupUser {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'vault_uuid',
name: 'group_uuid',
length: 36,
})
declare vaultUuid: string
declare groupUuid: string
@Column({
name: 'user_uuid',
@@ -0,0 +1,12 @@
import { RemovedGroupUser } from '../Model/RemovedGroupUser'
export type RemovedGroupUserFindAllForUserQuery = {
userUuid: string
}
export interface RemovedGroupUserRepositoryInterface {
create(group: RemovedGroupUser): Promise<RemovedGroupUser>
remove(group: RemovedGroupUser): Promise<RemovedGroupUser>
findAllForUser(query: RemovedGroupUserFindAllForUserQuery): Promise<RemovedGroupUser[]>
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<RemovedGroupUser | null>
}
@@ -0,0 +1,38 @@
import { RemovedGroupUser } from '../Model/RemovedGroupUser'
import { Repository, SelectQueryBuilder } from 'typeorm'
import {
RemovedGroupUserFindAllForUserQuery,
RemovedGroupUserRepositoryInterface,
} from './RemovedGroupUserRepositoryInterface'
export class TypeORMRemovedGroupUserRepository implements RemovedGroupUserRepositoryInterface {
constructor(private ormRepository: Repository<RemovedGroupUser>) {}
async create(removedGroupUser: RemovedGroupUser): Promise<RemovedGroupUser> {
return this.ormRepository.save(removedGroupUser)
}
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<RemovedGroupUser | null> {
return this.ormRepository
.createQueryBuilder('removed_group_user')
.where('removed_group_user.user_uuid = :userUuid', { userUuid })
.andWhere('removed_group_user.group_uuid = :groupUuid', { groupUuid })
.getOne()
}
async remove(removedGroupUser: RemovedGroupUser): Promise<RemovedGroupUser> {
return this.ormRepository.remove(removedGroupUser)
}
async findAllForUser(query: RemovedGroupUserFindAllForUserQuery): Promise<RemovedGroupUser[]> {
return this.createFindAllQueryBuilder(query).getMany()
}
private createFindAllQueryBuilder(query: RemovedGroupUserFindAllForUserQuery): SelectQueryBuilder<RemovedGroupUser> {
const queryBuilder = this.ormRepository.createQueryBuilder('removed_group_user')
queryBuilder.where('removed_group_user.user_uuid = :userUuid', { userUuid: query.userUuid })
return queryBuilder
}
}
@@ -0,0 +1,3 @@
export interface GetRemovedGroupUsersDTO {
userUuid: string
}
@@ -0,0 +1,58 @@
import { TimerInterface } from '@standardnotes/time'
import { RemovedGroupUser } from '../Model/RemovedGroupUser'
import { RemovedGroupUserFactoryInterface } from '../Factory/RemovedGroupUserFactoryInterface'
import { v4 as uuidv4 } from 'uuid'
import { RemovedGroupUserRepositoryInterface } from '../Repository/RemovedGroupUserRepositoryInterface'
import { RemovedGroupUserServiceInterface } from './RemovedGroupUserServiceInterface'
import { GetRemovedGroupUsersDTO } from './GetRemovedGroupUsersDTO'
import { GroupsRepositoryInterface } from '../../Group/Repository/GroupRepositoryInterface'
export class RemovedGroupUserService implements RemovedGroupUserServiceInterface {
constructor(
private groupRepository: GroupsRepositoryInterface,
private removedGroupUserRepository: RemovedGroupUserRepositoryInterface,
private removedGroupUserFactory: RemovedGroupUserFactoryInterface,
private timer: TimerInterface,
) {}
async addRemovedGroupUser(dto: {
groupUuid: string
userUuid: string
removedBy: string
}): Promise<RemovedGroupUser | null> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group) {
return null
}
const timestamp = this.timer.getTimestampInMicroseconds()
const removedGroupUser = this.removedGroupUserFactory.create({
uuid: uuidv4(),
user_uuid: dto.userUuid,
group_uuid: dto.groupUuid,
removed_by: dto.removedBy,
created_at_timestamp: timestamp,
updated_at_timestamp: timestamp,
})
return this.removedGroupUserRepository.create(removedGroupUser)
}
getAllRemovedGroupUsersForUser(dto: GetRemovedGroupUsersDTO): Promise<RemovedGroupUser[]> {
return this.removedGroupUserRepository.findAllForUser(dto)
}
async deleteRemovedGroupUser(dto: { groupUuid: string; userUuid: string }): Promise<boolean> {
const removedGroupUser = await this.removedGroupUserRepository.findByUserUuidAndGroupUuid(
dto.userUuid,
dto.groupUuid,
)
if (!removedGroupUser) {
return false
}
await this.removedGroupUserRepository.remove(removedGroupUser)
return true
}
}
@@ -0,0 +1,10 @@
import { RemovedGroupUser } from '../Model/RemovedGroupUser'
import { GetRemovedGroupUsersDTO } from './GetRemovedGroupUsersDTO'
export interface RemovedGroupUserServiceInterface {
addRemovedGroupUser(dto: { groupUuid: string; userUuid: string; removedBy: string }): Promise<RemovedGroupUser | null>
getAllRemovedGroupUsersForUser(dto: GetRemovedGroupUsersDTO): Promise<RemovedGroupUser[]>
deleteRemovedGroupUser(dto: { groupUuid: string; userUuid: string }): Promise<boolean>
}
@@ -1,31 +0,0 @@
import { TimerInterface } from '@standardnotes/time'
import { RemovedVaultUserFactoryInterface } from './RemovedVaultUserFactoryInterface'
import { RemovedVaultUserHash } from './RemovedVaultUserHash'
import { RemovedVaultUser } from '../Model/RemovedVaultUser'
export class RemovedVaultUserFactory implements RemovedVaultUserFactoryInterface {
constructor(private timer: TimerInterface) {}
create(dto: RemovedVaultUserHash): RemovedVaultUser {
const newRemovedVaultUser = new RemovedVaultUser()
newRemovedVaultUser.uuid = dto.uuid
newRemovedVaultUser.userUuid = dto.user_uuid
newRemovedVaultUser.vaultUuid = dto.vault_uuid
newRemovedVaultUser.removedBy = dto.removed_by
const now = this.timer.getTimestampInMicroseconds()
newRemovedVaultUser.updatedAtTimestamp = now
newRemovedVaultUser.createdAtTimestamp = now
if (dto.created_at_timestamp) {
newRemovedVaultUser.createdAtTimestamp = dto.created_at_timestamp
}
return newRemovedVaultUser
}
createStub(dto: RemovedVaultUserHash): RemovedVaultUser {
const item = this.create(dto)
return item
}
}
@@ -1,7 +0,0 @@
import { RemovedVaultUser } from '../Model/RemovedVaultUser'
import { RemovedVaultUserHash } from './RemovedVaultUserHash'
export interface RemovedVaultUserFactoryInterface {
create(dto: RemovedVaultUserHash): RemovedVaultUser
createStub(dto: RemovedVaultUserHash): RemovedVaultUser
}
@@ -1,12 +0,0 @@
import { RemovedVaultUser } from '../Model/RemovedVaultUser'
export type RemovedVaultUserFindAllForUserQuery = {
userUuid: string
}
export interface RemovedVaultUserRepositoryInterface {
create(vault: RemovedVaultUser): Promise<RemovedVaultUser>
remove(vault: RemovedVaultUser): Promise<RemovedVaultUser>
findAllForUser(query: RemovedVaultUserFindAllForUserQuery): Promise<RemovedVaultUser[]>
findByUserUuidAndVaultUuid(userUuid: string, vaultUuid: string): Promise<RemovedVaultUser | null>
}
@@ -1,38 +0,0 @@
import { RemovedVaultUser } from '../Model/RemovedVaultUser'
import { Repository, SelectQueryBuilder } from 'typeorm'
import {
RemovedVaultUserFindAllForUserQuery,
RemovedVaultUserRepositoryInterface,
} from './RemovedVaultUserRepositoryInterface'
export class TypeORMRemovedVaultUserRepository implements RemovedVaultUserRepositoryInterface {
constructor(private ormRepository: Repository<RemovedVaultUser>) {}
async create(removedVaultUser: RemovedVaultUser): Promise<RemovedVaultUser> {
return this.ormRepository.save(removedVaultUser)
}
findByUserUuidAndVaultUuid(userUuid: string, vaultUuid: string): Promise<RemovedVaultUser | null> {
return this.ormRepository
.createQueryBuilder('removed_vault_user')
.where('removed_vault_user.user_uuid = :userUuid', { userUuid })
.andWhere('removed_vault_user.vault_uuid = :vaultUuid', { vaultUuid })
.getOne()
}
async remove(removedVaultUser: RemovedVaultUser): Promise<RemovedVaultUser> {
return this.ormRepository.remove(removedVaultUser)
}
async findAllForUser(query: RemovedVaultUserFindAllForUserQuery): Promise<RemovedVaultUser[]> {
return this.createFindAllQueryBuilder(query).getMany()
}
private createFindAllQueryBuilder(query: RemovedVaultUserFindAllForUserQuery): SelectQueryBuilder<RemovedVaultUser> {
const queryBuilder = this.ormRepository.createQueryBuilder('removed_vault_user')
queryBuilder.where('removed_vault_user.user_uuid = :userUuid', { userUuid: query.userUuid })
return queryBuilder
}
}
@@ -1,3 +0,0 @@
export interface GetRemovedVaultUsersDTO {
userUuid: string
}
@@ -1,58 +0,0 @@
import { TimerInterface } from '@standardnotes/time'
import { RemovedVaultUser } from '../Model/RemovedVaultUser'
import { RemovedVaultUserFactoryInterface } from '../Factory/RemovedVaultUserFactoryInterface'
import { v4 as uuidv4 } from 'uuid'
import { RemovedVaultUserRepositoryInterface } from '../Repository/RemovedVaultUserRepositoryInterface'
import { RemovedVaultUserServiceInterface } from './RemovedVaultUserServiceInterface'
import { GetRemovedVaultUsersDTO } from './GetRemovedVaultUsersDTO'
import { VaultsRepositoryInterface } from '../../Vault/Repository/VaultRepositoryInterface'
export class RemovedVaultUserService implements RemovedVaultUserServiceInterface {
constructor(
private vaultRepository: VaultsRepositoryInterface,
private removedVaultUserRepository: RemovedVaultUserRepositoryInterface,
private removedVaultUserFactory: RemovedVaultUserFactoryInterface,
private timer: TimerInterface,
) {}
async addRemovedVaultUser(dto: {
vaultUuid: string
userUuid: string
removedBy: string
}): Promise<RemovedVaultUser | null> {
const vault = await this.vaultRepository.findByUuid(dto.vaultUuid)
if (!vault) {
return null
}
const timestamp = this.timer.getTimestampInMicroseconds()
const removedVaultUser = this.removedVaultUserFactory.create({
uuid: uuidv4(),
user_uuid: dto.userUuid,
vault_uuid: dto.vaultUuid,
removed_by: dto.removedBy,
created_at_timestamp: timestamp,
updated_at_timestamp: timestamp,
})
return this.removedVaultUserRepository.create(removedVaultUser)
}
getAllRemovedVaultUsersForUser(dto: GetRemovedVaultUsersDTO): Promise<RemovedVaultUser[]> {
return this.removedVaultUserRepository.findAllForUser(dto)
}
async deleteRemovedVaultUser(dto: { vaultUuid: string; userUuid: string }): Promise<boolean> {
const removedVaultUser = await this.removedVaultUserRepository.findByUserUuidAndVaultUuid(
dto.userUuid,
dto.vaultUuid,
)
if (!removedVaultUser) {
return false
}
await this.removedVaultUserRepository.remove(removedVaultUser)
return true
}
}
@@ -1,10 +0,0 @@
import { RemovedVaultUser } from '../Model/RemovedVaultUser'
import { GetRemovedVaultUsersDTO } from './GetRemovedVaultUsersDTO'
export interface RemovedVaultUserServiceInterface {
addRemovedVaultUser(dto: { vaultUuid: string; userUuid: string; removedBy: string }): Promise<RemovedVaultUser | null>
getAllRemovedVaultUsersForUser(dto: GetRemovedVaultUsersDTO): Promise<RemovedVaultUser[]>
deleteRemovedVaultUser(dto: { vaultUuid: string; userUuid: string }): Promise<boolean>
}
@@ -0,0 +1,104 @@
import { inject, injectable } from 'inversify'
import { GroupValetTokenData, TokenEncoderInterface, ValetTokenOperation } from '@standardnotes/security'
import { CreateValetTokenResponseData } from '@standardnotes/responses'
import TYPES from '../../../Bootstrap/Types'
import { UseCaseInterface } from '../UseCaseInterface'
import { CreateGroupFileValetTokenDTO } from './CreateGroupFileValetTokenDTO'
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
import { GroupServiceInterface } from '../../Group/Service/GroupServiceInterface'
@injectable()
export class CreateGroupFileValetToken implements UseCaseInterface {
constructor(
@inject(TYPES.GroupService) private groupService: GroupServiceInterface,
@inject(TYPES.GroupUserService) private groupUserService: GroupUserServiceInterface,
@inject(TYPES.ValetTokenEncoder) private tokenEncoder: TokenEncoderInterface<GroupValetTokenData>,
@inject(TYPES.VALET_TOKEN_TTL) private valetTokenTTL: number,
) {}
async execute(dto: CreateGroupFileValetTokenDTO): Promise<CreateValetTokenResponseData> {
const failValue: CreateValetTokenResponseData = {
success: false,
reason: 'invalid-parameters',
}
const groupUser = await this.groupUserService.getUserForGroup({
userUuid: dto.userUuid,
groupUuid: dto.groupUuid,
})
if (!groupUser) {
return failValue
}
if (groupUser.permissions === 'read' && dto.operation !== 'read') {
return failValue
}
const group = await this.groupService.getGroup({
groupUuid: dto.groupUuid,
})
if (!group) {
return failValue
}
if (dto.operation === ValetTokenOperation.Move) {
if (!dto.moveOperationType) {
return failValue
}
if (dto.moveOperationType === 'group-to-group') {
if (!dto.groupToGroupMoveTargetUuid) {
return failValue
}
const toGroupUser = await this.groupUserService.getUserForGroup({
userUuid: dto.userUuid,
groupUuid: dto.groupToGroupMoveTargetUuid,
})
if (!toGroupUser) {
return failValue
}
if (toGroupUser.permissions === 'read') {
return failValue
}
}
}
const moveOperation: GroupValetTokenData['moveOperation'] = !dto.moveOperationType
? undefined
: {
type: dto.moveOperationType,
fromUuid:
dto.moveOperationType === 'group-to-user'
? dto.groupUuid
: dto.moveOperationType === 'user-to-group'
? dto.userUuid
: dto.moveOperationType === 'group-to-group'
? dto.groupUuid
: 'invalid-uuid',
toUuid:
dto.moveOperationType === 'group-to-user'
? dto.userUuid
: dto.moveOperationType === 'user-to-group'
? dto.groupUuid
: dto.moveOperationType === 'group-to-group'
? (dto.groupToGroupMoveTargetUuid as string)
: 'invalid-uuid',
}
const tokenData: GroupValetTokenData = {
groupUuid: dto.groupUuid,
permittedOperation: dto.operation,
remoteIdentifier: dto.remoteIdentifier,
uploadBytesUsed: group.fileUploadBytesUsed,
uploadBytesLimit: group.fileUploadBytesLimit,
unencryptedFileSize: dto.unencryptedFileSize,
moveOperation: moveOperation,
}
const valetToken = this.tokenEncoder.encodeExpirableToken(tokenData, this.valetTokenTTL)
return { success: true, valetToken }
}
}
@@ -0,0 +1,12 @@
import { ValetTokenOperation, GroupMoveType } from '@standardnotes/security'
export type CreateGroupFileValetTokenDTO = {
userUuid: string
groupUuid: string
fileUuid?: string
remoteIdentifier: string
operation: ValetTokenOperation
unencryptedFileSize?: number
moveOperationType?: GroupMoveType
groupToGroupMoveTargetUuid?: string
}
@@ -1,104 +0,0 @@
import { inject, injectable } from 'inversify'
import { VaultValetTokenData, TokenEncoderInterface, ValetTokenOperation } from '@standardnotes/security'
import { CreateValetTokenResponseData } from '@standardnotes/responses'
import TYPES from '../../../Bootstrap/Types'
import { UseCaseInterface } from '../UseCaseInterface'
import { CreateVaultFileValetTokenDTO } from './CreateVaultFileValetTokenDTO'
import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface'
import { VaultServiceInterface } from '../../Vault/Service/VaultServiceInterface'
@injectable()
export class CreateVaultFileValetToken implements UseCaseInterface {
constructor(
@inject(TYPES.VaultService) private vaultService: VaultServiceInterface,
@inject(TYPES.VaultUserService) private vaultUserService: VaultUserServiceInterface,
@inject(TYPES.ValetTokenEncoder) private tokenEncoder: TokenEncoderInterface<VaultValetTokenData>,
@inject(TYPES.VALET_TOKEN_TTL) private valetTokenTTL: number,
) {}
async execute(dto: CreateVaultFileValetTokenDTO): Promise<CreateValetTokenResponseData> {
const failValue: CreateValetTokenResponseData = {
success: false,
reason: 'invalid-parameters',
}
const vaultUser = await this.vaultUserService.getUserForVault({
userUuid: dto.userUuid,
vaultUuid: dto.vaultUuid,
})
if (!vaultUser) {
return failValue
}
if (vaultUser.permissions === 'read' && dto.operation !== 'read') {
return failValue
}
const vault = await this.vaultService.getVault({
vaultUuid: dto.vaultUuid,
})
if (!vault) {
return failValue
}
if (dto.operation === ValetTokenOperation.Move) {
if (!dto.moveOperationType) {
return failValue
}
if (dto.moveOperationType === 'vault-to-vault') {
if (!dto.vaultToVaultMoveTargetUuid) {
return failValue
}
const toVaultUser = await this.vaultUserService.getUserForVault({
userUuid: dto.userUuid,
vaultUuid: dto.vaultToVaultMoveTargetUuid,
})
if (!toVaultUser) {
return failValue
}
if (toVaultUser.permissions === 'read') {
return failValue
}
}
}
const moveOperation: VaultValetTokenData['moveOperation'] = !dto.moveOperationType
? undefined
: {
type: dto.moveOperationType,
fromUuid:
dto.moveOperationType === 'vault-to-user'
? dto.vaultUuid
: dto.moveOperationType === 'user-to-vault'
? dto.userUuid
: dto.moveOperationType === 'vault-to-vault'
? dto.vaultUuid
: 'invalid-uuid',
toUuid:
dto.moveOperationType === 'vault-to-user'
? dto.userUuid
: dto.moveOperationType === 'user-to-vault'
? dto.vaultUuid
: dto.moveOperationType === 'vault-to-vault'
? (dto.vaultToVaultMoveTargetUuid as string)
: 'invalid-uuid',
}
const tokenData: VaultValetTokenData = {
vaultUuid: dto.vaultUuid,
permittedOperation: dto.operation,
remoteIdentifier: dto.remoteIdentifier,
uploadBytesUsed: vault.fileUploadBytesUsed,
uploadBytesLimit: vault.fileUploadBytesLimit,
unencryptedFileSize: dto.unencryptedFileSize,
moveOperation: moveOperation,
}
const valetToken = this.tokenEncoder.encodeExpirableToken(tokenData, this.valetTokenTTL)
return { success: true, valetToken }
}
}
@@ -1,12 +0,0 @@
import { ValetTokenOperation, VaultMoveType } from '@standardnotes/security'
export type CreateVaultFileValetTokenDTO = {
userUuid: string
vaultUuid: string
fileUuid?: string
remoteIdentifier: string
operation: ValetTokenOperation
unencryptedFileSize?: number
moveOperationType?: VaultMoveType
vaultToVaultMoveTargetUuid?: string
}
@@ -5,14 +5,14 @@ import { ItemServiceInterface } from '../Item/ItemServiceInterface'
import { SyncItemsDTO } from './SyncItemsDTO'
import { SyncItemsResponse } from './SyncItemsResponse'
import { UseCaseInterface } from './UseCaseInterface'
import { VaultInviteServiceInterface } from '../VaultInvite/Service/VaultInviteServiceInterface'
import { VaultServiceInterface } from '../Vault/Service/VaultServiceInterface'
import { GroupInviteServiceInterface } from '../GroupInvite/Service/GroupInviteServiceInterface'
import { GroupServiceInterface } from '../Group/Service/GroupServiceInterface'
export class SyncItems implements UseCaseInterface {
constructor(
private itemService: ItemServiceInterface,
private vaultService: VaultServiceInterface,
private vaultInviteService: VaultInviteServiceInterface,
private groupService: GroupServiceInterface,
private groupInviteService: GroupInviteServiceInterface,
private contactService: ContactServiceInterface,
) {}
@@ -20,7 +20,7 @@ export class SyncItems implements UseCaseInterface {
const getItemsResult = await this.itemService.getItems({
userUuid: dto.userUuid,
syncToken: dto.syncToken,
vaultUuids: dto.vaultUuids,
groupUuids: dto.groupUuids,
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 isVaultExclusiveSync = dto.vaultUuids && dto.vaultUuids.length > 0
if (this.isFirstSync(dto) && !isVaultExclusiveSync) {
const isGroupExclusiveSync = dto.groupUuids && dto.groupUuids.length > 0
if (this.isFirstSync(dto) && !isGroupExclusiveSync) {
retrievedItems = await this.itemService.frontLoadKeysItemsToTop(dto.userUuid, retrievedItems)
}
@@ -46,12 +46,12 @@ export class SyncItems implements UseCaseInterface {
cursorToken: dto.cursorToken,
})
const vaults = await this.vaultService.getVaults({
const groups = await this.groupService.getGroups({
userUuid: dto.userUuid,
lastSyncTime,
})
const vaultInvites = await this.vaultInviteService.getInvitesForUser({
const groupInvites = await this.groupInviteService.getInvitesForUser({
userUuid: dto.userUuid,
lastSyncTime,
})
@@ -67,8 +67,8 @@ export class SyncItems implements UseCaseInterface {
savedItems: saveItemsResult.savedItems,
conflicts: saveItemsResult.conflicts,
cursorToken: getItemsResult.cursorToken,
vaults,
vaultInvites,
groups,
groupInvites,
contacts,
}
@@ -5,7 +5,7 @@ export type SyncItemsDTO = {
itemHashes: Array<ItemHash>
computeIntegrityHash: boolean
limit: number
vaultUuids?: string[] | null
groupUuids?: string[] | null
syncToken?: string | null
cursorToken?: string | null
contentType?: string
@@ -1,15 +1,15 @@
import { VaultInvite } from '../VaultInvite/Model/VaultInvite'
import { GroupInvite } from '../GroupInvite/Model/GroupInvite'
import { Contact } from '../Contact/Model/Contact'
import { Item } from '../Item/Item'
import { ItemConflict } from '../Item/ItemConflict'
import { Vault } from '../Vault/Model/Vault'
import { Group } from '../Group/Model/Group'
export type SyncItemsResponse = {
retrievedItems: Array<Item>
savedItems: Array<Item>
conflicts: Array<ItemConflict>
vaults: Array<Vault>
vaultInvites: Array<VaultInvite>
groups: Array<Group>
groupInvites: Array<GroupInvite>
contacts: Array<Contact>
syncToken: string
cursorToken?: string
@@ -1,32 +0,0 @@
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.fileUploadBytesUsed = dto.vaultHash.file_upload_bytes_used
newVault.fileUploadBytesLimit = dto.vaultHash.file_upload_bytes_limit
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
}
}
@@ -1,7 +0,0 @@
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
}
@@ -1,56 +0,0 @@
import { Repository, SelectQueryBuilder } from 'typeorm'
import { VaultsRepositoryInterface, UserVaultsQuery } from './VaultRepositoryInterface'
import { Vault } from '../Model/Vault'
export class TypeORMVaultRepository implements VaultsRepositoryInterface {
constructor(private ormRepository: Repository<Vault>) {}
async create(vault: Vault): Promise<Vault> {
return this.ormRepository.save(vault)
}
async save(vault: Vault): Promise<Vault> {
return this.ormRepository.save(vault)
}
findByUuid(uuid: string): Promise<Vault | null> {
return this.ormRepository
.createQueryBuilder('vault')
.where('vault.uuid = :uuid', {
uuid,
})
.getOne()
}
async remove(vault: Vault): Promise<Vault> {
return this.ormRepository.remove(vault)
}
async findAll(query: UserVaultsQuery): Promise<Vault[]> {
return this.createFindAllQueryBuilder(query).getMany()
}
private createFindAllQueryBuilder(query: UserVaultsQuery): SelectQueryBuilder<Vault> {
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
}
}
@@ -1,19 +0,0 @@
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<Vault | null>
create(vault: Vault): Promise<Vault>
save(vault: Vault): Promise<Vault>
remove(vault: Vault): Promise<Vault>
findAll(query: UserVaultsQuery): Promise<Vault[]>
}
@@ -1,101 +0,0 @@
import { TimerInterface } from '@standardnotes/time'
import { Vault } from '../Model/Vault'
import { VaultsRepositoryInterface } from '../Repository/VaultRepositoryInterface'
import { CreateVaultDTO, VaultServiceInterface, UpdateVaultDTO, CreateVaultResult } 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<CreateVaultResult | null> {
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,
file_upload_bytes_limit: 1_000_000,
file_upload_bytes_used: 0,
created_at_timestamp: timestamp,
updated_at_timestamp: timestamp,
},
})
const savedVault = await this.vaultRepository.create(vault)
const vaultUser = await this.vaultUserService.addVaultUser({
vaultUuid: savedVault.uuid,
userUuid: dto.userUuid,
permissions: 'admin',
})
return { vault, vaultUser }
}
async getVault(dto: { vaultUuid: string }): Promise<Vault | null> {
const vault = await this.vaultRepository.findByUuid(dto.vaultUuid)
return vault
}
async getVaults(dto: { userUuid: string; lastSyncTime?: number }): Promise<Vault[]> {
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<Vault | null> {
const vault = await this.vaultRepository.findByUuid(dto.vaultUuid)
if (!vault || vault.userUuid !== dto.originatorUuid) {
return null
}
vault.specifiedItemsKeyUuid = dto.specifiedItemsKeyUuid
vault.updatedAtTimestamp = this.timer.getTimestampInMicroseconds()
const savedVault = await this.vaultRepository.save(vault)
return savedVault
}
async deleteVault(dto: { vaultUuid: string; originatorUuid: string }): Promise<boolean> {
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
}
}
@@ -1,31 +0,0 @@
import { VaultUser } from './../../VaultUser/Model/VaultUser'
import { Vault } from '../Model/Vault'
export type CreateVaultDTO = {
userUuid: string
vaultUuid: string
specifiedItemsKeyUuid: string
}
export type CreateVaultResult = {
vault: Vault
vaultUser: VaultUser
}
export type UpdateVaultDTO = {
originatorUuid: string
vaultUuid: string
specifiedItemsKeyUuid: string
}
export interface VaultServiceInterface {
createVault(dto: CreateVaultDTO): Promise<CreateVaultResult | null>
updateVault(dto: UpdateVaultDTO): Promise<Vault | null>
deleteVault(dto: { vaultUuid: string; originatorUuid: string }): Promise<boolean>
getVault(dto: { vaultUuid: string }): Promise<Vault | null>
getVaults(dto: { userUuid: string; lastSyncTime?: number }): Promise<Vault[]>
}
@@ -1,35 +0,0 @@
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
}
}
@@ -1,7 +0,0 @@
import { VaultInvite } from '../Model/VaultInvite'
import { VaultInviteHash } from './VaultInviteHash'
export interface VaultInviteFactoryInterface {
create(dto: VaultInviteHash): VaultInvite
createStub(dto: VaultInviteHash): VaultInvite
}
@@ -1,15 +0,0 @@
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
}
@@ -1,71 +0,0 @@
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<VaultInvite>) {}
async create(vaultInvite: VaultInvite): Promise<VaultInvite> {
return this.ormRepository.save(vaultInvite)
}
async save(vaultInvite: VaultInvite): Promise<VaultInvite> {
return this.ormRepository.save(vaultInvite)
}
findByUuid(uuid: string): Promise<VaultInvite | null> {
return this.ormRepository
.createQueryBuilder('vault_invite')
.where('vault_invite.uuid = :uuid', {
uuid,
})
.getOne()
}
findByUserUuidAndVaultUuid(userUuid: string, vaultUuid: string): Promise<VaultInvite | null> {
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<VaultInvite> {
return this.ormRepository.remove(vaultInvite)
}
findAllForVault(query: VaultInviteFindAllForVault): Promise<VaultInvite[]> {
return this.ormRepository
.createQueryBuilder('vault_invite')
.where('vault_invite.vault_uuid = :vaultUuid', { vaultUuid: query.vaultUuid })
.getMany()
}
async findAll(query: VaultInviteFindAllForUserQuery): Promise<VaultInvite[]> {
return this.createFindAllQueryBuilder(query).getMany()
}
private createFindAllQueryBuilder(query: VaultInviteFindAllForUserQuery): SelectQueryBuilder<VaultInvite> {
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
}
}
@@ -1,25 +0,0 @@
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<VaultInvite | null>
create(vault: VaultInvite): Promise<VaultInvite>
save(vaultInvite: VaultInvite): Promise<VaultInvite>
remove(vault: VaultInvite): Promise<VaultInvite>
findAll(query: VaultInviteFindAllForUserQuery): Promise<VaultInvite[]>
findAllForVault(query: VaultInviteFindAllForVault): Promise<VaultInvite[]>
findByUserUuidAndVaultUuid(userUuid: string, vaultUuid: string): Promise<VaultInvite | null>
}
@@ -1,12 +0,0 @@
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
}
@@ -1,9 +0,0 @@
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
export type UpdateInviteDTO = {
originatorUuid: string
inviteUuid: string
inviterPublicKey: string
encryptedVaultData: string
permissions?: VaultUserPermission
}
@@ -1,172 +0,0 @@
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<VaultInvite | null> {
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<VaultInvite | null> {
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<VaultInvite[]> {
return this.vaultInviteRepository.findAll(dto)
}
getOutboundInvitesForUser(dto: { userUuid: string }): Promise<VaultInvite[]> {
return this.vaultInviteRepository.findAll({
inviterUuid: dto.userUuid,
})
}
async deleteAllInboundInvites(dto: { userUuid: string }): Promise<void> {
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<VaultInvite[] | undefined> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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
}
}
@@ -1,26 +0,0 @@
import { VaultInvite } from '../Model/VaultInvite'
import { CreateInviteDTO } from './CreateInviteDTO'
import { GetUserVaultInvitesDTO } from './GetUserVaultInvitesDTO'
import { UpdateInviteDTO } from './UpdateInviteDTO'
export interface VaultInviteServiceInterface {
createInvite(dto: CreateInviteDTO): Promise<VaultInvite | null>
updateInvite(dto: UpdateInviteDTO): Promise<VaultInvite | null>
getInvitesForUser(dto: GetUserVaultInvitesDTO): Promise<VaultInvite[]>
getInvitesForVault(dto: { vaultUuid: string; originatorUuid: string }): Promise<VaultInvite[] | undefined>
getOutboundInvitesForUser(dto: { userUuid: string }): Promise<VaultInvite[]>
acceptInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean>
declineInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean>
deleteInvite(dto: { originatorUuid: string; vaultUuid: string; inviteUuid: string }): Promise<boolean>
deleteAllInvitesForVault(dto: { originatorUuid: string; vaultUuid: string }): Promise<boolean>
deleteAllInboundInvites(dto: { userUuid: string }): Promise<void>
}
@@ -1,31 +0,0 @@
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
}
}
@@ -1,7 +0,0 @@
import { VaultUser } from '../Model/VaultUser'
import { VaultUserHash } from './VaultUserHash'
export interface VaultUserFactoryInterface {
create(dto: VaultUserHash): VaultUser
createStub(dto: VaultUserHash): VaultUser
}
@@ -1,10 +0,0 @@
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
}

Some files were not shown because too many files have changed in this diff Show More