refactor: rename groups to vaults

This commit is contained in:
Mo
2023-05-28 10:30:10 -05:00
parent fc0f25e740
commit cd92f7827c
120 changed files with 1592 additions and 1517 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ import '../src/Controller/v1/InvoicesController'
import '../src/Controller/v1/RevisionsController'
import '../src/Controller/v1/ItemsController'
import '../src/Controller/v1/ContactsController'
import '../src/Controller/v1/GroupsController'
import '../src/Controller/v1/VaultsController'
import '../src/Controller/v1/PaymentsController'
import '../src/Controller/v1/WebSocketsController'
import '../src/Controller/v1/TokensController'
+1 -1
View File
@@ -10,7 +10,7 @@ export * from './v1/FilesController'
export * from './v1/InvoicesController'
export * from './v1/ItemsController'
export * from './v1/ContactsController'
export * from './v1/GroupsController'
export * from './v1/VaultsController'
export * from './v1/OfflineController'
export * from './v1/PaymentsController'
export * from './v1/RevisionsController'
@@ -4,8 +4,8 @@ import { BaseHttpController, controller, all } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/groups')
export class GroupsController extends BaseHttpController {
@controller('/v1/vaults')
export class VaultsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@@ -1,7 +1,7 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class removeGroups1665047863774 implements MigrationInterface {
name = 'removeGroups1665047863774'
export class removeVaults1665047863774 implements MigrationInterface {
name = 'removeVaults1665047863774'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `group_users` DROP FOREIGN KEY `FK_9d1bcb8c649eb05d7a2eb62114e`')
@@ -3,7 +3,7 @@ export enum ContentType {
Any = '*',
Item = 'SF|Item',
SharedItemsKey = 'SN|SharedItemsKey',
GroupKey = 'SN|GroupKey',
VaultKey = 'SN|VaultKey',
TrustedContact = 'SN|TrustedContact',
RootKey = 'SN|RootKey|NoSync',
ItemsKey = 'SN|ItemsKey',
+1 -1
View File
@@ -6,7 +6,7 @@ import * as busboy from 'connect-busboy'
import '../src/Controller/HealthCheckController'
import '../src/Controller/FilesController'
import '../src/Controller/GroupFilesController'
import '../src/Controller/VaultFilesController'
import helmet from 'helmet'
import * as cors from 'cors'
+2 -2
View File
@@ -44,7 +44,7 @@ import { MarkFilesToBeRemoved } from '../Domain/UseCase/MarkFilesToBeRemoved/Mar
import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountDeletionRequestedEventHandler'
import { SharedSubscriptionInvitationCanceledEventHandler } from '../Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler'
import { InMemoryUploadRepository } from '../Infra/InMemory/InMemoryUploadRepository'
import { GroupValetTokenAuthMiddleware } from '../Controller/GroupValetTokenAuthMiddleware'
import { VaultValetTokenAuthMiddleware } from '../Controller/VaultValetTokenAuthMiddleware'
export class ContainerConfigLoader {
async load(): Promise<Container> {
@@ -150,7 +150,7 @@ export class ContainerConfigLoader {
// middleware
container.bind<ValetTokenAuthMiddleware>(TYPES.ValetTokenAuthMiddleware).to(ValetTokenAuthMiddleware)
container.bind<GroupValetTokenAuthMiddleware>(TYPES.GroupValetTokenAuthMiddleware).to(GroupValetTokenAuthMiddleware)
container.bind<VaultValetTokenAuthMiddleware>(TYPES.VaultValetTokenAuthMiddleware).to(VaultValetTokenAuthMiddleware)
// services
container
+1 -1
View File
@@ -29,7 +29,7 @@ const TYPES = {
// middleware
ValetTokenAuthMiddleware: Symbol.for('ValetTokenAuthMiddleware'),
GroupValetTokenAuthMiddleware: Symbol.for('GroupValetTokenAuthMiddleware'),
VaultValetTokenAuthMiddleware: Symbol.for('VaultValetTokenAuthMiddleware'),
// env vars
S3_ENDPOINT: Symbol.for('S3_ENDPOINT'),
@@ -5,10 +5,10 @@ import { Writable } from 'stream'
import TYPES from '../Bootstrap/Types'
import { StreamDownloadFile } from '../Domain/UseCase/StreamDownloadFile/StreamDownloadFile'
import { GetFileMetadata } from '../Domain/UseCase/GetFileMetadata/GetFileMetadata'
import { GroupValetTokenData, ValetTokenOperation } from '@standardnotes/security'
import { VaultValetTokenData, ValetTokenOperation } from '@standardnotes/security'
@controller('/v1/groups/files', TYPES.GroupValetTokenAuthMiddleware)
export class GroupFilesController extends BaseHttpController {
@controller('/v1/vault/files', TYPES.VaultValetTokenAuthMiddleware)
export class VaultFilesController extends BaseHttpController {
constructor(
@inject(TYPES.StreamDownloadFile) private streamDownloadFile: StreamDownloadFile,
@inject(TYPES.GetFileMetadata) private getFileMetadata: GetFileMetadata,
@@ -36,10 +36,10 @@ export class GroupFilesController extends BaseHttpController {
chunkSize = this.maxChunkBytes
}
const locals = response.locals as GroupValetTokenData
const locals = response.locals as VaultValetTokenData
const fileMetadata = await this.getFileMetadata.execute({
userUuid: locals.fileOwnerUuid,
vaultUuid: locals.vaultUuid,
resourceRemoteIdentifier: locals.remoteIdentifier,
})
@@ -60,7 +60,7 @@ export class GroupFilesController extends BaseHttpController {
response.writeHead(206, headers)
const result = await this.streamDownloadFile.execute({
userUuid: locals.fileOwnerUuid,
vaultUuid: locals.vaultUuid,
resourceRemoteIdentifier: locals.remoteIdentifier,
startRange,
endRange,
@@ -1,4 +1,4 @@
import { GroupValetTokenData, TokenDecoderInterface } from '@standardnotes/security'
import { VaultValetTokenData, TokenDecoderInterface } from '@standardnotes/security'
import { Uuid } from '@standardnotes/domain-core'
import { NextFunction, Request, Response } from 'express'
import { inject, injectable } from 'inversify'
@@ -7,9 +7,9 @@ import { Logger } from 'winston'
import TYPES from '../Bootstrap/Types'
@injectable()
export class GroupValetTokenAuthMiddleware extends BaseMiddleware {
export class VaultValetTokenAuthMiddleware extends BaseMiddleware {
constructor(
@inject(TYPES.ValetTokenDecoder) private tokenDecoder: TokenDecoderInterface<GroupValetTokenData>,
@inject(TYPES.ValetTokenDecoder) private tokenDecoder: TokenDecoderInterface<VaultValetTokenData>,
@inject(TYPES.Logger) private logger: Logger,
) {
super()
@@ -19,7 +19,7 @@ export class GroupValetTokenAuthMiddleware extends BaseMiddleware {
try {
const valetToken = request.headers['x-valet-token'] || request.body.valetToken || request.query.valetToken
if (!valetToken) {
this.logger.debug('GroupValetTokenAuthMiddleware missing valet token.')
this.logger.debug('VaultValetTokenAuthMiddleware missing valet token.')
response.status(401).send({
error: {
@@ -34,7 +34,7 @@ export class GroupValetTokenAuthMiddleware extends BaseMiddleware {
const valetTokenData = this.tokenDecoder.decodeToken(valetToken)
if (valetTokenData === undefined) {
this.logger.debug('GroupValetTokenAuthMiddleware authentication failure.')
this.logger.debug('VaultValetTokenAuthMiddleware authentication failure.')
response.status(401).send({
error: {
@@ -60,9 +60,8 @@ export class GroupValetTokenAuthMiddleware extends BaseMiddleware {
return
}
const whitelistedData: GroupValetTokenData = {
groupUuid: valetTokenData.groupUuid,
fileOwnerUuid: valetTokenData.fileOwnerUuid,
const whitelistedData: VaultValetTokenData = {
vaultUuid: valetTokenData.vaultUuid,
remoteIdentifier: valetTokenData.remoteIdentifier,
permittedOperation: valetTokenData.permittedOperation,
}
@@ -3,7 +3,7 @@ import { Logger } from 'winston'
import TYPES from '../../../Bootstrap/Types'
import { FileDownloaderInterface } from '../../Services/FileDownloaderInterface'
import { UseCaseInterface } from '../UseCaseInterface'
import { GetFileMetadataDTO } from './GetFileMetadataDTO'
import { GetFileMetadataDTO, isGetFileMetadataVaultOwned } from './GetFileMetadataDTO'
import { GetFileMetadataResponse } from './GetFileMetadataResponse'
@injectable()
@@ -14,15 +14,16 @@ export class GetFileMetadata implements UseCaseInterface {
) {}
async execute(dto: GetFileMetadataDTO): Promise<GetFileMetadataResponse> {
const ownerUuid = isGetFileMetadataVaultOwned(dto) ? dto.vaultUuid : dto.userUuid
try {
const size = await this.fileDownloader.getFileSize(`${dto.userUuid}/${dto.resourceRemoteIdentifier}`)
const size = await this.fileDownloader.getFileSize(`${ownerUuid}/${dto.resourceRemoteIdentifier}`)
return {
success: true,
size,
}
} catch (error) {
this.logger.error(`Could not get file metadata for resource: ${dto.userUuid}/${dto.resourceRemoteIdentifier}`)
this.logger.error(`Could not get file metadata for resource: ${ownerUuid}/${dto.resourceRemoteIdentifier}`)
return {
success: false,
message: 'Could not get file metadata.',
@@ -1,4 +1,15 @@
export type GetFileMetadataDTO = {
export type GetFileMetadataDTO = GetVaultFileMetadataDTO | GetUserFileMetadataDTO
export type GetVaultFileMetadataDTO = {
vaultUuid: string
resourceRemoteIdentifier: string
}
export type GetUserFileMetadataDTO = {
userUuid: string
resourceRemoteIdentifier: string
}
export function isGetFileMetadataVaultOwned(dto: GetFileMetadataDTO): dto is GetVaultFileMetadataDTO {
return (dto as GetVaultFileMetadataDTO).vaultUuid !== undefined
}
@@ -3,7 +3,7 @@ import { Logger } from 'winston'
import TYPES from '../../../Bootstrap/Types'
import { FileDownloaderInterface } from '../../Services/FileDownloaderInterface'
import { UseCaseInterface } from '../UseCaseInterface'
import { StreamDownloadFileDTO } from './StreamDownloadFileDTO'
import { StreamDownloadFileDTO, isStreamDownloadFileVaultOwned } from './StreamDownloadFileDTO'
import { StreamDownloadFileResponse } from './StreamDownloadFileResponse'
@injectable()
@@ -14,9 +14,10 @@ export class StreamDownloadFile implements UseCaseInterface {
) {}
async execute(dto: StreamDownloadFileDTO): Promise<StreamDownloadFileResponse> {
const ownerUuid = isStreamDownloadFileVaultOwned(dto) ? dto.vaultUuid : dto.userUuid
try {
const readStream = await this.fileDownloader.createDownloadStream(
`${dto.userUuid}/${dto.resourceRemoteIdentifier}`,
`${ownerUuid}/${dto.resourceRemoteIdentifier}`,
dto.startRange,
dto.endRange,
)
@@ -26,9 +27,7 @@ export class StreamDownloadFile implements UseCaseInterface {
readStream,
}
} catch (error) {
this.logger.error(
`Could not create a download stream for resource: ${dto.userUuid}/${dto.resourceRemoteIdentifier}`,
)
this.logger.error(`Could not create a download stream for resource: ${ownerUuid}/${dto.resourceRemoteIdentifier}`)
return {
success: false,
@@ -1,6 +1,19 @@
export type StreamDownloadFileDTO = {
export type StreamDownloadFileDTO = StreamDownloadVaultFileDTO | StreamDownloadUserFileDTO
export type StreamDownloadVaultFileDTO = {
vaultUuid: string
resourceRemoteIdentifier: string
startRange: number
endRange: number
}
export type StreamDownloadUserFileDTO = {
userUuid: string
resourceRemoteIdentifier: string
startRange: number
endRange: number
}
export function isStreamDownloadFileVaultOwned(dto: StreamDownloadFileDTO): dto is StreamDownloadVaultFileDTO {
return (dto as StreamDownloadVaultFileDTO).vaultUuid !== undefined
}
@@ -1,8 +0,0 @@
import { ValetTokenOperation } from './ValetTokenOperation'
export type GroupValetTokenData = {
fileOwnerUuid: string
groupUuid: string
permittedOperation: ValetTokenOperation.Read
remoteIdentifier: string
}
@@ -0,0 +1,7 @@
import { ValetTokenOperation } from './ValetTokenOperation'
export type VaultValetTokenData = {
vaultUuid: string
permittedOperation: ValetTokenOperation
remoteIdentifier: string
}
+1 -1
View File
@@ -10,7 +10,7 @@ export * from './Token/CrossServiceTokenData'
export * from './Token/OfflineFeaturesTokenData'
export * from './Token/OfflineUserTokenData'
export * from './Token/SessionTokenData'
export * from './Token/GroupValetTokenData'
export * from './Token/VaultValetTokenData'
export * from './Token/ValetTokenData'
export * from './Token/ValetTokenOperation'
export * from './Token/WebSocketConnectionToken'
+1 -1
View File
@@ -3,5 +3,5 @@
### Generating a migration
```
yarn build && yarn typeorm migration:generate -d dist/src/Bootstrap/DataSource.js migrations/mysql/add_group_uuid_to_items
yarn build && yarn typeorm migration:generate -d dist/src/Bootstrap/DataSource.js migrations/mysql/add_vault_uuid_to_items
```
+3 -3
View File
@@ -4,9 +4,9 @@ import 'newrelic'
import '../src/Controller/HealthCheckController'
import '../src/Controller/ItemsController'
import '../src/Controller/GroupsController'
import '../src/Controller/GroupUsersController'
import '../src/Controller/GroupInvitesController'
import '../src/Controller/VaultsController'
import '../src/Controller/VaultUsersController'
import '../src/Controller/VaultInvitesController'
import '../src/Controller/ContactsController'
import helmet from 'helmet'
@@ -1,11 +1,11 @@
import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'
export class CreateGroup1684173017359 implements MigrationInterface {
name = 'createGroup1684173017359'
export class CreateVault1684173017359 implements MigrationInterface {
name = 'createVault1684173017359'
public async up(queryRunner: QueryRunner): Promise<void> {
const table = new Table({
name: 'groups',
name: 'vaults',
columns: [
new TableColumn({
name: 'uuid',
@@ -26,7 +26,7 @@ export class CreateGroup1684173017359 implements MigrationInterface {
isNullable: false,
}),
new TableColumn({
name: 'group_key_timestamp',
name: 'vault_key_timestamp',
type: 'bigint',
}),
new TableColumn({
@@ -44,6 +44,6 @@ export class CreateGroup1684173017359 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('groups')
await queryRunner.dropTable('vaults')
}
}
@@ -1,11 +1,11 @@
import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'
export class CreateGroupUser1684173023883 implements MigrationInterface {
name = 'createGroupUser1684173023883'
export class CreateVaultUser1684173023883 implements MigrationInterface {
name = 'createVaultUser1684173023883'
public async up(queryRunner: QueryRunner): Promise<void> {
const table = new Table({
name: 'group_users',
name: 'vault_users',
columns: [
new TableColumn({
name: 'uuid',
@@ -14,7 +14,7 @@ export class CreateGroupUser1684173023883 implements MigrationInterface {
isPrimary: true,
}),
new TableColumn({
name: 'group_uuid',
name: 'vault_uuid',
type: 'varchar',
length: '36',
isNullable: false,
@@ -46,6 +46,6 @@ export class CreateGroupUser1684173023883 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('group_users')
await queryRunner.dropTable('vault_users')
}
}
@@ -1,13 +1,13 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'
export class AddGroupUuidToItems1684318117584 implements MigrationInterface {
name = 'addGroupUuidToItems1684318117584'
export class AddVaultUuidToItems1684318117584 implements MigrationInterface {
name = 'addVaultUuidToItems1684318117584'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
'items',
new TableColumn({
name: 'group_uuid',
name: 'vault_uuid',
type: 'varchar',
length: '36',
isNullable: true,
@@ -25,7 +25,7 @@ export class AddGroupUuidToItems1684318117584 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('items', 'group_uuid')
await queryRunner.dropColumn('items', 'vault_uuid')
await queryRunner.dropColumn('items', 'last_edited_by_uuid')
}
}
@@ -1,11 +1,11 @@
import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'
export class CreateGroupInvites1684528646896 implements MigrationInterface {
name = 'createGroupInvites1684528646896'
export class CreateVaultInvites1684528646896 implements MigrationInterface {
name = 'createVaultInvites1684528646896'
public async up(queryRunner: QueryRunner): Promise<void> {
const table = new Table({
name: 'group_invites',
name: 'vault_invites',
columns: [
new TableColumn({
name: 'uuid',
@@ -14,7 +14,7 @@ export class CreateGroupInvites1684528646896 implements MigrationInterface {
isPrimary: true,
}),
new TableColumn({
name: 'group_uuid',
name: 'vault_uuid',
type: 'varchar',
length: '36',
isNullable: false,
@@ -44,7 +44,7 @@ export class CreateGroupInvites1684528646896 implements MigrationInterface {
isNullable: false,
}),
new TableColumn({
name: 'encrypted_group_data',
name: 'encrypted_vault_data',
type: 'text',
isNullable: false,
}),
@@ -69,6 +69,6 @@ export class CreateGroupInvites1684528646896 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('group_invites')
await queryRunner.dropTable('vault_invites')
}
}
@@ -0,0 +1,31 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'
export class AllowItemUserUuidToBeNull1685276859271 implements MigrationInterface {
name = 'allowItemUserUuidToBeNull1685276859271'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.changeColumn(
'items',
'user_uuid',
new TableColumn({
name: 'user_uuid',
type: 'varchar',
length: '36',
isNullable: true,
}),
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.changeColumn(
'items',
'user_uuid',
new TableColumn({
name: 'user_uuid',
type: 'varchar',
length: '36',
isNullable: false,
}),
)
}
}
@@ -1,9 +1,9 @@
import { TypeORMGroupInviteRepository } from './../Domain/GroupInvite/Repository/TypeORMGroupInviteRepository'
import { TypeORMVaultInviteRepository } from '../Domain/VaultInvite/Repository/TypeORMVaultInviteRepository'
import { Contact } from './../Domain/Contact/Model/Contact'
import { GroupInvite } from './../Domain/GroupInvite/Model/GroupInvite'
import { GroupUser } from '../Domain/GroupUser/Model/GroupUser'
import { Group } from './../Domain/Group/Model/Group'
import { TypeORMGroupRepository } from './../Domain/Group/Repository/TypeORMGroupRepository'
import { VaultInvite } from '../Domain/VaultInvite/Model/VaultInvite'
import { VaultUser } from '../Domain/VaultUser/Model/VaultUser'
import { Vault } from '../Domain/Vault/Model/Vault'
import { TypeORMVaultRepository } from '../Domain/Vault/Repository/TypeORMVaultRepository'
import * as winston from 'winston'
import { Container, interfaces } from 'inversify'
@@ -24,18 +24,18 @@ import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
import { Timer, TimerInterface } from '@standardnotes/time'
import { ItemTransferCalculatorInterface } from '../Domain/Item/ItemTransferCalculatorInterface'
import { ItemTransferCalculator } from '../Domain/Item/ItemTransferCalculator'
import { GroupsRepositoryInterface } from '../Domain/Group/Repository/GroupRepositoryInterface'
import { GroupUserRepositoryInterface } from '../Domain/GroupUser/Repository/GroupUserRepositoryInterface'
import { TypeORMGroupUserRepository } from '../Domain/GroupUser/Repository/TypeORMGroupUserRepository'
import { GroupUserProjection } from '../Projection/GroupUserProjection'
import { GroupUserProjector } from '../Projection/GroupUserProjector'
import { GroupProjection } from '../Projection/GroupProjection'
import { GroupProjector } from '../Projection/GroupProjector'
import { GroupInviteProjection } from '../Projection/GroupInviteProjection'
import { GroupInviteProjector } from '../Projection/GroupInviteProjector'
import { VaultsRepositoryInterface } from '../Domain/Vault/Repository/VaultRepositoryInterface'
import { VaultUserRepositoryInterface } from '../Domain/VaultUser/Repository/VaultUserRepositoryInterface'
import { TypeORMVaultUserRepository } from '../Domain/VaultUser/Repository/TypeORMVaultUserRepository'
import { VaultUserProjection } from '../Projection/VaultUserProjection'
import { VaultUserProjector } from '../Projection/VaultUserProjector'
import { VaultProjection } from '../Projection/VaultProjection'
import { VaultProjector } from '../Projection/VaultProjector'
import { VaultInviteProjection } from '../Projection/VaultInviteProjection'
import { VaultInviteProjector } from '../Projection/VaultInviteProjector'
import { ContactProjection } from '../Projection/ContactProjection'
import { ContactProjector } from '../Projection/ContactProjector'
import { GroupInviteRepositoryInterface } from '../Domain/GroupInvite/Repository/GroupInviteRepositoryInterface'
import { VaultInviteRepositoryInterface } from '../Domain/VaultInvite/Repository/VaultInviteRepositoryInterface'
import { ContactsRepositoryInterface } from '../Domain/Contact/Repository/ContactRepositoryInterface'
import { TypeORMContactRepository } from '../Domain/Contact/Repository/TypeORMContactRepository'
// eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -96,18 +96,18 @@ export class CommonContainerConfigLoader {
container.bind<ItemRepositoryInterface>(TYPES.ItemRepository).toDynamicValue((context: interfaces.Context) => {
return new TypeORMItemRepository(context.container.get(TYPES.ORMItemRepository))
})
container.bind<GroupsRepositoryInterface>(TYPES.GroupRepository).toDynamicValue((context: interfaces.Context) => {
return new TypeORMGroupRepository(context.container.get(TYPES.ORMGroupRepository))
container.bind<VaultsRepositoryInterface>(TYPES.VaultRepository).toDynamicValue((context: interfaces.Context) => {
return new TypeORMVaultRepository(context.container.get(TYPES.ORMVaultRepository))
})
container
.bind<GroupUserRepositoryInterface>(TYPES.GroupUserRepository)
.bind<VaultUserRepositoryInterface>(TYPES.VaultUserRepository)
.toDynamicValue((context: interfaces.Context) => {
return new TypeORMGroupUserRepository(context.container.get(TYPES.ORMGroupUserRepository))
return new TypeORMVaultUserRepository(context.container.get(TYPES.ORMVaultUserRepository))
})
container
.bind<GroupInviteRepositoryInterface>(TYPES.GroupInviteRepository)
.bind<VaultInviteRepositoryInterface>(TYPES.VaultInviteRepository)
.toDynamicValue((context: interfaces.Context) => {
return new TypeORMGroupInviteRepository(context.container.get(TYPES.ORMGroupInviteRepository))
return new TypeORMVaultInviteRepository(context.container.get(TYPES.ORMVaultInviteRepository))
})
container
.bind<ContactsRepositoryInterface>(TYPES.ContactRepository)
@@ -117,13 +117,13 @@ export class CommonContainerConfigLoader {
// ORM
container.bind<Repository<Item>>(TYPES.ORMItemRepository).toDynamicValue(() => AppDataSource.getRepository(Item))
container.bind<Repository<Group>>(TYPES.ORMGroupRepository).toDynamicValue(() => AppDataSource.getRepository(Group))
container.bind<Repository<Vault>>(TYPES.ORMVaultRepository).toDynamicValue(() => AppDataSource.getRepository(Vault))
container
.bind<Repository<GroupUser>>(TYPES.ORMGroupUserRepository)
.toDynamicValue(() => AppDataSource.getRepository(GroupUser))
.bind<Repository<VaultUser>>(TYPES.ORMVaultUserRepository)
.toDynamicValue(() => AppDataSource.getRepository(VaultUser))
container
.bind<Repository<GroupInvite>>(TYPES.ORMGroupInviteRepository)
.toDynamicValue(() => AppDataSource.getRepository(GroupInvite))
.bind<Repository<VaultInvite>>(TYPES.ORMVaultInviteRepository)
.toDynamicValue(() => AppDataSource.getRepository(VaultInvite))
container
.bind<Repository<Contact>>(TYPES.ORMContactRepository)
.toDynamicValue(() => AppDataSource.getRepository(Contact))
@@ -134,16 +134,16 @@ export class CommonContainerConfigLoader {
.toDynamicValue((context: interfaces.Context) => {
return new ItemProjector(context.container.get(TYPES.Timer))
})
container.bind<ProjectorInterface<Group, GroupProjection>>(TYPES.GroupProjector).toDynamicValue(() => {
return new GroupProjector()
container.bind<ProjectorInterface<Vault, VaultProjection>>(TYPES.VaultProjector).toDynamicValue(() => {
return new VaultProjector()
})
container.bind<ProjectorInterface<GroupUser, GroupUserProjection>>(TYPES.GroupUserProjector).toDynamicValue(() => {
return new GroupUserProjector()
container.bind<ProjectorInterface<VaultUser, VaultUserProjection>>(TYPES.VaultUserProjector).toDynamicValue(() => {
return new VaultUserProjector()
})
container
.bind<ProjectorInterface<GroupInvite, GroupInviteProjection>>(TYPES.GroupInviteProjector)
.bind<ProjectorInterface<VaultInvite, VaultInviteProjection>>(TYPES.VaultInviteProjector)
.toDynamicValue(() => {
return new GroupInviteProjector()
return new VaultInviteProjector()
})
container.bind<ProjectorInterface<Contact, ContactProjection>>(TYPES.ContactProjector).toDynamicValue(() => {
return new ContactProjector()
@@ -1,12 +1,12 @@
import { Contact } from './../Domain/Contact/Model/Contact'
import { GroupInvite } from './../Domain/GroupInvite/Model/GroupInvite'
import { GroupUser } from '../Domain/GroupUser/Model/GroupUser'
import { VaultInvite } from '../Domain/VaultInvite/Model/VaultInvite'
import { VaultUser } from '../Domain/VaultUser/Model/VaultUser'
import { DataSource, LoggerOptions } from 'typeorm'
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
import { Item } from '../Domain/Item/Item'
import { Env } from './Env'
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
import { Group } from '../Domain/Group/Model/Group'
import { Vault } from '../Domain/Vault/Model/Vault'
const env: Env = new Env()
env.load()
@@ -42,7 +42,7 @@ const replicationConfig = {
const commonDataSourceOptions = {
maxQueryExecutionTime,
entities: [Item, Group, GroupUser, GroupInvite, Contact],
entities: [Item, Vault, VaultUser, VaultInvite, Contact],
migrations: [`dist/migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
migrationsRun: true,
logging: <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 { GroupServiceInterface } from './../Domain/Group/Service/GroupServiceInterface'
import { GroupUserFactory } from '../Domain/GroupUser/Factory/GroupUserFactory'
import { GroupFactoryInterface } from '../Domain/Group/Factory/GroupFactoryInterface'
import { VaultServiceInterface } from '../Domain/Vault/Service/VaultServiceInterface'
import { VaultUserFactory } from '../Domain/VaultUser/Factory/VaultUserFactory'
import { VaultFactoryInterface } from '../Domain/Vault/Factory/VaultFactoryInterface'
import { Container, interfaces } from 'inversify'
import { Env } from './Env'
@@ -36,18 +36,18 @@ import { ItemConflict } from '../Domain/Item/ItemConflict'
import { ItemConflictProjection } from '../Projection/ItemConflictProjection'
import { CommonContainerConfigLoader } from './CommonContainerConfigLoader'
import { TokenEncoder, TokenEncoderInterface, ValetTokenData } from '@standardnotes/security'
import { GroupFactory } from '../Domain/Group/Factory/GroupFactory'
import { GroupService } from '../Domain/Group/Service/GroupService'
import { GroupUserService } from '../Domain/GroupUser/Service/GroupUserService'
import { GroupUserServiceInterface } from '../Domain/GroupUser/Service/GroupUserServiceInterface'
import { GroupInviteServiceInterface } from '../Domain/GroupInvite/Service/GroupInviteServiceInterface'
import { GroupInviteService } from '../Domain/GroupInvite/Service/GroupInviteService'
import { GroupInviteFactory } from '../Domain/GroupInvite/Factory/GroupInviteFactory'
import { VaultFactory } from '../Domain/Vault/Factory/VaultFactory'
import { VaultService } from '../Domain/Vault/Service/VaultService'
import { VaultUserService } from '../Domain/VaultUser/Service/VaultUserService'
import { VaultUserServiceInterface } from '../Domain/VaultUser/Service/VaultUserServiceInterface'
import { VaultInviteServiceInterface } from '../Domain/VaultInvite/Service/VaultInviteServiceInterface'
import { VaultInviteService } from '../Domain/VaultInvite/Service/VaultInviteService'
import { VaultInviteFactory } from '../Domain/VaultInvite/Factory/VaultInviteFactory'
import { ContactServiceInterface } from '../Domain/Contact/Service/ContactServiceInterface'
import { ContactService } from '../Domain/Contact/Service/ContactService'
import { ContactFactoryInterface } from '../Domain/Contact/Factory/ContactFactoryInterface'
import { SnjsVersionFilter } from '../Domain/Item/SaveRule/SnjsVersionFilter'
import { CreateGroupFileReadValetToken } from '../Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileReadValetToken'
import { CreateVaultFileReadValetToken } from '../Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileReadValetToken'
export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -99,8 +99,8 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
container.bind<SyncItems>(TYPES.SyncItems).toDynamicValue((context: interfaces.Context) => {
return new SyncItems(
context.container.get(TYPES.ItemService),
context.container.get(TYPES.GroupService),
context.container.get(TYPES.GroupInviteService),
context.container.get(TYPES.VaultService),
context.container.get(TYPES.VaultInviteService),
context.container.get(TYPES.ContactService),
)
})
@@ -115,11 +115,11 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
})
container
.bind<CreateGroupFileReadValetToken>(TYPES.CreateGroupFileReadValetToken)
.bind<CreateVaultFileReadValetToken>(TYPES.CreateVaultFileReadValetToken)
.toDynamicValue((context: interfaces.Context) => {
return new CreateGroupFileReadValetToken(
return new CreateVaultFileReadValetToken(
context.container.get(TYPES.GetGlobalItem),
context.container.get(TYPES.GroupUserService),
context.container.get(TYPES.VaultUserService),
context.container.get(TYPES.ValetTokenEncoder),
context.container.get(TYPES.VALET_TOKEN_TTL),
)
@@ -143,36 +143,36 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
context.container.get(TYPES.Timer),
context.container.get(TYPES.ItemProjector),
context.container.get(TYPES.MAX_ITEMS_LIMIT),
context.container.get(TYPES.GroupUserRepository),
context.container.get(TYPES.VaultUserRepository),
context.container.get(TYPES.Logger),
)
})
// Services
container.bind<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),
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),
context.container.get(TYPES.Timer),
)
})
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),
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.Timer),
)
})
container
.bind<GroupInviteServiceInterface>(TYPES.GroupInviteService)
.bind<VaultInviteServiceInterface>(TYPES.VaultInviteService)
.toDynamicValue((context: interfaces.Context) => {
return new GroupInviteService(
context.container.get(TYPES.GroupRepository),
context.container.get(TYPES.GroupInviteRepository),
context.container.get(TYPES.GroupInviteFactory),
context.container.get(TYPES.GroupUserService),
return new VaultInviteService(
context.container.get(TYPES.VaultRepository),
context.container.get(TYPES.VaultInviteRepository),
context.container.get(TYPES.VaultInviteFactory),
context.container.get(TYPES.VaultUserService),
context.container.get(TYPES.Timer),
)
})
@@ -196,9 +196,9 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
context.container.get(TYPES.ItemProjector),
context.container.get(TYPES.ItemConflictProjector),
context.container.get(TYPES.SavedItemProjector),
context.container.get(TYPES.GroupInviteProjector),
context.container.get(TYPES.VaultInviteProjector),
context.container.get(TYPES.ContactProjector),
context.container.get(TYPES.GroupProjector),
context.container.get(TYPES.VaultProjector),
)
})
container
@@ -213,14 +213,14 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
container.bind<ItemFactoryInterface>(TYPES.ItemFactory).toDynamicValue((context: interfaces.Context) => {
return new ItemFactory(context.container.get(TYPES.Timer), context.container.get(TYPES.ItemProjector))
})
container.bind<GroupFactoryInterface>(TYPES.GroupFactory).toDynamicValue((context: interfaces.Context) => {
return new GroupFactory(context.container.get(TYPES.Timer))
container.bind<VaultFactoryInterface>(TYPES.VaultFactory).toDynamicValue((context: interfaces.Context) => {
return new VaultFactory(context.container.get(TYPES.Timer))
})
container.bind<GroupUserFactory>(TYPES.GroupUserFactory).toDynamicValue((context: interfaces.Context) => {
return new GroupUserFactory(context.container.get(TYPES.Timer))
container.bind<VaultUserFactory>(TYPES.VaultUserFactory).toDynamicValue((context: interfaces.Context) => {
return new VaultUserFactory(context.container.get(TYPES.Timer))
})
container.bind<GroupInviteFactory>(TYPES.GroupInviteFactory).toDynamicValue((context: interfaces.Context) => {
return new GroupInviteFactory(context.container.get(TYPES.Timer))
container.bind<VaultInviteFactory>(TYPES.VaultInviteFactory).toDynamicValue((context: interfaces.Context) => {
return new VaultInviteFactory(context.container.get(TYPES.Timer))
})
container.bind<ContactFactoryInterface>(TYPES.ContactFactory).toDynamicValue((context: interfaces.Context) => {
return new ContactFactory(context.container.get(TYPES.Timer))
@@ -230,7 +230,7 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
.bind<OwnershipFilter>(TYPES.OwnershipFilter)
.toDynamicValue(
(context: interfaces.Context) =>
new OwnershipFilter(context.container.get(TYPES.GroupService), context.container.get(TYPES.GroupUserService)),
new OwnershipFilter(context.container.get(TYPES.VaultService), context.container.get(TYPES.VaultUserService)),
)
container
.bind<TimeDifferenceFilter>(TYPES.TimeDifferenceFilter)
+16 -16
View File
@@ -9,17 +9,17 @@ const TYPES = {
// Repositories
ItemRepository: Symbol.for('ItemRepository'),
GroupRepository: Symbol.for('GroupRepository'),
GroupUserRepository: Symbol.for('GroupUserRepository'),
VaultRepository: Symbol.for('VaultRepository'),
VaultUserRepository: Symbol.for('VaultUserRepository'),
ContactRepository: Symbol.for('ContactRepository'),
GroupInviteRepository: Symbol.for('GroupInviteRepository'),
VaultInviteRepository: Symbol.for('VaultInviteRepository'),
// ORM
ORMItemRepository: Symbol.for('ORMItemRepository'),
ORMGroupRepository: Symbol.for('ORMGroupRepository'),
ORMGroupUserRepository: Symbol.for('ORMGroupUserRepository'),
ORMVaultRepository: Symbol.for('ORMVaultRepository'),
ORMVaultUserRepository: Symbol.for('ORMVaultUserRepository'),
ORMContactRepository: Symbol.for('ORMContactRepository'),
ORMGroupInviteRepository: Symbol.for('ORMGroupInviteRepository'),
ORMVaultInviteRepository: Symbol.for('ORMVaultInviteRepository'),
// Middleware
AuthMiddleware: Symbol.for('AuthMiddleware'),
@@ -28,23 +28,23 @@ const TYPES = {
ItemProjector: Symbol.for('ItemProjector'),
SavedItemProjector: Symbol.for('SavedItemProjector'),
ItemConflictProjector: Symbol.for('ItemConflictProjector'),
GroupProjector: Symbol.for('GroupProjector'),
GroupUserProjector: Symbol.for('GroupUserProjector'),
VaultProjector: Symbol.for('VaultProjector'),
VaultUserProjector: Symbol.for('VaultUserProjector'),
ContactProjector: Symbol.for('ContactProjector'),
GroupInviteProjector: Symbol.for('GroupInviteProjector'),
VaultInviteProjector: Symbol.for('VaultInviteProjector'),
// Services
GroupService: Symbol.for('GroupService'),
GroupUserService: Symbol.for('GroupUserService'),
VaultService: Symbol.for('VaultService'),
VaultUserService: Symbol.for('VaultUserService'),
ContactService: Symbol.for('ContactService'),
GroupInviteService: Symbol.for('GroupInviteService'),
VaultInviteService: Symbol.for('VaultInviteService'),
// Factories
ItemFactory: Symbol.for('ItemFactory'),
GroupFactory: Symbol.for('GroupFactory'),
GroupUserFactory: Symbol.for('GroupUserFactory'),
VaultFactory: Symbol.for('VaultFactory'),
VaultUserFactory: Symbol.for('VaultUserFactory'),
ContactFactory: Symbol.for('ContactFactory'),
GroupInviteFactory: Symbol.for('GroupInviteFactory'),
VaultInviteFactory: Symbol.for('VaultInviteFactory'),
// env vars
REDIS_URL: Symbol.for('REDIS_URL'),
@@ -71,7 +71,7 @@ const TYPES = {
CheckIntegrity: Symbol.for('CheckIntegrity'),
GetItem: Symbol.for('GetItem'),
GetGlobalItem: Symbol.for('GetGlobalItem'),
CreateGroupFileReadValetToken: Symbol.for('CreateGroupFileReadValetToken'),
CreateVaultFileReadValetToken: Symbol.for('CreateVaultFileReadValetToken'),
// Handlers
UserCredentialsChangedEventHandler: Symbol.for('UserCredentialsChangedEventHandler'),
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
@@ -138,7 +138,7 @@ export class WorkerContainerConfigLoader extends CommonContainerConfigLoader {
.toDynamicValue((context: interfaces.Context) => {
return new UserCredentialsChangedEventHandler(
context.container.get(TYPES.ContactRepository),
context.container.get(TYPES.GroupInviteRepository),
context.container.get(TYPES.VaultInviteRepository),
context.container.get(TYPES.Timer),
)
})
@@ -1,135 +0,0 @@
import { GroupUser } from '../Domain/GroupUser/Model/GroupUser'
import { GroupServiceInterface } from './../Domain/Group/Service/GroupServiceInterface'
import { Request, Response } from 'express'
import {
BaseHttpController,
controller,
httpPost,
results,
httpDelete,
httpPatch,
httpGet,
} from 'inversify-express-utils'
import TYPES from '../Bootstrap/Types'
import { inject } from 'inversify'
import { ProjectorInterface } from '../Projection/ProjectorInterface'
import { Group } from '../Domain/Group/Model/Group'
import { GroupProjection } from '../Projection/GroupProjection'
import { GroupUserProjection } from '../Projection/GroupUserProjection'
import { GroupUserServiceInterface } from '../Domain/GroupUser/Service/GroupUserServiceInterface'
import { CreateGroupFileReadValetToken } from '../Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileReadValetToken'
@controller('/groups')
export class GroupsController extends BaseHttpController {
constructor(
@inject(TYPES.GroupService) private groupService: GroupServiceInterface,
@inject(TYPES.GroupUserService) private groupUserService: GroupUserServiceInterface,
@inject(TYPES.GroupProjector) private groupProjector: ProjectorInterface<Group, GroupProjection>,
@inject(TYPES.GroupUserProjector) private groupUserProjector: ProjectorInterface<GroupUser, GroupUserProjection>,
@inject(TYPES.CreateGroupFileReadValetToken) private createGroupFileReadValetToken: CreateGroupFileReadValetToken,
) {
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,
})
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,
groupKeyTimestamp: request.body.group_key_timestamp,
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not create group')
}
const groupUser = await this.groupUserService.addGroupUser({
groupUuid: result.uuid,
userUuid: response.locals.user.uuid,
permissions: 'admin',
})
if (!groupUser) {
return this.errorResponse(400, 'Could not add user to group')
}
return this.json({
group: this.groupProjector.projectFull(result),
groupUser: this.groupUserProjector.projectFull(groupUser),
})
}
@httpPatch('/:groupUuid', TYPES.AuthMiddleware)
public async updateGroup(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupService.updateGroup({
groupUuid: request.params.groupUuid,
originatorUuid: response.locals.user.uuid,
groupKeyTimestamp: request.body.group_key_timestamp,
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not update group')
}
return this.json({
group: this.groupProjector.projectFull(result),
})
}
@httpDelete('/:groupUuid', TYPES.AuthMiddleware)
public async deleteGroup(request: Request, response: Response): Promise<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 })
}
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,
groupUuids: request.body.group_uuids,
vaultUuids: request.body.vault_uuids,
limit: request.body.limit,
contentType: request.body.content_type,
apiVersion: request.body.api ?? ApiVersion.v20161215,
@@ -10,29 +10,29 @@ import {
} from 'inversify-express-utils'
import TYPES from '../Bootstrap/Types'
import { inject } from 'inversify'
import { GroupInviteServiceInterface } from '../Domain/GroupInvite/Service/GroupInviteServiceInterface'
import { GroupInviteProjector } from '../Projection/GroupInviteProjector'
import { VaultInviteServiceInterface } from '../Domain/VaultInvite/Service/VaultInviteServiceInterface'
import { VaultInviteProjector } from '../Projection/VaultInviteProjector'
@controller('/groups')
export class GroupInvitesController extends BaseHttpController {
@controller('/vaults')
export class VaultInvitesController extends BaseHttpController {
constructor(
@inject(TYPES.GroupInviteService) private groupInviteService: GroupInviteServiceInterface,
@inject(TYPES.GroupInviteProjector) private groupInviteProjector: GroupInviteProjector,
@inject(TYPES.VaultInviteService) private vaultInviteService: VaultInviteServiceInterface,
@inject(TYPES.VaultInviteProjector) private vaultInviteProjector: VaultInviteProjector,
) {
super()
}
@httpPost('/:groupUuid/invites', TYPES.AuthMiddleware)
public async createGroupInvite(
@httpPost('/:vaultUuid/invites', TYPES.AuthMiddleware)
public async createVaultInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupInviteService.createInvite({
const result = await this.vaultInviteService.createInvite({
originatorUuid: response.locals.user.uuid,
groupUuid: request.params.groupUuid,
vaultUuid: request.params.vaultUuid,
userUuid: request.body.invitee_uuid,
inviterPublicKey: request.body.inviter_public_key,
encryptedGroupData: request.body.encrypted_group_data,
encryptedVaultData: request.body.encrypted_vault_data,
inviteType: request.body.invite_type,
permissions: request.body.permissions,
})
@@ -41,19 +41,19 @@ export class GroupInvitesController extends BaseHttpController {
return this.errorResponse(400, 'Could not create invite')
}
return this.json({ invite: this.groupInviteProjector.projectFull(result) })
return this.json({ invite: this.vaultInviteProjector.projectFull(result) })
}
@httpPatch('/:groupUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
public async updateGroupInvite(
@httpPatch('/:vaultUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
public async updateVaultInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupInviteService.updateInvite({
const result = await this.vaultInviteService.updateInvite({
originatorUuid: response.locals.user.uuid,
inviteUuid: request.params.inviteUuid,
inviterPublicKey: request.body.inviter_public_key,
encryptedGroupData: request.body.encrypted_group_data,
encryptedVaultData: request.body.encrypted_vault_data,
permissions: request.body.permissions,
})
@@ -61,15 +61,15 @@ export class GroupInvitesController extends BaseHttpController {
return this.errorResponse(400, 'Could not update invite')
}
return this.json({ invite: this.groupInviteProjector.projectFull(result) })
return this.json({ invite: this.vaultInviteProjector.projectFull(result) })
}
@httpPost('/:groupUuid/invites/:inviteUuid/accept', TYPES.AuthMiddleware)
public async acceptGroupInvite(
@httpPost('/:vaultUuid/invites/:inviteUuid/accept', TYPES.AuthMiddleware)
public async acceptVaultInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupInviteService.acceptInvite({
const result = await this.vaultInviteService.acceptInvite({
originatorUuid: response.locals.user.uuid,
inviteUuid: request.params.inviteUuid,
})
@@ -81,12 +81,12 @@ export class GroupInvitesController extends BaseHttpController {
return this.json({ success: true })
}
@httpPost('/:groupUuid/invites/:inviteUuid/decline', TYPES.AuthMiddleware)
public async declineGroupInvite(
@httpPost('/:vaultUuid/invites/:inviteUuid/decline', TYPES.AuthMiddleware)
public async declineVaultInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupInviteService.declineInvite({
const result = await this.vaultInviteService.declineInvite({
originatorUuid: response.locals.user.uuid,
inviteUuid: request.params.inviteUuid,
})
@@ -103,7 +103,7 @@ export class GroupInvitesController extends BaseHttpController {
_request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
await this.groupInviteService.deleteAllInboundInvites({
await this.vaultInviteService.deleteAllInboundInvites({
userUuid: response.locals.user.uuid,
})
@@ -115,15 +115,15 @@ export class GroupInvitesController extends BaseHttpController {
_request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupInviteService.getOutboundInvitesForUser({
const result = await this.vaultInviteService.getOutboundInvitesForUser({
userUuid: response.locals.user.uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get group invites')
return this.errorResponse(400, 'Could not get vault invites')
}
const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite))
const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite))
return this.json({ invites: projected })
}
@@ -133,46 +133,46 @@ export class GroupInvitesController extends BaseHttpController {
_request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupInviteService.getInvitesForUser({
const result = await this.vaultInviteService.getInvitesForUser({
userUuid: response.locals.user.uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get group invites')
return this.errorResponse(400, 'Could not get vault invites')
}
const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite))
const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite))
return this.json({ invites: projected })
}
@httpGet('/:groupUuid/invites', TYPES.AuthMiddleware)
public async getGroupInvites(
@httpGet('/:vaultUuid/invites', TYPES.AuthMiddleware)
public async getVaultInvites(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupInviteService.getInvitesForGroup({
const result = await this.vaultInviteService.getInvitesForVault({
originatorUuid: response.locals.user.uuid,
groupUuid: request.params.groupUuid,
vaultUuid: request.params.vaultUuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get group invites')
return this.errorResponse(400, 'Could not get vault invites')
}
const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite))
const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite))
return this.json({ invites: projected })
}
@httpDelete('/:groupUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
public async deleteGroupInvite(
@httpDelete('/:vaultUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
public async deleteVaultInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupInviteService.deleteInvite({
const result = await this.vaultInviteService.deleteInvite({
originatorUuid: response.locals.user.uuid,
groupUuid: request.params.groupUuid,
vaultUuid: request.params.vaultUuid,
inviteUuid: request.params.inviteUuid,
})
@@ -2,48 +2,48 @@ import { Request, Response } from 'express'
import { BaseHttpController, controller, results, httpDelete, httpGet } from 'inversify-express-utils'
import TYPES from '../Bootstrap/Types'
import { inject } from 'inversify'
import { GroupUserServiceInterface } from '../Domain/GroupUser/Service/GroupUserServiceInterface'
import { GroupUserProjector } from '../Projection/GroupUserProjector'
import { VaultUserServiceInterface } from '../Domain/VaultUser/Service/VaultUserServiceInterface'
import { VaultUserProjector } from '../Projection/VaultUserProjector'
@controller('/groups/:groupUuid/users')
export class GroupUsersController extends BaseHttpController {
@controller('/vaults/:vaultUuid/users')
export class VaultUsersController extends BaseHttpController {
constructor(
@inject(TYPES.GroupUserService) private groupUserService: GroupUserServiceInterface,
@inject(TYPES.GroupUserProjector) private groupUserProjector: GroupUserProjector,
@inject(TYPES.VaultUserService) private vaultUserService: VaultUserServiceInterface,
@inject(TYPES.VaultUserProjector) private vaultUserProjector: VaultUserProjector,
) {
super()
}
@httpGet('/', TYPES.AuthMiddleware)
public async getGroupUsers(
public async getVaultUsers(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupUserService.getGroupUsersForGroup({
const result = await this.vaultUserService.getVaultUsersForVault({
originatorUuid: response.locals.user.uuid,
groupUuid: request.params.groupUuid,
vaultUuid: request.params.vaultUuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get group users')
return this.errorResponse(400, 'Could not get vault users')
}
const { users, isAdmin } = result
const projected = users.map((groupUser) =>
this.groupUserProjector.projectAsDisplayableUserForOtherGroupMembers(groupUser, isAdmin),
const projected = users.map((vaultUser) =>
this.vaultUserProjector.projectAsDisplayableUserForOtherVaultMembers(vaultUser, isAdmin),
)
return this.json({ users: projected })
}
@httpDelete('/:userUuid', TYPES.AuthMiddleware)
public async deleteGroupUser(
public async deleteVaultUser(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupUserService.deleteGroupUser({
groupUuid: request.params.groupUuid,
const result = await this.vaultUserService.deleteVaultUser({
vaultUuid: request.params.vaultUuid,
userUuid: request.params.userUuid,
originatorUuid: response.locals.user.uuid,
})
@@ -0,0 +1,135 @@
import { VaultUser } from '../Domain/VaultUser/Model/VaultUser'
import { VaultServiceInterface } from '../Domain/Vault/Service/VaultServiceInterface'
import { Request, Response } from 'express'
import {
BaseHttpController,
controller,
httpPost,
results,
httpDelete,
httpPatch,
httpGet,
} from 'inversify-express-utils'
import TYPES from '../Bootstrap/Types'
import { inject } from 'inversify'
import { ProjectorInterface } from '../Projection/ProjectorInterface'
import { Vault } from '../Domain/Vault/Model/Vault'
import { VaultProjection } from '../Projection/VaultProjection'
import { VaultUserProjection } from '../Projection/VaultUserProjection'
import { VaultUserServiceInterface } from '../Domain/VaultUser/Service/VaultUserServiceInterface'
import { CreateVaultFileReadValetToken } from '../Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileReadValetToken'
@controller('/vaults')
export class VaultsController extends BaseHttpController {
constructor(
@inject(TYPES.VaultService) private vaultService: VaultServiceInterface,
@inject(TYPES.VaultUserService) private vaultUserService: VaultUserServiceInterface,
@inject(TYPES.VaultProjector) private vaultProjector: ProjectorInterface<Vault, VaultProjection>,
@inject(TYPES.VaultUserProjector) private vaultUserProjector: ProjectorInterface<VaultUser, VaultUserProjection>,
@inject(TYPES.CreateVaultFileReadValetToken) private createVaultFileReadValetToken: CreateVaultFileReadValetToken,
) {
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,
})
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,
vaultKeyTimestamp: request.body.vault_key_timestamp,
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not create vault')
}
const vaultUser = await this.vaultUserService.addVaultUser({
vaultUuid: result.uuid,
userUuid: response.locals.user.uuid,
permissions: 'admin',
})
if (!vaultUser) {
return this.errorResponse(400, 'Could not add user to vault')
}
return this.json({
vault: this.vaultProjector.projectFull(result),
vaultUser: this.vaultUserProjector.projectFull(vaultUser),
})
}
@httpPatch('/:vaultUuid', TYPES.AuthMiddleware)
public async updateVault(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultService.updateVault({
vaultUuid: request.params.vaultUuid,
originatorUuid: response.locals.user.uuid,
vaultKeyTimestamp: request.body.vault_key_timestamp,
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not update vault')
}
return this.json({
vault: this.vaultProjector.projectFull(result),
})
}
@httpDelete('/:vaultUuid', TYPES.AuthMiddleware)
public async deleteVault(request: Request, response: Response): Promise<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 })
}
private errorResponse(status: number, message?: string, tag?: string) {
return this.json(
{
error: { message, tag },
},
status,
)
}
}
@@ -1,31 +0,0 @@
import { TimerInterface } from '@standardnotes/time'
import { GroupFactoryInterface } from './GroupFactoryInterface'
import { GroupHash } from './GroupHash'
import { Group } from '../Model/Group'
export class GroupFactory implements GroupFactoryInterface {
constructor(private timer: TimerInterface) {}
create(dto: { userUuid: string; groupHash: GroupHash }): Group {
const newGroup = new Group()
newGroup.uuid = dto.groupHash.uuid
newGroup.userUuid = dto.userUuid
newGroup.specifiedItemsKeyUuid = dto.groupHash.specified_items_key_uuid
newGroup.groupKeyTimestamp = dto.groupHash.group_key_timestamp
const now = this.timer.getTimestampInMicroseconds()
newGroup.updatedAtTimestamp = now
newGroup.createdAtTimestamp = now
if (dto.groupHash.created_at_timestamp) {
newGroup.createdAtTimestamp = dto.groupHash.created_at_timestamp
}
return newGroup
}
createStub(dto: { userUuid: string; groupHash: GroupHash }): Group {
const item = this.create(dto)
return item
}
}
@@ -1,7 +0,0 @@
import { Group } from '../Model/Group'
import { GroupHash } from './GroupHash'
export interface GroupFactoryInterface {
create(dto: { userUuid: string; groupHash: GroupHash }): Group
createStub(dto: { userUuid: string; groupHash: GroupHash }): Group
}
@@ -1,19 +0,0 @@
import { Group } from '../Model/Group'
export type GroupQuery = {
userUuid: string
}
export type UserGroupsQuery = {
userUuid?: string
groupUuids?: string[]
lastSyncTime?: number
}
export interface GroupsRepositoryInterface {
findByUuid(uuid: string): Promise<Group | null>
create(group: Group): Promise<Group>
save(group: Group): Promise<Group>
remove(group: Group): Promise<Group>
findAll(query: UserGroupsQuery): Promise<Group[]>
}
@@ -1,56 +0,0 @@
import { Repository, SelectQueryBuilder } from 'typeorm'
import { GroupsRepositoryInterface, UserGroupsQuery } from './GroupRepositoryInterface'
import { Group } from '../Model/Group'
export class TypeORMGroupRepository implements GroupsRepositoryInterface {
constructor(private ormRepository: Repository<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
}
}
@@ -1,99 +0,0 @@
import { TimerInterface } from '@standardnotes/time'
import { Group } from '../Model/Group'
import { GroupsRepositoryInterface } from '../Repository/GroupRepositoryInterface'
import { CreateGroupDTO, GroupServiceInterface, UpdateGroupDTO } from './GroupServiceInterface'
import { GroupFactoryInterface } from '../Factory/GroupFactoryInterface'
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
import { GroupInviteServiceInterface } from '../../GroupInvite/Service/GroupInviteServiceInterface'
export class GroupService implements GroupServiceInterface {
constructor(
private groupRepository: GroupsRepositoryInterface,
private groupFactory: GroupFactoryInterface,
private groupUserService: GroupUserServiceInterface,
private groupInviteService: GroupInviteServiceInterface,
private timer: TimerInterface,
) {}
async createGroup(dto: CreateGroupDTO): Promise<Group | 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,
group_key_timestamp: dto.groupKeyTimestamp,
created_at_timestamp: timestamp,
updated_at_timestamp: timestamp,
},
})
const savedGroup = await this.groupRepository.create(group)
return savedGroup
}
async getGroup(dto: { groupUuid: string }): Promise<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
}
if (dto.groupKeyTimestamp < group.groupKeyTimestamp) {
return null
}
group.specifiedItemsKeyUuid = dto.specifiedItemsKeyUuid
group.groupKeyTimestamp = dto.groupKeyTimestamp
group.updatedAtTimestamp = this.timer.getTimestampInMicroseconds()
const savedGroup = await this.groupRepository.save(group)
return savedGroup
}
async deleteGroup(dto: { groupUuid: string; originatorUuid: string }): Promise<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
}
}
@@ -1,27 +0,0 @@
import { Group } from '../Model/Group'
export type CreateGroupDTO = {
userUuid: string
groupUuid: string
groupKeyTimestamp: number
specifiedItemsKeyUuid: string
}
export type UpdateGroupDTO = {
originatorUuid: string
groupUuid: string
groupKeyTimestamp: number
specifiedItemsKeyUuid: string
}
export interface GroupServiceInterface {
createGroup(dto: CreateGroupDTO): Promise<Group | 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[]>
}
@@ -1,35 +0,0 @@
import { TimerInterface } from '@standardnotes/time'
import { GroupInviteFactoryInterface } from './GroupInviteFactoryInterface'
import { GroupInviteHash } from './GroupInviteHash'
import { GroupInvite } from '../Model/GroupInvite'
export class GroupInviteFactory implements GroupInviteFactoryInterface {
constructor(private timer: TimerInterface) {}
create(dto: GroupInviteHash): GroupInvite {
const newGroupInvite = new GroupInvite()
newGroupInvite.uuid = dto.uuid
newGroupInvite.userUuid = dto.user_uuid
newGroupInvite.groupUuid = dto.group_uuid
newGroupInvite.inviterUuid = dto.inviter_uuid
newGroupInvite.inviterPublicKey = dto.inviter_public_key
newGroupInvite.encryptedGroupData = dto.encrypted_group_data
newGroupInvite.inviteType = dto.invite_type
newGroupInvite.permissions = dto.permissions
const now = this.timer.getTimestampInMicroseconds()
newGroupInvite.updatedAtTimestamp = now
newGroupInvite.createdAtTimestamp = now
if (dto.created_at_timestamp) {
newGroupInvite.createdAtTimestamp = dto.created_at_timestamp
}
return newGroupInvite
}
createStub(dto: GroupInviteHash): GroupInvite {
const item = this.create(dto)
return item
}
}
@@ -1,7 +0,0 @@
import { GroupInvite } from '../Model/GroupInvite'
import { GroupInviteHash } from './GroupInviteHash'
export interface GroupInviteFactoryInterface {
create(dto: GroupInviteHash): GroupInvite
createStub(dto: GroupInviteHash): GroupInvite
}
@@ -1,15 +0,0 @@
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
import { GroupInviteType } from '../Model/GroupInviteType'
export type GroupInviteHash = {
uuid: string
user_uuid: string
group_uuid: string
inviter_uuid: string
inviter_public_key: string
encrypted_group_data: string
invite_type: GroupInviteType
permissions: GroupUserPermission
created_at_timestamp?: number
updated_at_timestamp?: number
}
@@ -1,25 +0,0 @@
import { GroupInvite } from '../Model/GroupInvite'
export type GroupInviteQuery = {
userUuid: string
}
export type GroupInviteFindAllForUserQuery = {
userUuid?: string
lastSyncTime?: number
inviterUuid?: string
}
export type GroupInviteFindAllForGroup = {
groupUuid: string
}
export interface GroupInviteRepositoryInterface {
findByUuid(groupInviteUuid: string): Promise<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>
}
@@ -1,71 +0,0 @@
import { GroupInvite } from '../Model/GroupInvite'
import { Repository, SelectQueryBuilder } from 'typeorm'
import {
GroupInviteFindAllForGroup,
GroupInviteFindAllForUserQuery,
GroupInviteRepositoryInterface,
} from './GroupInviteRepositoryInterface'
export class TypeORMGroupInviteRepository implements GroupInviteRepositoryInterface {
constructor(private ormRepository: Repository<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
}
}
@@ -1,12 +0,0 @@
import { GroupInviteType } from '../Model/GroupInviteType'
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
export type CreateInviteDTO = {
originatorUuid: string
groupUuid: string
userUuid: string
inviterPublicKey: string
encryptedGroupData: string
inviteType: GroupInviteType
permissions: GroupUserPermission
}
@@ -1,172 +0,0 @@
import { TimerInterface } from '@standardnotes/time'
import { GroupInvite } from '../Model/GroupInvite'
import { GroupInviteFactoryInterface } from '../Factory/GroupInviteFactoryInterface'
import { v4 as uuidv4 } from 'uuid'
import { GroupInviteRepositoryInterface } from '../Repository/GroupInviteRepositoryInterface'
import { GroupInviteServiceInterface } from './GroupInviteServiceInterface'
import { GetUserGroupInvitesDTO } from './GetUserGroupInvitesDTO'
import { GroupsRepositoryInterface } from '../../Group/Repository/GroupRepositoryInterface'
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
import { GroupInviteType } from '../Model/GroupInviteType'
import { CreateInviteDTO } from './CreateInviteDTO'
import { UpdateInviteDTO } from './UpdateInviteDTO'
export class GroupInviteService implements GroupInviteServiceInterface {
constructor(
private groupRepository: GroupsRepositoryInterface,
private groupInviteRepository: GroupInviteRepositoryInterface,
private groupInviteFactory: GroupInviteFactoryInterface,
private groupUserService: GroupUserServiceInterface,
private timer: TimerInterface,
) {}
async createInvite(dto: CreateInviteDTO): Promise<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
}
}
@@ -1,26 +0,0 @@
import { GroupInvite } from '../Model/GroupInvite'
import { CreateInviteDTO } from './CreateInviteDTO'
import { GetUserGroupInvitesDTO } from './GetUserGroupInvitesDTO'
import { UpdateInviteDTO } from './UpdateInviteDTO'
export interface GroupInviteServiceInterface {
createInvite(dto: CreateInviteDTO): Promise<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>
}
@@ -1,9 +0,0 @@
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
export type UpdateInviteDTO = {
originatorUuid: string
inviteUuid: string
inviterPublicKey: string
encryptedGroupData: string
permissions?: GroupUserPermission
}
@@ -1,31 +0,0 @@
import { TimerInterface } from '@standardnotes/time'
import { GroupUserFactoryInterface } from './GroupUserFactoryInterface'
import { GroupUserHash } from './GroupUserHash'
import { GroupUser } from '../Model/GroupUser'
export class GroupUserFactory implements GroupUserFactoryInterface {
constructor(private timer: TimerInterface) {}
create(dto: GroupUserHash): GroupUser {
const newGroupUser = new GroupUser()
newGroupUser.uuid = dto.uuid
newGroupUser.userUuid = dto.user_uuid
newGroupUser.groupUuid = dto.group_uuid
newGroupUser.permissions = dto.permissions
const now = this.timer.getTimestampInMicroseconds()
newGroupUser.updatedAtTimestamp = now
newGroupUser.createdAtTimestamp = now
if (dto.created_at_timestamp) {
newGroupUser.createdAtTimestamp = dto.created_at_timestamp
}
return newGroupUser
}
createStub(dto: GroupUserHash): GroupUser {
const item = this.create(dto)
return item
}
}
@@ -1,7 +0,0 @@
import { GroupUser } from '../Model/GroupUser'
import { GroupUserHash } from './GroupUserHash'
export interface GroupUserFactoryInterface {
create(dto: GroupUserHash): GroupUser
createStub(dto: GroupUserHash): GroupUser
}
@@ -1,10 +0,0 @@
import { GroupUserPermission } from '../Model/GroupUserPermission'
export type GroupUserHash = {
uuid: string
user_uuid: string
group_uuid: string
permissions: GroupUserPermission
created_at_timestamp?: number
updated_at_timestamp?: number
}
@@ -1 +0,0 @@
export type GroupUserPermission = 'read' | 'write' | 'admin'
@@ -1,24 +0,0 @@
import { GroupUser } from '../Model/GroupUser'
export type GroupUserQuery = {
userUuid: string
}
export type GroupUserFindAllForUserQuery = {
userUuid: string
lastSyncTime?: number
}
export type GroupUserFindAllForGroup = {
groupUuid: string
}
export interface GroupUserRepositoryInterface {
findByUuid(groupUserUuid: string): Promise<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>
}
@@ -1,65 +0,0 @@
import { GroupUser } from '../Model/GroupUser'
import { Repository, SelectQueryBuilder } from 'typeorm'
import {
GroupUserFindAllForGroup,
GroupUserFindAllForUserQuery,
GroupUserRepositoryInterface,
} from './GroupUserRepositoryInterface'
export class TypeORMGroupUserRepository implements GroupUserRepositoryInterface {
constructor(private ormRepository: Repository<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,111 +0,0 @@
import { TimerInterface } from '@standardnotes/time'
import { GroupUser } from '../Model/GroupUser'
import { GroupUserFactoryInterface } from '../Factory/GroupUserFactoryInterface'
import { v4 as uuidv4 } from 'uuid'
import { GroupUserRepositoryInterface } from '../Repository/GroupUserRepositoryInterface'
import { GroupUserServiceInterface } from './GroupUserServiceInterface'
import { GetGroupUsersDTO } from './GetGroupUsersDTO'
import { GroupsRepositoryInterface } from '../../Group/Repository/GroupRepositoryInterface'
import { GroupUserPermission } from '../Model/GroupUserPermission'
export class GroupUserService implements GroupUserServiceInterface {
constructor(
private groupRepository: GroupsRepositoryInterface,
private groupUserRepository: GroupUserRepositoryInterface,
private groupUserFactory: GroupUserFactoryInterface,
private timer: TimerInterface,
) {}
async addGroupUser(dto: {
groupUuid: string
userUuid: string
permissions: GroupUserPermission
}): Promise<GroupUser | null> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group) {
return null
}
const timestamp = this.timer.getTimestampInMicroseconds()
const groupUser = this.groupUserFactory.create({
uuid: uuidv4(),
user_uuid: dto.userUuid,
group_uuid: dto.groupUuid,
permissions: dto.permissions,
created_at_timestamp: timestamp,
updated_at_timestamp: timestamp,
})
return this.groupUserRepository.create(groupUser)
}
getAllGroupUsersForUser(dto: GetGroupUsersDTO): Promise<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
}
const isAuthorized = group.userUuid === dto.originatorUuid || dto.userUuid === dto.originatorUuid
if (!isAuthorized) {
return false
}
const groupUser = await this.groupUserRepository.findByUserUuidAndGroupUuid(dto.userUuid, dto.groupUuid)
if (!groupUser) {
return false
}
await this.groupUserRepository.remove(groupUser)
return true
}
async deleteAllGroupUsersForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<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)
}
return true
}
}
@@ -1,24 +0,0 @@
import { GroupUser } from '../Model/GroupUser'
import { GroupUserPermission } from '../Model/GroupUserPermission'
import { GetGroupUsersDTO } from './GetGroupUsersDTO'
export interface GroupUserServiceInterface {
addGroupUser(dto: {
groupUuid: string
userUuid: string
permissions: GroupUserPermission
}): Promise<GroupUser | null>
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>
}
@@ -5,18 +5,18 @@ import {
} from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
import { ContactsRepositoryInterface } from '../Contact/Repository/ContactRepositoryInterface'
import { GroupInviteRepositoryInterface } from '../GroupInvite/Repository/GroupInviteRepositoryInterface'
import { VaultInviteRepositoryInterface } from '../VaultInvite/Repository/VaultInviteRepositoryInterface'
export class UserCredentialsChangedEventHandler implements DomainEventHandlerInterface {
constructor(
private contactRepository: ContactsRepositoryInterface,
private groupInviteRepository: GroupInviteRepositoryInterface,
private vaultInviteRepository: VaultInviteRepositoryInterface,
private timer: TimerInterface,
) {}
async handle(event: UserCredentialsChangedEvent): Promise<void> {
await this.updatePublicKeyOfAllContacts(event.payload)
await this.nullifyPendingInboundGroupInvites(event.payload)
await this.nullifyPendingInboundVaultInvites(event.payload)
}
private async updatePublicKeyOfAllContacts(payload: UserCredentialsChangedEventPayload): Promise<void> {
@@ -31,13 +31,13 @@ export class UserCredentialsChangedEventHandler implements DomainEventHandlerInt
}
}
private async nullifyPendingInboundGroupInvites(payload: UserCredentialsChangedEventPayload): Promise<void> {
const inboundInvites = await this.groupInviteRepository.findAll({
private async nullifyPendingInboundVaultInvites(payload: UserCredentialsChangedEventPayload): Promise<void> {
const inboundInvites = await this.vaultInviteRepository.findAll({
userUuid: payload.userUuid,
})
for (const invite of inboundInvites) {
await this.groupInviteRepository.remove(invite)
await this.vaultInviteRepository.remove(invite)
}
}
}
@@ -4,5 +4,5 @@ export type GetItemsDTO = {
cursorToken?: string | null
limit?: number
contentType?: string
groupUuids?: string[] | null
vaultUuids?: string[] | null
}
@@ -15,11 +15,11 @@ export class Item {
@Column({
type: 'varchar',
name: 'group_uuid',
name: 'vault_uuid',
length: 255,
nullable: true,
})
declare groupUuid: string | null
declare vaultUuid: string | null
@Column({
type: 'varchar',
@@ -77,9 +77,11 @@ export class Item {
@Column({
name: 'user_uuid',
length: 36,
type: 'varchar',
nullable: true,
})
@Index('index_items_on_user_uuid')
declare userUuid: string
declare userUuid: string | null
@Column({
name: 'last_edited_by_uuid',
@@ -40,8 +40,8 @@ export class ItemFactory implements ItemFactoryInterface {
if (dto.itemHash.last_edited_by_uuid) {
newItem.lastEditedByUuid = dto.itemHash.last_edited_by_uuid
}
if (dto.itemHash.group_uuid) {
newItem.groupUuid = dto.itemHash.group_uuid
if (dto.itemHash.vault_uuid) {
newItem.vaultUuid = dto.itemHash.vault_uuid
}
if (dto.itemHash.content_type) {
newItem.contentType = dto.itemHash.content_type
@@ -10,7 +10,7 @@ export type ItemHash = {
auth_hash?: string
enc_item_key?: string
items_key_id?: string
group_uuid?: string
vault_uuid?: string
last_edited_by_uuid?: string | null
created_at?: string
created_at_timestamp?: number
@@ -6,8 +6,8 @@ export type ItemQuery = {
lastSyncTime?: number
syncTimeComparison?: '>' | '>='
contentType?: string | string[]
includeGroupUuids?: string[]
exclusiveGroupUuids?: string[] | null
includeVaultUuids?: string[]
exclusiveVaultUuids?: string[] | null
deleted?: boolean
offset?: number
limit?: number
@@ -20,7 +20,7 @@ import { ConflictType } from '@standardnotes/responses'
import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { ItemProjection } from '../../Projection/ItemProjection'
import { GroupUserRepositoryInterface } from '../GroupUser/Repository/GroupUserRepositoryInterface'
import { VaultUserRepositoryInterface } from '../VaultUser/Repository/VaultUserRepositoryInterface'
export class ItemService implements ItemServiceInterface {
private readonly DEFAULT_ITEMS_LIMIT = 150
@@ -38,7 +38,7 @@ export class ItemService implements ItemServiceInterface {
private timer: TimerInterface,
private itemProjector: ProjectorInterface<Item, ItemProjection>,
private maxItemsSyncLimit: number,
private groupUsersRepository: GroupUserRepositoryInterface,
private vaultUsersRepository: VaultUserRepositoryInterface,
private logger: Logger,
) {}
@@ -48,16 +48,16 @@ export class ItemService implements ItemServiceInterface {
const limit = dto.limit === undefined || dto.limit < 1 ? this.DEFAULT_ITEMS_LIMIT : dto.limit
const upperBoundLimit = limit < this.maxItemsSyncLimit ? limit : this.maxItemsSyncLimit
const groupUsers = await this.groupUsersRepository.findAllForUser({ userUuid: dto.userUuid })
const userGroupUuids = groupUsers.map((groupUser) => groupUser.groupUuid)
const exclusiveGroupUuids = dto.groupUuids
? dto.groupUuids.filter((groupUuid) => userGroupUuids.includes(groupUuid))
const vaultUsers = await this.vaultUsersRepository.findAllForUser({ userUuid: dto.userUuid })
const userVaultUuids = vaultUsers.map((vaultUser) => vaultUser.vaultUuid)
const exclusiveVaultUuids = dto.vaultUuids
? dto.vaultUuids.filter((vaultUuid) => userVaultUuids.includes(vaultUuid))
: undefined
const itemQuery: ItemQuery = {
userUuid: dto.userUuid,
includeGroupUuids: !dto.groupUuids ? userGroupUuids : undefined,
exclusiveGroupUuids: exclusiveGroupUuids,
includeVaultUuids: !dto.vaultUuids ? userVaultUuids : undefined,
exclusiveVaultUuids: exclusiveVaultUuids,
lastSyncTime,
syncTimeComparison,
contentType: dto.contentType,
@@ -199,6 +199,28 @@ export class ItemService implements ItemServiceInterface {
).toString('base64')
}
async addItemToVault(dto: {
userUuid: string
itemUuid: string
vaultUuid: string
sessionUuid: string | null
}): Promise<Item | null> {
const item = await this.itemRepository.findByUuid(dto.itemUuid)
if (!item || item.userUuid !== dto.userUuid) {
return null
}
// item.userUuid = null
item.vaultUuid = dto.vaultUuid
item.updatedWithSession = dto.sessionUuid
item.lastEditedByUuid = dto.userUuid
const updatedAt = this.timer.getTimestampInMicroseconds()
item.updatedAtTimestamp = updatedAt
item.updatedAt = this.timer.convertMicrosecondsToDate(updatedAt)
const savedItem = await this.itemRepository.save(item)
return savedItem
}
private async updateExistingItem(dto: {
existingItem: Item
itemHash: ItemHash
@@ -207,7 +229,6 @@ export class ItemService implements ItemServiceInterface {
}): Promise<Item> {
dto.existingItem.updatedWithSession = dto.sessionUuid
dto.existingItem.contentSize = 0
dto.existingItem.groupUuid = dto.itemHash.group_uuid ?? null
dto.existingItem.lastEditedByUuid = dto.userUuid
if (dto.itemHash.content) {
@@ -265,14 +286,14 @@ export class ItemService implements ItemServiceInterface {
const savedItem = await this.itemRepository.save(dto.existingItem)
if (secondsFromLastUpdate >= this.revisionFrequency) {
if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType)) {
if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType) && savedItem.userUuid) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createItemRevisionCreationRequested(savedItem.uuid, savedItem.userUuid),
)
}
}
if (wasMarkedAsDuplicate) {
if (wasMarkedAsDuplicate && savedItem.userUuid) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createDuplicateItemSyncedEvent(savedItem.uuid, savedItem.userUuid),
)
@@ -286,13 +307,13 @@ export class ItemService implements ItemServiceInterface {
const savedItem = await this.itemRepository.save(newItem)
if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType)) {
if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType) && savedItem.userUuid) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createItemRevisionCreationRequested(savedItem.uuid, savedItem.userUuid),
)
}
if (savedItem.duplicateOf) {
if (savedItem.duplicateOf && savedItem.userUuid) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createDuplicateItemSyncedEvent(savedItem.uuid, savedItem.userUuid),
)
@@ -2,14 +2,14 @@ import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
import { ConflictType } from '@standardnotes/responses'
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface'
import { ItemHash } from '../ItemHash'
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
import { GroupServiceInterface } from '../../Group/Service/GroupServiceInterface'
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
import { VaultServiceInterface } from '../../Vault/Service/VaultServiceInterface'
import { ContentType } from '@standardnotes/common'
export class OwnershipFilter implements ItemSaveRuleInterface {
constructor(private groupService: GroupServiceInterface, private groupUserService: GroupUserServiceInterface) {}
constructor(private vaultService: VaultServiceInterface, private vaultUserService: VaultUserServiceInterface) {}
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
const itemBelongsToADifferentUser = dto.existingItem != null && dto.existingItem.userUuid !== dto.userUuid
@@ -18,7 +18,7 @@ export class OwnershipFilter implements ItemSaveRuleInterface {
passed: true,
}
const groupReadonlyFail = {
const vaultReadonlyFail = {
passed: false,
conflict: {
unsavedItem: dto.itemHash,
@@ -34,39 +34,39 @@ export class OwnershipFilter implements ItemSaveRuleInterface {
},
}
const groupUuidInvolved = dto.existingItem?.groupUuid || dto.itemHash.group_uuid
if (itemBelongsToADifferentUser && !groupUuidInvolved) {
const vaultUuidInvolved = dto.existingItem?.vaultUuid || dto.itemHash.vault_uuid
if (itemBelongsToADifferentUser && !vaultUuidInvolved) {
return ownershipFail
}
if (groupUuidInvolved) {
const groupAuthorization = await this.groupAuthorizationForItem(dto.userUuid, groupUuidInvolved)
if (!groupAuthorization) {
if (vaultUuidInvolved) {
const vaultAuthorization = await this.vaultAuthorizationForItem(dto.userUuid, vaultUuidInvolved)
if (!vaultAuthorization) {
return ownershipFail
}
if (groupAuthorization === 'read') {
return groupReadonlyFail
if (vaultAuthorization === 'read') {
return vaultReadonlyFail
}
const isItemBeingRemovedFromGroup =
dto.existingItem != null && dto.existingItem.groupUuid != null && dto.itemHash.group_uuid == null
const isItemBeingRemovedFromVault =
dto.existingItem != null && dto.existingItem.vaultUuid != null && dto.itemHash.vault_uuid == null
const isItemBeingDeleted = dto.itemHash.deleted === true
if (isItemBeingRemovedFromGroup || isItemBeingDeleted) {
if (isItemBeingRemovedFromVault || isItemBeingDeleted) {
if (itemBelongsToADifferentUser) {
return groupAuthorization === 'admin' ? successValue : groupReadonlyFail
return vaultAuthorization === 'admin' ? successValue : vaultReadonlyFail
} else {
return successValue
}
}
if (dto.itemHash.content_type === ContentType.SharedItemsKey && groupAuthorization !== 'admin') {
return groupReadonlyFail
if (dto.itemHash.content_type === ContentType.SharedItemsKey && vaultAuthorization !== 'admin') {
return vaultReadonlyFail
}
const usingValidKey = await this.groupItemIsBeingSavedWithValidItemsKey(dto.itemHash)
const usingValidKey = await this.vaultItemIsBeingSavedWithValidItemsKey(dto.itemHash)
if (!usingValidKey) {
return {
@@ -82,32 +82,32 @@ export class OwnershipFilter implements ItemSaveRuleInterface {
return successValue
}
private async groupItemIsBeingSavedWithValidItemsKey(itemHash: ItemHash): Promise<boolean> {
private async vaultItemIsBeingSavedWithValidItemsKey(itemHash: ItemHash): Promise<boolean> {
const isItemNotEncryptedByItemsKey = itemHash.content_type === ContentType.SharedItemsKey
if (isItemNotEncryptedByItemsKey) {
return true
}
const group = await this.groupService.getGroup({ groupUuid: itemHash.group_uuid as string })
const vault = await this.vaultService.getVault({ vaultUuid: itemHash.vault_uuid as string })
if (!group) {
if (!vault) {
return false
}
return itemHash.items_key_id === group.specifiedItemsKeyUuid
return itemHash.items_key_id === vault.specifiedItemsKeyUuid
}
private async groupAuthorizationForItem(
private async vaultAuthorizationForItem(
userUuid: string,
groupUuid: string,
): Promise<GroupUserPermission | undefined> {
const groupUser = await this.groupUserService.getUserForGroup({
vaultUuid: string,
): Promise<VaultUserPermission | undefined> {
const vaultUser = await this.vaultUserService.getUserForVault({
userUuid,
groupUuid: groupUuid,
vaultUuid: vaultUuid,
})
if (groupUser) {
return groupUser.permissions
if (vaultUser) {
return vaultUser.permissions
}
return undefined
@@ -4,7 +4,7 @@ import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
import { ConflictType } from '@standardnotes/responses'
import { lt } from 'semver'
const MINIMUM_SNJS_VERSION_FOR_GROUP_OPERATIONS = '2.200.0'
const MINIMUM_SNJS_VERSION_FOR_VAULT_OPERATIONS = '2.200.0'
export class SnjsVersionFilter implements ItemSaveRuleInterface {
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
@@ -12,12 +12,12 @@ export class SnjsVersionFilter implements ItemSaveRuleInterface {
passed: true,
}
const groupUuidInvolved = dto.existingItem?.groupUuid || dto.itemHash.group_uuid
if (!groupUuidInvolved) {
const vaultUuidInvolved = dto.existingItem?.vaultUuid || dto.itemHash.vault_uuid
if (!vaultUuidInvolved) {
return successValue
}
const isClientSnjsVersionLessThanMinimum = lt(dto.snjsVersion, MINIMUM_SNJS_VERSION_FOR_GROUP_OPERATIONS)
const isClientSnjsVersionLessThanMinimum = lt(dto.snjsVersion, MINIMUM_SNJS_VERSION_FOR_VAULT_OPERATIONS)
if (isClientSnjsVersionLessThanMinimum) {
return {
@@ -1,6 +1,6 @@
import { ContactProjection } from '../../../Projection/ContactProjection'
import { GroupInviteProjection } from '../../../Projection/GroupInviteProjection'
import { GroupProjection } from '../../../Projection/GroupProjection'
import { VaultInviteProjection } from '../../../Projection/VaultInviteProjection'
import { VaultProjection } from '../../../Projection/VaultProjection'
import { ItemConflictProjection } from '../../../Projection/ItemConflictProjection'
import { ItemProjection } from '../../../Projection/ItemProjection'
import { SavedItemProjection } from '../../../Projection/SavedItemProjection'
@@ -12,6 +12,6 @@ export type SyncResponse20200115 = {
sync_token: string
cursor_token?: string
contacts: Array<ContactProjection>
groups: Array<GroupProjection>
group_invites: Array<GroupInviteProjection>
vaults: Array<VaultProjection>
vault_invites: Array<VaultInviteProjection>
}
@@ -1,4 +1,4 @@
import { GroupInvite } from './../../GroupInvite/Model/GroupInvite'
import { VaultInvite } from '../../VaultInvite/Model/VaultInvite'
import { Contact } from './../../Contact/Model/Contact'
import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
import { SyncItemsResponse } from '../../UseCase/SyncItemsResponse'
@@ -10,18 +10,18 @@ import { SyncResponse20200115 } from './SyncResponse20200115'
import { SyncResponseFactoryInterface } from './SyncResponseFactoryInterface'
import { SavedItemProjection } from '../../../Projection/SavedItemProjection'
import { ContactProjection } from '../../../Projection/ContactProjection'
import { GroupInviteProjection } from '../../../Projection/GroupInviteProjection'
import { GroupProjection } from '../../../Projection/GroupProjection'
import { Group } from '../../Group/Model/Group'
import { VaultInviteProjection } from '../../../Projection/VaultInviteProjection'
import { VaultProjection } from '../../../Projection/VaultProjection'
import { Vault } from '../../Vault/Model/Vault'
export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface {
constructor(
private itemProjector: ProjectorInterface<Item, ItemProjection>,
private itemConflictProjector: ProjectorInterface<ItemConflict, ItemConflictProjection>,
private savedItemProjector: ProjectorInterface<Item, SavedItemProjection>,
private groupInviteProjector: ProjectorInterface<GroupInvite, GroupInviteProjection>,
private vaultInviteProjector: ProjectorInterface<VaultInvite, VaultInviteProjection>,
private contactProjector: ProjectorInterface<Contact, ContactProjection>,
private groupProjector: ProjectorInterface<Group, GroupProjection>,
private vaultProjector: ProjectorInterface<Vault, VaultProjection>,
) {}
async createResponse(syncItemsResponse: SyncItemsResponse): Promise<SyncResponse20200115> {
@@ -40,14 +40,14 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface
conflicts.push(this.itemConflictProjector.projectFull(itemConflict))
}
const groups = []
for (const group of syncItemsResponse.groups) {
groups.push(this.groupProjector.projectFull(group))
const vaults = []
for (const vault of syncItemsResponse.vaults) {
vaults.push(this.vaultProjector.projectFull(vault))
}
const groupInvites = []
for (const groupInvite of syncItemsResponse.groupInvites) {
groupInvites.push(this.groupInviteProjector.projectFull(groupInvite))
const vaultInvites = []
for (const vaultInvite of syncItemsResponse.vaultInvites) {
vaultInvites.push(this.vaultInviteProjector.projectFull(vaultInvite))
}
const contacts = []
@@ -61,8 +61,8 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface
conflicts,
sync_token: syncItemsResponse.syncToken,
cursor_token: syncItemsResponse.cursorToken,
groups,
group_invites: groupInvites,
vaults,
vault_invites: vaultInvites,
contacts,
}
}
@@ -1,6 +0,0 @@
export type CreateGroupFileReadValetTokenDTO = {
userUuid: string
groupUuid: string
fileUuid: string
remoteIdentifier: string
}
@@ -1,30 +1,30 @@
import { inject, injectable } from 'inversify'
import { GroupValetTokenData, TokenEncoderInterface, ValetTokenOperation } from '@standardnotes/security'
import { VaultValetTokenData, TokenEncoderInterface, ValetTokenOperation } from '@standardnotes/security'
import { CreateValetTokenResponseData } from '@standardnotes/responses'
import TYPES from '../../../Bootstrap/Types'
import { UseCaseInterface } from '../UseCaseInterface'
import { CreateGroupFileReadValetTokenDTO } from './CreateGroupFileReadValetTokenDTO'
import { CreateVaultFileReadValetTokenDTO } from './CreateVaultFileReadValetTokenDTO'
import { GetGlobalItem } from '../GetGlobalItem/GetGlobalItem'
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface'
@injectable()
export class CreateGroupFileReadValetToken implements UseCaseInterface {
export class CreateVaultFileReadValetToken implements UseCaseInterface {
constructor(
@inject(TYPES.GetGlobalItem) private getGlobalItem: GetGlobalItem,
@inject(TYPES.GroupUserService) private groupUserService: GroupUserServiceInterface,
@inject(TYPES.ValetTokenEncoder) private tokenEncoder: TokenEncoderInterface<GroupValetTokenData>,
@inject(TYPES.VaultUserService) private vaultUserService: VaultUserServiceInterface,
@inject(TYPES.ValetTokenEncoder) private tokenEncoder: TokenEncoderInterface<VaultValetTokenData>,
@inject(TYPES.VALET_TOKEN_TTL) private valetTokenTTL: number,
) {}
async execute(dto: CreateGroupFileReadValetTokenDTO): Promise<CreateValetTokenResponseData> {
async execute(dto: CreateVaultFileReadValetTokenDTO): Promise<CreateValetTokenResponseData> {
const itemResponse = await this.getGlobalItem.execute({
itemUuid: dto.fileUuid,
})
if (
itemResponse.success === false ||
!itemResponse.item.groupUuid ||
itemResponse.item.groupUuid !== dto.groupUuid
!itemResponse.item.vaultUuid ||
itemResponse.item.vaultUuid !== dto.vaultUuid
) {
return {
success: false,
@@ -32,21 +32,20 @@ export class CreateGroupFileReadValetToken implements UseCaseInterface {
}
}
const groupUser = await this.groupUserService.getUserForGroup({
const vaultUser = await this.vaultUserService.getUserForVault({
userUuid: dto.userUuid,
groupUuid: dto.groupUuid,
vaultUuid: dto.vaultUuid,
})
if (!groupUser) {
if (!vaultUser) {
return {
success: false,
reason: 'invalid-parameters',
}
}
const tokenData: GroupValetTokenData = {
groupUuid: dto.groupUuid,
fileOwnerUuid: itemResponse.item.userUuid,
const tokenData: VaultValetTokenData = {
vaultUuid: dto.vaultUuid,
permittedOperation: ValetTokenOperation.Read,
remoteIdentifier: dto.remoteIdentifier,
}
@@ -0,0 +1,6 @@
export type CreateVaultFileReadValetTokenDTO = {
userUuid: string
vaultUuid: string
fileUuid: string
remoteIdentifier: string
}
@@ -5,14 +5,14 @@ import { ItemServiceInterface } from '../Item/ItemServiceInterface'
import { SyncItemsDTO } from './SyncItemsDTO'
import { SyncItemsResponse } from './SyncItemsResponse'
import { UseCaseInterface } from './UseCaseInterface'
import { GroupInviteServiceInterface } from '../GroupInvite/Service/GroupInviteServiceInterface'
import { GroupServiceInterface } from '../Group/Service/GroupServiceInterface'
import { VaultInviteServiceInterface } from '../VaultInvite/Service/VaultInviteServiceInterface'
import { VaultServiceInterface } from '../Vault/Service/VaultServiceInterface'
export class SyncItems implements UseCaseInterface {
constructor(
private itemService: ItemServiceInterface,
private groupService: GroupServiceInterface,
private groupInviteService: GroupInviteServiceInterface,
private vaultService: VaultServiceInterface,
private vaultInviteService: VaultInviteServiceInterface,
private contactService: ContactServiceInterface,
) {}
@@ -20,7 +20,7 @@ export class SyncItems implements UseCaseInterface {
const getItemsResult = await this.itemService.getItems({
userUuid: dto.userUuid,
syncToken: dto.syncToken,
groupUuids: dto.groupUuids,
vaultUuids: dto.vaultUuids,
cursorToken: dto.cursorToken,
limit: dto.limit,
contentType: dto.contentType,
@@ -36,8 +36,8 @@ export class SyncItems implements UseCaseInterface {
})
let retrievedItems = this.filterOutSyncConflictsForConsecutiveSyncs(getItemsResult.items, saveItemsResult.conflicts)
const isGroupExclusiveSync = dto.groupUuids && dto.groupUuids.length > 0
if (this.isFirstSync(dto) && !isGroupExclusiveSync) {
const isVaultExclusiveSync = dto.vaultUuids && dto.vaultUuids.length > 0
if (this.isFirstSync(dto) && !isVaultExclusiveSync) {
retrievedItems = await this.itemService.frontLoadKeysItemsToTop(dto.userUuid, retrievedItems)
}
@@ -46,12 +46,12 @@ export class SyncItems implements UseCaseInterface {
cursorToken: dto.cursorToken,
})
const groups = await this.groupService.getGroups({
const vaults = await this.vaultService.getVaults({
userUuid: dto.userUuid,
lastSyncTime,
})
const groupInvites = await this.groupInviteService.getInvitesForUser({
const vaultInvites = await this.vaultInviteService.getInvitesForUser({
userUuid: dto.userUuid,
lastSyncTime,
})
@@ -67,8 +67,8 @@ export class SyncItems implements UseCaseInterface {
savedItems: saveItemsResult.savedItems,
conflicts: saveItemsResult.conflicts,
cursorToken: getItemsResult.cursorToken,
groups,
groupInvites,
vaults,
vaultInvites,
contacts,
}
@@ -5,7 +5,7 @@ export type SyncItemsDTO = {
itemHashes: Array<ItemHash>
computeIntegrityHash: boolean
limit: number
groupUuids?: string[] | null
vaultUuids?: string[] | null
syncToken?: string | null
cursorToken?: string | null
contentType?: string
@@ -1,15 +1,15 @@
import { GroupInvite } from './../GroupInvite/Model/GroupInvite'
import { VaultInvite } from '../VaultInvite/Model/VaultInvite'
import { Contact } from '../Contact/Model/Contact'
import { Item } from '../Item/Item'
import { ItemConflict } from '../Item/ItemConflict'
import { Group } from '../Group/Model/Group'
import { Vault } from '../Vault/Model/Vault'
export type SyncItemsResponse = {
retrievedItems: Array<Item>
savedItems: Array<Item>
conflicts: Array<ItemConflict>
groups: Array<Group>
groupInvites: Array<GroupInvite>
vaults: Array<Vault>
vaultInvites: Array<VaultInvite>
contacts: Array<Contact>
syncToken: string
cursorToken?: string
@@ -0,0 +1,31 @@
import { TimerInterface } from '@standardnotes/time'
import { VaultFactoryInterface } from './VaultFactoryInterface'
import { VaultHash } from './VaultHash'
import { Vault } from '../Model/Vault'
export class VaultFactory implements VaultFactoryInterface {
constructor(private timer: TimerInterface) {}
create(dto: { userUuid: string; vaultHash: VaultHash }): Vault {
const newVault = new Vault()
newVault.uuid = dto.vaultHash.uuid
newVault.userUuid = dto.userUuid
newVault.specifiedItemsKeyUuid = dto.vaultHash.specified_items_key_uuid
newVault.vaultKeyTimestamp = dto.vaultHash.vault_key_timestamp
const now = this.timer.getTimestampInMicroseconds()
newVault.updatedAtTimestamp = now
newVault.createdAtTimestamp = now
if (dto.vaultHash.created_at_timestamp) {
newVault.createdAtTimestamp = dto.vaultHash.created_at_timestamp
}
return newVault
}
createStub(dto: { userUuid: string; vaultHash: VaultHash }): Vault {
const item = this.create(dto)
return item
}
}
@@ -0,0 +1,7 @@
import { Vault } from '../Model/Vault'
import { VaultHash } from './VaultHash'
export interface VaultFactoryInterface {
create(dto: { userUuid: string; vaultHash: VaultHash }): Vault
createStub(dto: { userUuid: string; vaultHash: VaultHash }): Vault
}
@@ -1,8 +1,8 @@
export type GroupHash = {
export type VaultHash = {
uuid: string
user_uuid: string
specified_items_key_uuid: string
group_key_timestamp: number
vault_key_timestamp: number
created_at_timestamp?: number
updated_at_timestamp?: number
}
@@ -1,7 +1,7 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'groups' })
export class Group {
@Entity({ name: 'vaults' })
export class Vault {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@@ -9,7 +9,7 @@ export class Group {
name: 'user_uuid',
length: 36,
})
@Index('index_groups_on_user_uuid')
@Index('index_vaults_on_user_uuid')
declare userUuid: string
@Column({
@@ -19,10 +19,10 @@ export class Group {
declare specifiedItemsKeyUuid: string
@Column({
name: 'group_key_timestamp',
name: 'vault_key_timestamp',
type: 'bigint',
})
declare groupKeyTimestamp: number
declare vaultKeyTimestamp: number
@Column({
name: 'created_at_timestamp',
@@ -0,0 +1,56 @@
import { Repository, SelectQueryBuilder } from 'typeorm'
import { VaultsRepositoryInterface, UserVaultsQuery } from './VaultRepositoryInterface'
import { Vault } from '../Model/Vault'
export class TypeORMVaultRepository implements VaultsRepositoryInterface {
constructor(private ormRepository: Repository<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
}
}
@@ -0,0 +1,19 @@
import { Vault } from '../Model/Vault'
export type VaultQuery = {
userUuid: string
}
export type UserVaultsQuery = {
userUuid?: string
vaultUuids?: string[]
lastSyncTime?: number
}
export interface VaultsRepositoryInterface {
findByUuid(uuid: string): Promise<Vault | null>
create(vault: Vault): Promise<Vault>
save(vault: Vault): Promise<Vault>
remove(vault: Vault): Promise<Vault>
findAll(query: UserVaultsQuery): Promise<Vault[]>
}
@@ -0,0 +1,99 @@
import { TimerInterface } from '@standardnotes/time'
import { Vault } from '../Model/Vault'
import { VaultsRepositoryInterface } from '../Repository/VaultRepositoryInterface'
import { CreateVaultDTO, VaultServiceInterface, UpdateVaultDTO } from './VaultServiceInterface'
import { VaultFactoryInterface } from '../Factory/VaultFactoryInterface'
import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface'
import { VaultInviteServiceInterface } from '../../VaultInvite/Service/VaultInviteServiceInterface'
export class VaultService implements VaultServiceInterface {
constructor(
private vaultRepository: VaultsRepositoryInterface,
private vaultFactory: VaultFactoryInterface,
private vaultUserService: VaultUserServiceInterface,
private vaultInviteService: VaultInviteServiceInterface,
private timer: TimerInterface,
) {}
async createVault(dto: CreateVaultDTO): Promise<Vault | 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,
vault_key_timestamp: dto.vaultKeyTimestamp,
created_at_timestamp: timestamp,
updated_at_timestamp: timestamp,
},
})
const savedVault = await this.vaultRepository.create(vault)
return savedVault
}
async getVault(dto: { vaultUuid: string }): Promise<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
}
if (dto.vaultKeyTimestamp < vault.vaultKeyTimestamp) {
return null
}
vault.specifiedItemsKeyUuid = dto.specifiedItemsKeyUuid
vault.vaultKeyTimestamp = dto.vaultKeyTimestamp
vault.updatedAtTimestamp = this.timer.getTimestampInMicroseconds()
const savedVault = await this.vaultRepository.save(vault)
return savedVault
}
async deleteVault(dto: { vaultUuid: string; originatorUuid: string }): Promise<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
}
}
@@ -0,0 +1,27 @@
import { Vault } from '../Model/Vault'
export type CreateVaultDTO = {
userUuid: string
vaultUuid: string
vaultKeyTimestamp: number
specifiedItemsKeyUuid: string
}
export type UpdateVaultDTO = {
originatorUuid: string
vaultUuid: string
vaultKeyTimestamp: number
specifiedItemsKeyUuid: string
}
export interface VaultServiceInterface {
createVault(dto: CreateVaultDTO): Promise<Vault | 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[]>
}
@@ -0,0 +1,35 @@
import { TimerInterface } from '@standardnotes/time'
import { VaultInviteFactoryInterface } from './VaultInviteFactoryInterface'
import { VaultInviteHash } from './VaultInviteHash'
import { VaultInvite } from '../Model/VaultInvite'
export class VaultInviteFactory implements VaultInviteFactoryInterface {
constructor(private timer: TimerInterface) {}
create(dto: VaultInviteHash): VaultInvite {
const newVaultInvite = new VaultInvite()
newVaultInvite.uuid = dto.uuid
newVaultInvite.userUuid = dto.user_uuid
newVaultInvite.vaultUuid = dto.vault_uuid
newVaultInvite.inviterUuid = dto.inviter_uuid
newVaultInvite.inviterPublicKey = dto.inviter_public_key
newVaultInvite.encryptedVaultData = dto.encrypted_vault_data
newVaultInvite.inviteType = dto.invite_type
newVaultInvite.permissions = dto.permissions
const now = this.timer.getTimestampInMicroseconds()
newVaultInvite.updatedAtTimestamp = now
newVaultInvite.createdAtTimestamp = now
if (dto.created_at_timestamp) {
newVaultInvite.createdAtTimestamp = dto.created_at_timestamp
}
return newVaultInvite
}
createStub(dto: VaultInviteHash): VaultInvite {
const item = this.create(dto)
return item
}
}
@@ -0,0 +1,7 @@
import { VaultInvite } from '../Model/VaultInvite'
import { VaultInviteHash } from './VaultInviteHash'
export interface VaultInviteFactoryInterface {
create(dto: VaultInviteHash): VaultInvite
createStub(dto: VaultInviteHash): VaultInvite
}
@@ -0,0 +1,15 @@
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
import { VaultInviteType } from '../Model/VaultInviteType'
export type VaultInviteHash = {
uuid: string
user_uuid: string
vault_uuid: string
inviter_uuid: string
inviter_public_key: string
encrypted_vault_data: string
invite_type: VaultInviteType
permissions: VaultUserPermission
created_at_timestamp?: number
updated_at_timestamp?: number
}
@@ -1,17 +1,17 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import { GroupInviteType } from './GroupInviteType'
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
import { VaultInviteType } from './VaultInviteType'
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
@Entity({ name: 'group_invites' })
export class GroupInvite {
@Entity({ name: 'vault_invites' })
export class VaultInvite {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'group_uuid',
name: 'vault_uuid',
length: 36,
})
declare groupUuid: string
declare vaultUuid: string
@Column({
name: 'user_uuid',
@@ -27,7 +27,7 @@ export class GroupInvite {
@Column({
name: 'invite_type',
})
declare inviteType: GroupInviteType
declare inviteType: VaultInviteType
@Column({
name: 'inviter_public_key',
@@ -35,14 +35,14 @@ export class GroupInvite {
declare inviterPublicKey: string
@Column({
name: 'encrypted_group_data',
name: 'encrypted_vault_data',
})
declare encryptedGroupData: string
declare encryptedVaultData: string
@Column({
name: 'permissions',
})
declare permissions: GroupUserPermission
declare permissions: VaultUserPermission
@Column({
name: 'created_at_timestamp',
@@ -1,4 +1,4 @@
export enum GroupInviteType {
export enum VaultInviteType {
Join = 'join',
KeyChange = 'key-change',
}
@@ -0,0 +1,71 @@
import { VaultInvite } from '../Model/VaultInvite'
import { Repository, SelectQueryBuilder } from 'typeorm'
import {
VaultInviteFindAllForVault,
VaultInviteFindAllForUserQuery,
VaultInviteRepositoryInterface,
} from './VaultInviteRepositoryInterface'
export class TypeORMVaultInviteRepository implements VaultInviteRepositoryInterface {
constructor(private ormRepository: Repository<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
}
}
@@ -0,0 +1,25 @@
import { VaultInvite } from '../Model/VaultInvite'
export type VaultInviteQuery = {
userUuid: string
}
export type VaultInviteFindAllForUserQuery = {
userUuid?: string
lastSyncTime?: number
inviterUuid?: string
}
export type VaultInviteFindAllForVault = {
vaultUuid: string
}
export interface VaultInviteRepositoryInterface {
findByUuid(vaultInviteUuid: string): Promise<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>
}
@@ -0,0 +1,12 @@
import { VaultInviteType } from '../Model/VaultInviteType'
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
export type CreateInviteDTO = {
originatorUuid: string
vaultUuid: string
userUuid: string
inviterPublicKey: string
encryptedVaultData: string
inviteType: VaultInviteType
permissions: VaultUserPermission
}
@@ -1,4 +1,4 @@
export interface GetUserGroupInvitesDTO {
export interface GetUserVaultInvitesDTO {
userUuid: string
lastSyncTime?: number
}
@@ -0,0 +1,9 @@
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
export type UpdateInviteDTO = {
originatorUuid: string
inviteUuid: string
inviterPublicKey: string
encryptedVaultData: string
permissions?: VaultUserPermission
}
@@ -0,0 +1,172 @@
import { TimerInterface } from '@standardnotes/time'
import { VaultInvite } from '../Model/VaultInvite'
import { VaultInviteFactoryInterface } from '../Factory/VaultInviteFactoryInterface'
import { v4 as uuidv4 } from 'uuid'
import { VaultInviteRepositoryInterface } from '../Repository/VaultInviteRepositoryInterface'
import { VaultInviteServiceInterface } from './VaultInviteServiceInterface'
import { GetUserVaultInvitesDTO } from './GetUserVaultInvitesDTO'
import { VaultsRepositoryInterface } from '../../Vault/Repository/VaultRepositoryInterface'
import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface'
import { VaultInviteType } from '../Model/VaultInviteType'
import { CreateInviteDTO } from './CreateInviteDTO'
import { UpdateInviteDTO } from './UpdateInviteDTO'
export class VaultInviteService implements VaultInviteServiceInterface {
constructor(
private vaultRepository: VaultsRepositoryInterface,
private vaultInviteRepository: VaultInviteRepositoryInterface,
private vaultInviteFactory: VaultInviteFactoryInterface,
private vaultUserService: VaultUserServiceInterface,
private timer: TimerInterface,
) {}
async createInvite(dto: CreateInviteDTO): Promise<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
}
}
@@ -0,0 +1,26 @@
import { VaultInvite } from '../Model/VaultInvite'
import { CreateInviteDTO } from './CreateInviteDTO'
import { GetUserVaultInvitesDTO } from './GetUserVaultInvitesDTO'
import { UpdateInviteDTO } from './UpdateInviteDTO'
export interface VaultInviteServiceInterface {
createInvite(dto: CreateInviteDTO): Promise<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>
}
@@ -0,0 +1,31 @@
import { TimerInterface } from '@standardnotes/time'
import { VaultUserFactoryInterface } from './VaultUserFactoryInterface'
import { VaultUserHash } from './VaultUserHash'
import { VaultUser } from '../Model/VaultUser'
export class VaultUserFactory implements VaultUserFactoryInterface {
constructor(private timer: TimerInterface) {}
create(dto: VaultUserHash): VaultUser {
const newVaultUser = new VaultUser()
newVaultUser.uuid = dto.uuid
newVaultUser.userUuid = dto.user_uuid
newVaultUser.vaultUuid = dto.vault_uuid
newVaultUser.permissions = dto.permissions
const now = this.timer.getTimestampInMicroseconds()
newVaultUser.updatedAtTimestamp = now
newVaultUser.createdAtTimestamp = now
if (dto.created_at_timestamp) {
newVaultUser.createdAtTimestamp = dto.created_at_timestamp
}
return newVaultUser
}
createStub(dto: VaultUserHash): VaultUser {
const item = this.create(dto)
return item
}
}
@@ -0,0 +1,7 @@
import { VaultUser } from '../Model/VaultUser'
import { VaultUserHash } from './VaultUserHash'
export interface VaultUserFactoryInterface {
create(dto: VaultUserHash): VaultUser
createStub(dto: VaultUserHash): VaultUser
}
@@ -0,0 +1,10 @@
import { VaultUserPermission } from '../Model/VaultUserPermission'
export type VaultUserHash = {
uuid: string
user_uuid: string
vault_uuid: string
permissions: VaultUserPermission
created_at_timestamp?: number
updated_at_timestamp?: number
}
@@ -1,16 +1,16 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import { GroupUserPermission } from './GroupUserPermission'
import { VaultUserPermission } from './VaultUserPermission'
@Entity({ name: 'group_users' })
export class GroupUser {
@Entity({ name: 'vault_users' })
export class VaultUser {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'group_uuid',
name: 'vault_uuid',
length: 36,
})
declare groupUuid: string
declare vaultUuid: string
@Column({
name: 'user_uuid',
@@ -21,7 +21,7 @@ export class GroupUser {
@Column({
name: 'permissions',
})
declare permissions: GroupUserPermission
declare permissions: VaultUserPermission
@Column({
name: 'created_at_timestamp',
@@ -0,0 +1 @@
export type VaultUserPermission = 'read' | 'write' | 'admin'
@@ -0,0 +1,65 @@
import { VaultUser } from '../Model/VaultUser'
import { Repository, SelectQueryBuilder } from 'typeorm'
import {
VaultUserFindAllForVault,
VaultUserFindAllForUserQuery,
VaultUserRepositoryInterface,
} from './VaultUserRepositoryInterface'
export class TypeORMVaultUserRepository implements VaultUserRepositoryInterface {
constructor(private ormRepository: Repository<VaultUser>) {}
async create(vaultUser: VaultUser): Promise<VaultUser> {
return this.ormRepository.save(vaultUser)
}
async save(vaultUser: VaultUser): Promise<VaultUser> {
return this.ormRepository.save(vaultUser)
}
findByUuid(uuid: string): Promise<VaultUser | null> {
return this.ormRepository
.createQueryBuilder('vault_user')
.where('vault_user.uuid = :uuid', {
uuid,
})
.getOne()
}
findByUserUuidAndVaultUuid(userUuid: string, vaultUuid: string): Promise<VaultUser | null> {
return this.ormRepository
.createQueryBuilder('vault_user')
.where('vault_user.user_uuid = :userUuid', { userUuid })
.andWhere('vault_user.vault_uuid = :vaultUuid', { vaultUuid })
.getOne()
}
async remove(vaultUser: VaultUser): Promise<VaultUser> {
return this.ormRepository.remove(vaultUser)
}
findAllForVault(query: VaultUserFindAllForVault): Promise<VaultUser[]> {
return this.ormRepository
.createQueryBuilder('vault_user')
.where('vault_user.vault_uuid = :vaultUuid', { vaultUuid: query.vaultUuid })
.getMany()
}
async findAllForUser(query: VaultUserFindAllForUserQuery): Promise<VaultUser[]> {
return this.createFindAllQueryBuilder(query).getMany()
}
private createFindAllQueryBuilder(query: VaultUserFindAllForUserQuery): SelectQueryBuilder<VaultUser> {
const queryBuilder = this.ormRepository.createQueryBuilder('vault_user')
queryBuilder.where('vault_user.user_uuid = :userUuid', { userUuid: query.userUuid })
if (query.lastSyncTime) {
queryBuilder.andWhere('vault_user.updated_at_timestamp > :lastSyncTime', {
lastSyncTime: query.lastSyncTime,
})
}
return queryBuilder
}
}

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