mirror of
https://github.com/standardnotes/server
synced 2026-10-17 03:03:10 -04:00
wip: vault sync filter
This commit is contained in:
@@ -20,6 +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 { OwnershipFilter } from '../Domain/Item/SaveRule/OwnershipFilter'
|
||||
import { TimeDifferenceFilter } from '../Domain/Item/SaveRule/TimeDifferenceFilter'
|
||||
import { ItemFactoryInterface } from '../Domain/Item/ItemFactoryInterface'
|
||||
@@ -46,7 +47,7 @@ import { VaultInviteFactory } from '../Domain/VaultInvite/Factory/VaultInviteFac
|
||||
import { ContactServiceInterface } from '../Domain/Contact/Service/ContactServiceInterface'
|
||||
import { ContactService } from '../Domain/Contact/Service/ContactService'
|
||||
import { ContactFactoryInterface } from '../Domain/Contact/Factory/ContactFactoryInterface'
|
||||
import { SnjsVersionFilter } from '../Domain/Item/SaveRule/SnjsVersionFilter'
|
||||
import { VaultSnjsVersionFilter } from '../Domain/Item/SaveRule/VaultSnjsVersionFilter'
|
||||
import { CreateVaultFileValetToken } from '../Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileValetToken'
|
||||
|
||||
export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
@@ -226,11 +227,12 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
return new ContactFactory(context.container.get(TYPES.Timer))
|
||||
})
|
||||
|
||||
container.bind<OwnershipFilter>(TYPES.OwnershipFilter).toDynamicValue(() => new OwnershipFilter())
|
||||
container
|
||||
.bind<OwnershipFilter>(TYPES.OwnershipFilter)
|
||||
.bind<VaultFilter>(TYPES.VaultFilter)
|
||||
.toDynamicValue(
|
||||
(context: interfaces.Context) =>
|
||||
new OwnershipFilter(context.container.get(TYPES.VaultService), context.container.get(TYPES.VaultUserService)),
|
||||
new VaultFilter(context.container.get(TYPES.VaultService), context.container.get(TYPES.VaultUserService)),
|
||||
)
|
||||
container
|
||||
.bind<TimeDifferenceFilter>(TYPES.TimeDifferenceFilter)
|
||||
@@ -238,14 +240,17 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
container.bind<UuidFilter>(TYPES.UuidFilter).toDynamicValue(() => new UuidFilter())
|
||||
container.bind<ContentTypeFilter>(TYPES.ContentTypeFilter).toDynamicValue(() => new ContentTypeFilter())
|
||||
container.bind<ContentFilter>(TYPES.ContentFilter).toDynamicValue(() => new ContentFilter())
|
||||
container.bind<SnjsVersionFilter>(TYPES.SnjsVersionFilter).toDynamicValue(() => new SnjsVersionFilter())
|
||||
container
|
||||
.bind<VaultSnjsVersionFilter>(TYPES.VaultSnjsVersionFilter)
|
||||
.toDynamicValue(() => new VaultSnjsVersionFilter())
|
||||
|
||||
container
|
||||
.bind<ItemSaveValidatorInterface>(TYPES.ItemSaveValidator)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new ItemSaveValidator([
|
||||
context.container.get(TYPES.SnjsVersionFilter),
|
||||
context.container.get(TYPES.OwnershipFilter),
|
||||
context.container.get(TYPES.VaultFilter),
|
||||
context.container.get(TYPES.VaultSnjsVersionFilter),
|
||||
context.container.get(TYPES.TimeDifferenceFilter),
|
||||
context.container.get(TYPES.UuidFilter),
|
||||
context.container.get(TYPES.ContentTypeFilter),
|
||||
|
||||
@@ -97,7 +97,8 @@ const TYPES = {
|
||||
ItemBackupService: Symbol.for('ItemBackupService'),
|
||||
ItemSaveValidator: Symbol.for('ItemSaveValidator'),
|
||||
OwnershipFilter: Symbol.for('OwnershipFilter'),
|
||||
SnjsVersionFilter: Symbol.for('SnjsVersionFilter'),
|
||||
VaultFilter: Symbol.for('VaultFilter'),
|
||||
VaultSnjsVersionFilter: Symbol.for('VaultSnjsVersionFilter'),
|
||||
TimeDifferenceFilter: Symbol.for('TimeDifferenceFilter'),
|
||||
UuidFilter: Symbol.for('UuidFilter'),
|
||||
ContentTypeFilter: Symbol.for('ContentTypeFilter'),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
import { ConflictType } from '../../Tmp/ConflictType'
|
||||
import { Item } from './Item'
|
||||
import { ItemHash } from './ItemHash'
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ import { ItemServiceInterface } from './ItemServiceInterface'
|
||||
import { SaveItemsDTO } from './SaveItemsDTO'
|
||||
import { SaveItemsResult } from './SaveItemsResult'
|
||||
import { ItemSaveValidatorInterface } from './SaveValidator/ItemSaveValidatorInterface'
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
|
||||
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
|
||||
import { ItemProjection } from '../../Projection/ItemProjection'
|
||||
import { VaultUserRepositoryInterface } from '../VaultUser/Repository/VaultUserRepositoryInterface'
|
||||
import { ConflictType } from '../../Tmp/ConflictType'
|
||||
|
||||
export class ItemService implements ItemServiceInterface {
|
||||
private readonly DEFAULT_ITEMS_LIMIT = 150
|
||||
@@ -100,16 +100,18 @@ export class ItemService implements ItemServiceInterface {
|
||||
const lastUpdatedTimestamp = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
for (const itemHash of dto.itemHashes) {
|
||||
const existingItem = await this.itemRepository.findByUuid(itemHash.uuid)
|
||||
|
||||
if (dto.readOnlyAccess) {
|
||||
conflicts.push({
|
||||
unsavedItem: itemHash,
|
||||
serverItem: existingItem ?? undefined,
|
||||
type: ConflictType.ReadOnlyError,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
const existingItem = await this.itemRepository.findByUuid(itemHash.uuid)
|
||||
const processingResult = await this.itemSaveValidator.validate({
|
||||
userUuid: dto.userUuid,
|
||||
apiVersion: dto.apiVersion,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
|
||||
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
|
||||
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
import { ConflictType } from '../../../Tmp/ConflictType'
|
||||
|
||||
export class ContentFilter implements ItemSaveRuleInterface {
|
||||
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ContentType } from '@standardnotes/common'
|
||||
import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
|
||||
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
|
||||
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
import { ConflictType } from '../../../Tmp/ConflictType'
|
||||
|
||||
export class ContentTypeFilter implements ItemSaveRuleInterface {
|
||||
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
|
||||
|
||||
@@ -1,152 +1,30 @@
|
||||
import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
|
||||
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
|
||||
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
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'
|
||||
|
||||
export class OwnershipFilter implements ItemSaveRuleInterface {
|
||||
constructor(private vaultService: VaultServiceInterface, private vaultUserService: VaultUserServiceInterface) {}
|
||||
|
||||
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
|
||||
const successValue = {
|
||||
const deferToVaultFilter = dto.existingItem?.vaultUuid || dto.itemHash.vault_uuid
|
||||
if (deferToVaultFilter) {
|
||||
return {
|
||||
passed: true,
|
||||
}
|
||||
}
|
||||
|
||||
const itemBelongsToADifferentUser = dto.existingItem !== null && dto.existingItem.userUuid !== dto.userUuid
|
||||
if (itemBelongsToADifferentUser) {
|
||||
return {
|
||||
passed: false,
|
||||
conflict: {
|
||||
unsavedItem: dto.itemHash,
|
||||
type: ConflictType.UuidConflict,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
passed: true,
|
||||
}
|
||||
|
||||
const vaultReadonlyFail = {
|
||||
passed: false,
|
||||
conflict: {
|
||||
unsavedItem: dto.itemHash,
|
||||
type: ConflictType.ReadOnlyError,
|
||||
},
|
||||
}
|
||||
|
||||
const ownershipFail = {
|
||||
passed: false,
|
||||
conflict: {
|
||||
unsavedItem: dto.itemHash,
|
||||
type: ConflictType.UuidConflict,
|
||||
},
|
||||
}
|
||||
|
||||
const contentFail = {
|
||||
passed: false,
|
||||
conflict: {
|
||||
unsavedItem: dto.itemHash,
|
||||
type: ConflictType.ContentError,
|
||||
},
|
||||
}
|
||||
|
||||
const vaultUuidInvolved = dto.existingItem?.vaultUuid || dto.itemHash.vault_uuid
|
||||
if (vaultUuidInvolved) {
|
||||
const result = await this.authorizationToSaveVaultInvolvedItem(dto)
|
||||
if (result === 'ownership-fail') {
|
||||
return ownershipFail
|
||||
} else if (result === 'readonly-fail') {
|
||||
return vaultReadonlyFail
|
||||
} else if (result === 'content-fail') {
|
||||
return contentFail
|
||||
} else if (result === 'success') {
|
||||
return successValue
|
||||
} else {
|
||||
throw new Error(`Unexpected vault authorization result: ${result}`)
|
||||
}
|
||||
}
|
||||
|
||||
const itemBelongsToADifferentUser =
|
||||
dto.existingItem != null && dto.existingItem.userUuid && dto.existingItem.userUuid !== dto.userUuid
|
||||
if (itemBelongsToADifferentUser) {
|
||||
return ownershipFail
|
||||
}
|
||||
|
||||
return successValue
|
||||
}
|
||||
|
||||
private async authorizationToSaveVaultInvolvedItem(
|
||||
dto: ItemSaveValidationDTO,
|
||||
): Promise<'ownership-fail' | 'readonly-fail' | 'content-fail' | 'success'> {
|
||||
const existingItemVaultUuid = dto.existingItem?.vaultUuid
|
||||
const targetItemVaultUuid = dto.itemHash.vault_uuid
|
||||
|
||||
const vaultUuidInvolved = existingItemVaultUuid || targetItemVaultUuid
|
||||
|
||||
const isMovingVaults = existingItemVaultUuid && targetItemVaultUuid && existingItemVaultUuid !== targetItemVaultUuid
|
||||
if (isMovingVaults) {
|
||||
const existingVaultAuthorization = await this.vaultAuthorizationForItem(dto.userUuid, existingItemVaultUuid)
|
||||
const targetVaultAuthorization = await this.vaultAuthorizationForItem(dto.userUuid, targetItemVaultUuid)
|
||||
if (!existingVaultAuthorization || !targetVaultAuthorization) {
|
||||
return 'ownership-fail'
|
||||
}
|
||||
|
||||
if (existingVaultAuthorization === 'read' || targetVaultAuthorization === 'read') {
|
||||
return 'readonly-fail'
|
||||
}
|
||||
}
|
||||
|
||||
const vaultAuthorization = await this.vaultAuthorizationForItem(dto.userUuid, vaultUuidInvolved as string)
|
||||
if (!vaultAuthorization) {
|
||||
return 'ownership-fail'
|
||||
}
|
||||
|
||||
if (vaultAuthorization === 'read') {
|
||||
return 'readonly-fail'
|
||||
}
|
||||
|
||||
const isItemBeingRemovedFromVault =
|
||||
dto.existingItem != null && dto.existingItem.vaultUuid != null && dto.itemHash.vault_uuid == null
|
||||
|
||||
const isItemBeingDeleted = dto.itemHash.deleted === true
|
||||
|
||||
if (isItemBeingRemovedFromVault || isItemBeingDeleted) {
|
||||
const doesHavePermissionToRemoveItemFromVault =
|
||||
vaultAuthorization === 'admin' || dto.existingItem?.createdByUuid === dto.userUuid
|
||||
|
||||
return doesHavePermissionToRemoveItemFromVault ? 'success' : 'readonly-fail'
|
||||
}
|
||||
|
||||
if (dto.itemHash.content_type === ContentType.VaultItemsKey && vaultAuthorization !== 'admin') {
|
||||
return 'readonly-fail'
|
||||
}
|
||||
|
||||
const usingValidKey = await this.vaultItemIsBeingSavedWithValidItemsKey(dto.itemHash)
|
||||
if (!usingValidKey) {
|
||||
return 'content-fail'
|
||||
}
|
||||
|
||||
return 'success'
|
||||
}
|
||||
|
||||
private async vaultItemIsBeingSavedWithValidItemsKey(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 vaultAuthorizationForItem(
|
||||
userUuid: string,
|
||||
vaultUuid: string,
|
||||
): Promise<VaultUserPermission | undefined> {
|
||||
const vaultUser = await this.vaultUserService.getUserForVault({
|
||||
userUuid,
|
||||
vaultUuid: vaultUuid,
|
||||
})
|
||||
|
||||
if (vaultUser) {
|
||||
return vaultUser.permissions
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ItemHash } from '../ItemHash'
|
||||
import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
|
||||
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
|
||||
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
import { ConflictType } from '../../../Tmp/ConflictType'
|
||||
|
||||
export class TimeDifferenceFilter implements ItemSaveRuleInterface {
|
||||
constructor(private timer: TimerInterface) {}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { validate } from 'uuid'
|
||||
import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
|
||||
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
|
||||
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
import { ConflictType } from '../../../Tmp/ConflictType'
|
||||
|
||||
export class UuidFilter implements ItemSaveRuleInterface {
|
||||
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
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'
|
||||
|
||||
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',
|
||||
sourceVaultUuid: 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) {
|
||||
return {
|
||||
passed: false,
|
||||
conflict: {
|
||||
unsavedItem: operation.incomingItem,
|
||||
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)
|
||||
}
|
||||
|
||||
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 (!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 handleMoveToOtherVaultOperation(operation: MoveToOtherVaultSaveOperation): Promise<ItemSaveRuleResult> {
|
||||
const sourceVaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.sourceVaultUuid)
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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'
|
||||
sourceVaultUuid: 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
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
|
||||
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
|
||||
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
import { lt } from 'semver'
|
||||
import { ConflictType } from '../../../Tmp/ConflictType'
|
||||
|
||||
const MINIMUM_SNJS_VERSION_FOR_VAULT_OPERATIONS = '2.200.0'
|
||||
|
||||
export class SnjsVersionFilter implements ItemSaveRuleInterface {
|
||||
export class VaultSnjsVersionFilter implements ItemSaveRuleInterface {
|
||||
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
|
||||
const successValue = {
|
||||
passed: true,
|
||||
@@ -18,13 +18,13 @@ export class SnjsVersionFilter implements ItemSaveRuleInterface {
|
||||
}
|
||||
|
||||
const isClientSnjsVersionLessThanMinimum = lt(dto.snjsVersion, MINIMUM_SNJS_VERSION_FOR_VAULT_OPERATIONS)
|
||||
|
||||
if (isClientSnjsVersionLessThanMinimum) {
|
||||
return {
|
||||
passed: false,
|
||||
conflict: {
|
||||
unsavedItem: dto.itemHash,
|
||||
type: ConflictType.ContentError,
|
||||
serverItem: dto.existingItem ?? undefined,
|
||||
type: ConflictType.SnjsVersionError,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
|
||||
import { ItemHash } from '../ItemHash'
|
||||
import { ItemProjection } from '../../../Projection/ItemProjection'
|
||||
import { ConflictType } from '../../../Tmp/ConflictType'
|
||||
|
||||
export type SyncResponse20161215 = {
|
||||
retrieved_items: Array<ItemProjection>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ItemHash } from '../ItemHash'
|
||||
import { ItemProjection } from '../../../Projection/ItemProjection'
|
||||
import { SyncResponse20161215 } from './SyncResponse20161215'
|
||||
import { SyncResponseFactoryInterface } from './SyncResponseFactoryInterface'
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
import { ConflictType } from '../../../Tmp/ConflictType'
|
||||
|
||||
export class SyncResponseFactory20161215 implements SyncResponseFactoryInterface {
|
||||
private readonly LEGACY_MIN_CONFLICT_INTERVAL = 20_000_000
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
|
||||
import { ItemHash } from '../Domain/Item/ItemHash'
|
||||
import { ConflictType } from '../Tmp/ConflictType'
|
||||
import { ItemProjection } from './ItemProjection'
|
||||
|
||||
export type ItemConflictProjection = {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export enum ConflictType {
|
||||
ConflictingData = 'sync_conflict',
|
||||
UuidConflict = 'uuid_conflict',
|
||||
ContentTypeError = 'content_type_error',
|
||||
ContentError = 'content_error',
|
||||
ReadOnlyError = 'readonly_error',
|
||||
UuidError = 'uuid_error',
|
||||
SnjsVersionError = 'snjs_version_error',
|
||||
VaultInsufficientPermissionsError = 'vault_insufficient_permissions_error',
|
||||
VaultNotMemberError = 'vault_not_member_error',
|
||||
VaultNotFoundError = 'vault_not_found_error',
|
||||
VaultInvalidState = 'vault_invalid_state',
|
||||
VaultInvalidItemsKey = 'vault_invalid_items_key',
|
||||
}
|
||||
Reference in New Issue
Block a user