refactor: rename vaults to groups

This commit is contained in:
Mo
2023-05-31 11:42:36 -05:00
parent ed48350d3e
commit 9323ca79c6
161 changed files with 2327 additions and 2317 deletions
+1 -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/VaultsController'
import '../src/Controller/v1/GroupsController'
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/VaultsController'
export * from './v1/GroupsController'
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/vaults')
export class VaultsController extends BaseHttpController {
@controller('/v1/groups')
export class GroupsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@@ -27,8 +27,8 @@ export class SettingService implements SettingServiceInterface {
async applyDefaultSettingsUponRegistration(user: User): Promise<void> {
let defaultSettingsWithValues = this.settingsAssociationService.getDefaultSettingsAndValuesForNewUser()
if (user.isPotentiallyAVaultAccount()) {
defaultSettingsWithValues = this.settingsAssociationService.getDefaultSettingsAndValuesForNewVaultAccount()
if (user.isPotentiallyAPrivateUsernameAccount()) {
defaultSettingsWithValues = this.settingsAssociationService.getDefaultSettingsAndValuesForNewPrivateUsernameAccount()
}
for (const settingName of defaultSettingsWithValues.keys()) {
@@ -66,7 +66,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
],
])
private readonly vaultAccountDefaultSettingsOverwrites = new Map<string, SettingDescription>([
private readonly privateUsernameAccountDefaultSettingsOverwrites = new Map<string, SettingDescription>([
[
SettingName.NAMES.LogSessionUserAgent,
{
@@ -114,16 +114,18 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
return this.defaultSettings
}
getDefaultSettingsAndValuesForNewVaultAccount(): Map<string, SettingDescription> {
const defaultVaultSettings = new Map(this.defaultSettings)
getDefaultSettingsAndValuesForNewPrivateUsernameAccount(): Map<string, SettingDescription> {
const defaultPrivateUsernameSettings = new Map(this.defaultSettings)
for (const vaultAccountDefaultSettingOverwriteKey of this.vaultAccountDefaultSettingsOverwrites.keys()) {
defaultVaultSettings.set(
vaultAccountDefaultSettingOverwriteKey,
this.vaultAccountDefaultSettingsOverwrites.get(vaultAccountDefaultSettingOverwriteKey) as SettingDescription,
for (const privateUsernameAccountDefaultSettingOverwriteKey of this.privateUsernameAccountDefaultSettingsOverwrites.keys()) {
defaultPrivateUsernameSettings.set(
privateUsernameAccountDefaultSettingOverwriteKey,
this.privateUsernameAccountDefaultSettingsOverwrites.get(
privateUsernameAccountDefaultSettingOverwriteKey,
) as SettingDescription,
)
}
return defaultVaultSettings
return defaultPrivateUsernameSettings
}
}
@@ -6,7 +6,7 @@ import { SettingDescription } from './SettingDescription'
export interface SettingsAssociationServiceInterface {
getDefaultSettingsAndValuesForNewUser(): Map<string, SettingDescription>
getDefaultSettingsAndValuesForNewVaultAccount(): Map<string, SettingDescription>
getDefaultSettingsAndValuesForNewPrivateUsernameAccount(): Map<string, SettingDescription>
getPermissionAssociatedWithSetting(settingName: SettingName): PermissionName | undefined
getEncryptionVersionForSetting(settingName: SettingName): EncryptionVersion
getSensitivityForSetting(settingName: SettingName): boolean
@@ -68,7 +68,7 @@ export class InviteToSharedSubscription implements UseCaseInterface {
sharedSubscriptionInvition.inviterIdentifier = dto.inviterEmail
sharedSubscriptionInvition.inviterIdentifierType = InviterIdentifierType.Email
sharedSubscriptionInvition.inviteeIdentifier = dto.inviteeIdentifier
sharedSubscriptionInvition.inviteeIdentifierType = this.isInviteeIdentifierPotentiallyAVaultAccount(
sharedSubscriptionInvition.inviteeIdentifierType = this.isInviteeIdentifierPotentiallyAPrivateUsernameAccount(
dto.inviteeIdentifier,
)
? InviteeIdentifierType.Hash
@@ -106,7 +106,7 @@ export class InviteToSharedSubscription implements UseCaseInterface {
}
}
private isInviteeIdentifierPotentiallyAVaultAccount(identifier: string): boolean {
private isInviteeIdentifierPotentiallyAPrivateUsernameAccount(identifier: string): boolean {
return identifier.length === 64 && !identifier.includes('@')
}
}
+1 -1
View File
@@ -218,7 +218,7 @@ export class User {
return parseInt(this.version) >= parseInt(ProtocolVersion.V004)
}
isPotentiallyAVaultAccount(): boolean {
isPotentiallyAPrivateUsernameAccount(): boolean {
return this.email.length === 64 && !this.email.includes('@')
}
}
@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { GroupFileRemovedEventPayload } from './GroupFileRemovedEventPayload'
export interface GroupFileRemovedEvent extends DomainEventInterface {
type: 'GROUP_FILE_REMOVED'
payload: GroupFileRemovedEventPayload
}
@@ -0,0 +1,6 @@
export interface GroupFileRemovedEventPayload {
groupUuid: string
fileByteSize: number
filePath: string
fileName: string
}
@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { GroupFileUploadedEventPayload } from './GroupFileUploadedEventPayload'
export interface GroupFileUploadedEvent extends DomainEventInterface {
type: 'GROUP_FILE_UPLOADED'
payload: GroupFileUploadedEventPayload
}
@@ -0,0 +1,6 @@
export interface GroupFileUploadedEventPayload {
groupUuid: string
fileByteSize: number
filePath: string
fileName: string
}
@@ -1,7 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { VaultFileRemovedEventPayload } from './VaultFileRemovedEventPayload'
export interface VaultFileRemovedEvent extends DomainEventInterface {
type: 'VAULT_FILE_REMOVED'
payload: VaultFileRemovedEventPayload
}
@@ -1,6 +0,0 @@
export interface VaultFileRemovedEventPayload {
vaultUuid: string
fileByteSize: number
filePath: string
fileName: string
}
@@ -1,7 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { VaultFileUploadedEventPayload } from './VaultFileUploadedEventPayload'
export interface VaultFileUploadedEvent extends DomainEventInterface {
type: 'VAULT_FILE_UPLOADED'
payload: VaultFileUploadedEventPayload
}
@@ -1,6 +0,0 @@
export interface VaultFileUploadedEventPayload {
vaultUuid: string
fileByteSize: number
filePath: string
fileName: string
}
+4 -4
View File
@@ -32,10 +32,10 @@ export * from './Event/UserFileRemovedEventPayload'
export * from './Event/UserFileUploadedEvent'
export * from './Event/UserFileUploadedEventPayload'
export * from './Event/VaultFileRemovedEvent'
export * from './Event/VaultFileRemovedEventPayload'
export * from './Event/VaultFileUploadedEvent'
export * from './Event/VaultFileUploadedEventPayload'
export * from './Event/GroupFileRemovedEvent'
export * from './Event/GroupFileRemovedEventPayload'
export * from './Event/GroupFileUploadedEvent'
export * from './Event/GroupFileUploadedEventPayload'
export * from './Event/ItemDumpedEvent'
export * from './Event/ItemDumpedEventPayload'
+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/VaultFilesController'
import '../src/Controller/GroupFilesController'
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 { VaultValetTokenAuthMiddleware } from '../Controller/VaultValetTokenAuthMiddleware'
import { GroupValetTokenAuthMiddleware } from '../Controller/GroupValetTokenAuthMiddleware'
import { FileMoverInterface } from '../Domain/Services/FileMoverInterface'
import { S3FileMover } from '../Infra/S3/S3FileMover'
import { FSFileMover } from '../Infra/FS/FSFileMover'
@@ -157,7 +157,7 @@ export class ContainerConfigLoader {
// middleware
container.bind<ValetTokenAuthMiddleware>(TYPES.ValetTokenAuthMiddleware).to(ValetTokenAuthMiddleware)
container.bind<VaultValetTokenAuthMiddleware>(TYPES.VaultValetTokenAuthMiddleware).to(VaultValetTokenAuthMiddleware)
container.bind<GroupValetTokenAuthMiddleware>(TYPES.GroupValetTokenAuthMiddleware).to(GroupValetTokenAuthMiddleware)
// services
container
+1 -1
View File
@@ -31,7 +31,7 @@ const TYPES = {
// middleware
ValetTokenAuthMiddleware: Symbol.for('ValetTokenAuthMiddleware'),
VaultValetTokenAuthMiddleware: Symbol.for('VaultValetTokenAuthMiddleware'),
GroupValetTokenAuthMiddleware: Symbol.for('GroupValetTokenAuthMiddleware'),
// env vars
S3_ENDPOINT: Symbol.for('S3_ENDPOINT'),
@@ -9,11 +9,11 @@ import { CreateUploadSession } from '../Domain/UseCase/CreateUploadSession/Creat
import { FinishUploadSession } from '../Domain/UseCase/FinishUploadSession/FinishUploadSession'
import { GetFileMetadata } from '../Domain/UseCase/GetFileMetadata/GetFileMetadata'
import { RemoveFile } from '../Domain/UseCase/RemoveFile/RemoveFile'
import { ValetTokenOperation, VaultValetTokenData } from '@standardnotes/security'
import { ValetTokenOperation, GroupValetTokenData } from '@standardnotes/security'
import { MoveFile } from '../Domain/UseCase/MoveFile/MoveFile'
@controller('/v1/vault/files', TYPES.VaultValetTokenAuthMiddleware)
export class VaultFilesController extends BaseHttpController {
@controller('/v1/group/files', TYPES.GroupValetTokenAuthMiddleware)
export class GroupFilesController extends BaseHttpController {
constructor(
@inject(TYPES.UploadFileChunk) private uploadFileChunk: UploadFileChunk,
@inject(TYPES.CreateUploadSession) private createUploadSession: CreateUploadSession,
@@ -32,7 +32,7 @@ export class VaultFilesController extends BaseHttpController {
_request: Request,
response: Response,
): Promise<results.BadRequestErrorMessageResult | results.JsonResult> {
const locals = response.locals as VaultValetTokenData
const locals = response.locals as GroupValetTokenData
if (locals.permittedOperation !== ValetTokenOperation.Move) {
return this.badRequest('Not permitted for this operation')
}
@@ -61,13 +61,13 @@ export class VaultFilesController extends BaseHttpController {
_request: Request,
response: Response,
): Promise<results.BadRequestErrorMessageResult | results.JsonResult> {
const locals = response.locals as VaultValetTokenData
const locals = response.locals as GroupValetTokenData
if (locals.permittedOperation !== ValetTokenOperation.Write) {
return this.badRequest('Not permitted for this operation')
}
const result = await this.createUploadSession.execute({
ownerUuid: locals.vaultUuid,
ownerUuid: locals.groupUuid,
resourceRemoteIdentifier: locals.remoteIdentifier,
})
@@ -83,7 +83,7 @@ export class VaultFilesController extends BaseHttpController {
request: Request,
response: Response,
): Promise<results.BadRequestErrorMessageResult | results.JsonResult> {
const locals = response.locals as VaultValetTokenData
const locals = response.locals as GroupValetTokenData
if (locals.permittedOperation !== ValetTokenOperation.Write) {
return this.badRequest('Not permitted for this operation')
}
@@ -94,7 +94,7 @@ export class VaultFilesController extends BaseHttpController {
}
const result = await this.uploadFileChunk.execute({
ownerUuid: locals.vaultUuid,
ownerUuid: locals.groupUuid,
resourceRemoteIdentifier: locals.remoteIdentifier,
resourceUnencryptedFileSize: locals.unencryptedFileSize as number,
chunkId,
@@ -113,14 +113,14 @@ export class VaultFilesController extends BaseHttpController {
_request: Request,
response: Response,
): Promise<results.BadRequestErrorMessageResult | results.JsonResult> {
const locals = response.locals as VaultValetTokenData
const locals = response.locals as GroupValetTokenData
if (locals.permittedOperation !== ValetTokenOperation.Write) {
return this.badRequest('Not permitted for this operation')
}
const result = await this.finishUploadSession.execute({
ownerUuid: locals.vaultUuid,
ownerType: 'vault',
ownerUuid: locals.groupUuid,
ownerType: 'group',
resourceRemoteIdentifier: locals.remoteIdentifier,
uploadBytesLimit: locals.uploadBytesLimit,
uploadBytesUsed: locals.uploadBytesUsed,
@@ -138,13 +138,13 @@ export class VaultFilesController extends BaseHttpController {
_request: Request,
response: Response,
): Promise<results.BadRequestErrorMessageResult | results.JsonResult> {
const locals = response.locals as VaultValetTokenData
const locals = response.locals as GroupValetTokenData
if (locals.permittedOperation !== ValetTokenOperation.Delete) {
return this.badRequest('Not permitted for this operation')
}
const result = await this.removeFile.execute({
vaultUuid: locals.vaultUuid,
groupUuid: locals.groupUuid,
resourceRemoteIdentifier: locals.remoteIdentifier,
})
@@ -160,7 +160,7 @@ export class VaultFilesController extends BaseHttpController {
request: Request,
response: Response,
): Promise<results.BadRequestErrorMessageResult | (() => Writable)> {
const locals = response.locals as VaultValetTokenData
const locals = response.locals as GroupValetTokenData
if (locals.permittedOperation !== ValetTokenOperation.Read) {
return this.badRequest('Not permitted for this operation')
}
@@ -176,7 +176,7 @@ export class VaultFilesController extends BaseHttpController {
}
const fileMetadata = await this.getFileMetadata.execute({
ownerUuid: locals.vaultUuid,
ownerUuid: locals.groupUuid,
resourceRemoteIdentifier: locals.remoteIdentifier,
})
@@ -197,7 +197,7 @@ export class VaultFilesController extends BaseHttpController {
response.writeHead(206, headers)
const result = await this.streamDownloadFile.execute({
ownerUuid: locals.vaultUuid,
ownerUuid: locals.groupUuid,
resourceRemoteIdentifier: locals.remoteIdentifier,
startRange,
endRange,
@@ -1,4 +1,4 @@
import { VaultValetTokenData, TokenDecoderInterface } from '@standardnotes/security'
import { GroupValetTokenData, 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 VaultValetTokenAuthMiddleware extends BaseMiddleware {
export class GroupValetTokenAuthMiddleware extends BaseMiddleware {
constructor(
@inject(TYPES.ValetTokenDecoder) private tokenDecoder: TokenDecoderInterface<VaultValetTokenData>,
@inject(TYPES.ValetTokenDecoder) private tokenDecoder: TokenDecoderInterface<GroupValetTokenData>,
@inject(TYPES.Logger) private logger: Logger,
) {
super()
@@ -19,7 +19,7 @@ export class VaultValetTokenAuthMiddleware extends BaseMiddleware {
try {
const valetToken = request.headers['x-valet-token'] || request.body.valetToken || request.query.valetToken
if (!valetToken) {
this.logger.debug('VaultValetTokenAuthMiddleware missing valet token.')
this.logger.debug('GroupValetTokenAuthMiddleware missing valet token.')
response.status(401).send({
error: {
@@ -34,7 +34,7 @@ export class VaultValetTokenAuthMiddleware extends BaseMiddleware {
const valetTokenData = this.tokenDecoder.decodeToken(valetToken)
if (valetTokenData === undefined) {
this.logger.debug('VaultValetTokenAuthMiddleware authentication failure.')
this.logger.debug('GroupValetTokenAuthMiddleware authentication failure.')
response.status(401).send({
error: {
@@ -60,8 +60,8 @@ export class VaultValetTokenAuthMiddleware extends BaseMiddleware {
return
}
const whitelistedData: VaultValetTokenData = {
vaultUuid: valetTokenData.vaultUuid,
const whitelistedData: GroupValetTokenData = {
groupUuid: valetTokenData.groupUuid,
remoteIdentifier: valetTokenData.remoteIdentifier,
permittedOperation: valetTokenData.permittedOperation,
uploadBytesUsed: valetTokenData.uploadBytesUsed,
@@ -2,8 +2,8 @@ import {
UserFileRemovedEvent,
UserFileUploadedEvent,
DomainEventService,
VaultFileUploadedEvent,
VaultFileRemovedEvent,
GroupFileUploadedEvent,
GroupFileRemovedEvent,
} from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
@@ -56,18 +56,18 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
}
}
createVaultFileUploadedEvent(payload: {
vaultUuid: string
createGroupFileUploadedEvent(payload: {
groupUuid: string
filePath: string
fileName: string
fileByteSize: number
}): VaultFileUploadedEvent {
}): GroupFileUploadedEvent {
return {
type: 'VAULT_FILE_UPLOADED',
type: 'GROUP_FILE_UPLOADED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: payload.vaultUuid,
userIdentifier: payload.groupUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Files,
@@ -76,18 +76,18 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
}
}
createVaultFileRemovedEvent(payload: {
vaultUuid: string
createGroupFileRemovedEvent(payload: {
groupUuid: string
filePath: string
fileName: string
fileByteSize: number
}): VaultFileRemovedEvent {
}): GroupFileRemovedEvent {
return {
type: 'VAULT_FILE_REMOVED',
type: 'GROUP_FILE_REMOVED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: payload.vaultUuid,
userIdentifier: payload.groupUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Files,
@@ -1,8 +1,8 @@
import {
UserFileUploadedEvent,
UserFileRemovedEvent,
VaultFileUploadedEvent,
VaultFileRemovedEvent,
GroupFileUploadedEvent,
GroupFileRemovedEvent,
} from '@standardnotes/domain-events'
export interface DomainEventFactoryInterface {
@@ -20,16 +20,16 @@ export interface DomainEventFactoryInterface {
regularSubscriptionUuid: string
}): UserFileRemovedEvent
createVaultFileUploadedEvent(payload: {
vaultUuid: string
createGroupFileUploadedEvent(payload: {
groupUuid: string
filePath: string
fileName: string
fileByteSize: number
}): VaultFileUploadedEvent
createVaultFileRemovedEvent(payload: {
vaultUuid: string
}): GroupFileUploadedEvent
createGroupFileRemovedEvent(payload: {
groupUuid: string
filePath: string
fileName: string
fileByteSize: number
}): VaultFileRemovedEvent
}): GroupFileRemovedEvent
}
@@ -64,8 +64,8 @@ export class FinishUploadSession implements UseCaseInterface {
)
} else {
await this.domainEventPublisher.publish(
this.domainEventFactory.createVaultFileUploadedEvent({
vaultUuid: dto.ownerUuid,
this.domainEventFactory.createGroupFileUploadedEvent({
groupUuid: dto.ownerUuid,
filePath: `${dto.ownerUuid}/${dto.resourceRemoteIdentifier}`,
fileName: dto.resourceRemoteIdentifier,
fileByteSize: totalFileSize,
@@ -1,6 +1,6 @@
export type FinishUploadSessionDTO = {
ownerUuid: string
ownerType: 'user' | 'vault'
ownerType: 'user' | 'group'
resourceRemoteIdentifier: string
uploadBytesUsed: number
uploadBytesLimit: number
@@ -1,7 +1,7 @@
import { VaultMoveType } from '@standardnotes/security'
import { GroupMoveType } from '@standardnotes/security'
export type MoveFileDTO = {
moveType: VaultMoveType
moveType: GroupMoveType
fromUuid: string
toUuid: string
resourceRemoteIdentifier: string
@@ -22,7 +22,7 @@ export class RemoveFile implements UseCaseInterface {
try {
this.logger.debug(`Removing file: ${dto.resourceRemoteIdentifier}`)
const ownerUuid = isRemoveUserFileDTO(dto) ? dto.userUuid : dto.vaultUuid
const ownerUuid = isRemoveUserFileDTO(dto) ? dto.userUuid : dto.groupUuid
const filePath = `${ownerUuid}/${dto.resourceRemoteIdentifier}`
@@ -40,9 +40,9 @@ export class RemoveFile implements UseCaseInterface {
)
} else {
await this.domainEventPublisher.publish(
this.domainEventFactory.createVaultFileRemovedEvent({
vaultUuid: dto.vaultUuid,
filePath: `${dto.vaultUuid}/${dto.resourceRemoteIdentifier}`,
this.domainEventFactory.createGroupFileRemovedEvent({
groupUuid: dto.groupUuid,
filePath: `${dto.groupUuid}/${dto.resourceRemoteIdentifier}`,
fileName: dto.resourceRemoteIdentifier,
fileByteSize: removedFileSize,
}),
@@ -1,4 +1,4 @@
export type RemoveFileDTO = RemoveUserFileDTO | RemoveVaultFileDTO
export type RemoveFileDTO = RemoveUserFileDTO | RemoveGroupFileDTO
export type RemoveUserFileDTO = {
userUuid: string
@@ -6,8 +6,8 @@ export type RemoveUserFileDTO = {
regularSubscriptionUuid: string
}
export type RemoveVaultFileDTO = {
vaultUuid: string
export type RemoveGroupFileDTO = {
groupUuid: string
resourceRemoteIdentifier: string
}
@@ -0,0 +1 @@
export type GroupMoveType = 'group-to-user' | 'user-to-group' | 'group-to-group'
@@ -1,15 +1,15 @@
import { ValetTokenOperation } from './ValetTokenOperation'
import { VaultMoveType } from './VaultMoveType'
import { GroupMoveType } from './GroupMoveType'
export type VaultValetTokenData = {
vaultUuid: string
export type GroupValetTokenData = {
groupUuid: string
permittedOperation: ValetTokenOperation
remoteIdentifier: string
unencryptedFileSize?: number
uploadBytesUsed: number
uploadBytesLimit: number
moveOperation?: {
type: VaultMoveType
type: GroupMoveType
fromUuid: string
toUuid: string
}
@@ -1 +0,0 @@
export type VaultMoveType = 'vault-to-user' | 'user-to-vault' | 'vault-to-vault'
+2 -2
View File
@@ -10,8 +10,8 @@ export * from './Token/CrossServiceTokenData'
export * from './Token/OfflineFeaturesTokenData'
export * from './Token/OfflineUserTokenData'
export * from './Token/SessionTokenData'
export * from './Token/VaultValetTokenData'
export * from './Token/GroupValetTokenData'
export * from './Token/ValetTokenData'
export * from './Token/ValetTokenOperation'
export * from './Token/VaultMoveType'
export * from './Token/GroupMoveType'
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_vault_uuid_to_items
yarn build && yarn typeorm migration:generate -d dist/src/Bootstrap/DataSource.js migrations/mysql/add_group_uuid_to_items
```
+3 -3
View File
@@ -4,9 +4,9 @@ import 'newrelic'
import '../src/Controller/HealthCheckController'
import '../src/Controller/ItemsController'
import '../src/Controller/VaultsController'
import '../src/Controller/VaultUsersController'
import '../src/Controller/VaultInvitesController'
import '../src/Controller/GroupsController'
import '../src/Controller/GroupUsersController'
import '../src/Controller/GroupInvitesController'
import '../src/Controller/ContactsController'
import helmet from 'helmet'
@@ -1,11 +1,11 @@
import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'
export class CreateVault1684173017359 implements MigrationInterface {
name = 'createVault1684173017359'
export class CreateGroup1684173017359 implements MigrationInterface {
name = 'createGroup1684173017359'
public async up(queryRunner: QueryRunner): Promise<void> {
const table = new Table({
name: 'vaults',
name: 'groups',
columns: [
new TableColumn({
name: 'uuid',
@@ -48,6 +48,6 @@ export class CreateVault1684173017359 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('vaults')
await queryRunner.dropTable('groups')
}
}
@@ -1,11 +1,11 @@
import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'
export class CreateVaultUser1684173023883 implements MigrationInterface {
name = 'createVaultUser1684173023883'
export class CreateGroupUser1684173023883 implements MigrationInterface {
name = 'createGroupUser1684173023883'
public async up(queryRunner: QueryRunner): Promise<void> {
const table = new Table({
name: 'vault_users',
name: 'group_users',
columns: [
new TableColumn({
name: 'uuid',
@@ -14,7 +14,7 @@ export class CreateVaultUser1684173023883 implements MigrationInterface {
isPrimary: true,
}),
new TableColumn({
name: 'vault_uuid',
name: 'group_uuid',
type: 'varchar',
length: '36',
isNullable: false,
@@ -46,6 +46,6 @@ export class CreateVaultUser1684173023883 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('vault_users')
await queryRunner.dropTable('group_users')
}
}
@@ -1,13 +1,13 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'
export class AddVaultUuidToItems1684318117584 implements MigrationInterface {
name = 'addVaultUuidToItems1684318117584'
export class AddGroupUuidToItems1684318117584 implements MigrationInterface {
name = 'addGroupUuidToItems1684318117584'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
'items',
new TableColumn({
name: 'vault_uuid',
name: 'group_uuid',
type: 'varchar',
length: '36',
isNullable: true,
@@ -34,7 +34,7 @@ export class AddVaultUuidToItems1684318117584 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('items', 'vault_uuid')
await queryRunner.dropColumn('items', 'group_uuid')
await queryRunner.dropColumn('items', 'last_edited_by_uuid')
}
}
@@ -1,11 +1,11 @@
import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'
export class CreateVaultInvites1684528646896 implements MigrationInterface {
name = 'createVaultInvites1684528646896'
export class CreateGroupInvites1684528646896 implements MigrationInterface {
name = 'createGroupInvites1684528646896'
public async up(queryRunner: QueryRunner): Promise<void> {
const table = new Table({
name: 'vault_invites',
name: 'group_invites',
columns: [
new TableColumn({
name: 'uuid',
@@ -14,7 +14,7 @@ export class CreateVaultInvites1684528646896 implements MigrationInterface {
isPrimary: true,
}),
new TableColumn({
name: 'vault_uuid',
name: 'group_uuid',
type: 'varchar',
length: '36',
isNullable: false,
@@ -44,7 +44,7 @@ export class CreateVaultInvites1684528646896 implements MigrationInterface {
isNullable: false,
}),
new TableColumn({
name: 'encrypted_vault_data',
name: 'encrypted_group_data',
type: 'text',
isNullable: false,
}),
@@ -69,6 +69,6 @@ export class CreateVaultInvites1684528646896 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('vault_invites')
await queryRunner.dropTable('group_invites')
}
}
@@ -1,11 +1,11 @@
import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'
export class CreateRemoveVaultUsers1685469710749 implements MigrationInterface {
name = 'createRemoveVaultUsers1685469710749'
export class CreateRemoveGroupUsers1685469710749 implements MigrationInterface {
name = 'createRemoveGroupUsers1685469710749'
public async up(queryRunner: QueryRunner): Promise<void> {
const table = new Table({
name: 'removed_vault_users',
name: 'removed_group_users',
columns: [
new TableColumn({
name: 'uuid',
@@ -14,7 +14,7 @@ export class CreateRemoveVaultUsers1685469710749 implements MigrationInterface {
isPrimary: true,
}),
new TableColumn({
name: 'vault_uuid',
name: 'group_uuid',
type: 'varchar',
length: '36',
isNullable: false,
@@ -46,6 +46,6 @@ export class CreateRemoveVaultUsers1685469710749 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('removed_vault_users')
await queryRunner.dropTable('removed_group_users')
}
}
@@ -1,9 +1,9 @@
import { TypeORMVaultInviteRepository } from '../Domain/VaultInvite/Repository/TypeORMVaultInviteRepository'
import { TypeORMGroupInviteRepository } from '../Domain/GroupInvite/Repository/TypeORMGroupInviteRepository'
import { Contact } from './../Domain/Contact/Model/Contact'
import { VaultInvite } from '../Domain/VaultInvite/Model/VaultInvite'
import { VaultUser } from '../Domain/VaultUser/Model/VaultUser'
import { Vault } from '../Domain/Vault/Model/Vault'
import { TypeORMVaultRepository } from '../Domain/Vault/Repository/TypeORMVaultRepository'
import { GroupInvite } from '../Domain/GroupInvite/Model/GroupInvite'
import { GroupUser } from '../Domain/GroupUser/Model/GroupUser'
import { Group } from '../Domain/Group/Model/Group'
import { TypeORMGroupRepository } from '../Domain/Group/Repository/TypeORMGroupRepository'
import * as winston from 'winston'
import { Container, interfaces } from 'inversify'
@@ -24,23 +24,23 @@ import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
import { Timer, TimerInterface } from '@standardnotes/time'
import { ItemTransferCalculatorInterface } from '../Domain/Item/ItemTransferCalculatorInterface'
import { ItemTransferCalculator } from '../Domain/Item/ItemTransferCalculator'
import { VaultsRepositoryInterface } from '../Domain/Vault/Repository/VaultRepositoryInterface'
import { VaultUserRepositoryInterface } from '../Domain/VaultUser/Repository/VaultUserRepositoryInterface'
import { TypeORMVaultUserRepository } from '../Domain/VaultUser/Repository/TypeORMVaultUserRepository'
import { VaultUserProjection } from '../Projection/VaultUserProjection'
import { VaultUserProjector } from '../Projection/VaultUserProjector'
import { VaultProjection } from '../Projection/VaultProjection'
import { VaultProjector } from '../Projection/VaultProjector'
import { VaultInviteProjection } from '../Projection/VaultInviteProjection'
import { VaultInviteProjector } from '../Projection/VaultInviteProjector'
import { GroupsRepositoryInterface } from '../Domain/Group/Repository/GroupRepositoryInterface'
import { GroupUserRepositoryInterface } from '../Domain/GroupUser/Repository/GroupUserRepositoryInterface'
import { TypeORMGroupUserRepository } from '../Domain/GroupUser/Repository/TypeORMGroupUserRepository'
import { GroupUserProjection } from '../Projection/GroupUserProjection'
import { GroupUserProjector } from '../Projection/GroupUserProjector'
import { GroupProjection } from '../Projection/GroupProjection'
import { GroupProjector } from '../Projection/GroupProjector'
import { GroupInviteProjection } from '../Projection/GroupInviteProjection'
import { GroupInviteProjector } from '../Projection/GroupInviteProjector'
import { ContactProjection } from '../Projection/ContactProjection'
import { ContactProjector } from '../Projection/ContactProjector'
import { VaultInviteRepositoryInterface } from '../Domain/VaultInvite/Repository/VaultInviteRepositoryInterface'
import { GroupInviteRepositoryInterface } from '../Domain/GroupInvite/Repository/GroupInviteRepositoryInterface'
import { ContactsRepositoryInterface } from '../Domain/Contact/Repository/ContactRepositoryInterface'
import { TypeORMContactRepository } from '../Domain/Contact/Repository/TypeORMContactRepository'
import { RemovedVaultUserRepositoryInterface } from '../Domain/RemovedVaultUser/Repository/RemovedVaultUserRepositoryInterface'
import { TypeORMRemovedVaultUserRepository } from '../Domain/RemovedVaultUser/Repository/TypeORMRemovedVaultUserRepository'
import { RemovedVaultUser } from '../Domain/RemovedVaultUser/Model/RemovedVaultUser'
import { RemovedGroupUserRepositoryInterface } from '../Domain/RemovedGroupUser/Repository/RemovedGroupUserRepositoryInterface'
import { TypeORMRemovedGroupUserRepository } from '../Domain/RemovedGroupUser/Repository/TypeORMRemovedGroupUserRepository'
import { RemovedGroupUser } from '../Domain/RemovedGroupUser/Model/RemovedGroupUser'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -99,23 +99,23 @@ export class CommonContainerConfigLoader {
container.bind<ItemRepositoryInterface>(TYPES.ItemRepository).toDynamicValue((context: interfaces.Context) => {
return new TypeORMItemRepository(context.container.get(TYPES.ORMItemRepository))
})
container.bind<VaultsRepositoryInterface>(TYPES.VaultRepository).toDynamicValue((context: interfaces.Context) => {
return new TypeORMVaultRepository(context.container.get(TYPES.ORMVaultRepository))
container.bind<GroupsRepositoryInterface>(TYPES.GroupRepository).toDynamicValue((context: interfaces.Context) => {
return new TypeORMGroupRepository(context.container.get(TYPES.ORMGroupRepository))
})
container
.bind<VaultUserRepositoryInterface>(TYPES.VaultUserRepository)
.bind<GroupUserRepositoryInterface>(TYPES.GroupUserRepository)
.toDynamicValue((context: interfaces.Context) => {
return new TypeORMVaultUserRepository(context.container.get(TYPES.ORMVaultUserRepository))
return new TypeORMGroupUserRepository(context.container.get(TYPES.ORMGroupUserRepository))
})
container
.bind<RemovedVaultUserRepositoryInterface>(TYPES.RemovedVaultUserRepository)
.bind<RemovedGroupUserRepositoryInterface>(TYPES.RemovedGroupUserRepository)
.toDynamicValue((context: interfaces.Context) => {
return new TypeORMRemovedVaultUserRepository(context.container.get(TYPES.ORMRemovedVaultUserRepository))
return new TypeORMRemovedGroupUserRepository(context.container.get(TYPES.ORMRemovedGroupUserRepository))
})
container
.bind<VaultInviteRepositoryInterface>(TYPES.VaultInviteRepository)
.bind<GroupInviteRepositoryInterface>(TYPES.GroupInviteRepository)
.toDynamicValue((context: interfaces.Context) => {
return new TypeORMVaultInviteRepository(context.container.get(TYPES.ORMVaultInviteRepository))
return new TypeORMGroupInviteRepository(context.container.get(TYPES.ORMGroupInviteRepository))
})
container
.bind<ContactsRepositoryInterface>(TYPES.ContactRepository)
@@ -125,16 +125,16 @@ export class CommonContainerConfigLoader {
// ORM
container.bind<Repository<Item>>(TYPES.ORMItemRepository).toDynamicValue(() => AppDataSource.getRepository(Item))
container.bind<Repository<Vault>>(TYPES.ORMVaultRepository).toDynamicValue(() => AppDataSource.getRepository(Vault))
container.bind<Repository<Group>>(TYPES.ORMGroupRepository).toDynamicValue(() => AppDataSource.getRepository(Group))
container
.bind<Repository<VaultUser>>(TYPES.ORMVaultUserRepository)
.toDynamicValue(() => AppDataSource.getRepository(VaultUser))
.bind<Repository<GroupUser>>(TYPES.ORMGroupUserRepository)
.toDynamicValue(() => AppDataSource.getRepository(GroupUser))
container
.bind<Repository<RemovedVaultUser>>(TYPES.ORMRemovedVaultUserRepository)
.toDynamicValue(() => AppDataSource.getRepository(RemovedVaultUser))
.bind<Repository<RemovedGroupUser>>(TYPES.ORMRemovedGroupUserRepository)
.toDynamicValue(() => AppDataSource.getRepository(RemovedGroupUser))
container
.bind<Repository<VaultInvite>>(TYPES.ORMVaultInviteRepository)
.toDynamicValue(() => AppDataSource.getRepository(VaultInvite))
.bind<Repository<GroupInvite>>(TYPES.ORMGroupInviteRepository)
.toDynamicValue(() => AppDataSource.getRepository(GroupInvite))
container
.bind<Repository<Contact>>(TYPES.ORMContactRepository)
.toDynamicValue(() => AppDataSource.getRepository(Contact))
@@ -145,16 +145,16 @@ export class CommonContainerConfigLoader {
.toDynamicValue((context: interfaces.Context) => {
return new ItemProjector(context.container.get(TYPES.Timer))
})
container.bind<ProjectorInterface<Vault, VaultProjection>>(TYPES.VaultProjector).toDynamicValue(() => {
return new VaultProjector()
container.bind<ProjectorInterface<Group, GroupProjection>>(TYPES.GroupProjector).toDynamicValue(() => {
return new GroupProjector()
})
container.bind<ProjectorInterface<VaultUser, VaultUserProjection>>(TYPES.VaultUserProjector).toDynamicValue(() => {
return new VaultUserProjector()
container.bind<ProjectorInterface<GroupUser, GroupUserProjection>>(TYPES.GroupUserProjector).toDynamicValue(() => {
return new GroupUserProjector()
})
container
.bind<ProjectorInterface<VaultInvite, VaultInviteProjection>>(TYPES.VaultInviteProjector)
.bind<ProjectorInterface<GroupInvite, GroupInviteProjection>>(TYPES.GroupInviteProjector)
.toDynamicValue(() => {
return new VaultInviteProjector()
return new GroupInviteProjector()
})
container.bind<ProjectorInterface<Contact, ContactProjection>>(TYPES.ContactProjector).toDynamicValue(() => {
return new ContactProjector()
@@ -1,13 +1,13 @@
import { RemovedVaultUser } from './../Domain/RemovedVaultUser/Model/RemovedVaultUser'
import { RemovedGroupUser } from './../Domain/RemovedGroupUser/Model/RemovedGroupUser'
import { Contact } from './../Domain/Contact/Model/Contact'
import { VaultInvite } from '../Domain/VaultInvite/Model/VaultInvite'
import { VaultUser } from '../Domain/VaultUser/Model/VaultUser'
import { GroupInvite } from '../Domain/GroupInvite/Model/GroupInvite'
import { GroupUser } from '../Domain/GroupUser/Model/GroupUser'
import { DataSource, LoggerOptions } from 'typeorm'
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
import { Item } from '../Domain/Item/Item'
import { Env } from './Env'
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
import { Vault } from '../Domain/Vault/Model/Vault'
import { Group } from '../Domain/Group/Model/Group'
const env: Env = new Env()
env.load()
@@ -43,7 +43,7 @@ const replicationConfig = {
const commonDataSourceOptions = {
maxQueryExecutionTime,
entities: [Item, Vault, VaultUser, RemovedVaultUser, VaultInvite, Contact],
entities: [Item, Group, GroupUser, RemovedGroupUser, GroupInvite, Contact],
migrations: [`dist/migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
migrationsRun: true,
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
@@ -1,9 +1,9 @@
import { GetGlobalItem } from './../Domain/UseCase/GetGlobalItem/GetGlobalItem'
import { ContactFactory } from './../Domain/Contact/Factory/ContactFactory'
import { VaultServiceInterface } from '../Domain/Vault/Service/VaultServiceInterface'
import { VaultUserFactory } from '../Domain/VaultUser/Factory/VaultUserFactory'
import { VaultFactoryInterface } from '../Domain/Vault/Factory/VaultFactoryInterface'
import { GroupServiceInterface } from '../Domain/Group/Service/GroupServiceInterface'
import { GroupUserFactory } from '../Domain/GroupUser/Factory/GroupUserFactory'
import { GroupFactoryInterface } from '../Domain/Group/Factory/GroupFactoryInterface'
import { Container, interfaces } from 'inversify'
import { Env } from './Env'
@@ -20,7 +20,7 @@ import { SyncItems } from '../Domain/UseCase/SyncItems'
import { ItemConflictProjector } from '../Projection/ItemConflictProjector'
import { ItemSaveValidatorInterface } from '../Domain/Item/SaveValidator/ItemSaveValidatorInterface'
import { ItemSaveValidator } from '../Domain/Item/SaveValidator/ItemSaveValidator'
import { VaultFilter } from '../Domain/Item/SaveRule/VaultFilter'
import { GroupFilter } from '../Domain/Item/SaveRule/GroupFilter'
import { OwnershipFilter } from '../Domain/Item/SaveRule/OwnershipFilter'
import { TimeDifferenceFilter } from '../Domain/Item/SaveRule/TimeDifferenceFilter'
import { ItemFactoryInterface } from '../Domain/Item/ItemFactoryInterface'
@@ -37,21 +37,21 @@ import { ItemConflict } from '../Domain/Item/ItemConflict'
import { ItemConflictProjection } from '../Projection/ItemConflictProjection'
import { CommonContainerConfigLoader } from './CommonContainerConfigLoader'
import { TokenEncoder, TokenEncoderInterface, ValetTokenData } from '@standardnotes/security'
import { VaultFactory } from '../Domain/Vault/Factory/VaultFactory'
import { VaultService } from '../Domain/Vault/Service/VaultService'
import { VaultUserService } from '../Domain/VaultUser/Service/VaultUserService'
import { VaultUserServiceInterface } from '../Domain/VaultUser/Service/VaultUserServiceInterface'
import { VaultInviteServiceInterface } from '../Domain/VaultInvite/Service/VaultInviteServiceInterface'
import { VaultInviteService } from '../Domain/VaultInvite/Service/VaultInviteService'
import { VaultInviteFactory } from '../Domain/VaultInvite/Factory/VaultInviteFactory'
import { GroupFactory } from '../Domain/Group/Factory/GroupFactory'
import { GroupService } from '../Domain/Group/Service/GroupService'
import { GroupUserService } from '../Domain/GroupUser/Service/GroupUserService'
import { GroupUserServiceInterface } from '../Domain/GroupUser/Service/GroupUserServiceInterface'
import { GroupInviteServiceInterface } from '../Domain/GroupInvite/Service/GroupInviteServiceInterface'
import { GroupInviteService } from '../Domain/GroupInvite/Service/GroupInviteService'
import { GroupInviteFactory } from '../Domain/GroupInvite/Factory/GroupInviteFactory'
import { ContactServiceInterface } from '../Domain/Contact/Service/ContactServiceInterface'
import { ContactService } from '../Domain/Contact/Service/ContactService'
import { ContactFactoryInterface } from '../Domain/Contact/Factory/ContactFactoryInterface'
import { VaultSnjsVersionFilter } from '../Domain/Item/SaveRule/VaultSnjsVersionFilter'
import { CreateVaultFileValetToken } from '../Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileValetToken'
import { RemovedVaultUserService } from '../Domain/RemovedVaultUser/Service/RemovedVaultUserService'
import { RemovedVaultUserServiceInterface } from '../Domain/RemovedVaultUser/Service/RemovedVaultUserServiceInterface'
import { RemovedVaultUserFactory } from '../Domain/RemovedVaultUser/Factory/RemovedVaultUserFactory'
import { GroupSnjsVersionFilter } from '../Domain/Item/SaveRule/GroupSnjsVersionFilter'
import { CreateGroupFileValetToken } from '../Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileValetToken'
import { RemovedGroupUserService } from '../Domain/RemovedGroupUser/Service/RemovedGroupUserService'
import { RemovedGroupUserServiceInterface } from '../Domain/RemovedGroupUser/Service/RemovedGroupUserServiceInterface'
import { RemovedGroupUserFactory } from '../Domain/RemovedGroupUser/Factory/RemovedGroupUserFactory'
export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -103,8 +103,8 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
container.bind<SyncItems>(TYPES.SyncItems).toDynamicValue((context: interfaces.Context) => {
return new SyncItems(
context.container.get(TYPES.ItemService),
context.container.get(TYPES.VaultService),
context.container.get(TYPES.VaultInviteService),
context.container.get(TYPES.GroupService),
context.container.get(TYPES.GroupInviteService),
context.container.get(TYPES.ContactService),
)
})
@@ -119,11 +119,11 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
})
container
.bind<CreateVaultFileValetToken>(TYPES.CreateVaultFileReadValetToken)
.bind<CreateGroupFileValetToken>(TYPES.CreateGroupFileReadValetToken)
.toDynamicValue((context: interfaces.Context) => {
return new CreateVaultFileValetToken(
context.container.get(TYPES.VaultService),
context.container.get(TYPES.VaultUserService),
return new CreateGroupFileValetToken(
context.container.get(TYPES.GroupService),
context.container.get(TYPES.GroupUserService),
context.container.get(TYPES.ValetTokenEncoder),
context.container.get(TYPES.VALET_TOKEN_TTL),
)
@@ -147,48 +147,48 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
context.container.get(TYPES.Timer),
context.container.get(TYPES.ItemProjector),
context.container.get(TYPES.MAX_ITEMS_LIMIT),
context.container.get(TYPES.VaultUserRepository),
context.container.get(TYPES.VaultService),
context.container.get(TYPES.GroupUserRepository),
context.container.get(TYPES.GroupService),
context.container.get(TYPES.Logger),
)
})
// Services
container.bind<VaultServiceInterface>(TYPES.VaultService).toDynamicValue((context: interfaces.Context) => {
return new VaultService(
context.container.get(TYPES.VaultRepository),
context.container.get(TYPES.VaultFactory),
context.container.get(TYPES.VaultUserService),
context.container.get(TYPES.VaultInviteService),
container.bind<GroupServiceInterface>(TYPES.GroupService).toDynamicValue((context: interfaces.Context) => {
return new GroupService(
context.container.get(TYPES.GroupRepository),
context.container.get(TYPES.GroupFactory),
context.container.get(TYPES.GroupUserService),
context.container.get(TYPES.GroupInviteService),
context.container.get(TYPES.Timer),
)
})
container.bind<VaultUserServiceInterface>(TYPES.VaultUserService).toDynamicValue((context: interfaces.Context) => {
return new VaultUserService(
context.container.get(TYPES.VaultRepository),
context.container.get(TYPES.VaultUserRepository),
context.container.get(TYPES.VaultUserFactory),
context.container.get(TYPES.RemovedVaultUserService),
container.bind<GroupUserServiceInterface>(TYPES.GroupUserService).toDynamicValue((context: interfaces.Context) => {
return new GroupUserService(
context.container.get(TYPES.GroupRepository),
context.container.get(TYPES.GroupUserRepository),
context.container.get(TYPES.GroupUserFactory),
context.container.get(TYPES.RemovedGroupUserService),
context.container.get(TYPES.Timer),
)
})
container
.bind<RemovedVaultUserServiceInterface>(TYPES.RemovedVaultUserService)
.bind<RemovedGroupUserServiceInterface>(TYPES.RemovedGroupUserService)
.toDynamicValue((context: interfaces.Context) => {
return new RemovedVaultUserService(
context.container.get(TYPES.VaultRepository),
context.container.get(TYPES.RemovedVaultUserRepository),
context.container.get(TYPES.RemovedVaultUserFactory),
return new RemovedGroupUserService(
context.container.get(TYPES.GroupRepository),
context.container.get(TYPES.RemovedGroupUserRepository),
context.container.get(TYPES.RemovedGroupUserFactory),
context.container.get(TYPES.Timer),
)
})
container
.bind<VaultInviteServiceInterface>(TYPES.VaultInviteService)
.bind<GroupInviteServiceInterface>(TYPES.GroupInviteService)
.toDynamicValue((context: interfaces.Context) => {
return new VaultInviteService(
context.container.get(TYPES.VaultRepository),
context.container.get(TYPES.VaultInviteRepository),
context.container.get(TYPES.VaultInviteFactory),
context.container.get(TYPES.VaultUserService),
return new GroupInviteService(
context.container.get(TYPES.GroupRepository),
context.container.get(TYPES.GroupInviteRepository),
context.container.get(TYPES.GroupInviteFactory),
context.container.get(TYPES.GroupUserService),
context.container.get(TYPES.Timer),
)
})
@@ -212,9 +212,9 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
context.container.get(TYPES.ItemProjector),
context.container.get(TYPES.ItemConflictProjector),
context.container.get(TYPES.SavedItemProjector),
context.container.get(TYPES.VaultInviteProjector),
context.container.get(TYPES.GroupInviteProjector),
context.container.get(TYPES.ContactProjector),
context.container.get(TYPES.VaultProjector),
context.container.get(TYPES.GroupProjector),
)
})
container
@@ -229,19 +229,19 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
container.bind<ItemFactoryInterface>(TYPES.ItemFactory).toDynamicValue((context: interfaces.Context) => {
return new ItemFactory(context.container.get(TYPES.Timer), context.container.get(TYPES.ItemProjector))
})
container.bind<VaultFactoryInterface>(TYPES.VaultFactory).toDynamicValue((context: interfaces.Context) => {
return new VaultFactory(context.container.get(TYPES.Timer))
container.bind<GroupFactoryInterface>(TYPES.GroupFactory).toDynamicValue((context: interfaces.Context) => {
return new GroupFactory(context.container.get(TYPES.Timer))
})
container.bind<VaultUserFactory>(TYPES.VaultUserFactory).toDynamicValue((context: interfaces.Context) => {
return new VaultUserFactory(context.container.get(TYPES.Timer))
container.bind<GroupUserFactory>(TYPES.GroupUserFactory).toDynamicValue((context: interfaces.Context) => {
return new GroupUserFactory(context.container.get(TYPES.Timer))
})
container
.bind<RemovedVaultUserFactory>(TYPES.RemovedVaultUserFactory)
.bind<RemovedGroupUserFactory>(TYPES.RemovedGroupUserFactory)
.toDynamicValue((context: interfaces.Context) => {
return new RemovedVaultUserFactory(context.container.get(TYPES.Timer))
return new RemovedGroupUserFactory(context.container.get(TYPES.Timer))
})
container.bind<VaultInviteFactory>(TYPES.VaultInviteFactory).toDynamicValue((context: interfaces.Context) => {
return new VaultInviteFactory(context.container.get(TYPES.Timer))
container.bind<GroupInviteFactory>(TYPES.GroupInviteFactory).toDynamicValue((context: interfaces.Context) => {
return new GroupInviteFactory(context.container.get(TYPES.Timer))
})
container.bind<ContactFactoryInterface>(TYPES.ContactFactory).toDynamicValue((context: interfaces.Context) => {
return new ContactFactory(context.container.get(TYPES.Timer))
@@ -249,10 +249,10 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
container.bind<OwnershipFilter>(TYPES.OwnershipFilter).toDynamicValue(() => new OwnershipFilter())
container
.bind<VaultFilter>(TYPES.VaultFilter)
.bind<GroupFilter>(TYPES.GroupFilter)
.toDynamicValue(
(context: interfaces.Context) =>
new VaultFilter(context.container.get(TYPES.VaultService), context.container.get(TYPES.VaultUserService)),
new GroupFilter(context.container.get(TYPES.GroupService), context.container.get(TYPES.GroupUserService)),
)
container
.bind<TimeDifferenceFilter>(TYPES.TimeDifferenceFilter)
@@ -261,16 +261,16 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
container.bind<ContentTypeFilter>(TYPES.ContentTypeFilter).toDynamicValue(() => new ContentTypeFilter())
container.bind<ContentFilter>(TYPES.ContentFilter).toDynamicValue(() => new ContentFilter())
container
.bind<VaultSnjsVersionFilter>(TYPES.VaultSnjsVersionFilter)
.toDynamicValue(() => new VaultSnjsVersionFilter())
.bind<GroupSnjsVersionFilter>(TYPES.GroupSnjsVersionFilter)
.toDynamicValue(() => new GroupSnjsVersionFilter())
container
.bind<ItemSaveValidatorInterface>(TYPES.ItemSaveValidator)
.toDynamicValue((context: interfaces.Context) => {
return new ItemSaveValidator([
context.container.get(TYPES.OwnershipFilter),
context.container.get(TYPES.VaultFilter),
context.container.get(TYPES.VaultSnjsVersionFilter),
context.container.get(TYPES.GroupFilter),
context.container.get(TYPES.GroupSnjsVersionFilter),
context.container.get(TYPES.TimeDifferenceFilter),
context.container.get(TYPES.UuidFilter),
context.container.get(TYPES.ContentTypeFilter),
+24 -24
View File
@@ -9,19 +9,19 @@ const TYPES = {
// Repositories
ItemRepository: Symbol.for('ItemRepository'),
VaultRepository: Symbol.for('VaultRepository'),
VaultUserRepository: Symbol.for('VaultUserRepository'),
RemovedVaultUserRepository: Symbol.for('RemovedVaultUserRepository'),
GroupRepository: Symbol.for('GroupRepository'),
GroupUserRepository: Symbol.for('GroupUserRepository'),
RemovedGroupUserRepository: Symbol.for('RemovedGroupUserRepository'),
ContactRepository: Symbol.for('ContactRepository'),
VaultInviteRepository: Symbol.for('VaultInviteRepository'),
GroupInviteRepository: Symbol.for('GroupInviteRepository'),
// ORM
ORMItemRepository: Symbol.for('ORMItemRepository'),
ORMVaultRepository: Symbol.for('ORMVaultRepository'),
ORMVaultUserRepository: Symbol.for('ORMVaultUserRepository'),
ORMRemovedVaultUserRepository: Symbol.for('ORMRemovedVaultUserRepository'),
ORMGroupRepository: Symbol.for('ORMGroupRepository'),
ORMGroupUserRepository: Symbol.for('ORMGroupUserRepository'),
ORMRemovedGroupUserRepository: Symbol.for('ORMRemovedGroupUserRepository'),
ORMContactRepository: Symbol.for('ORMContactRepository'),
ORMVaultInviteRepository: Symbol.for('ORMVaultInviteRepository'),
ORMGroupInviteRepository: Symbol.for('ORMGroupInviteRepository'),
// Middleware
AuthMiddleware: Symbol.for('AuthMiddleware'),
@@ -30,25 +30,25 @@ const TYPES = {
ItemProjector: Symbol.for('ItemProjector'),
SavedItemProjector: Symbol.for('SavedItemProjector'),
ItemConflictProjector: Symbol.for('ItemConflictProjector'),
VaultProjector: Symbol.for('VaultProjector'),
VaultUserProjector: Symbol.for('VaultUserProjector'),
GroupProjector: Symbol.for('GroupProjector'),
GroupUserProjector: Symbol.for('GroupUserProjector'),
ContactProjector: Symbol.for('ContactProjector'),
VaultInviteProjector: Symbol.for('VaultInviteProjector'),
GroupInviteProjector: Symbol.for('GroupInviteProjector'),
// Services
VaultService: Symbol.for('VaultService'),
VaultUserService: Symbol.for('VaultUserService'),
RemovedVaultUserService: Symbol.for('RemovedVaultUserService'),
GroupService: Symbol.for('GroupService'),
GroupUserService: Symbol.for('GroupUserService'),
RemovedGroupUserService: Symbol.for('RemovedGroupUserService'),
ContactService: Symbol.for('ContactService'),
VaultInviteService: Symbol.for('VaultInviteService'),
GroupInviteService: Symbol.for('GroupInviteService'),
// Factories
ItemFactory: Symbol.for('ItemFactory'),
VaultFactory: Symbol.for('VaultFactory'),
VaultUserFactory: Symbol.for('VaultUserFactory'),
RemovedVaultUserFactory: Symbol.for('RemovedVaultUserFactory'),
GroupFactory: Symbol.for('GroupFactory'),
GroupUserFactory: Symbol.for('GroupUserFactory'),
RemovedGroupUserFactory: Symbol.for('RemovedGroupUserFactory'),
ContactFactory: Symbol.for('ContactFactory'),
VaultInviteFactory: Symbol.for('VaultInviteFactory'),
GroupInviteFactory: Symbol.for('GroupInviteFactory'),
// env vars
REDIS_URL: Symbol.for('REDIS_URL'),
@@ -75,15 +75,15 @@ const TYPES = {
CheckIntegrity: Symbol.for('CheckIntegrity'),
GetItem: Symbol.for('GetItem'),
GetGlobalItem: Symbol.for('GetGlobalItem'),
CreateVaultFileReadValetToken: Symbol.for('CreateVaultFileReadValetToken'),
CreateGroupFileReadValetToken: Symbol.for('CreateGroupFileReadValetToken'),
// Handlers
UserCredentialsChangedEventHandler: Symbol.for('UserCredentialsChangedEventHandler'),
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
DuplicateItemSyncedEventHandler: Symbol.for('DuplicateItemSyncedEventHandler'),
EmailBackupRequestedEventHandler: Symbol.for('EmailBackupRequestedEventHandler'),
ItemRevisionCreationRequestedEventHandler: Symbol.for('ItemRevisionCreationRequestedEventHandler'),
VaultFileUploadedEventHandler: Symbol.for('VaultFileUploadedEventHandler'),
VaultFileRemovedEventHandler: Symbol.for('VaultFileRemovedEventHandler'),
GroupFileUploadedEventHandler: Symbol.for('GroupFileUploadedEventHandler'),
GroupFileRemovedEventHandler: Symbol.for('GroupFileRemovedEventHandler'),
ContentDecoder: Symbol.for('ContentDecoder'),
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
@@ -101,8 +101,8 @@ const TYPES = {
ItemBackupService: Symbol.for('ItemBackupService'),
ItemSaveValidator: Symbol.for('ItemSaveValidator'),
OwnershipFilter: Symbol.for('OwnershipFilter'),
VaultFilter: Symbol.for('VaultFilter'),
VaultSnjsVersionFilter: Symbol.for('VaultSnjsVersionFilter'),
GroupFilter: Symbol.for('GroupFilter'),
GroupSnjsVersionFilter: Symbol.for('GroupSnjsVersionFilter'),
TimeDifferenceFilter: Symbol.for('TimeDifferenceFilter'),
UuidFilter: Symbol.for('UuidFilter'),
ContentTypeFilter: Symbol.for('ContentTypeFilter'),
@@ -31,8 +31,8 @@ import { ItemRevisionCreationRequestedEventHandler } from '../Domain/Handler/Ite
import { FSItemBackupService } from '../Infra/FS/FSItemBackupService'
import { CommonContainerConfigLoader } from './CommonContainerConfigLoader'
import { UserCredentialsChangedEventHandler } from '../Domain/Handler/UserCredentialsChangedEventHandler'
import { VaultFileUploadedEventHandler } from '../Domain/Handler/VaultFileUploadedEventHandler'
import { VaultFileRemovedEventHandler } from '../Domain/Handler/VaultFileRemovedEventHandler'
import { GroupFileUploadedEventHandler } from '../Domain/Handler/GroupFileUploadedEventHandler'
import { GroupFileRemovedEventHandler } from '../Domain/Handler/GroupFileRemovedEventHandler'
export class WorkerContainerConfigLoader extends CommonContainerConfigLoader {
private readonly DEFAULT_FILE_UPLOAD_PATH = `${__dirname}/../../uploads`
@@ -140,13 +140,13 @@ export class WorkerContainerConfigLoader extends CommonContainerConfigLoader {
.toDynamicValue((context: interfaces.Context) => {
return new UserCredentialsChangedEventHandler(
context.container.get(TYPES.ContactRepository),
context.container.get(TYPES.VaultInviteRepository),
context.container.get(TYPES.GroupInviteRepository),
context.container.get(TYPES.Timer),
)
})
container.bind<VaultFileUploadedEventHandler>(TYPES.VaultFileUploadedEventHandler).to(VaultFileUploadedEventHandler)
container.bind<VaultFileRemovedEventHandler>(TYPES.VaultFileRemovedEventHandler).to(VaultFileRemovedEventHandler)
container.bind<GroupFileUploadedEventHandler>(TYPES.GroupFileUploadedEventHandler).to(GroupFileUploadedEventHandler)
container.bind<GroupFileRemovedEventHandler>(TYPES.GroupFileRemovedEventHandler).to(GroupFileRemovedEventHandler)
// Services
container.bind<ContentDecoder>(TYPES.ContentDecoder).toDynamicValue(() => new ContentDecoder())
@@ -199,8 +199,8 @@ export class WorkerContainerConfigLoader extends CommonContainerConfigLoader {
['EMAIL_BACKUP_REQUESTED', context.container.get(TYPES.EmailBackupRequestedEventHandler)],
['ITEM_REVISION_CREATION_REQUESTED', context.container.get(TYPES.ItemRevisionCreationRequestedEventHandler)],
['USER_CREDENTIALS_CHANGED', container.get(TYPES.UserCredentialsChangedEventHandler)],
['VAULT_FILE_REMOVED', container.get(TYPES.VaultFileRemovedEventHandler)],
['VAULT_FILE_UPLOADED', container.get(TYPES.VaultFileUploadedEventHandler)],
['GROUP_FILE_REMOVED', container.get(TYPES.GroupFileRemovedEventHandler)],
['GROUP_FILE_UPLOADED', container.get(TYPES.GroupFileUploadedEventHandler)],
])
const handler =
@@ -10,29 +10,29 @@ import {
} from 'inversify-express-utils'
import TYPES from '../Bootstrap/Types'
import { inject } from 'inversify'
import { VaultInviteServiceInterface } from '../Domain/VaultInvite/Service/VaultInviteServiceInterface'
import { VaultInviteProjector } from '../Projection/VaultInviteProjector'
import { GroupInviteServiceInterface } from '../Domain/GroupInvite/Service/GroupInviteServiceInterface'
import { GroupInviteProjector } from '../Projection/GroupInviteProjector'
@controller('/vaults')
export class VaultInvitesController extends BaseHttpController {
@controller('/groups')
export class GroupInvitesController extends BaseHttpController {
constructor(
@inject(TYPES.VaultInviteService) private vaultInviteService: VaultInviteServiceInterface,
@inject(TYPES.VaultInviteProjector) private vaultInviteProjector: VaultInviteProjector,
@inject(TYPES.GroupInviteService) private groupInviteService: GroupInviteServiceInterface,
@inject(TYPES.GroupInviteProjector) private groupInviteProjector: GroupInviteProjector,
) {
super()
}
@httpPost('/:vaultUuid/invites', TYPES.AuthMiddleware)
public async createVaultInvite(
@httpPost('/:groupUuid/invites', TYPES.AuthMiddleware)
public async createGroupInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.createInvite({
const result = await this.groupInviteService.createInvite({
originatorUuid: response.locals.user.uuid,
vaultUuid: request.params.vaultUuid,
groupUuid: request.params.groupUuid,
userUuid: request.body.invitee_uuid,
inviterPublicKey: request.body.inviter_public_key,
encryptedVaultData: request.body.encrypted_vault_data,
encryptedGroupData: request.body.encrypted_group_data,
inviteType: request.body.invite_type,
permissions: request.body.permissions,
})
@@ -41,19 +41,19 @@ export class VaultInvitesController extends BaseHttpController {
return this.errorResponse(400, 'Could not create invite')
}
return this.json({ invite: this.vaultInviteProjector.projectFull(result) })
return this.json({ invite: this.groupInviteProjector.projectFull(result) })
}
@httpPatch('/:vaultUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
public async updateVaultInvite(
@httpPatch('/:groupUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
public async updateGroupInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.updateInvite({
const result = await this.groupInviteService.updateInvite({
originatorUuid: response.locals.user.uuid,
inviteUuid: request.params.inviteUuid,
inviterPublicKey: request.body.inviter_public_key,
encryptedVaultData: request.body.encrypted_vault_data,
encryptedGroupData: request.body.encrypted_group_data,
permissions: request.body.permissions,
})
@@ -61,15 +61,15 @@ export class VaultInvitesController extends BaseHttpController {
return this.errorResponse(400, 'Could not update invite')
}
return this.json({ invite: this.vaultInviteProjector.projectFull(result) })
return this.json({ invite: this.groupInviteProjector.projectFull(result) })
}
@httpPost('/:vaultUuid/invites/:inviteUuid/accept', TYPES.AuthMiddleware)
public async acceptVaultInvite(
@httpPost('/:groupUuid/invites/:inviteUuid/accept', TYPES.AuthMiddleware)
public async acceptGroupInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.acceptInvite({
const result = await this.groupInviteService.acceptInvite({
originatorUuid: response.locals.user.uuid,
inviteUuid: request.params.inviteUuid,
})
@@ -81,12 +81,12 @@ export class VaultInvitesController extends BaseHttpController {
return this.json({ success: true })
}
@httpPost('/:vaultUuid/invites/:inviteUuid/decline', TYPES.AuthMiddleware)
public async declineVaultInvite(
@httpPost('/:groupUuid/invites/:inviteUuid/decline', TYPES.AuthMiddleware)
public async declineGroupInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.declineInvite({
const result = await this.groupInviteService.declineInvite({
originatorUuid: response.locals.user.uuid,
inviteUuid: request.params.inviteUuid,
})
@@ -103,7 +103,7 @@ export class VaultInvitesController extends BaseHttpController {
_request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
await this.vaultInviteService.deleteAllInboundInvites({
await this.groupInviteService.deleteAllInboundInvites({
userUuid: response.locals.user.uuid,
})
@@ -115,15 +115,15 @@ export class VaultInvitesController extends BaseHttpController {
_request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.getOutboundInvitesForUser({
const result = await this.groupInviteService.getOutboundInvitesForUser({
userUuid: response.locals.user.uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get vault invites')
return this.errorResponse(400, 'Could not get group invites')
}
const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite))
const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite))
return this.json({ invites: projected })
}
@@ -133,46 +133,46 @@ export class VaultInvitesController extends BaseHttpController {
_request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.getInvitesForUser({
const result = await this.groupInviteService.getInvitesForUser({
userUuid: response.locals.user.uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get vault invites')
return this.errorResponse(400, 'Could not get group invites')
}
const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite))
const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite))
return this.json({ invites: projected })
}
@httpGet('/:vaultUuid/invites', TYPES.AuthMiddleware)
public async getVaultInvites(
@httpGet('/:groupUuid/invites', TYPES.AuthMiddleware)
public async getGroupInvites(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.getInvitesForVault({
const result = await this.groupInviteService.getInvitesForGroup({
originatorUuid: response.locals.user.uuid,
vaultUuid: request.params.vaultUuid,
groupUuid: request.params.groupUuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get vault invites')
return this.errorResponse(400, 'Could not get group invites')
}
const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite))
const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite))
return this.json({ invites: projected })
}
@httpDelete('/:vaultUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
public async deleteVaultInvite(
@httpDelete('/:groupUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
public async deleteGroupInvite(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultInviteService.deleteInvite({
const result = await this.groupInviteService.deleteInvite({
originatorUuid: response.locals.user.uuid,
vaultUuid: request.params.vaultUuid,
groupUuid: request.params.groupUuid,
inviteUuid: request.params.inviteUuid,
})
@@ -2,48 +2,48 @@ import { Request, Response } from 'express'
import { BaseHttpController, controller, results, httpDelete, httpGet } from 'inversify-express-utils'
import TYPES from '../Bootstrap/Types'
import { inject } from 'inversify'
import { VaultUserServiceInterface } from '../Domain/VaultUser/Service/VaultUserServiceInterface'
import { VaultUserProjector } from '../Projection/VaultUserProjector'
import { GroupUserServiceInterface } from '../Domain/GroupUser/Service/GroupUserServiceInterface'
import { GroupUserProjector } from '../Projection/GroupUserProjector'
@controller('/vaults/:vaultUuid/users')
export class VaultUsersController extends BaseHttpController {
@controller('/groups/:groupUuid/users')
export class GroupUsersController extends BaseHttpController {
constructor(
@inject(TYPES.VaultUserService) private vaultUserService: VaultUserServiceInterface,
@inject(TYPES.VaultUserProjector) private vaultUserProjector: VaultUserProjector,
@inject(TYPES.GroupUserService) private groupUserService: GroupUserServiceInterface,
@inject(TYPES.GroupUserProjector) private groupUserProjector: GroupUserProjector,
) {
super()
}
@httpGet('/', TYPES.AuthMiddleware)
public async getVaultUsers(
public async getGroupUsers(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultUserService.getVaultUsersForVault({
const result = await this.groupUserService.getGroupUsersForGroup({
originatorUuid: response.locals.user.uuid,
vaultUuid: request.params.vaultUuid,
groupUuid: request.params.groupUuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get vault users')
return this.errorResponse(400, 'Could not get group users')
}
const { users, isAdmin } = result
const projected = users.map((vaultUser) =>
this.vaultUserProjector.projectAsDisplayableUserForOtherVaultMembers(vaultUser, isAdmin),
const projected = users.map((groupUser) =>
this.groupUserProjector.projectAsDisplayableUserForOtherGroupMembers(groupUser, isAdmin),
)
return this.json({ users: projected })
}
@httpDelete('/:userUuid', TYPES.AuthMiddleware)
public async deleteVaultUser(
public async deleteGroupUser(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultUserService.deleteVaultUser({
vaultUuid: request.params.vaultUuid,
const result = await this.groupUserService.deleteGroupUser({
groupUuid: request.params.groupUuid,
userUuid: request.params.userUuid,
originatorUuid: response.locals.user.uuid,
})
@@ -0,0 +1,150 @@
import { GroupUser } from '../Domain/GroupUser/Model/GroupUser'
import { GroupServiceInterface } from '../Domain/Group/Service/GroupServiceInterface'
import { Request, Response } from 'express'
import {
BaseHttpController,
controller,
httpPost,
results,
httpDelete,
httpPatch,
httpGet,
} from 'inversify-express-utils'
import TYPES from '../Bootstrap/Types'
import { inject } from 'inversify'
import { ProjectorInterface } from '../Projection/ProjectorInterface'
import { Group } from '../Domain/Group/Model/Group'
import { GroupProjection } from '../Projection/GroupProjection'
import { GroupUserProjection } from '../Projection/GroupUserProjection'
import { CreateGroupFileValetToken } from '../Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileValetToken'
import { RemovedGroupUserServiceInterface } from '../Domain/RemovedGroupUser/Service/RemovedGroupUserServiceInterface'
@controller('/groups')
export class GroupsController extends BaseHttpController {
constructor(
@inject(TYPES.GroupService) private groupService: GroupServiceInterface,
@inject(TYPES.RemovedGroupUserService) private removedGroupUserService: RemovedGroupUserServiceInterface,
@inject(TYPES.GroupProjector) private groupProjector: ProjectorInterface<Group, GroupProjection>,
@inject(TYPES.GroupUserProjector) private groupUserProjector: ProjectorInterface<GroupUser, GroupUserProjection>,
@inject(TYPES.CreateGroupFileReadValetToken) private createGroupFileReadValetToken: CreateGroupFileValetToken,
) {
super()
}
@httpGet('/', TYPES.AuthMiddleware)
public async getGroups(_request: Request, response: Response): Promise<results.JsonResult> {
const groups = await this.groupService.getGroups({
userUuid: response.locals.user.uuid,
})
return this.json({
groups: groups.map((group) => this.groupProjector.projectFull(group)),
})
}
@httpPost('/:groupUuid/valet-tokens', TYPES.AuthMiddleware)
public async getValetTokenForGroupFile(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const valetTokenResult = await this.createGroupFileReadValetToken.execute({
userUuid: response.locals.user.uuid,
groupUuid: request.params.groupUuid,
fileUuid: request.body.file_uuid,
remoteIdentifier: request.body.remote_identifier,
operation: request.body.operation,
unencryptedFileSize: request.body.unencrypted_file_size,
moveOperationType: request.body.move_operation_type,
groupToGroupMoveTargetUuid: request.body.group_to_group_move_target_uuid,
})
if (valetTokenResult.success === false) {
return this.errorResponse(400, `Failed to create group valet token: ${valetTokenResult.reason}`)
}
return this.json({
valetToken: valetTokenResult.valetToken,
})
}
@httpPost('/', TYPES.AuthMiddleware)
public async createGroup(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupService.createGroup({
userUuid: response.locals.user.uuid,
groupUuid: request.body.group_uuid,
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not create group')
}
return this.json({
group: this.groupProjector.projectFull(result.group),
groupUser: this.groupUserProjector.projectFull(result.groupUser),
})
}
@httpPatch('/:groupUuid', TYPES.AuthMiddleware)
public async updateGroup(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupService.updateGroup({
groupUuid: request.params.groupUuid,
originatorUuid: response.locals.user.uuid,
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not update group')
}
return this.json({
group: this.groupProjector.projectFull(result),
})
}
@httpDelete('/:groupUuid', TYPES.AuthMiddleware)
public async deleteGroup(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.groupService.deleteGroup({
groupUuid: request.params.groupUuid,
originatorUuid: response.locals.user.uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not delete group')
}
return this.json({ success: true })
}
@httpGet('/removed', TYPES.AuthMiddleware)
public async getAllRemovedFromGroupsForCurrentUser(
_request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.removedGroupUserService.getAllRemovedGroupUsersForUser({
userUuid: response.locals.user.uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get group users')
}
return this.json({
removedGroups: result.map((removedUser) => {
return {
groupUuid: removedUser.groupUuid,
removedAt: removedUser.createdAtTimestamp,
}
}),
})
}
private errorResponse(status: number, message?: string, tag?: string) {
return this.json(
{
error: { message, tag },
},
status,
)
}
}
@@ -37,7 +37,7 @@ export class ItemsController extends BaseHttpController {
computeIntegrityHash: request.body.compute_integrity === true,
syncToken: request.body.sync_token,
cursorToken: request.body.cursor_token,
vaultUuids: request.body.vault_uuids,
groupUuids: request.body.group_uuids,
limit: request.body.limit,
contentType: request.body.content_type,
apiVersion: request.body.api ?? ApiVersion.v20161215,
@@ -1,150 +0,0 @@
import { VaultUser } from '../Domain/VaultUser/Model/VaultUser'
import { VaultServiceInterface } from '../Domain/Vault/Service/VaultServiceInterface'
import { Request, Response } from 'express'
import {
BaseHttpController,
controller,
httpPost,
results,
httpDelete,
httpPatch,
httpGet,
} from 'inversify-express-utils'
import TYPES from '../Bootstrap/Types'
import { inject } from 'inversify'
import { ProjectorInterface } from '../Projection/ProjectorInterface'
import { Vault } from '../Domain/Vault/Model/Vault'
import { VaultProjection } from '../Projection/VaultProjection'
import { VaultUserProjection } from '../Projection/VaultUserProjection'
import { CreateVaultFileValetToken } from '../Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileValetToken'
import { RemovedVaultUserServiceInterface } from '../Domain/RemovedVaultUser/Service/RemovedVaultUserServiceInterface'
@controller('/vaults')
export class VaultsController extends BaseHttpController {
constructor(
@inject(TYPES.VaultService) private vaultService: VaultServiceInterface,
@inject(TYPES.RemovedVaultUserService) private removedVaultUserService: RemovedVaultUserServiceInterface,
@inject(TYPES.VaultProjector) private vaultProjector: ProjectorInterface<Vault, VaultProjection>,
@inject(TYPES.VaultUserProjector) private vaultUserProjector: ProjectorInterface<VaultUser, VaultUserProjection>,
@inject(TYPES.CreateVaultFileReadValetToken) private createVaultFileReadValetToken: CreateVaultFileValetToken,
) {
super()
}
@httpGet('/', TYPES.AuthMiddleware)
public async getVaults(_request: Request, response: Response): Promise<results.JsonResult> {
const vaults = await this.vaultService.getVaults({
userUuid: response.locals.user.uuid,
})
return this.json({
vaults: vaults.map((vault) => this.vaultProjector.projectFull(vault)),
})
}
@httpPost('/:vaultUuid/valet-tokens', TYPES.AuthMiddleware)
public async getValetTokenForVaultFile(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const valetTokenResult = await this.createVaultFileReadValetToken.execute({
userUuid: response.locals.user.uuid,
vaultUuid: request.params.vaultUuid,
fileUuid: request.body.file_uuid,
remoteIdentifier: request.body.remote_identifier,
operation: request.body.operation,
unencryptedFileSize: request.body.unencrypted_file_size,
moveOperationType: request.body.move_operation_type,
vaultToVaultMoveTargetUuid: request.body.vault_to_vault_move_target_uuid,
})
if (valetTokenResult.success === false) {
return this.errorResponse(400, `Failed to create vault valet token: ${valetTokenResult.reason}`)
}
return this.json({
valetToken: valetTokenResult.valetToken,
})
}
@httpPost('/', TYPES.AuthMiddleware)
public async createVault(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultService.createVault({
userUuid: response.locals.user.uuid,
vaultUuid: request.body.vault_uuid,
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not create vault')
}
return this.json({
vault: this.vaultProjector.projectFull(result.vault),
vaultUser: this.vaultUserProjector.projectFull(result.vaultUser),
})
}
@httpPatch('/:vaultUuid', TYPES.AuthMiddleware)
public async updateVault(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultService.updateVault({
vaultUuid: request.params.vaultUuid,
originatorUuid: response.locals.user.uuid,
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not update vault')
}
return this.json({
vault: this.vaultProjector.projectFull(result),
})
}
@httpDelete('/:vaultUuid', TYPES.AuthMiddleware)
public async deleteVault(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.vaultService.deleteVault({
vaultUuid: request.params.vaultUuid,
originatorUuid: response.locals.user.uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not delete vault')
}
return this.json({ success: true })
}
@httpGet('/removed', TYPES.AuthMiddleware)
public async getAllRemovedFromVaultsForCurrentUser(
_request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.removedVaultUserService.getAllRemovedVaultUsersForUser({
userUuid: response.locals.user.uuid,
})
if (!result) {
return this.errorResponse(400, 'Could not get vault users')
}
return this.json({
removedVaults: result.map((removedUser) => {
return {
vaultUuid: removedUser.vaultUuid,
removedAt: removedUser.createdAtTimestamp,
}
}),
})
}
private errorResponse(status: number, message?: string, tag?: string) {
return this.json(
{
error: { message, tag },
},
status,
)
}
}
@@ -0,0 +1,32 @@
import { TimerInterface } from '@standardnotes/time'
import { GroupFactoryInterface } from './GroupFactoryInterface'
import { GroupHash } from './GroupHash'
import { Group } from '../Model/Group'
export class GroupFactory implements GroupFactoryInterface {
constructor(private timer: TimerInterface) {}
create(dto: { userUuid: string; groupHash: GroupHash }): Group {
const newGroup = new Group()
newGroup.uuid = dto.groupHash.uuid
newGroup.userUuid = dto.userUuid
newGroup.specifiedItemsKeyUuid = dto.groupHash.specified_items_key_uuid
newGroup.fileUploadBytesUsed = dto.groupHash.file_upload_bytes_used
newGroup.fileUploadBytesLimit = dto.groupHash.file_upload_bytes_limit
const now = this.timer.getTimestampInMicroseconds()
newGroup.updatedAtTimestamp = now
newGroup.createdAtTimestamp = now
if (dto.groupHash.created_at_timestamp) {
newGroup.createdAtTimestamp = dto.groupHash.created_at_timestamp
}
return newGroup
}
createStub(dto: { userUuid: string; groupHash: GroupHash }): Group {
const item = this.create(dto)
return item
}
}
@@ -0,0 +1,7 @@
import { Group } from '../Model/Group'
import { GroupHash } from './GroupHash'
export interface GroupFactoryInterface {
create(dto: { userUuid: string; groupHash: GroupHash }): Group
createStub(dto: { userUuid: string; groupHash: GroupHash }): Group
}
@@ -1,4 +1,4 @@
export type VaultHash = {
export type GroupHash = {
uuid: string
user_uuid: string
specified_items_key_uuid: string
@@ -1,7 +1,7 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'vaults' })
export class Vault {
@Entity({ name: 'groups' })
export class Group {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@@ -9,7 +9,7 @@ export class Vault {
name: 'user_uuid',
length: 36,
})
@Index('index_vaults_on_user_uuid')
@Index('index_groups_on_user_uuid')
declare userUuid: string
@Column({
@@ -0,0 +1,19 @@
import { Group } from '../Model/Group'
export type GroupQuery = {
userUuid: string
}
export type UserGroupsQuery = {
userUuid?: string
groupUuids?: string[]
lastSyncTime?: number
}
export interface GroupsRepositoryInterface {
findByUuid(uuid: string): Promise<Group | null>
create(group: Group): Promise<Group>
save(group: Group): Promise<Group>
remove(group: Group): Promise<Group>
findAll(query: UserGroupsQuery): Promise<Group[]>
}
@@ -0,0 +1,56 @@
import { Repository, SelectQueryBuilder } from 'typeorm'
import { GroupsRepositoryInterface, UserGroupsQuery } from './GroupRepositoryInterface'
import { Group } from '../Model/Group'
export class TypeORMGroupRepository implements GroupsRepositoryInterface {
constructor(private ormRepository: Repository<Group>) {}
async create(group: Group): Promise<Group> {
return this.ormRepository.save(group)
}
async save(group: Group): Promise<Group> {
return this.ormRepository.save(group)
}
findByUuid(uuid: string): Promise<Group | null> {
return this.ormRepository
.createQueryBuilder('group')
.where('group.uuid = :uuid', {
uuid,
})
.getOne()
}
async remove(group: Group): Promise<Group> {
return this.ormRepository.remove(group)
}
async findAll(query: UserGroupsQuery): Promise<Group[]> {
return this.createFindAllQueryBuilder(query).getMany()
}
private createFindAllQueryBuilder(query: UserGroupsQuery): SelectQueryBuilder<Group> {
const queryBuilder = this.ormRepository.createQueryBuilder('group')
if (!query.userUuid && !query.groupUuids) {
throw new Error('Either userUuid or groupUuids must be provided')
}
if (query.userUuid) {
queryBuilder.where('group.user_uuid = :userUuid', { userUuid: query.userUuid })
}
if (query.groupUuids) {
queryBuilder.where('group.uuid IN (:...groupUuids)', { groupUuids: query.groupUuids })
}
if (query.lastSyncTime) {
queryBuilder.andWhere('group.updated_at_timestamp > :lastSyncTime', {
lastSyncTime: query.lastSyncTime,
})
}
return queryBuilder
}
}
@@ -0,0 +1,101 @@
import { TimerInterface } from '@standardnotes/time'
import { Group } from '../Model/Group'
import { GroupsRepositoryInterface } from '../Repository/GroupRepositoryInterface'
import { CreateGroupDTO, GroupServiceInterface, UpdateGroupDTO, CreateGroupResult } from './GroupServiceInterface'
import { GroupFactoryInterface } from '../Factory/GroupFactoryInterface'
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
import { GroupInviteServiceInterface } from '../../GroupInvite/Service/GroupInviteServiceInterface'
export class GroupService implements GroupServiceInterface {
constructor(
private groupRepository: GroupsRepositoryInterface,
private groupFactory: GroupFactoryInterface,
private groupUserService: GroupUserServiceInterface,
private groupInviteService: GroupInviteServiceInterface,
private timer: TimerInterface,
) {}
async createGroup(dto: CreateGroupDTO): Promise<CreateGroupResult | null> {
const existingGroup = await this.groupRepository.findByUuid(dto.groupUuid)
if (existingGroup) {
return null
}
const timestamp = this.timer.getTimestampInMicroseconds()
const group = this.groupFactory.create({
userUuid: dto.userUuid,
groupHash: {
uuid: dto.groupUuid,
user_uuid: dto.userUuid,
specified_items_key_uuid: dto.specifiedItemsKeyUuid,
file_upload_bytes_limit: 1_000_000,
file_upload_bytes_used: 0,
created_at_timestamp: timestamp,
updated_at_timestamp: timestamp,
},
})
const savedGroup = await this.groupRepository.create(group)
const groupUser = await this.groupUserService.addGroupUser({
groupUuid: savedGroup.uuid,
userUuid: dto.userUuid,
permissions: 'admin',
})
return { group, groupUser }
}
async getGroup(dto: { groupUuid: string }): Promise<Group | null> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
return group
}
async getGroups(dto: { userUuid: string; lastSyncTime?: number }): Promise<Group[]> {
const groupUsers = await this.groupUserService.getAllGroupUsersForUser({
userUuid: dto.userUuid,
})
const groupUuids = groupUsers.map((groupUser) => groupUser.groupUuid)
if (groupUuids.length === 0) {
return []
}
return this.groupRepository.findAll({ groupUuids, lastSyncTime: dto.lastSyncTime })
}
async updateGroup(dto: UpdateGroupDTO): Promise<Group | null> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group || group.userUuid !== dto.originatorUuid) {
return null
}
group.specifiedItemsKeyUuid = dto.specifiedItemsKeyUuid
group.updatedAtTimestamp = this.timer.getTimestampInMicroseconds()
const savedGroup = await this.groupRepository.save(group)
return savedGroup
}
async deleteGroup(dto: { groupUuid: string; originatorUuid: string }): Promise<boolean> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group || group.userUuid !== dto.originatorUuid) {
return false
}
await this.groupRepository.remove(group)
await this.groupUserService.deleteAllGroupUsersForGroup({
groupUuid: dto.groupUuid,
originatorUuid: dto.originatorUuid,
})
await this.groupInviteService.deleteAllInvitesForGroup({
groupUuid: dto.groupUuid,
originatorUuid: dto.originatorUuid,
})
return true
}
}
@@ -0,0 +1,31 @@
import { GroupUser } from './../../GroupUser/Model/GroupUser'
import { Group } from '../Model/Group'
export type CreateGroupDTO = {
userUuid: string
groupUuid: string
specifiedItemsKeyUuid: string
}
export type CreateGroupResult = {
group: Group
groupUser: GroupUser
}
export type UpdateGroupDTO = {
originatorUuid: string
groupUuid: string
specifiedItemsKeyUuid: string
}
export interface GroupServiceInterface {
createGroup(dto: CreateGroupDTO): Promise<CreateGroupResult | null>
updateGroup(dto: UpdateGroupDTO): Promise<Group | null>
deleteGroup(dto: { groupUuid: string; originatorUuid: string }): Promise<boolean>
getGroup(dto: { groupUuid: string }): Promise<Group | null>
getGroups(dto: { userUuid: string; lastSyncTime?: number }): Promise<Group[]>
}
@@ -0,0 +1,35 @@
import { TimerInterface } from '@standardnotes/time'
import { GroupInviteFactoryInterface } from './GroupInviteFactoryInterface'
import { GroupInviteHash } from './GroupInviteHash'
import { GroupInvite } from '../Model/GroupInvite'
export class GroupInviteFactory implements GroupInviteFactoryInterface {
constructor(private timer: TimerInterface) {}
create(dto: GroupInviteHash): GroupInvite {
const newGroupInvite = new GroupInvite()
newGroupInvite.uuid = dto.uuid
newGroupInvite.userUuid = dto.user_uuid
newGroupInvite.groupUuid = dto.group_uuid
newGroupInvite.inviterUuid = dto.inviter_uuid
newGroupInvite.inviterPublicKey = dto.inviter_public_key
newGroupInvite.encryptedGroupData = dto.encrypted_group_data
newGroupInvite.inviteType = dto.invite_type
newGroupInvite.permissions = dto.permissions
const now = this.timer.getTimestampInMicroseconds()
newGroupInvite.updatedAtTimestamp = now
newGroupInvite.createdAtTimestamp = now
if (dto.created_at_timestamp) {
newGroupInvite.createdAtTimestamp = dto.created_at_timestamp
}
return newGroupInvite
}
createStub(dto: GroupInviteHash): GroupInvite {
const item = this.create(dto)
return item
}
}
@@ -0,0 +1,7 @@
import { GroupInvite } from '../Model/GroupInvite'
import { GroupInviteHash } from './GroupInviteHash'
export interface GroupInviteFactoryInterface {
create(dto: GroupInviteHash): GroupInvite
createStub(dto: GroupInviteHash): GroupInvite
}
@@ -0,0 +1,15 @@
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
import { GroupInviteType } from '../Model/GroupInviteType'
export type GroupInviteHash = {
uuid: string
user_uuid: string
group_uuid: string
inviter_uuid: string
inviter_public_key: string
encrypted_group_data: string
invite_type: GroupInviteType
permissions: GroupUserPermission
created_at_timestamp?: number
updated_at_timestamp?: number
}
@@ -1,17 +1,17 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import { VaultInviteType } from './VaultInviteType'
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
import { GroupInviteType } from './GroupInviteType'
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
@Entity({ name: 'vault_invites' })
export class VaultInvite {
@Entity({ name: 'group_invites' })
export class GroupInvite {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'vault_uuid',
name: 'group_uuid',
length: 36,
})
declare vaultUuid: string
declare groupUuid: string
@Column({
name: 'user_uuid',
@@ -27,7 +27,7 @@ export class VaultInvite {
@Column({
name: 'invite_type',
})
declare inviteType: VaultInviteType
declare inviteType: GroupInviteType
@Column({
name: 'inviter_public_key',
@@ -35,14 +35,14 @@ export class VaultInvite {
declare inviterPublicKey: string
@Column({
name: 'encrypted_vault_data',
name: 'encrypted_group_data',
})
declare encryptedVaultData: string
declare encryptedGroupData: string
@Column({
name: 'permissions',
})
declare permissions: VaultUserPermission
declare permissions: GroupUserPermission
@Column({
name: 'created_at_timestamp',
@@ -1,4 +1,4 @@
export enum VaultInviteType {
export enum GroupInviteType {
Join = 'join',
KeyChange = 'key-change',
}
@@ -0,0 +1,25 @@
import { GroupInvite } from '../Model/GroupInvite'
export type GroupInviteQuery = {
userUuid: string
}
export type GroupInviteFindAllForUserQuery = {
userUuid?: string
lastSyncTime?: number
inviterUuid?: string
}
export type GroupInviteFindAllForGroup = {
groupUuid: string
}
export interface GroupInviteRepositoryInterface {
findByUuid(groupInviteUuid: string): Promise<GroupInvite | null>
create(group: GroupInvite): Promise<GroupInvite>
save(groupInvite: GroupInvite): Promise<GroupInvite>
remove(group: GroupInvite): Promise<GroupInvite>
findAll(query: GroupInviteFindAllForUserQuery): Promise<GroupInvite[]>
findAllForGroup(query: GroupInviteFindAllForGroup): Promise<GroupInvite[]>
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupInvite | null>
}
@@ -0,0 +1,71 @@
import { GroupInvite } from '../Model/GroupInvite'
import { Repository, SelectQueryBuilder } from 'typeorm'
import {
GroupInviteFindAllForGroup,
GroupInviteFindAllForUserQuery,
GroupInviteRepositoryInterface,
} from './GroupInviteRepositoryInterface'
export class TypeORMGroupInviteRepository implements GroupInviteRepositoryInterface {
constructor(private ormRepository: Repository<GroupInvite>) {}
async create(groupInvite: GroupInvite): Promise<GroupInvite> {
return this.ormRepository.save(groupInvite)
}
async save(groupInvite: GroupInvite): Promise<GroupInvite> {
return this.ormRepository.save(groupInvite)
}
findByUuid(uuid: string): Promise<GroupInvite | null> {
return this.ormRepository
.createQueryBuilder('group_invite')
.where('group_invite.uuid = :uuid', {
uuid,
})
.getOne()
}
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupInvite | null> {
return this.ormRepository
.createQueryBuilder('group_invite')
.where('group_invite.user_uuid = :userUuid', { userUuid })
.andWhere('group_invite.group_uuid = :groupUuid', { groupUuid })
.getOne()
}
async remove(groupInvite: GroupInvite): Promise<GroupInvite> {
return this.ormRepository.remove(groupInvite)
}
findAllForGroup(query: GroupInviteFindAllForGroup): Promise<GroupInvite[]> {
return this.ormRepository
.createQueryBuilder('group_invite')
.where('group_invite.group_uuid = :groupUuid', { groupUuid: query.groupUuid })
.getMany()
}
async findAll(query: GroupInviteFindAllForUserQuery): Promise<GroupInvite[]> {
return this.createFindAllQueryBuilder(query).getMany()
}
private createFindAllQueryBuilder(query: GroupInviteFindAllForUserQuery): SelectQueryBuilder<GroupInvite> {
const queryBuilder = this.ormRepository.createQueryBuilder('group_invite')
if (query.userUuid) {
queryBuilder.where('group_invite.user_uuid = :userUuid', { userUuid: query.userUuid })
} else if (query.inviterUuid) {
queryBuilder.where('group_invite.inviter_uuid = :inviterUuid', {
inviterUuid: query.inviterUuid,
})
}
if (query.lastSyncTime) {
queryBuilder.andWhere('group_invite.updated_at_timestamp > :lastSyncTime', {
lastSyncTime: query.lastSyncTime,
})
}
return queryBuilder
}
}
@@ -0,0 +1,12 @@
import { GroupInviteType } from '../Model/GroupInviteType'
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
export type CreateInviteDTO = {
originatorUuid: string
groupUuid: string
userUuid: string
inviterPublicKey: string
encryptedGroupData: string
inviteType: GroupInviteType
permissions: GroupUserPermission
}
@@ -1,4 +1,4 @@
export interface GetUserVaultInvitesDTO {
export interface GetUserGroupInvitesDTO {
userUuid: string
lastSyncTime?: number
}
@@ -0,0 +1,172 @@
import { TimerInterface } from '@standardnotes/time'
import { GroupInvite } from '../Model/GroupInvite'
import { GroupInviteFactoryInterface } from '../Factory/GroupInviteFactoryInterface'
import { v4 as uuidv4 } from 'uuid'
import { GroupInviteRepositoryInterface } from '../Repository/GroupInviteRepositoryInterface'
import { GroupInviteServiceInterface } from './GroupInviteServiceInterface'
import { GetUserGroupInvitesDTO } from './GetUserGroupInvitesDTO'
import { GroupsRepositoryInterface } from '../../Group/Repository/GroupRepositoryInterface'
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
import { GroupInviteType } from '../Model/GroupInviteType'
import { CreateInviteDTO } from './CreateInviteDTO'
import { UpdateInviteDTO } from './UpdateInviteDTO'
export class GroupInviteService implements GroupInviteServiceInterface {
constructor(
private groupRepository: GroupsRepositoryInterface,
private groupInviteRepository: GroupInviteRepositoryInterface,
private groupInviteFactory: GroupInviteFactoryInterface,
private groupUserService: GroupUserServiceInterface,
private timer: TimerInterface,
) {}
async createInvite(dto: CreateInviteDTO): Promise<GroupInvite | null> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group || group.userUuid !== dto.originatorUuid) {
return null
}
const timestamp = this.timer.getTimestampInMicroseconds()
const groupInvite = this.groupInviteFactory.create({
uuid: uuidv4(),
user_uuid: dto.userUuid,
group_uuid: dto.groupUuid,
inviter_uuid: dto.originatorUuid,
inviter_public_key: dto.inviterPublicKey,
encrypted_group_data: dto.encryptedGroupData,
invite_type: dto.inviteType,
permissions: dto.permissions,
created_at_timestamp: timestamp,
updated_at_timestamp: timestamp,
})
return this.groupInviteRepository.create(groupInvite)
}
async updateInvite(dto: UpdateInviteDTO): Promise<GroupInvite | null> {
const groupInvite = await this.groupInviteRepository.findByUuid(dto.inviteUuid)
if (!groupInvite || groupInvite.inviterUuid !== dto.originatorUuid) {
return null
}
groupInvite.inviterPublicKey = dto.inviterPublicKey
groupInvite.encryptedGroupData = dto.encryptedGroupData
if (dto.permissions) {
groupInvite.permissions = dto.permissions
}
groupInvite.updatedAtTimestamp = this.timer.getTimestampInMicroseconds()
return this.groupInviteRepository.save(groupInvite)
}
getInvitesForUser(dto: GetUserGroupInvitesDTO): Promise<GroupInvite[]> {
return this.groupInviteRepository.findAll(dto)
}
getOutboundInvitesForUser(dto: { userUuid: string }): Promise<GroupInvite[]> {
return this.groupInviteRepository.findAll({
inviterUuid: dto.userUuid,
})
}
async deleteAllInboundInvites(dto: { userUuid: string }): Promise<void> {
const inboundInvites = await this.groupInviteRepository.findAll({
userUuid: dto.userUuid,
})
for (const invite of inboundInvites) {
await this.groupInviteRepository.remove(invite)
}
}
async getInvitesForGroup(dto: { groupUuid: string; originatorUuid: string }): Promise<GroupInvite[] | undefined> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group) {
return undefined
}
const isUserGroupAdmin = group && group.userUuid === dto.originatorUuid
if (!isUserGroupAdmin) {
return undefined
}
const users = await this.groupInviteRepository.findAllForGroup({ groupUuid: dto.groupUuid })
return users
}
async acceptInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean> {
const invite = await this.groupInviteRepository.findByUuid(dto.inviteUuid)
if (!invite) {
return false
}
const isAuthorized = invite.userUuid === dto.originatorUuid
if (!isAuthorized) {
return false
}
if (invite.inviteType === GroupInviteType.Join) {
const addedUser = await this.groupUserService.addGroupUser({
userUuid: dto.originatorUuid,
groupUuid: invite.groupUuid,
permissions: invite.permissions,
})
if (!addedUser) {
return false
}
}
await this.groupInviteRepository.remove(invite)
return true
}
async declineInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean> {
const invite = await this.groupInviteRepository.findByUuid(dto.inviteUuid)
if (!invite) {
return false
}
const isAuthorized = invite.userUuid === dto.originatorUuid
if (!isAuthorized) {
return false
}
await this.groupInviteRepository.remove(invite)
return true
}
async deleteInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean> {
const invite = await this.groupInviteRepository.findByUuid(dto.inviteUuid)
if (!invite) {
return false
}
const isAuthorized = invite.inviterUuid === dto.originatorUuid || invite.userUuid === dto.originatorUuid
if (!isAuthorized) {
return false
}
await this.groupInviteRepository.remove(invite)
return true
}
async deleteAllInvitesForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<boolean> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group || group.userUuid !== dto.originatorUuid) {
return false
}
const groupInvites = await this.groupInviteRepository.findAllForGroup({ groupUuid: dto.groupUuid })
for (const groupInvite of groupInvites) {
await this.groupInviteRepository.remove(groupInvite)
}
return true
}
}
@@ -0,0 +1,26 @@
import { GroupInvite } from '../Model/GroupInvite'
import { CreateInviteDTO } from './CreateInviteDTO'
import { GetUserGroupInvitesDTO } from './GetUserGroupInvitesDTO'
import { UpdateInviteDTO } from './UpdateInviteDTO'
export interface GroupInviteServiceInterface {
createInvite(dto: CreateInviteDTO): Promise<GroupInvite | null>
updateInvite(dto: UpdateInviteDTO): Promise<GroupInvite | null>
getInvitesForUser(dto: GetUserGroupInvitesDTO): Promise<GroupInvite[]>
getInvitesForGroup(dto: { groupUuid: string; originatorUuid: string }): Promise<GroupInvite[] | undefined>
getOutboundInvitesForUser(dto: { userUuid: string }): Promise<GroupInvite[]>
acceptInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean>
declineInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean>
deleteInvite(dto: { originatorUuid: string; groupUuid: string; inviteUuid: string }): Promise<boolean>
deleteAllInvitesForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<boolean>
deleteAllInboundInvites(dto: { userUuid: string }): Promise<void>
}
@@ -0,0 +1,9 @@
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
export type UpdateInviteDTO = {
originatorUuid: string
inviteUuid: string
inviterPublicKey: string
encryptedGroupData: string
permissions?: GroupUserPermission
}
@@ -0,0 +1,31 @@
import { TimerInterface } from '@standardnotes/time'
import { GroupUserFactoryInterface } from './GroupUserFactoryInterface'
import { GroupUserHash } from './GroupUserHash'
import { GroupUser } from '../Model/GroupUser'
export class GroupUserFactory implements GroupUserFactoryInterface {
constructor(private timer: TimerInterface) {}
create(dto: GroupUserHash): GroupUser {
const newGroupUser = new GroupUser()
newGroupUser.uuid = dto.uuid
newGroupUser.userUuid = dto.user_uuid
newGroupUser.groupUuid = dto.group_uuid
newGroupUser.permissions = dto.permissions
const now = this.timer.getTimestampInMicroseconds()
newGroupUser.updatedAtTimestamp = now
newGroupUser.createdAtTimestamp = now
if (dto.created_at_timestamp) {
newGroupUser.createdAtTimestamp = dto.created_at_timestamp
}
return newGroupUser
}
createStub(dto: GroupUserHash): GroupUser {
const item = this.create(dto)
return item
}
}
@@ -0,0 +1,7 @@
import { GroupUser } from '../Model/GroupUser'
import { GroupUserHash } from './GroupUserHash'
export interface GroupUserFactoryInterface {
create(dto: GroupUserHash): GroupUser
createStub(dto: GroupUserHash): GroupUser
}
@@ -0,0 +1,10 @@
import { GroupUserPermission } from '../Model/GroupUserPermission'
export type GroupUserHash = {
uuid: string
user_uuid: string
group_uuid: string
permissions: GroupUserPermission
created_at_timestamp?: number
updated_at_timestamp?: number
}
@@ -1,16 +1,16 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import { VaultUserPermission } from './VaultUserPermission'
import { GroupUserPermission } from './GroupUserPermission'
@Entity({ name: 'vault_users' })
export class VaultUser {
@Entity({ name: 'group_users' })
export class GroupUser {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'vault_uuid',
name: 'group_uuid',
length: 36,
})
declare vaultUuid: string
declare groupUuid: string
@Column({
name: 'user_uuid',
@@ -21,7 +21,7 @@ export class VaultUser {
@Column({
name: 'permissions',
})
declare permissions: VaultUserPermission
declare permissions: GroupUserPermission
@Column({
name: 'created_at_timestamp',
@@ -0,0 +1 @@
export type GroupUserPermission = 'read' | 'write' | 'admin'
@@ -0,0 +1,20 @@
import { GroupUser } from '../Model/GroupUser'
export type GroupUserFindAllForUserQuery = {
userUuid: string
lastSyncTime?: number
}
export type GroupUserFindAllForGroup = {
groupUuid: string
}
export interface GroupUserRepositoryInterface {
findByUuid(groupUserUuid: string): Promise<GroupUser | null>
create(group: GroupUser): Promise<GroupUser>
save(groupUser: GroupUser): Promise<GroupUser>
remove(group: GroupUser): Promise<GroupUser>
findAllForUser(query: GroupUserFindAllForUserQuery): Promise<GroupUser[]>
findAllForGroup(query: GroupUserFindAllForGroup): Promise<GroupUser[]>
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupUser | null>
}
@@ -0,0 +1,65 @@
import { GroupUser } from '../Model/GroupUser'
import { Repository, SelectQueryBuilder } from 'typeorm'
import {
GroupUserFindAllForGroup,
GroupUserFindAllForUserQuery,
GroupUserRepositoryInterface,
} from './GroupUserRepositoryInterface'
export class TypeORMGroupUserRepository implements GroupUserRepositoryInterface {
constructor(private ormRepository: Repository<GroupUser>) {}
async create(groupUser: GroupUser): Promise<GroupUser> {
return this.ormRepository.save(groupUser)
}
async save(groupUser: GroupUser): Promise<GroupUser> {
return this.ormRepository.save(groupUser)
}
findByUuid(uuid: string): Promise<GroupUser | null> {
return this.ormRepository
.createQueryBuilder('group_user')
.where('group_user.uuid = :uuid', {
uuid,
})
.getOne()
}
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupUser | null> {
return this.ormRepository
.createQueryBuilder('group_user')
.where('group_user.user_uuid = :userUuid', { userUuid })
.andWhere('group_user.group_uuid = :groupUuid', { groupUuid })
.getOne()
}
async remove(groupUser: GroupUser): Promise<GroupUser> {
return this.ormRepository.remove(groupUser)
}
findAllForGroup(query: GroupUserFindAllForGroup): Promise<GroupUser[]> {
return this.ormRepository
.createQueryBuilder('group_user')
.where('group_user.group_uuid = :groupUuid', { groupUuid: query.groupUuid })
.getMany()
}
async findAllForUser(query: GroupUserFindAllForUserQuery): Promise<GroupUser[]> {
return this.createFindAllQueryBuilder(query).getMany()
}
private createFindAllQueryBuilder(query: GroupUserFindAllForUserQuery): SelectQueryBuilder<GroupUser> {
const queryBuilder = this.ormRepository.createQueryBuilder('group_user')
queryBuilder.where('group_user.user_uuid = :userUuid', { userUuid: query.userUuid })
if (query.lastSyncTime) {
queryBuilder.andWhere('group_user.updated_at_timestamp > :lastSyncTime', {
lastSyncTime: query.lastSyncTime,
})
}
return queryBuilder
}
}
@@ -1,4 +1,4 @@
export interface GetVaultUsersDTO {
export interface GetGroupUsersDTO {
userUuid: string
lastSyncTime?: number
}
@@ -0,0 +1,132 @@
import { RemovedGroupUserServiceInterface } from './../../RemovedGroupUser/Service/RemovedGroupUserServiceInterface'
import { TimerInterface } from '@standardnotes/time'
import { GroupUser } from '../Model/GroupUser'
import { GroupUserFactoryInterface } from '../Factory/GroupUserFactoryInterface'
import { v4 as uuidv4 } from 'uuid'
import { GroupUserRepositoryInterface } from '../Repository/GroupUserRepositoryInterface'
import { GroupUserServiceInterface } from './GroupUserServiceInterface'
import { GetGroupUsersDTO } from './GetGroupUsersDTO'
import { GroupsRepositoryInterface } from '../../Group/Repository/GroupRepositoryInterface'
import { GroupUserPermission } from '../Model/GroupUserPermission'
export class GroupUserService implements GroupUserServiceInterface {
constructor(
private groupRepository: GroupsRepositoryInterface,
private groupUserRepository: GroupUserRepositoryInterface,
private groupUserFactory: GroupUserFactoryInterface,
private removedGroupUserService: RemovedGroupUserServiceInterface,
private timer: TimerInterface,
) {}
async addGroupUser(dto: {
groupUuid: string
userUuid: string
permissions: GroupUserPermission
}): Promise<GroupUser> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group) {
throw new Error('Attempting to add a group user to a non-existent group')
}
await this.removedGroupUserService.deleteRemovedGroupUser({
groupUuid: dto.groupUuid,
userUuid: dto.userUuid,
})
const timestamp = this.timer.getTimestampInMicroseconds()
const groupUser = this.groupUserFactory.create({
uuid: uuidv4(),
user_uuid: dto.userUuid,
group_uuid: dto.groupUuid,
permissions: dto.permissions,
created_at_timestamp: timestamp,
updated_at_timestamp: timestamp,
})
return this.groupUserRepository.create(groupUser)
}
getAllGroupUsersForUser(dto: GetGroupUsersDTO): Promise<GroupUser[]> {
return this.groupUserRepository.findAllForUser(dto)
}
getUserForGroup(dto: { userUuid: string; groupUuid: string }): Promise<GroupUser | null> {
return this.groupUserRepository.findByUserUuidAndGroupUuid(dto.userUuid, dto.groupUuid)
}
async getGroupUsersForGroup(dto: {
groupUuid: string
originatorUuid: string
}): Promise<{ users: GroupUser[]; isAdmin: boolean } | undefined> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group) {
return undefined
}
const isUserGroupAdmin = group && group.userUuid === dto.originatorUuid
const doesUserBelongToGroup = await this.groupUserRepository.findByUserUuidAndGroupUuid(
dto.originatorUuid,
dto.groupUuid,
)
if (!isUserGroupAdmin && !doesUserBelongToGroup) {
return undefined
}
const users = await this.groupUserRepository.findAllForGroup({ groupUuid: dto.groupUuid })
return {
users,
isAdmin: isUserGroupAdmin,
}
}
async deleteGroupUser(dto: { originatorUuid: string; groupUuid: string; userUuid: string }): Promise<boolean> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group) {
return false
}
if (group.userUuid === dto.userUuid) {
return false
}
const isAuthorized = group.userUuid === dto.originatorUuid || dto.userUuid === dto.originatorUuid
if (!isAuthorized) {
return false
}
const groupUser = await this.groupUserRepository.findByUserUuidAndGroupUuid(dto.userUuid, dto.groupUuid)
if (!groupUser) {
return false
}
await this.groupUserRepository.remove(groupUser)
await this.removedGroupUserService.addRemovedGroupUser({
groupUuid: dto.groupUuid,
userUuid: dto.userUuid,
removedBy: dto.originatorUuid,
})
return true
}
async deleteAllGroupUsersForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<boolean> {
const group = await this.groupRepository.findByUuid(dto.groupUuid)
if (!group || group.userUuid !== dto.originatorUuid) {
return false
}
const groupUsers = await this.groupUserRepository.findAllForGroup({ groupUuid: dto.groupUuid })
for (const groupUser of groupUsers) {
await this.groupUserRepository.remove(groupUser)
await this.removedGroupUserService.addRemovedGroupUser({
groupUuid: dto.groupUuid,
userUuid: groupUser.userUuid,
removedBy: dto.originatorUuid,
})
}
return true
}
}
@@ -0,0 +1,20 @@
import { GroupUser } from '../Model/GroupUser'
import { GroupUserPermission } from '../Model/GroupUserPermission'
import { GetGroupUsersDTO } from './GetGroupUsersDTO'
export interface GroupUserServiceInterface {
addGroupUser(dto: { groupUuid: string; userUuid: string; permissions: GroupUserPermission }): Promise<GroupUser>
getAllGroupUsersForUser(dto: GetGroupUsersDTO): Promise<GroupUser[]>
getUserForGroup(dto: { userUuid: string; groupUuid: string }): Promise<GroupUser | null>
getGroupUsersForGroup(dto: {
groupUuid: string
originatorUuid: string
}): Promise<{ users: GroupUser[]; isAdmin: boolean } | undefined>
deleteGroupUser(dto: { originatorUuid: string; groupUuid: string; userUuid: string }): Promise<boolean>
deleteAllGroupUsersForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<boolean>
}
@@ -0,0 +1,33 @@
import { DomainEventHandlerInterface, GroupFileRemovedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { GroupServiceInterface } from '../Group/Service/GroupServiceInterface'
import { GroupsRepositoryInterface } from '../Group/Repository/GroupRepositoryInterface'
@injectable()
export class GroupFileRemovedEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.GroupService) private groupService: GroupServiceInterface,
@inject(TYPES.GroupRepository) private groupRepository: GroupsRepositoryInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
async handle(event: GroupFileRemovedEvent): Promise<void> {
const group = await this.groupService.getGroup({
groupUuid: event.payload.groupUuid,
})
if (group === null) {
this.logger.warn(`Could not find group with uuid: ${event.payload.groupUuid}`)
return
}
group.fileUploadBytesUsed -= event.payload.fileByteSize
await this.groupRepository.save(group)
}
}
@@ -0,0 +1,33 @@
import { DomainEventHandlerInterface, GroupFileUploadedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { GroupServiceInterface } from '../Group/Service/GroupServiceInterface'
import { GroupsRepositoryInterface } from '../Group/Repository/GroupRepositoryInterface'
@injectable()
export class GroupFileUploadedEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.GroupService) private groupService: GroupServiceInterface,
@inject(TYPES.GroupRepository) private groupRepository: GroupsRepositoryInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
async handle(event: GroupFileUploadedEvent): Promise<void> {
const group = await this.groupService.getGroup({
groupUuid: event.payload.groupUuid,
})
if (group === null) {
this.logger.warn(`Could not find group with uuid: ${event.payload.groupUuid}`)
return
}
group.fileUploadBytesUsed += event.payload.fileByteSize
await this.groupRepository.save(group)
}
}
@@ -5,18 +5,18 @@ import {
} from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
import { ContactsRepositoryInterface } from '../Contact/Repository/ContactRepositoryInterface'
import { VaultInviteRepositoryInterface } from '../VaultInvite/Repository/VaultInviteRepositoryInterface'
import { GroupInviteRepositoryInterface } from '../GroupInvite/Repository/GroupInviteRepositoryInterface'
export class UserCredentialsChangedEventHandler implements DomainEventHandlerInterface {
constructor(
private contactRepository: ContactsRepositoryInterface,
private vaultInviteRepository: VaultInviteRepositoryInterface,
private groupInviteRepository: GroupInviteRepositoryInterface,
private timer: TimerInterface,
) {}
async handle(event: UserCredentialsChangedEvent): Promise<void> {
await this.updatePublicKeyOfAllContacts(event.payload)
await this.nullifyPendingInboundVaultInvites(event.payload)
await this.nullifyPendingInboundGroupInvites(event.payload)
}
private async updatePublicKeyOfAllContacts(payload: UserCredentialsChangedEventPayload): Promise<void> {
@@ -31,13 +31,13 @@ export class UserCredentialsChangedEventHandler implements DomainEventHandlerInt
}
}
private async nullifyPendingInboundVaultInvites(payload: UserCredentialsChangedEventPayload): Promise<void> {
const inboundInvites = await this.vaultInviteRepository.findAll({
private async nullifyPendingInboundGroupInvites(payload: UserCredentialsChangedEventPayload): Promise<void> {
const inboundInvites = await this.groupInviteRepository.findAll({
userUuid: payload.userUuid,
})
for (const invite of inboundInvites) {
await this.vaultInviteRepository.remove(invite)
await this.groupInviteRepository.remove(invite)
}
}
}
@@ -1,33 +0,0 @@
import { DomainEventHandlerInterface, VaultFileRemovedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { VaultServiceInterface } from '../Vault/Service/VaultServiceInterface'
import { VaultsRepositoryInterface } from '../Vault/Repository/VaultRepositoryInterface'
@injectable()
export class VaultFileRemovedEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.VaultService) private vaultService: VaultServiceInterface,
@inject(TYPES.VaultRepository) private vaultRepository: VaultsRepositoryInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
async handle(event: VaultFileRemovedEvent): Promise<void> {
const vault = await this.vaultService.getVault({
vaultUuid: event.payload.vaultUuid,
})
if (vault === null) {
this.logger.warn(`Could not find vault with uuid: ${event.payload.vaultUuid}`)
return
}
vault.fileUploadBytesUsed -= event.payload.fileByteSize
await this.vaultRepository.save(vault)
}
}
@@ -1,33 +0,0 @@
import { DomainEventHandlerInterface, VaultFileUploadedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { VaultServiceInterface } from '../Vault/Service/VaultServiceInterface'
import { VaultsRepositoryInterface } from '../Vault/Repository/VaultRepositoryInterface'
@injectable()
export class VaultFileUploadedEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.VaultService) private vaultService: VaultServiceInterface,
@inject(TYPES.VaultRepository) private vaultRepository: VaultsRepositoryInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
async handle(event: VaultFileUploadedEvent): Promise<void> {
const vault = await this.vaultService.getVault({
vaultUuid: event.payload.vaultUuid,
})
if (vault === null) {
this.logger.warn(`Could not find vault with uuid: ${event.payload.vaultUuid}`)
return
}
vault.fileUploadBytesUsed += event.payload.fileByteSize
await this.vaultRepository.save(vault)
}
}
@@ -4,5 +4,5 @@ export type GetItemsDTO = {
cursorToken?: string | null
limit?: number
contentType?: string
vaultUuids?: string[] | null
groupUuids?: string[] | null
}
@@ -15,11 +15,19 @@ export class Item {
@Column({
type: 'varchar',
name: 'vault_uuid',
name: 'group_uuid',
length: 255,
nullable: true,
})
declare vaultUuid: string | null
declare groupUuid: string | null
@Column({
type: 'varchar',
name: 'vault_identifier',
length: 255,
nullable: true,
})
declare vault_identifier: string | null
@Column({
type: 'varchar',
@@ -34,8 +34,8 @@ export class ItemFactory implements ItemFactoryInterface {
newItem.contentSize = 0
newItem.createdByUuid = dto.userUuid
if (dto.itemHash.vault_uuid) {
newItem.vaultUuid = dto.itemHash.vault_uuid
if (dto.itemHash.group_uuid) {
newItem.groupUuid = dto.itemHash.group_uuid
} else {
newItem.userUuid = dto.userUuid
}
@@ -10,7 +10,7 @@ export type ItemHash = {
auth_hash?: string
enc_item_key?: string
items_key_id?: string
vault_uuid?: string
group_uuid?: string
last_edited_by_uuid?: string | null
created_at?: string
created_at_timestamp?: number
@@ -6,8 +6,8 @@ export type ItemQuery = {
lastSyncTime?: number
syncTimeComparison?: '>' | '>='
contentType?: string | string[]
includeVaultUuids?: string[]
exclusiveVaultUuids?: string[] | null
includeGroupUuids?: string[]
exclusiveGroupUuids?: string[] | null
deleted?: boolean
offset?: number
limit?: number
@@ -19,9 +19,9 @@ import { ItemSaveValidatorInterface } from './SaveValidator/ItemSaveValidatorInt
import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { ItemProjection } from '../../Projection/ItemProjection'
import { VaultUserRepositoryInterface } from '../VaultUser/Repository/VaultUserRepositoryInterface'
import { GroupUserRepositoryInterface } from '../GroupUser/Repository/GroupUserRepositoryInterface'
import { ConflictType } from '../../Tmp/ConflictType'
import { VaultServiceInterface } from '../Vault/Service/VaultServiceInterface'
import { GroupServiceInterface } from '../Group/Service/GroupServiceInterface'
export class ItemService implements ItemServiceInterface {
private readonly DEFAULT_ITEMS_LIMIT = 150
@@ -39,8 +39,8 @@ export class ItemService implements ItemServiceInterface {
private timer: TimerInterface,
private itemProjector: ProjectorInterface<Item, ItemProjection>,
private maxItemsSyncLimit: number,
private vaultUsersRepository: VaultUserRepositoryInterface,
private vaultService: VaultServiceInterface,
private groupUsersRepository: GroupUserRepositoryInterface,
private groupService: GroupServiceInterface,
private logger: Logger,
) {}
@@ -50,16 +50,16 @@ export class ItemService implements ItemServiceInterface {
const limit = dto.limit === undefined || dto.limit < 1 ? this.DEFAULT_ITEMS_LIMIT : dto.limit
const upperBoundLimit = limit < this.maxItemsSyncLimit ? limit : this.maxItemsSyncLimit
const vaultUsers = await this.vaultUsersRepository.findAllForUser({ userUuid: dto.userUuid })
const userVaultUuids = vaultUsers.map((vaultUser) => vaultUser.vaultUuid)
const exclusiveVaultUuids = dto.vaultUuids
? dto.vaultUuids.filter((vaultUuid) => userVaultUuids.includes(vaultUuid))
const groupUsers = await this.groupUsersRepository.findAllForUser({ userUuid: dto.userUuid })
const userGroupUuids = groupUsers.map((groupUser) => groupUser.groupUuid)
const exclusiveGroupUuids = dto.groupUuids
? dto.groupUuids.filter((groupUuid) => userGroupUuids.includes(groupUuid))
: undefined
const itemQuery: ItemQuery = {
userUuid: dto.userUuid,
includeVaultUuids: !dto.vaultUuids ? userVaultUuids : undefined,
exclusiveVaultUuids: exclusiveVaultUuids,
includeGroupUuids: !dto.groupUuids ? userGroupUuids : undefined,
exclusiveGroupUuids: exclusiveGroupUuids,
lastSyncTime,
syncTimeComparison,
contentType: dto.contentType,
@@ -99,7 +99,7 @@ export class ItemService implements ItemServiceInterface {
const savedItems: Array<Item> = []
const conflicts: Array<ItemConflict> = []
await this.dynamicallyCreateVaultsBasedOnIncomingItems(dto)
await this.dynamicallyCreateGroupsBasedOnIncomingItems(dto)
const lastUpdatedTimestamp = this.timer.getTimestampInMicroseconds()
@@ -187,15 +187,15 @@ export class ItemService implements ItemServiceInterface {
return retrievedItems
}
private async dynamicallyCreateVaultsBasedOnIncomingItems(dto: SaveItemsDTO): Promise<void> {
const processedVaultUuids = new Set()
private async dynamicallyCreateGroupsBasedOnIncomingItems(dto: SaveItemsDTO): Promise<void> {
const processedGroupUuids = new Set()
for (const itemHash of dto.itemHashes) {
if (!itemHash.vault_uuid || processedVaultUuids.has(itemHash.vault_uuid)) {
if (!itemHash.group_uuid || processedGroupUuids.has(itemHash.group_uuid)) {
continue
}
processedVaultUuids.add(itemHash.vault_uuid)
processedGroupUuids.add(itemHash.group_uuid)
const vaultItemsKeyUuid =
itemHash.content_type === ContentType.VaultItemsKey ? itemHash.uuid : itemHash.items_key_id
@@ -203,9 +203,9 @@ export class ItemService implements ItemServiceInterface {
continue
}
await this.vaultService.createVault({
await this.groupService.createGroup({
userUuid: dto.userUuid,
vaultUuid: itemHash.vault_uuid,
groupUuid: itemHash.group_uuid,
specifiedItemsKeyUuid: vaultItemsKeyUuid,
})
}
@@ -239,10 +239,10 @@ export class ItemService implements ItemServiceInterface {
dto.existingItem.contentSize = 0
dto.existingItem.lastEditedByUuid = dto.userUuid
if (dto.itemHash.vault_uuid) {
dto.existingItem.vaultUuid = dto.itemHash.vault_uuid
if (dto.itemHash.group_uuid) {
dto.existingItem.groupUuid = dto.itemHash.group_uuid
} else {
dto.existingItem.vaultUuid = null
dto.existingItem.groupUuid = null
}
if (dto.itemHash.content) {
@@ -0,0 +1,301 @@
import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
import { ItemHash } from '../ItemHash'
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
import { GroupServiceInterface } from '../../Group/Service/GroupServiceInterface'
import { ContentType } from '@standardnotes/common'
import { ConflictType } from '../../../Tmp/ConflictType'
import {
GroupSaveOperation,
AddToGroupSaveOperation,
RemoveFromGroupSaveOperation,
MoveToOtherGroupSaveOperation,
SaveToGroupSaveOperation,
CreateToGroupSaveOperation,
} from './GroupSaveOperation'
import { Item } from '../Item'
export class GroupFilter implements ItemSaveRuleInterface {
constructor(private groupService: GroupServiceInterface, private groupUserService: GroupUserServiceInterface) {}
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
if (!dto.existingItem?.groupUuid && !dto.itemHash.group_uuid) {
return {
passed: true,
}
}
const operation = this.getSaveOperationType(dto)
return this.getResultForOperation(operation)
}
private async getResultForOperation(operation: GroupSaveOperation): Promise<ItemSaveRuleResult> {
if (operation.type === 'add-to-group') {
return this.handleAddToGroupOperation(operation)
} else if (operation.type === 'remove-from-group') {
return this.handleRemoveFromGroupOperation(operation)
} else if (operation.type === 'move-to-other-group') {
return this.handleMoveToOtherGroupOperation(operation)
} else if (operation.type === 'save-to-group') {
return this.handleSaveToGroupOperation(operation)
} else if (operation.type === 'create-to-group') {
return this.handleCreateToGroupOperation(operation)
}
throw new Error(`Unsupported group operation: ${operation}`)
}
private getSaveOperationType(dto: ItemSaveValidationDTO): GroupSaveOperation {
const existingItemGroupUuid = dto.existingItem?.groupUuid
const targetItemGroupUuid = dto.itemHash.group_uuid
const common = {
incomingItem: dto.itemHash,
userUuid: dto.userUuid,
}
if (
dto.existingItem &&
existingItemGroupUuid &&
targetItemGroupUuid &&
existingItemGroupUuid !== targetItemGroupUuid
) {
return {
type: 'move-to-other-group',
groupUuid: existingItemGroupUuid,
targetGroupUuid: targetItemGroupUuid,
existingItem: dto.existingItem,
...common,
}
}
if (dto.existingItem && existingItemGroupUuid && !targetItemGroupUuid) {
return {
type: 'remove-from-group',
groupUuid: existingItemGroupUuid,
existingItem: dto.existingItem,
...common,
}
}
if (dto.existingItem && !existingItemGroupUuid && targetItemGroupUuid) {
return {
type: 'add-to-group',
groupUuid: targetItemGroupUuid,
existingItem: dto.existingItem,
...common,
}
}
if (dto.existingItem && existingItemGroupUuid && existingItemGroupUuid === targetItemGroupUuid) {
return {
type: 'save-to-group',
groupUuid: existingItemGroupUuid,
existingItem: dto.existingItem,
...common,
}
}
if (!dto.existingItem && targetItemGroupUuid) {
return {
type: 'create-to-group',
groupUuid: targetItemGroupUuid,
...common,
}
}
throw new Error('Invalid save operation')
}
private isAuthorizedToSaveContentType(contentType: ContentType, permissions: GroupUserPermission): boolean {
if (contentType === ContentType.VaultItemsKey) {
return permissions === 'admin'
}
return true
}
private buildFailResult(operation: GroupSaveOperation, type: ConflictType, serverItem?: Item) {
return {
passed: false,
conflict: {
unsavedItem: operation.incomingItem,
serverItem,
type,
},
}
}
private buildSuccessValue(): ItemSaveRuleResult {
return {
passed: true,
}
}
private async handleAddToGroupOperation(operation: AddToGroupSaveOperation): Promise<ItemSaveRuleResult> {
const groupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
if (!groupPermissions) {
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
}
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
return this.buildFailResult(operation, ConflictType.GroupInvalidState)
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, groupPermissions)) {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
if (groupPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
if (operation.existingItem.userUuid !== operation.userUuid) {
return this.buildFailResult(operation, ConflictType.UuidConflict)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleRemoveFromGroupOperation(operation: RemoveFromGroupSaveOperation): Promise<ItemSaveRuleResult> {
const groupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
if (!groupPermissions) {
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
}
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
return this.buildFailResult(operation, ConflictType.GroupInvalidState)
}
if (operation.existingItem.userUuid === operation.userUuid) {
return this.buildSuccessValue()
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, groupPermissions)) {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
if (groupPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleMoveToOtherGroupOperation(operation: MoveToOtherGroupSaveOperation): Promise<ItemSaveRuleResult> {
const sourceGroupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
const targetGroupPermissions = await this.getGroupPermissions(operation.userUuid, operation.targetGroupUuid)
if (!sourceGroupPermissions || !targetGroupPermissions) {
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
}
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
return this.buildFailResult(operation, ConflictType.GroupInvalidState)
}
if (sourceGroupPermissions === 'read' || targetGroupPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
if (
!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, sourceGroupPermissions) ||
!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, targetGroupPermissions)
) {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleSaveToGroupOperation(operation: SaveToGroupSaveOperation): Promise<ItemSaveRuleResult> {
const groupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
if (!groupPermissions) {
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, groupPermissions)) {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
if (groupPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleCreateToGroupOperation(operation: CreateToGroupSaveOperation): Promise<ItemSaveRuleResult> {
const groupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
if (!groupPermissions) {
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, groupPermissions)) {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
if (groupPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey)
}
return this.buildSuccessValue()
}
private async incomingItemUsesValidItemsKey(itemHash: ItemHash): Promise<boolean> {
const isItemNotEncryptedByItemsKey = itemHash.content_type === ContentType.VaultItemsKey
if (isItemNotEncryptedByItemsKey) {
return true
}
const group = await this.groupService.getGroup({ groupUuid: itemHash.group_uuid as string })
if (!group) {
return false
}
return itemHash.items_key_id === group.specifiedItemsKeyUuid
}
private async getGroupPermissions(userUuid: string, groupUuid: string): Promise<GroupUserPermission | undefined> {
const groupUser = await this.groupUserService.getUserForGroup({
userUuid,
groupUuid: groupUuid,
})
if (groupUser) {
return groupUser.permissions
}
return undefined
}
}
@@ -0,0 +1,44 @@
import { ItemHash } from '../ItemHash'
import { Item } from '../Item'
type SaveOperationCommon = {
incomingItem: ItemHash
userUuid: string
}
export type AddToGroupSaveOperation = SaveOperationCommon & {
type: 'add-to-group'
groupUuid: string
existingItem: Item
}
export type RemoveFromGroupSaveOperation = SaveOperationCommon & {
type: 'remove-from-group'
groupUuid: string
existingItem: Item
}
export type MoveToOtherGroupSaveOperation = SaveOperationCommon & {
type: 'move-to-other-group'
groupUuid: string
targetGroupUuid: string
existingItem: Item
}
export type SaveToGroupSaveOperation = SaveOperationCommon & {
type: 'save-to-group'
groupUuid: string
existingItem: Item
}
export type CreateToGroupSaveOperation = SaveOperationCommon & {
type: 'create-to-group'
groupUuid: string
}
export type GroupSaveOperation =
| AddToGroupSaveOperation
| RemoveFromGroupSaveOperation
| MoveToOtherGroupSaveOperation
| SaveToGroupSaveOperation
| CreateToGroupSaveOperation
@@ -4,20 +4,20 @@ import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
import { lt } from 'semver'
import { ConflictType } from '../../../Tmp/ConflictType'
const MINIMUM_SNJS_VERSION_FOR_VAULT_OPERATIONS = '2.200.0'
const MINIMUM_SNJS_VERSION_FOR_GROUP_OPERATIONS = '2.200.0'
export class VaultSnjsVersionFilter implements ItemSaveRuleInterface {
export class GroupSnjsVersionFilter implements ItemSaveRuleInterface {
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
const successValue = {
passed: true,
}
const vaultUuidInvolved = dto.existingItem?.vaultUuid || dto.itemHash.vault_uuid
if (!vaultUuidInvolved) {
const groupUuidInvolved = dto.existingItem?.groupUuid || dto.itemHash.group_uuid
if (!groupUuidInvolved) {
return successValue
}
const isClientSnjsVersionLessThanMinimum = lt(dto.snjsVersion, MINIMUM_SNJS_VERSION_FOR_VAULT_OPERATIONS)
const isClientSnjsVersionLessThanMinimum = lt(dto.snjsVersion, MINIMUM_SNJS_VERSION_FOR_GROUP_OPERATIONS)
if (isClientSnjsVersionLessThanMinimum) {
return {
passed: false,
@@ -5,8 +5,8 @@ import { ConflictType } from '../../../Tmp/ConflictType'
export class OwnershipFilter implements ItemSaveRuleInterface {
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
const deferToVaultFilter = dto.existingItem?.vaultUuid || dto.itemHash.vault_uuid
if (deferToVaultFilter) {
const deferToGroupFilter = dto.existingItem?.groupUuid || dto.itemHash.group_uuid
if (deferToGroupFilter) {
return {
passed: true,
}
@@ -1,301 +0,0 @@
import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface'
import { ItemHash } from '../ItemHash'
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
import { VaultServiceInterface } from '../../Vault/Service/VaultServiceInterface'
import { ContentType } from '@standardnotes/common'
import { ConflictType } from '../../../Tmp/ConflictType'
import {
VaultSaveOperation,
AddToVaultSaveOperation,
RemoveFromVaultSaveOperation,
MoveToOtherVaultSaveOperation,
SaveToVaultSaveOperation,
CreateToVaultSaveOperation,
} from './VaultSaveOperation'
import { Item } from '../Item'
export class VaultFilter implements ItemSaveRuleInterface {
constructor(private vaultService: VaultServiceInterface, private vaultUserService: VaultUserServiceInterface) {}
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
if (!dto.existingItem?.vaultUuid && !dto.itemHash.vault_uuid) {
return {
passed: true,
}
}
const operation = this.getSaveOperationType(dto)
return this.getResultForOperation(operation)
}
private async getResultForOperation(operation: VaultSaveOperation): Promise<ItemSaveRuleResult> {
if (operation.type === 'add-to-vault') {
return this.handleAddToVaultOperation(operation)
} else if (operation.type === 'remove-from-vault') {
return this.handleRemoveFromVaultOperation(operation)
} else if (operation.type === 'move-to-other-vault') {
return this.handleMoveToOtherVaultOperation(operation)
} else if (operation.type === 'save-to-vault') {
return this.handleSaveToVaultOperation(operation)
} else if (operation.type === 'create-to-vault') {
return this.handleCreateToVaultOperation(operation)
}
throw new Error(`Unsupported vault operation: ${operation}`)
}
private getSaveOperationType(dto: ItemSaveValidationDTO): VaultSaveOperation {
const existingItemVaultUuid = dto.existingItem?.vaultUuid
const targetItemVaultUuid = dto.itemHash.vault_uuid
const common = {
incomingItem: dto.itemHash,
userUuid: dto.userUuid,
}
if (
dto.existingItem &&
existingItemVaultUuid &&
targetItemVaultUuid &&
existingItemVaultUuid !== targetItemVaultUuid
) {
return {
type: 'move-to-other-vault',
vaultUuid: existingItemVaultUuid,
targetVaultUuid: targetItemVaultUuid,
existingItem: dto.existingItem,
...common,
}
}
if (dto.existingItem && existingItemVaultUuid && !targetItemVaultUuid) {
return {
type: 'remove-from-vault',
vaultUuid: existingItemVaultUuid,
existingItem: dto.existingItem,
...common,
}
}
if (dto.existingItem && !existingItemVaultUuid && targetItemVaultUuid) {
return {
type: 'add-to-vault',
vaultUuid: targetItemVaultUuid,
existingItem: dto.existingItem,
...common,
}
}
if (dto.existingItem && existingItemVaultUuid && existingItemVaultUuid === targetItemVaultUuid) {
return {
type: 'save-to-vault',
vaultUuid: existingItemVaultUuid,
existingItem: dto.existingItem,
...common,
}
}
if (!dto.existingItem && targetItemVaultUuid) {
return {
type: 'create-to-vault',
vaultUuid: targetItemVaultUuid,
...common,
}
}
throw new Error('Invalid save operation')
}
private isAuthorizedToSaveContentType(contentType: ContentType, permissions: VaultUserPermission): boolean {
if (contentType === ContentType.VaultItemsKey) {
return permissions === 'admin'
}
return true
}
private buildFailResult(operation: VaultSaveOperation, type: ConflictType, serverItem?: Item) {
return {
passed: false,
conflict: {
unsavedItem: operation.incomingItem,
serverItem,
type,
},
}
}
private buildSuccessValue(): ItemSaveRuleResult {
return {
passed: true,
}
}
private async handleAddToVaultOperation(operation: AddToVaultSaveOperation): Promise<ItemSaveRuleResult> {
const vaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.vaultUuid)
if (!vaultPermissions) {
return this.buildFailResult(operation, ConflictType.VaultNotMemberError)
}
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
return this.buildFailResult(operation, ConflictType.VaultInvalidState)
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, vaultPermissions)) {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
if (vaultPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
if (operation.existingItem.userUuid !== operation.userUuid) {
return this.buildFailResult(operation, ConflictType.UuidConflict)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.VaultInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleRemoveFromVaultOperation(operation: RemoveFromVaultSaveOperation): Promise<ItemSaveRuleResult> {
const vaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.vaultUuid)
if (!vaultPermissions) {
return this.buildFailResult(operation, ConflictType.VaultNotMemberError)
}
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
return this.buildFailResult(operation, ConflictType.VaultInvalidState)
}
if (operation.existingItem.userUuid === operation.userUuid) {
return this.buildSuccessValue()
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, vaultPermissions)) {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
if (vaultPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.VaultInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleMoveToOtherVaultOperation(operation: MoveToOtherVaultSaveOperation): Promise<ItemSaveRuleResult> {
const sourceVaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.vaultUuid)
const targetVaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.targetVaultUuid)
if (!sourceVaultPermissions || !targetVaultPermissions) {
return this.buildFailResult(operation, ConflictType.VaultNotMemberError)
}
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
return this.buildFailResult(operation, ConflictType.VaultInvalidState)
}
if (sourceVaultPermissions === 'read' || targetVaultPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
if (
!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, sourceVaultPermissions) ||
!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, targetVaultPermissions)
) {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.VaultInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleSaveToVaultOperation(operation: SaveToVaultSaveOperation): Promise<ItemSaveRuleResult> {
const vaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.vaultUuid)
if (!vaultPermissions) {
return this.buildFailResult(operation, ConflictType.VaultNotMemberError)
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, vaultPermissions)) {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
if (vaultPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.VaultInvalidItemsKey, operation.existingItem)
}
return this.buildSuccessValue()
}
private async handleCreateToVaultOperation(operation: CreateToVaultSaveOperation): Promise<ItemSaveRuleResult> {
const vaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.vaultUuid)
if (!vaultPermissions) {
return this.buildFailResult(operation, ConflictType.VaultNotMemberError)
}
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, vaultPermissions)) {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
if (vaultPermissions === 'read') {
return this.buildFailResult(operation, ConflictType.VaultInsufficientPermissionsError)
}
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
if (!usesValidKey) {
return this.buildFailResult(operation, ConflictType.VaultInvalidItemsKey)
}
return this.buildSuccessValue()
}
private async incomingItemUsesValidItemsKey(itemHash: ItemHash): Promise<boolean> {
const isItemNotEncryptedByItemsKey = itemHash.content_type === ContentType.VaultItemsKey
if (isItemNotEncryptedByItemsKey) {
return true
}
const vault = await this.vaultService.getVault({ vaultUuid: itemHash.vault_uuid as string })
if (!vault) {
return false
}
return itemHash.items_key_id === vault.specifiedItemsKeyUuid
}
private async getVaultPermissions(userUuid: string, vaultUuid: string): Promise<VaultUserPermission | undefined> {
const vaultUser = await this.vaultUserService.getUserForVault({
userUuid,
vaultUuid: vaultUuid,
})
if (vaultUser) {
return vaultUser.permissions
}
return undefined
}
}
@@ -1,44 +0,0 @@
import { ItemHash } from '../ItemHash'
import { Item } from '../Item'
type SaveOperationCommon = {
incomingItem: ItemHash
userUuid: string
}
export type AddToVaultSaveOperation = SaveOperationCommon & {
type: 'add-to-vault'
vaultUuid: string
existingItem: Item
}
export type RemoveFromVaultSaveOperation = SaveOperationCommon & {
type: 'remove-from-vault'
vaultUuid: string
existingItem: Item
}
export type MoveToOtherVaultSaveOperation = SaveOperationCommon & {
type: 'move-to-other-vault'
vaultUuid: string
targetVaultUuid: string
existingItem: Item
}
export type SaveToVaultSaveOperation = SaveOperationCommon & {
type: 'save-to-vault'
vaultUuid: string
existingItem: Item
}
export type CreateToVaultSaveOperation = SaveOperationCommon & {
type: 'create-to-vault'
vaultUuid: string
}
export type VaultSaveOperation =
| AddToVaultSaveOperation
| RemoveFromVaultSaveOperation
| MoveToOtherVaultSaveOperation
| SaveToVaultSaveOperation
| CreateToVaultSaveOperation
@@ -1,6 +1,6 @@
import { ContactProjection } from '../../../Projection/ContactProjection'
import { VaultInviteProjection } from '../../../Projection/VaultInviteProjection'
import { VaultProjection } from '../../../Projection/VaultProjection'
import { GroupInviteProjection } from '../../../Projection/GroupInviteProjection'
import { GroupProjection } from '../../../Projection/GroupProjection'
import { ItemConflictProjection } from '../../../Projection/ItemConflictProjection'
import { ItemProjection } from '../../../Projection/ItemProjection'
import { SavedItemProjection } from '../../../Projection/SavedItemProjection'
@@ -12,6 +12,6 @@ export type SyncResponse20200115 = {
sync_token: string
cursor_token?: string
contacts: Array<ContactProjection>
vaults: Array<VaultProjection>
vault_invites: Array<VaultInviteProjection>
groups: Array<GroupProjection>
group_invites: Array<GroupInviteProjection>
}
@@ -1,4 +1,4 @@
import { VaultInvite } from '../../VaultInvite/Model/VaultInvite'
import { GroupInvite } from '../../GroupInvite/Model/GroupInvite'
import { Contact } from './../../Contact/Model/Contact'
import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
import { SyncItemsResponse } from '../../UseCase/SyncItemsResponse'
@@ -10,18 +10,18 @@ import { SyncResponse20200115 } from './SyncResponse20200115'
import { SyncResponseFactoryInterface } from './SyncResponseFactoryInterface'
import { SavedItemProjection } from '../../../Projection/SavedItemProjection'
import { ContactProjection } from '../../../Projection/ContactProjection'
import { VaultInviteProjection } from '../../../Projection/VaultInviteProjection'
import { VaultProjection } from '../../../Projection/VaultProjection'
import { Vault } from '../../Vault/Model/Vault'
import { GroupInviteProjection } from '../../../Projection/GroupInviteProjection'
import { GroupProjection } from '../../../Projection/GroupProjection'
import { Group } from '../../Group/Model/Group'
export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface {
constructor(
private itemProjector: ProjectorInterface<Item, ItemProjection>,
private itemConflictProjector: ProjectorInterface<ItemConflict, ItemConflictProjection>,
private savedItemProjector: ProjectorInterface<Item, SavedItemProjection>,
private vaultInviteProjector: ProjectorInterface<VaultInvite, VaultInviteProjection>,
private groupInviteProjector: ProjectorInterface<GroupInvite, GroupInviteProjection>,
private contactProjector: ProjectorInterface<Contact, ContactProjection>,
private vaultProjector: ProjectorInterface<Vault, VaultProjection>,
private groupProjector: ProjectorInterface<Group, GroupProjection>,
) {}
async createResponse(syncItemsResponse: SyncItemsResponse): Promise<SyncResponse20200115> {
@@ -40,14 +40,14 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface
conflicts.push(this.itemConflictProjector.projectFull(itemConflict))
}
const vaults = []
for (const vault of syncItemsResponse.vaults) {
vaults.push(this.vaultProjector.projectFull(vault))
const groups = []
for (const group of syncItemsResponse.groups) {
groups.push(this.groupProjector.projectFull(group))
}
const vaultInvites = []
for (const vaultInvite of syncItemsResponse.vaultInvites) {
vaultInvites.push(this.vaultInviteProjector.projectFull(vaultInvite))
const groupInvites = []
for (const groupInvite of syncItemsResponse.groupInvites) {
groupInvites.push(this.groupInviteProjector.projectFull(groupInvite))
}
const contacts = []
@@ -61,8 +61,8 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface
conflicts,
sync_token: syncItemsResponse.syncToken,
cursor_token: syncItemsResponse.cursorToken,
vaults,
vault_invites: vaultInvites,
groups,
group_invites: groupInvites,
contacts,
}
}
@@ -0,0 +1,31 @@
import { TimerInterface } from '@standardnotes/time'
import { RemovedGroupUserFactoryInterface } from './RemovedGroupUserFactoryInterface'
import { RemovedGroupUserHash } from './RemovedGroupUserHash'
import { RemovedGroupUser } from '../Model/RemovedGroupUser'
export class RemovedGroupUserFactory implements RemovedGroupUserFactoryInterface {
constructor(private timer: TimerInterface) {}
create(dto: RemovedGroupUserHash): RemovedGroupUser {
const newRemovedGroupUser = new RemovedGroupUser()
newRemovedGroupUser.uuid = dto.uuid
newRemovedGroupUser.userUuid = dto.user_uuid
newRemovedGroupUser.groupUuid = dto.group_uuid
newRemovedGroupUser.removedBy = dto.removed_by
const now = this.timer.getTimestampInMicroseconds()
newRemovedGroupUser.updatedAtTimestamp = now
newRemovedGroupUser.createdAtTimestamp = now
if (dto.created_at_timestamp) {
newRemovedGroupUser.createdAtTimestamp = dto.created_at_timestamp
}
return newRemovedGroupUser
}
createStub(dto: RemovedGroupUserHash): RemovedGroupUser {
const item = this.create(dto)
return item
}
}

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