chore: display shared vault file usage (#2399)

* chore: display shared vault file usage

* fix: specs

* fix: reshape filtering result

* fix: resolving invalid server items

* fix: get revisions specs

* fix: processing issue

* fix: tests

---------

Co-authored-by: Mo <mo@standardnotes.com>
This commit is contained in:
Karol Sójko
2023-08-11 08:59:16 +02:00
committed by GitHub
parent 05f3672526
commit 5bca53736b
87 changed files with 505 additions and 169 deletions

View File

@@ -36,7 +36,7 @@
},
"dependencies": {
"@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.24.0",
"@standardnotes/domain-core": "^1.25.0",
"@standardnotes/models": "workspace:*",
"@standardnotes/responses": "workspace:*",
"@standardnotes/utils": "workspace:*",

View File

@@ -35,7 +35,7 @@
},
"dependencies": {
"@electron/remote": "^2.0.9",
"@standardnotes/domain-core": "^1.24.0",
"@standardnotes/domain-core": "^1.25.0",
"@standardnotes/electron-clear-data": "1.1.1",
"@standardnotes/web": "workspace:*",
"axios": "^1.1.3",

View File

@@ -29,7 +29,7 @@
},
"dependencies": {
"@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.24.0",
"@standardnotes/domain-core": "^1.25.0",
"@standardnotes/models": "workspace:*",
"@standardnotes/responses": "workspace:*",
"@standardnotes/sncrypto-common": "workspace:*",

View File

@@ -1,4 +1,3 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ContentType } from '@standardnotes/domain-core'
import {
ConflictStrategy,
@@ -8,6 +7,7 @@ import {
HistoryEntryInterface,
ItemsKeyContent,
ItemsKeyInterface,
ProtocolVersion,
} from '@standardnotes/models'
export function isItemsKey(x: unknown): x is ItemsKeyInterface {

View File

@@ -1,6 +1,6 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ContentType } from '@standardnotes/domain-core'
import {
ProtocolVersion,
ConflictStrategy,
DecryptedItem,
DecryptedItemInterface,

View File

@@ -1,5 +1,5 @@
import { ProtocolVersion } from '@standardnotes/common'
import {
ProtocolVersion,
DecryptedPayload,
FillItemContentSpecialized,
PayloadTimestampDefaults,

View File

@@ -1,5 +1,5 @@
import { ProtocolVersion } from '@standardnotes/common'
import {
ProtocolVersion,
DecryptedItem,
DecryptedPayloadInterface,
NamespacedRootKeyInKeychain,

View File

@@ -1,5 +1,10 @@
import { ProtocolVersion } from '@standardnotes/common'
import { DecryptedPayload, ItemContent, ItemsKeyContent, PayloadTimestampDefaults } from '@standardnotes/models'
import {
DecryptedPayload,
ItemContent,
ItemsKeyContent,
PayloadTimestampDefaults,
ProtocolVersion,
} from '@standardnotes/models'
import { SNItemsKey } from '../../Keys/ItemsKey/ItemsKey'
import { SNProtocolOperator004 } from './Operator004'
import { getMockedCrypto } from './MockedCrypto'

View File

@@ -7,10 +7,10 @@ import {
KeySystemItemsKeyContentSpecialized,
KeySystemItemsKeyInterface,
PayloadTimestampDefaults,
ProtocolVersion,
} from '@standardnotes/models'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { V004Algorithm } from '../../../../Algorithm'
import { ProtocolVersion } from '@standardnotes/common'
import { ContentType } from '@standardnotes/domain-core'
export class CreateKeySystemItemsKeyUseCase {

View File

@@ -4,8 +4,8 @@ import {
KeySystemRootKeyInterface,
KeySystemRootKeyParamsInterface,
KeySystemPasswordType,
ProtocolVersion,
} from '@standardnotes/models'
import { ProtocolVersion } from '@standardnotes/common'
import { DeriveKeySystemRootKeyUseCase } from './DeriveKeySystemRootKey'
export class CreateRandomKeySystemRootKey {

View File

@@ -5,8 +5,8 @@ import {
KeySystemRootKeyInterface,
KeySystemRootKeyParamsInterface,
KeySystemPasswordType,
ProtocolVersion,
} from '@standardnotes/models'
import { ProtocolVersion } from '@standardnotes/common'
import { DeriveKeySystemRootKeyUseCase } from './DeriveKeySystemRootKey'
export class CreateUserInputKeySystemRootKey {

View File

@@ -11,8 +11,8 @@ import {
KeySystemRootKeyInterface,
PayloadTimestampDefaults,
KeySystemRootKeyParamsInterface,
ProtocolVersion,
} from '@standardnotes/models'
import { ProtocolVersion } from '@standardnotes/common'
import { ContentType } from '@standardnotes/domain-core'
export class DeriveKeySystemRootKeyUseCase {

View File

@@ -2,10 +2,9 @@ import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { splitString, truncateHexString } from '@standardnotes/utils'
import { V004PartitionCharacter } from '../../V004AlgorithmTypes'
import { V004Algorithm } from '../../../../Algorithm'
import { RootKeyInterface } from '@standardnotes/models'
import { RootKeyInterface, ProtocolVersion } from '@standardnotes/models'
import { SNRootKeyParams } from '../../../../Keys/RootKey/RootKeyParams'
import { CreateNewRootKey } from '../../../../Keys/RootKey/Functions'
import { ProtocolVersion } from '@standardnotes/common'
export class DeriveRootKeyUseCase {
constructor(private readonly crypto: PureCryptoInterface) {}

View File

@@ -7,11 +7,11 @@ import {
isKeySystemRootKey,
ContentTypeUsesRootKeyEncryption,
ContentTypeUsesKeySystemRootKeyEncryption,
ProtocolVersion,
} from '@standardnotes/models'
import { ItemAuthenticatedData } from '../../../../Types/ItemAuthenticatedData'
import { RootKeyEncryptedAuthenticatedData } from '../../../../Types/RootKeyEncryptedAuthenticatedData'
import { KeySystemItemsKeyAuthenticatedData } from '../../../../Types/KeySystemItemsKeyAuthenticatedData'
import { ProtocolVersion } from '@standardnotes/common'
import { isItemsKey } from '../../../../Keys/ItemsKey/ItemsKey'
import { isKeySystemItemsKey } from '../../../../Keys/KeySystemItemsKey/KeySystemItemsKey'

View File

@@ -1,4 +1,3 @@
import { ProtocolVersion } from '@standardnotes/common'
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import {
DecryptedPayloadInterface,
@@ -6,6 +5,7 @@ import {
KeySystemItemsKeyInterface,
KeySystemRootKeyInterface,
RootKeyInterface,
ProtocolVersion,
} from '@standardnotes/models'
import { CreateConsistentBase64JsonPayloadUseCase } from '../Utils/CreateConsistentBase64JsonPayload'
import { doesPayloadRequireSigning } from '../../V004AlgorithmHelpers'

View File

@@ -1,6 +1,5 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '@standardnotes/models'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { ItemAuthenticatedData } from './../../../../Types/ItemAuthenticatedData'
import { GenerateEncryptedProtocolStringUseCase } from './GenerateEncryptedProtocolString'
import { AdditionalData } from '../../../../Types/EncryptionAdditionalData'

View File

@@ -1,6 +1,6 @@
import { Base64String, HexString, PureCryptoInterface, Utf8String } from '@standardnotes/sncrypto-common'
import { V004PartitionCharacter, V004StringComponents } from '../../V004AlgorithmTypes'
import { ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '@standardnotes/models'
import { V004Algorithm } from '../../../../Algorithm'
export class GenerateEncryptedProtocolStringUseCase {

View File

@@ -1,4 +1,4 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '@standardnotes/models'
import { AnyOperatorInterface } from './OperatorInterface/TypeCheck'
export interface EncryptionOperatorsInterface {

View File

@@ -1,4 +1,4 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '@standardnotes/models'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { SNProtocolOperator001 } from '../Operator/001/Operator001'
import { SNProtocolOperator002 } from '../Operator/002/Operator002'

View File

@@ -1,5 +1,9 @@
import { ProtocolVersion } from '@standardnotes/common'
import { EncryptedPayloadInterface, DecryptedPayloadInterface, PersistentSignatureData } from '@standardnotes/models'
import {
EncryptedPayloadInterface,
DecryptedPayloadInterface,
PersistentSignatureData,
ProtocolVersion,
} from '@standardnotes/models'
import { DecryptedParameters } from './DecryptedParameters'
export type EncryptedOutputParameters = {

View File

@@ -1,4 +1,4 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '@standardnotes/models'
type UserUuid = string
type KeySystemIdentifier = string

View File

@@ -26,7 +26,7 @@
},
"dependencies": {
"@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.24.0",
"@standardnotes/domain-core": "^1.25.0",
"reflect-metadata": "^0.1.13"
},
"devDependencies": {

View File

@@ -23,7 +23,7 @@
},
"dependencies": {
"@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.24.0",
"@standardnotes/domain-core": "^1.25.0",
"@standardnotes/features": "workspace:*",
"@standardnotes/responses": "workspace:*",
"@standardnotes/sncrypto-common": "workspace:^",

View File

@@ -12,14 +12,38 @@ function CreateFilteredServerItem(item: ServerItemResponse): FilteredServerItem
}
}
export function FilterDisallowedRemotePayloadsAndMap(payloads: ServerItemResponse[]): FilteredServerItem[] {
return payloads.filter(isRemotePayloadAllowed).map(CreateFilteredServerItem)
}
export function isRemotePayloadAllowed(payload: ServerItemResponse): boolean {
if (isCorruptTransferPayload(payload)) {
return false
export function FilterDisallowedRemotePayloadsAndMap(payloads: ServerItemResponse[]): {
filtered: FilteredServerItem[]
disallowed: ServerItemResponse[]
} {
const filtered = []
const disallowed = []
for (const payload of payloads) {
const result = checkRemotePayloadAllowed(payload)
if (result.allowed === undefined) {
disallowed.push(payload)
} else {
filtered.push(CreateFilteredServerItem(result.allowed))
}
}
return isEncryptedTransferPayload(payload) || payload.content == undefined
return {
filtered,
disallowed,
}
}
export function checkRemotePayloadAllowed(payload: ServerItemResponse): {
allowed?: ServerItemResponse
disallowed?: ServerItemResponse
} {
if (isCorruptTransferPayload(payload)) {
return { disallowed: payload }
}
if (isEncryptedTransferPayload(payload) || payload.content == undefined) {
return { allowed: payload }
} else {
return { disallowed: payload }
}
}

View File

@@ -1,4 +1,4 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '../../../Local/Protocol/ProtocolVersion'
import { EncryptedPayloadInterface } from '../../Payload/Interfaces/EncryptedPayload'
import { ItemInterface } from './ItemInterface'

View File

@@ -1,4 +1,5 @@
import { ProtocolVersion, protocolVersionFromEncryptedString } from '@standardnotes/common'
import { ProtocolVersion } from '../../../Local/Protocol/ProtocolVersion'
import { ProtocolVersionFromEncryptedString } from '../../../Local/Protocol/ProtocolVersionFromEncryptedString'
import { SyncResolvedParams, SyncResolvedPayload } from '../../../Runtime/Deltas/Utilities/SyncResolvedPayload'
import { EncryptedTransferPayload } from '../../TransferPayload/Interfaces/EncryptedTransferPayload'
import { EncryptedPayloadInterface } from '../Interfaces/EncryptedPayload'
@@ -18,13 +19,18 @@ export class EncryptedPayload extends PurePayload<EncryptedTransferPayload> impl
constructor(rawPayload: EncryptedTransferPayload, source = PayloadSource.Constructor) {
super(rawPayload, source)
const versionResult = ProtocolVersionFromEncryptedString(rawPayload.content)
if (versionResult.isFailed()) {
throw new Error('EncryptedPayload constructor versionResult is failed')
}
this.auth_hash = rawPayload.auth_hash
this.content = rawPayload.content
this.deleted = false
this.enc_item_key = rawPayload.enc_item_key
this.errorDecrypting = rawPayload.errorDecrypting
this.items_key_id = rawPayload.items_key_id
this.version = protocolVersionFromEncryptedString(this.content)
this.version = versionResult.getValue()
this.waitingForKey = rawPayload.waitingForKey
}

View File

@@ -1,4 +1,4 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '../../../Local/Protocol/ProtocolVersion'
import { EncryptedTransferPayload } from '../../TransferPayload/Interfaces/EncryptedTransferPayload'
import { PayloadInterface } from './PayloadInterface'

View File

@@ -42,7 +42,7 @@ describe('type check', () => {
expect(
isCorruptTransferPayload({
uuid: '123',
content_type: ContentType.TYPES.Unknown,
content_type: 'Unknown',
content: '123',
...PayloadTimestampDefaults(),
}),

View File

@@ -26,5 +26,7 @@ export function isDeletedTransferPayload(payload: TransferPayload): payload is D
export function isCorruptTransferPayload(payload: TransferPayload): boolean {
const invalidDeletedState = payload.deleted === true && payload.content != undefined
return payload.uuid == undefined || invalidDeletedState || payload.content_type === ContentType.TYPES.Unknown
const contenTypeOrError = ContentType.create(payload.content_type)
return payload.uuid == undefined || invalidDeletedState || contenTypeOrError.isFailed()
}

View File

@@ -0,0 +1 @@
export type ApplicationIdentifier = string

View File

@@ -1,5 +1,5 @@
import { ProtocolVersion } from '@standardnotes/common'
import { KeySystemIdentifier } from '../../Syncable/KeySystemRootKey/KeySystemIdentifier'
import { ProtocolVersion } from '../Protocol/ProtocolVersion'
import { KeySystemPasswordType } from './KeySystemPasswordType'
/**

View File

@@ -0,0 +1,47 @@
export enum ProtocolVersion {
V001 = '001',
V002 = '002',
V003 = '003',
V004 = '004',
}
export const ProtocolVersionLatest = ProtocolVersion.V004
/** The last protocol version to not use root-key based items keys */
export const ProtocolVersionLastNonrootItemsKey = ProtocolVersion.V003
export const ProtocolExpirationDates: Partial<Record<ProtocolVersion, number>> = Object.freeze({
[ProtocolVersion.V001]: Date.parse('2018-01-01'),
[ProtocolVersion.V002]: Date.parse('2020-01-01'),
})
export function isProtocolVersionExpired(version: ProtocolVersion) {
const expireDate = ProtocolExpirationDates[version]
if (!expireDate) {
return false
}
const expired = new Date().getTime() > expireDate
return expired
}
export const ProtocolVersionLength = 3
/**
* -1 if a < b
* 0 if a == b
* 1 if a > b
*/
export function compareVersions(a: ProtocolVersion, b: ProtocolVersion): number {
const aNum = Number(a)
const bNum = Number(b)
return aNum - bNum
}
export function leftVersionGreaterThanOrEqualToRight(a: ProtocolVersion, b: ProtocolVersion): boolean {
return compareVersions(a, b) >= 0
}
export function isVersionLessThanOrEqualTo(input: ProtocolVersion, compareTo: ProtocolVersion): boolean {
return compareVersions(input, compareTo) <= 0
}

View File

@@ -0,0 +1,15 @@
import { Result } from '@standardnotes/domain-core'
import { ProtocolVersion, ProtocolVersionLength } from './ProtocolVersion'
export function ProtocolVersionFromEncryptedString(string: string): Result<ProtocolVersion> {
try {
const version = string.substring(0, ProtocolVersionLength) as ProtocolVersion
if (Object.values(ProtocolVersion).includes(version)) {
return Result.ok(version)
}
} catch (error) {
return Result.fail(JSON.stringify(error))
}
return Result.fail(`Invalid encrypted string ${string}`)
}

View File

@@ -1,9 +1,9 @@
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { ProtocolVersion } from '@standardnotes/common'
import { DecryptedItemInterface } from '../../Abstract/Item/Interfaces/DecryptedItem'
import { RootKeyParamsInterface } from '../KeyParams/RootKeyParamsInterface'
import { NamespacedRootKeyInKeychain, RootKeyContentInStorage } from './KeychainTypes'
import { RootKeyContent } from './RootKeyContent'
import { ProtocolVersion } from '../Protocol/ProtocolVersion'
export interface RootKeyInterface extends DecryptedItemInterface<RootKeyContent> {
readonly keyParams: RootKeyParamsInterface

View File

@@ -19,6 +19,7 @@ export type AsymmetricMessageSharedVaultInvite = {
name: string
description?: string
iconString: IconType | EmojiString
fileBytesUsed: number
}
}
}

View File

@@ -1,6 +1,6 @@
import { ProtocolVersion } from '@standardnotes/common'
import { DecryptedItemInterface } from './../../Abstract/Item/Interfaces/DecryptedItem'
import { ItemContent, SpecializedContent } from '../../Abstract/Content/ItemContent'
import { ProtocolVersion } from '../../Local/Protocol/ProtocolVersion'
export interface ItemsKeyContentSpecialized extends SpecializedContent {
version: ProtocolVersion

View File

@@ -1,5 +1,5 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ItemContent, SpecializedContent } from '../../Abstract/Content/ItemContent'
import { ProtocolVersion } from '../../Local/Protocol/ProtocolVersion'
export interface KeySystemItemsKeyContentSpecialized extends SpecializedContent {
version: ProtocolVersion

View File

@@ -1,5 +1,5 @@
import { ProtocolVersion } from '@standardnotes/common'
import { DecryptedItemInterface } from '../../Abstract/Item/Interfaces/DecryptedItem'
import { ProtocolVersion } from '../../Local/Protocol/ProtocolVersion'
import { KeySystemItemsKeyContent } from './KeySystemItemsKeyContent'
export interface KeySystemItemsKeyInterface extends DecryptedItemInterface<KeySystemItemsKeyContent> {

View File

@@ -1,4 +1,3 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ConflictStrategy, DecryptedItem } from '../../Abstract/Item'
import { DecryptedPayloadInterface } from '../../Abstract/Payload'
import { HistoryEntryInterface } from '../../Runtime/History'
@@ -7,6 +6,7 @@ import { KeySystemRootKeyInterface } from './KeySystemRootKeyInterface'
import { KeySystemIdentifier } from './KeySystemIdentifier'
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
import { ContentType } from '@standardnotes/domain-core'
import { ProtocolVersion } from '../../Local/Protocol/ProtocolVersion'
export function isKeySystemRootKey(x: { content_type: string }): x is KeySystemRootKey {
return x.content_type === ContentType.TYPES.KeySystemRootKey

View File

@@ -1,7 +1,7 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ItemContent } from '../../Abstract/Content/ItemContent'
import { KeySystemIdentifier } from './KeySystemIdentifier'
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
import { ProtocolVersion } from '../../Local/Protocol/ProtocolVersion'
export type KeySystemRootKeyContentSpecialized = {
keyParams: KeySystemRootKeyParamsInterface

View File

@@ -1,8 +1,8 @@
import { ProtocolVersion } from '@standardnotes/common'
import { DecryptedItemInterface } from '../../Abstract/Item/Interfaces/DecryptedItem'
import { KeySystemRootKeyContent } from './KeySystemRootKeyContent'
import { KeySystemIdentifier } from './KeySystemIdentifier'
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
import { ProtocolVersion } from '../../Local/Protocol/ProtocolVersion'
export interface KeySystemRootKeyInterface extends DecryptedItemInterface<KeySystemRootKeyContent> {
keyParams: KeySystemRootKeyParamsInterface

View File

@@ -1,4 +1,5 @@
export type VaultListingSharingInfo = {
sharedVaultUuid: string
ownerUserUuid: string
fileBytesUsed: number
}

View File

@@ -15,7 +15,6 @@ export * from './Abstract/Contextual/ComponentCreate'
export * from './Abstract/Contextual/ComponentRetrieved'
export * from './Abstract/Contextual/ContextPayload'
export * from './Abstract/Contextual/FilteredServerItem'
export * from './Abstract/Contextual/TrustedConflictParams'
export * from './Abstract/Contextual/Functions'
export * from './Abstract/Contextual/LocalStorage'
export * from './Abstract/Contextual/OfflineSyncPush'
@@ -23,30 +22,33 @@ export * from './Abstract/Contextual/OfflineSyncSaved'
export * from './Abstract/Contextual/ServerSyncPush'
export * from './Abstract/Contextual/ServerSyncSaved'
export * from './Abstract/Contextual/SessionHistory'
export * from './Abstract/Contextual/TrustedConflictParams'
export * from './Abstract/Item'
export * from './Abstract/Payload'
export * from './Abstract/TransferPayload'
export * from './Api/Subscription/Invitation'
export * from './Api/Subscription/InvitationStatus'
export * from './Api/Subscription/InviteeIdentifierType'
export * from './Api/Subscription/InviterIdentifierType'
export * from './Device/Environment'
export * from './Device/Platform'
export * from './Local/KeyParams/RootKeyParamsInterface'
export * from './Local/KeyParams/KeySystemRootKeyParamsInterface'
export * from './Local/ApplicationIdentifier'
export * from './Local/KeyParams/KeySystemPasswordType'
export * from './Local/KeyParams/KeySystemRootKeyParamsInterface'
export * from './Local/KeyParams/RootKeyParamsInterface'
export * from './Local/Protocol/ProtocolVersion'
export * from './Local/Protocol/ProtocolVersionFromEncryptedString'
export * from './Local/RootKey/KeychainTypes'
export * from './Local/RootKey/RootKeyContent'
export * from './Local/RootKey/RootKeyInterface'
export * from './Local/RootKey/RootKeyWithKeyPairsInterface'
export * from './Runtime/Feature/TypeGuards'
export * from './Runtime/Feature/UIFeature'
export * from './Runtime/Feature/UIFeatureInterface'
export * from './Runtime/AsymmetricMessage/AsymmetricMessagePayload'
export * from './Runtime/AsymmetricMessage/AsymmetricMessagePayloadType'
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSenderKeypairChanged'
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSharedVaultInvite'
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSharedVaultMetadataChanged'
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSharedVaultRootKeyChanged'
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageTrustedContactShare'
export * from './Runtime/Collection/CollectionSort'
export * from './Runtime/Collection/Item/ItemCollection'
export * from './Runtime/Collection/Item/ItemCounter'
@@ -57,6 +59,13 @@ export * from './Runtime/DirtyCounter/DirtyCounter'
export * from './Runtime/Display'
export * from './Runtime/Display/ItemDisplayController'
export * from './Runtime/Display/Types'
export * from './Runtime/Encryption/ContentTypesUsingRootKeyEncryption'
export * from './Runtime/Encryption/ContentTypeUsesKeySystemRootKeyEncryption'
export * from './Runtime/Encryption/ContentTypeUsesRootKeyEncryption'
export * from './Runtime/Encryption/PersistentSignatureData'
export * from './Runtime/Feature/TypeGuards'
export * from './Runtime/Feature/UIFeature'
export * from './Runtime/Feature/UIFeatureInterface'
export * from './Runtime/History'
export * from './Runtime/Index/ItemDelta'
export * from './Runtime/Index/SNIndex'
@@ -69,20 +78,6 @@ export * from './Runtime/Predicate/NotPredicate'
export * from './Runtime/Predicate/Operator'
export * from './Runtime/Predicate/Predicate'
export * from './Runtime/Predicate/Utils'
export * from './Runtime/AsymmetricMessage/AsymmetricMessagePayload'
export * from './Runtime/AsymmetricMessage/AsymmetricMessagePayloadType'
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSenderKeypairChanged'
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSharedVaultInvite'
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSharedVaultMetadataChanged'
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSharedVaultRootKeyChanged'
export * from './Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageTrustedContactShare'
export * from './Runtime/Encryption/PersistentSignatureData'
export * from './Runtime/Encryption/ContentTypeUsesRootKeyEncryption'
export * from './Runtime/Encryption/ContentTypesUsingRootKeyEncryption'
export * from './Runtime/Encryption/ContentTypeUsesKeySystemRootKeyEncryption'
export * from './Syncable/ActionsExtension'
export * from './Syncable/Component'
export * from './Syncable/Editor'
@@ -90,36 +85,32 @@ export * from './Syncable/FeatureRepo'
export * from './Syncable/File'
export * from './Syncable/ItemsKey/ItemsKeyInterface'
export * from './Syncable/ItemsKey/ItemsKeyMutatorInterface'
export * from './Syncable/KeySystemItemsKey/KeySystemItemsKeyContent'
export * from './Syncable/KeySystemItemsKey/KeySystemItemsKeyInterface'
export * from './Syncable/KeySystemItemsKey/KeySystemItemsKeyMutatorInterface'
export * from './Syncable/KeySystemRootKey/KeySystemIdentifier'
export * from './Syncable/KeySystemRootKey/KeySystemRootKey'
export * from './Syncable/KeySystemRootKey/KeySystemRootKeyContent'
export * from './Syncable/KeySystemRootKey/KeySystemRootKeyInterface'
export * from './Syncable/KeySystemRootKey/KeySystemRootKeyMutator'
export * from './Syncable/KeySystemRootKey/KeySystemRootKeyStorageMode'
export * from './Syncable/Note'
export * from './Syncable/SmartView'
export * from './Syncable/Tag'
export * from './Syncable/UserPrefs'
export * from './Syncable/TrustedContact/TrustedContact'
export * from './Syncable/TrustedContact/Mutator/TrustedContactMutator'
export * from './Syncable/TrustedContact/Content/TrustedContactContent'
export * from './Syncable/TrustedContact/TrustedContactInterface'
export * from './Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetInterface'
export * from './Syncable/TrustedContact/Mutator/TrustedContactMutator'
export * from './Syncable/TrustedContact/PublicKeySet/ContactPublicKeySet'
export * from './Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetInterface'
export * from './Syncable/TrustedContact/TrustedContact'
export * from './Syncable/TrustedContact/TrustedContactInterface'
export * from './Syncable/TrustedContact/Types/PortablePublicKeySet'
export * from './Syncable/TrustedContact/Types/PublicKeyTrustStatus'
export * from './Syncable/KeySystemRootKey/KeySystemRootKey'
export * from './Syncable/KeySystemRootKey/KeySystemRootKeyMutator'
export * from './Syncable/KeySystemRootKey/KeySystemRootKeyContent'
export * from './Syncable/KeySystemRootKey/KeySystemRootKeyInterface'
export * from './Syncable/KeySystemRootKey/KeySystemRootKeyStorageMode'
export * from './Syncable/KeySystemItemsKey/KeySystemItemsKeyInterface'
export * from './Syncable/KeySystemItemsKey/KeySystemItemsKeyContent'
export * from './Syncable/KeySystemItemsKey/KeySystemItemsKeyMutatorInterface'
export * from './Syncable/UserPrefs'
export * from './Syncable/VaultListing/VaultListing'
export * from './Syncable/VaultListing/VaultListingContent'
export * from './Syncable/VaultListing/VaultListingInterface'
export * from './Syncable/VaultListing/VaultListingMutator'
export * from './Syncable/VaultListing/VaultListingSharingInfo'
export * from './Utilities/Icon/IconType'
export * from './Utilities/Item/FindItem'
export * from './Utilities/Item/ItemContentsDiffer'
@@ -132,4 +123,3 @@ export * from './Utilities/Payload/PayloadContentsEqual'
export * from './Utilities/Payload/PayloadsByAlternatingUuid'
export * from './Utilities/Payload/PayloadsByDuplicating'
export * from './Utilities/Payload/PayloadSplit'
export * from './Syncable/KeySystemRootKey/KeySystemIdentifier'

View File

@@ -5,6 +5,7 @@ export enum ConflictType {
ContentError = 'content_error',
ReadOnlyError = 'readonly_error',
UuidError = 'uuid_error',
InvalidServerItem = 'invalid_server_item',
SharedVaultSnjsVersionError = 'shared_vault_snjs_version_error',
SharedVaultInsufficientPermissionsError = 'shared_vault_insufficient_permissions_error',

View File

@@ -1,4 +1,8 @@
export interface SharedVaultServerHash {
uuid: string
user_uuid: string
file_upload_bytes_used: number
file_upload_bytes_limit: number
created_at_timestamp: number
updated_at_timestamp: number
}

View File

@@ -18,7 +18,7 @@
"dependencies": {
"@standardnotes/api": "workspace:^",
"@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.24.0",
"@standardnotes/domain-core": "^1.25.0",
"@standardnotes/encryption": "workspace:^",
"@standardnotes/features": "workspace:^",
"@standardnotes/files": "workspace:^",

View File

@@ -22,8 +22,7 @@ import { AsymmetricMessageServiceInterface } from './../AsymmetricMessage/Asymme
import { ImportDataResult } from '../Import/ImportDataResult'
import { ChallengeServiceInterface } from './../Challenge/ChallengeServiceInterface'
import { VaultServiceInterface } from '../Vault/VaultServiceInterface'
import { ApplicationIdentifier } from '@standardnotes/common'
import { BackupFile, Environment, Platform, PrefKey, PrefValue } from '@standardnotes/models'
import { BackupFile, Environment, Platform, PrefKey, PrefValue, ApplicationIdentifier } from '@standardnotes/models'
import { BackupServiceInterface, FilesClientInterface } from '@standardnotes/files'
import { AlertService } from '../Alert/AlertService'

View File

@@ -1,5 +1,4 @@
import { Environment, Platform } from '@standardnotes/models'
import { ApplicationIdentifier } from '@standardnotes/common'
import { Environment, Platform, ApplicationIdentifier } from '@standardnotes/models'
import { AlertService, DeviceInterface } from '@standardnotes/services'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'

View File

@@ -38,6 +38,7 @@ describe('ProcessAcceptedVaultInvite', () => {
metadata: {
name: 'test-name',
iconString: 'safe-square',
fileBytesUsed: 0,
},
trustedContacts: [],
},

View File

@@ -36,6 +36,7 @@ export class ProcessAcceptedVaultInvite {
sharing: {
sharedVaultUuid: sharedVaultUuid,
ownerUserUuid: ownerUuid,
fileBytesUsed: metadata.fileBytesUsed,
},
}

View File

@@ -1,5 +1,5 @@
import { ApplicationIdentifier } from '@standardnotes/common'
import {
ApplicationIdentifier,
FullyFormedTransferPayload,
TransferPayload,
NamespacedRootKeyInKeychain,

View File

@@ -3,10 +3,10 @@ import {
ContactPublicKeySetInterface,
PublicKeyTrustStatus,
TrustedContactInterface,
ProtocolVersion,
} from '@standardnotes/models'
import { DecryptMessage } from './DecryptMessage'
import { OperatorInterface, EncryptionOperatorsInterface } from '@standardnotes/encryption'
import { ProtocolVersion } from '@standardnotes/common'
function createMockPublicKeySetChain(): ContactPublicKeySetInterface {
const nMinusOne = new ContactPublicKeySet({

View File

@@ -1,5 +1,4 @@
import { FindDefaultItemsKey } from './../Encryption/UseCase/ItemsKey/FindDefaultItemsKey'
import { ProtocolVersion } from '@standardnotes/common'
import {
DecryptedParameters,
ErrorDecryptingParameters,
@@ -23,6 +22,7 @@ import {
PayloadEmitSource,
KeySystemItemsKeyInterface,
SureFindPayload,
ProtocolVersion,
ContentTypeUsesRootKeyEncryption,
} from '@standardnotes/models'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'

View File

@@ -1,7 +1,6 @@
import { UserRegistrationResponseBody } from '@standardnotes/api'
import { ProtocolVersion } from '@standardnotes/common'
import { SNRootKey } from '@standardnotes/encryption'
import { RootKeyInterface } from '@standardnotes/models'
import { RootKeyInterface, ProtocolVersion } from '@standardnotes/models'
import {
SessionBody,
SignInResponse,

View File

@@ -14,9 +14,11 @@ import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
import { ContactPublicKeySetInterface, TrustedContactInterface } from '@standardnotes/models'
import { SyncLocalVaultsWithRemoteSharedVaults } from './UseCase/SyncLocalVaultsWithRemoteSharedVaults'
describe('SharedVaultService', () => {
let service: SharedVaultService
let syncLocalVaultsWithRemoteSharedVaults: SyncLocalVaultsWithRemoteSharedVaults
beforeEach(() => {
const sync = {} as jest.Mocked<SyncServiceInterface>
@@ -37,12 +39,16 @@ describe('SharedVaultService', () => {
const deleteSharedVaultUseCase = {} as jest.Mocked<DeleteSharedVault>
const discardItemsLocally = {} as jest.Mocked<DiscardItemsLocally>
syncLocalVaultsWithRemoteSharedVaults = {} as jest.Mocked<SyncLocalVaultsWithRemoteSharedVaults>
syncLocalVaultsWithRemoteSharedVaults.execute = jest.fn()
const eventBus = {} as jest.Mocked<InternalEventBusInterface>
eventBus.addEventHandler = jest.fn()
service = new SharedVaultService(
items,
session,
syncLocalVaultsWithRemoteSharedVaults,
getVault,
getOwnedVaults,
createSharedVaultUseCase,

View File

@@ -32,6 +32,7 @@ import { ContentType, NotificationType, Uuid } from '@standardnotes/domain-core'
import { HandleKeyPairChange } from '../Contacts/UseCase/HandleKeyPairChange'
import { FindContact } from '../Contacts/UseCase/FindContact'
import { GetOwnedSharedVaults } from './UseCase/GetOwnedSharedVaults'
import { SyncLocalVaultsWithRemoteSharedVaults } from './UseCase/SyncLocalVaultsWithRemoteSharedVaults'
export class SharedVaultService
extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload>
@@ -40,6 +41,7 @@ export class SharedVaultService
constructor(
private items: ItemManagerInterface,
private session: SessionsClientInterface,
private _syncLocalVaultsWithRemoteSharedVaults: SyncLocalVaultsWithRemoteSharedVaults,
private _getVault: GetVault,
private _getOwnedSharedVaults: GetOwnedSharedVaults,
private _createSharedVault: CreateSharedVault,
@@ -67,6 +69,7 @@ export class SharedVaultService
super.deinit()
;(this.items as unknown) = undefined
;(this.session as unknown) = undefined
;(this._syncLocalVaultsWithRemoteSharedVaults as unknown) = undefined
;(this._getVault as unknown) = undefined
;(this._createSharedVault as unknown) = undefined
;(this._handleKeyPairChange as unknown) = undefined
@@ -88,7 +91,7 @@ export class SharedVaultService
break
}
case NotificationServiceEvent.NotificationReceived:
await this.handleUserEvent(event.payload as NotificationServiceEventPayload)
await this.handleNotification(event.payload as NotificationServiceEventPayload)
break
case SyncEvent.ReceivedRemoteSharedVaults:
void this.notifyEventSync(SharedVaultServiceEvent.SharedVaultStatusChanged)
@@ -96,7 +99,7 @@ export class SharedVaultService
}
}
private async handleUserEvent(event: NotificationServiceEventPayload): Promise<void> {
private async handleNotification(event: NotificationServiceEventPayload): Promise<void> {
switch (event.eventPayload.props.type.value) {
case NotificationType.TYPES.RemovedFromSharedVault: {
const vault = this._getVault.execute<SharedVaultListingInterface>({
@@ -114,6 +117,19 @@ export class SharedVaultService
}
break
}
case NotificationType.TYPES.SharedVaultFileRemoved:
case NotificationType.TYPES.SharedVaultFileUploaded: {
const vaultOrError = this._getVault.execute<SharedVaultListingInterface>({
sharedVaultUuid: event.eventPayload.props.sharedVaultUuid.value,
})
if (!vaultOrError.isFailed()) {
await this._syncLocalVaultsWithRemoteSharedVaults.execute([vaultOrError.getValue()])
void this.notifyEventSync(SharedVaultServiceEvent.SharedVaultStatusChanged)
}
break
}
}
}

View File

@@ -2,6 +2,7 @@ import { KeySystemIdentifier } from '@standardnotes/models'
export enum SharedVaultServiceEvent {
SharedVaultStatusChanged = 'SharedVaultStatusChanged',
SharedVaultFileStorageUsageChanged = 'SharedVaultFileStorageUsageChanged',
}
export type SharedVaultServiceEventPayload = {

View File

@@ -31,6 +31,7 @@ export class ConvertToSharedVault {
mutator.sharing = {
sharedVaultUuid: serverVaultHash.uuid,
ownerUserUuid: serverVaultHash.user_uuid,
fileBytesUsed: serverVaultHash.file_upload_bytes_used,
}
},
)

View File

@@ -50,6 +50,7 @@ export class CreateSharedVault {
mutator.sharing = {
sharedVaultUuid: serverVaultHash.uuid,
ownerUserUuid: serverVaultHash.user_uuid,
fileBytesUsed: serverVaultHash.file_upload_bytes_used,
}
},
)

View File

@@ -0,0 +1,80 @@
import { MutatorClientInterface, SharedVaultServerInterface, VaultListingInterface } from '@standardnotes/snjs'
import { SyncLocalVaultsWithRemoteSharedVaults } from './SyncLocalVaultsWithRemoteSharedVaults'
describe('SyncLocalVaultsWithRemoteSharedVaults', () => {
let sharedVaultServer: SharedVaultServerInterface
let mutator: MutatorClientInterface
const createUseCase = () => new SyncLocalVaultsWithRemoteSharedVaults(sharedVaultServer, mutator)
beforeEach(() => {
sharedVaultServer = {} as jest.Mocked<SharedVaultServerInterface>
sharedVaultServer.getSharedVaults = jest.fn().mockResolvedValue({ data: { sharedVaults: [{
uuid: '1-2-3',
user_uuid: '2-3-4',
file_upload_bytes_used: 123,
file_upload_bytes_limit: 10000000,
created_at_timestamp: 123,
updated_at_timestamp: 123,
}] } })
mutator = {} as jest.Mocked<MutatorClientInterface>
mutator.changeItem = jest.fn()
})
it('should sync local vaults with remote shared vaults to update file storage usage', async () => {
const localVaults = [{
uuid: '1-2-3',
name: 'Vault',
isSharedVaultListing: () => true,
sharing: {
sharedVaultUuid: '1-2-3',
ownerUserUuid: '2-3-4',
fileBytesUsed: 0,
},
} as jest.Mocked<VaultListingInterface>]
const useCase = createUseCase()
await useCase.execute(localVaults)
expect(mutator.changeItem).toHaveBeenCalledWith(localVaults[0], expect.any(Function))
})
it('should fail if shared vault server returns error', async () => {
sharedVaultServer.getSharedVaults = jest.fn().mockResolvedValue({ data: { error: { message: 'test-error' } } })
const localVaults = [{
uuid: '1-2-3',
name: 'Vault',
isSharedVaultListing: () => true,
sharing: {
sharedVaultUuid: '1-2-3',
ownerUserUuid: '2-3-4',
fileBytesUsed: 0,
},
} as jest.Mocked<VaultListingInterface>]
const useCase = createUseCase()
const result = await useCase.execute(localVaults)
expect(result.isFailed()).toBe(true)
})
it('should not sync local vaults with remote shared vaults if local vault is not shared', async () => {
const localVaults = [{
uuid: '1-2-3',
name: 'Vault',
isSharedVaultListing: () => false,
sharing: {
sharedVaultUuid: '1-2-3',
ownerUserUuid: '2-3-4',
fileBytesUsed: 0,
},
} as jest.Mocked<VaultListingInterface>]
const useCase = createUseCase()
await useCase.execute(localVaults)
expect(mutator.changeItem).not.toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,40 @@
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { SharedVaultServerInterface } from '@standardnotes/api'
import { HttpError, isErrorResponse } from '@standardnotes/responses'
import { SharedVaultListingInterface, VaultListingInterface, VaultListingMutator } from '@standardnotes/models'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
export class SyncLocalVaultsWithRemoteSharedVaults implements UseCaseInterface<void> {
constructor(
private sharedVaultServer: SharedVaultServerInterface,
private mutator: MutatorClientInterface,
) {}
async execute(localVaults: VaultListingInterface[]): Promise<Result<void>> {
const remoteVaultsResponse = await this.sharedVaultServer.getSharedVaults()
if (isErrorResponse(remoteVaultsResponse)) {
return Result.fail((remoteVaultsResponse.data.error as HttpError).message as string)
}
const remoteVaults = remoteVaultsResponse.data.sharedVaults
for (const localVault of localVaults) {
if (!localVault.isSharedVaultListing()) {
continue
}
const remoteVault = remoteVaults.find((vault) => vault.uuid === localVault.sharing.sharedVaultUuid)
if (remoteVault) {
await this.mutator.changeItem<VaultListingMutator, SharedVaultListingInterface>(localVault, (mutator) => {
/* istanbul ignore next */
mutator.sharing = {
sharedVaultUuid: remoteVault.uuid,
ownerUserUuid: remoteVault.user_uuid,
fileBytesUsed: remoteVault.file_upload_bytes_used,
}
})
}
}
return Result.ok()
}
}

View File

@@ -1,4 +1,4 @@
import { ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '@standardnotes/models'
export const API_MESSAGE_GENERIC_INVALID_LOGIN = 'A server error occurred while trying to sign in. Please try again.'
export const API_MESSAGE_GENERIC_REGISTRATION_FAIL =

View File

@@ -123,6 +123,7 @@ export class InviteToVault implements UseCaseInterface<SharedVaultInviteServerHa
name: params.sharedVault.name,
description: params.sharedVault.description,
iconString: params.sharedVault.iconString,
fileBytesUsed: params.sharedVault.sharing.fileBytesUsed,
},
},
},

View File

@@ -161,6 +161,7 @@ export * from './SharedVaults/UseCase/NotifyVaultUsersOfKeyRotation'
export * from './SharedVaults/UseCase/SendVaultDataChangedMessage'
export * from './SharedVaults/UseCase/SendVaultKeyChangedMessage'
export * from './SharedVaults/UseCase/ShareContactWithVault'
export * from './SharedVaults/UseCase/SyncLocalVaultsWithRemoteSharedVaults'
export * from './Singleton/SingletonManagerInterface'
export * from './Status/StatusService'
export * from './Status/StatusServiceInterface'

View File

@@ -140,6 +140,7 @@ import {
IsApplicationUsingThirdPartyHost,
CreateDecryptedBackupFile,
CreateEncryptedBackupFile,
SyncLocalVaultsWithRemoteSharedVaults,
} from '@standardnotes/services'
import { ItemManager } from '../../Services/Items/ItemManager'
import { PayloadManager } from '../../Services/Payloads/PayloadManager'
@@ -399,6 +400,13 @@ export class Dependencies {
return new GetVaults(this.get<ItemManager>(TYPES.ItemManager))
})
this.factory.set(TYPES.SyncLocalVaultsWithRemoteSharedVaults, () => {
return new SyncLocalVaultsWithRemoteSharedVaults(
this.get<SharedVaultServer>(TYPES.SharedVaultServer),
this.get<MutatorService>(TYPES.MutatorService),
)
})
this.factory.set(TYPES.ChangeAndSaveItem, () => {
return new ChangeAndSaveItem(
this.get<ItemManager>(TYPES.ItemManager),
@@ -893,6 +901,7 @@ export class Dependencies {
return new SharedVaultService(
this.get<ItemManager>(TYPES.ItemManager),
this.get<SessionManager>(TYPES.SessionManager),
this.get<SyncLocalVaultsWithRemoteSharedVaults>(TYPES.SyncLocalVaultsWithRemoteSharedVaults),
this.get<GetVault>(TYPES.GetVault),
this.get<GetOwnedSharedVaults>(TYPES.GetOwnedSharedVaults),
this.get<CreateSharedVault>(TYPES.CreateSharedVault),

View File

@@ -98,6 +98,7 @@ export const TYPES = {
ValidateItemSigner: Symbol.for('ValidateItemSigner'),
GetVault: Symbol.for('GetVault'),
GetVaults: Symbol.for('GetVaults'),
SyncLocalVaultsWithRemoteSharedVaults: Symbol.for('SyncLocalVaultsWithRemoteSharedVaults'),
GetSharedVaults: Symbol.for('GetSharedVaults'),
GetOwnedSharedVaults: Symbol.for('GetOwnedSharedVaults'),
ChangeVaultKeyOptions: Symbol.for('ChangeVaultKeyOptions'),

View File

@@ -1,4 +1,4 @@
import { ApplicationIdentifier } from '@standardnotes/common'
import { ApplicationIdentifier } from '@standardnotes/models'
export type ApplicationDescriptor = {
identifier: ApplicationIdentifier

View File

@@ -5,10 +5,10 @@ jest.mock('@standardnotes/models', () => {
return {
...original,
isRemotePayloadAllowed: jest.fn(),
checkRemotePayloadAllowed: jest.fn(),
}
})
const isRemotePayloadAllowed = require('@standardnotes/models').isRemotePayloadAllowed
const checkRemotePayloadAllowed = require('@standardnotes/models').checkRemotePayloadAllowed
import { Revision } from '../../Revision/Revision'
@@ -44,7 +44,7 @@ describe('GetRevision', () => {
encryptedPayload.copy = jest.fn().mockReturnValue(encryptedPayload)
encryptionService.decryptSplitSingle = jest.fn().mockReturnValue(encryptedPayload)
isRemotePayloadAllowed.mockImplementation(() => true)
checkRemotePayloadAllowed.mockImplementation(() => ({ allowed: {} }))
})
it('should get revision', async () => {
@@ -145,7 +145,7 @@ describe('GetRevision', () => {
})
it('should fail if remote payload is not allowed', async () => {
isRemotePayloadAllowed.mockImplementation(() => false)
checkRemotePayloadAllowed.mockImplementation(() => ({ disallowed: {} }))
const useCase = createUseCase()

View File

@@ -5,7 +5,7 @@ import {
EncryptedPayload,
HistoryEntry,
isErrorDecryptingPayload,
isRemotePayloadAllowed,
checkRemotePayloadAllowed,
NoteContent,
PayloadTimestampDefaults,
} from '@standardnotes/models'
@@ -71,7 +71,8 @@ export class GetRevision implements UseCaseInterface<HistoryEntry> {
uuid: sourceItemUuid || revision.item_uuid,
})
if (!isRemotePayloadAllowed(payload as ServerItemResponse)) {
const remotePayloadAllowedResult = checkRemotePayloadAllowed(payload as ServerItemResponse)
if (remotePayloadAllowedResult.disallowed !== undefined) {
return Result.fail(`Remote payload is disallowed: ${JSON.stringify(payload)}`)
}

View File

@@ -1,5 +1,5 @@
import { BackupServiceInterface } from '@standardnotes/files'
import { Environment, Platform } from '@standardnotes/models'
import { Environment, Platform, ApplicationIdentifier } from '@standardnotes/models'
import {
DeviceInterface,
InternalEventBusInterface,
@@ -8,7 +8,6 @@ import {
PreferenceServiceInterface,
} from '@standardnotes/services'
import { SessionManager } from '../Services/Session/SessionManager'
import { ApplicationIdentifier } from '@standardnotes/common'
import { ItemManager } from '@Lib/Services/Items/ItemManager'
import { ChallengeService, SingletonManager, FeaturesService, DiskStorageService } from '@Lib/Services'
import { LegacySession, MapperInterface } from '@standardnotes/domain-core'

View File

@@ -1,5 +1,4 @@
import { ApplicationIdentifier } from '@standardnotes/common'
import { Environment } from '@standardnotes/models'
import { Environment, ApplicationIdentifier } from '@standardnotes/models'
import { compareSemVersions, isRightVersionGreaterThanLeft } from '@Lib/Version'
import { DeviceInterface } from '@standardnotes/services'
import { StorageReader } from './Reader'

View File

@@ -1,5 +1,4 @@
import { Environment } from '@standardnotes/models'
import { ApplicationIdentifier } from '@standardnotes/common'
import { Environment, ApplicationIdentifier } from '@standardnotes/models'
import { DeviceInterface } from '@standardnotes/services'
/**

View File

@@ -10,6 +10,8 @@ import {
NotificationServerHash,
AsymmetricMessageServerHash,
getErrorFromErrorResponse,
ConflictType,
ServerItemResponse,
} from '@standardnotes/responses'
import {
FilterDisallowedRemotePayloadsAndMap,
@@ -44,15 +46,21 @@ export class ServerSyncResponse {
const legacyConflicts = this.successResponseData?.unsaved || []
this.rawConflictObjects = conflicts.concat(legacyConflicts)
this.savedPayloads = FilterDisallowedRemotePayloadsAndMap(this.successResponseData?.saved_items || []).map(
(rawItem) => {
const disallowedPayloads = []
const savedItemsFilteringResult = FilterDisallowedRemotePayloadsAndMap(this.successResponseData?.saved_items || [])
this.savedPayloads = savedItemsFilteringResult.filtered.map((rawItem) => {
return CreateServerSyncSavedPayload(rawItem)
},
})
disallowedPayloads.push(...savedItemsFilteringResult.disallowed)
const retrievedItemsFilteringResult = FilterDisallowedRemotePayloadsAndMap(
this.successResponseData?.retrieved_items || [],
)
this.retrievedPayloads = retrievedItemsFilteringResult.filtered
disallowedPayloads.push(...retrievedItemsFilteringResult.disallowed)
this.retrievedPayloads = FilterDisallowedRemotePayloadsAndMap(this.successResponseData?.retrieved_items || [])
this.conflicts = this.filterConflicts()
this.conflicts = this.filterConflictsAndDisallowedPayloads(disallowedPayloads)
this.vaults = this.successResponseData?.shared_vaults || []
@@ -65,20 +73,48 @@ export class ServerSyncResponse {
deepFreeze(this)
}
private filterConflicts(): TrustedServerConflictMap {
private filterConflictsAndDisallowedPayloads(disallowedPayloads: ServerItemResponse[]): TrustedServerConflictMap {
const conflicts = this.rawConflictObjects
const trustedConflicts: TrustedServerConflictMap = {}
trustedConflicts[ConflictType.InvalidServerItem] = []
const invalidServerConflictsArray = trustedConflicts[ConflictType.InvalidServerItem]
for (const payload of disallowedPayloads) {
invalidServerConflictsArray.push(<TrustedConflictParams>{
type: ConflictType.InvalidServerItem,
server_item: payload,
})
}
for (const conflict of conflicts) {
let serverItem: FilteredServerItem | undefined
let unsavedItem: FilteredServerItem | undefined
if (conflict.unsaved_item) {
unsavedItem = FilterDisallowedRemotePayloadsAndMap([conflict.unsaved_item])[0]
const unsavedItemFilteringResult = FilterDisallowedRemotePayloadsAndMap([conflict.unsaved_item])
if (unsavedItemFilteringResult.filtered.length === 1) {
unsavedItem = unsavedItemFilteringResult.filtered[0]
}
if (unsavedItemFilteringResult.disallowed.length === 1) {
invalidServerConflictsArray.push(<TrustedConflictParams>{
type: ConflictType.InvalidServerItem,
unsaved_item: unsavedItemFilteringResult.disallowed[0],
})
}
}
if (conflict.server_item) {
serverItem = FilterDisallowedRemotePayloadsAndMap([conflict.server_item])[0]
const serverItemFilteringResult = FilterDisallowedRemotePayloadsAndMap([conflict.server_item])
if (serverItemFilteringResult.filtered.length === 1) {
serverItem = serverItemFilteringResult.filtered[0]
}
if (serverItemFilteringResult.disallowed.length === 1) {
invalidServerConflictsArray.push(<TrustedConflictParams>{
type: ConflictType.InvalidServerItem,
server_item: serverItemFilteringResult.disallowed[0],
})
}
}
if (!trustedConflicts[conflict.type]) {

View File

@@ -92,6 +92,7 @@ export class ServerSyncResponseResolver {
...this.getConflictsForType(ConflictType.SharedVaultInsufficientPermissionsError),
...this.getConflictsForType(ConflictType.SharedVaultNotMemberError),
...this.getConflictsForType(ConflictType.SharedVaultInvalidState),
...this.getConflictsForType(ConflictType.InvalidServerItem),
]
const delta = new DeltaRemoteRejected(this.baseCollection, conflicts)

View File

@@ -1,3 +1,4 @@
import { Result } from '@standardnotes/domain-core'
import {
EncryptedPayloadInterface,
DeletedPayloadInterface,
@@ -10,11 +11,13 @@ import {
export function CreatePayloadFromRawServerItem(
rawItem: FilteredServerItem,
source: PayloadSource,
): EncryptedPayloadInterface | DeletedPayloadInterface {
): Result<EncryptedPayloadInterface | DeletedPayloadInterface> {
if (rawItem.deleted) {
return new DeletedPayload({ ...rawItem, content: undefined, deleted: true }, source)
return Result.ok(new DeletedPayload({ ...rawItem, content: undefined, deleted: true }, source))
} else if (rawItem.content != undefined) {
return new EncryptedPayload(
try {
return Result.ok(
new EncryptedPayload(
{
...rawItem,
items_key_id: rawItem.items_key_id,
@@ -24,8 +27,11 @@ export function CreatePayloadFromRawServerItem(
waitingForKey: false,
},
source,
),
)
} else {
throw Error('Unhandled case in createPayloadFromRawItem')
} catch (error) {
return Result.fail(JSON.stringify(error))
}
}
return Result.fail('Unhandled case in createPayloadFromRawItem')
}

View File

@@ -1110,7 +1110,12 @@ export class SyncService
items: FilteredServerItem[],
source: PayloadSource,
): Promise<FullyFormedPayloadInterface[]> {
const payloads = items.map((i) => CreatePayloadFromRawServerItem(i, source))
const payloads = items
.map((i) => {
const result = CreatePayloadFromRawServerItem(i, source)
return result.isFailed() ? undefined : result.getValue()
})
.filter(isNotUndefined)
const { encrypted, deleted } = CreateNonDecryptedPayloadSplit(payloads)
@@ -1408,9 +1413,16 @@ export class SyncService
return
}
const receivedPayloads = FilterDisallowedRemotePayloadsAndMap(rawPayloads).map((rawPayload) => {
return CreatePayloadFromRawServerItem(rawPayload, PayloadSource.RemoteRetrieved)
const rawPayloadsFilteringResult = FilterDisallowedRemotePayloadsAndMap(rawPayloads)
const receivedPayloads = rawPayloadsFilteringResult.filtered
.map((rawPayload) => {
const result = CreatePayloadFromRawServerItem(rawPayload, PayloadSource.RemoteRetrieved)
if (result.isFailed()) {
return undefined
}
return result.getValue()
})
.filter(isNotUndefined)
const payloadSplit = CreateNonDecryptedPayloadSplit(receivedPayloads)

View File

@@ -8,7 +8,7 @@ export * from './Migrations'
export * from './Services'
export * from './Types'
export * from './Version'
export * from '@standardnotes/common'
export { KeyParamsOrigination } from '@standardnotes/common'
export * from '@standardnotes/domain-core'
export * from '@standardnotes/api'
export * from '@standardnotes/encryption'

View File

@@ -1,4 +1,3 @@
import FakeWebCrypto from './fake_web_crypto.js'
import { AppContext } from './AppContext.js'
import { VaultsContext } from './VaultsContext.js'
@@ -303,8 +302,10 @@ export function tomorrow() {
return new Date(new Date().setDate(new Date().getDate() + 1))
}
export async function sleep(seconds, reason) {
export async function sleep(seconds, reason, dontLog = false) {
if (!dontLog) {
console.log('[Factory] Sleeping for reason', reason)
}
return Utils.sleep(seconds)
}

View File

@@ -74,7 +74,7 @@ describe('payload', () => {
content_type: ContentType.TYPES.Note,
content: '000:somebase64string',
}),
'Unrecognized protocol version 000',
'EncryptedPayload constructor versionResult is failed',
)
})

View File

@@ -113,7 +113,7 @@ describe('online syncing', function () {
for (let i = 0; i < syncCount; i++) {
application.sync.sync(syncOptions)
await Factory.sleep(0.01)
await Factory.sleep(0.01, undefined, true)
}
await promise
expect(promise).to.be.fulfilled
@@ -958,6 +958,11 @@ describe('online syncing', function () {
},
})
context.anticipateConsoleError(
'Error decrypting payload',
'The encrypted payload above is not a valid encrypted payload.',
)
await application.sync.handleSuccessServerResponse({ payloadsSavedOrSaving: [], options: {} }, response)
expect(application.payloads.findOne(invalidPayload.uuid)).to.not.be.ok

View File

@@ -37,7 +37,7 @@
"@babel/preset-env": "*",
"@standardnotes/api": "workspace:*",
"@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.24.0",
"@standardnotes/domain-core": "^1.25.0",
"@standardnotes/domain-events": "^2.108.1",
"@standardnotes/encryption": "workspace:*",
"@standardnotes/features": "workspace:*",

View File

@@ -16,7 +16,7 @@
},
"dependencies": {
"@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.24.0",
"@standardnotes/domain-core": "^1.25.0",
"@standardnotes/features": "workspace:^",
"@standardnotes/filepicker": "workspace:^",
"@standardnotes/models": "workspace:^",

View File

@@ -77,6 +77,14 @@ const Vaults = () => {
})
}, [application.sharedVaults, updateAllData])
useEffect(() => {
return application.sharedVaults.addEventObserver((event) => {
if (event === SharedVaultServiceEvent.SharedVaultFileStorageUsageChanged) {
void updateAllData()
}
})
}, [application.sharedVaults, updateAllData])
useEffect(() => {
return application.vaultUsers.addEventObserver((event) => {
if (event === VaultUserServiceEvent.UsersChanged) {

View File

@@ -1,12 +1,13 @@
import { formatSizeToReadableString } from '@standardnotes/filepicker'
import { ButtonType, VaultListingInterface, isClientDisplayableError } from '@standardnotes/snjs'
import { useCallback, useState } from 'react'
import { useApplication } from '@/Components/ApplicationProvider'
import Button from '@/Components/Button/Button'
import Icon from '@/Components/Icon/Icon'
import ModalOverlay from '@/Components/Modal/ModalOverlay'
import { ButtonType, VaultListingInterface, isClientDisplayableError } from '@standardnotes/snjs'
import { useCallback, useState } from 'react'
import ContactInviteModal from '../Invites/ContactInviteModal'
import EditVaultModal from './VaultModal/EditVaultModal'
type Props = {
vault: VaultListingInterface
}
@@ -123,6 +124,10 @@ const VaultItem = ({ vault }: Props) => {
)}
<span className="mr-auto overflow-hidden text-ellipsis text-sm">Vault ID: {vault.systemIdentifier}</span>
<span className="mr-auto overflow-hidden text-ellipsis text-sm">
File storage used: {formatSizeToReadableString(vault.sharing?.fileBytesUsed ?? 0)}
</span>
<div className="mt-2 flex w-full">
<Button label="Edit" className="mr-3 text-xs" onClick={openEditModal} />
{isAdmin && <Button colorStyle="danger" label="Delete" className="mr-3 text-xs" onClick={deleteVault} />}

View File

@@ -18,8 +18,8 @@ import {
SyncServiceInterface,
User,
UserServiceInterface,
UserRequestType,
} from '@standardnotes/snjs'
import { UserRequestType } from '@standardnotes/common'
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
import { PreferencesController } from '@/Controllers/PreferencesController'

View File

@@ -4140,7 +4140,7 @@ __metadata:
resolution: "@standardnotes/api@workspace:packages/api"
dependencies:
"@standardnotes/common": ^1.50.0
"@standardnotes/domain-core": ^1.24.0
"@standardnotes/domain-core": ^1.25.0
"@standardnotes/models": "workspace:*"
"@standardnotes/responses": "workspace:*"
"@standardnotes/utils": "workspace:*"
@@ -4290,7 +4290,7 @@ __metadata:
"@babel/core": "*"
"@babel/preset-env": "*"
"@electron/remote": ^2.0.9
"@standardnotes/domain-core": ^1.24.0
"@standardnotes/domain-core": ^1.25.0
"@standardnotes/electron-clear-data": 1.1.1
"@standardnotes/web": "workspace:*"
"@types/fs-extra": ^11.0.1
@@ -4333,7 +4333,7 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/domain-core@npm:^1.24.0, @standardnotes/domain-core@npm:^1.24.1":
"@standardnotes/domain-core@npm:^1.24.1":
version: 1.24.1
resolution: "@standardnotes/domain-core@npm:1.24.1"
dependencies:
@@ -4342,6 +4342,15 @@ __metadata:
languageName: node
linkType: hard
"@standardnotes/domain-core@npm:^1.25.0":
version: 1.25.0
resolution: "@standardnotes/domain-core@npm:1.25.0"
dependencies:
uuid: ^9.0.0
checksum: f99196f6209a05a50e84371d3a59cf870937a5589f4ba4caab0f2eb17c2cfa733142b18ca8788016c81ca609d1f5a0461e51bcf159a67d631e249a04a5568244
languageName: node
linkType: hard
"@standardnotes/domain-events@npm:^2.108.1":
version: 2.113.1
resolution: "@standardnotes/domain-events@npm:2.113.1"
@@ -4385,7 +4394,7 @@ __metadata:
dependencies:
"@standardnotes/common": ^1.50.0
"@standardnotes/config": 2.4.3
"@standardnotes/domain-core": ^1.24.0
"@standardnotes/domain-core": ^1.25.0
"@standardnotes/models": "workspace:*"
"@standardnotes/responses": "workspace:*"
"@standardnotes/sncrypto-common": "workspace:*"
@@ -4420,7 +4429,7 @@ __metadata:
resolution: "@standardnotes/features@workspace:packages/features"
dependencies:
"@standardnotes/common": ^1.50.0
"@standardnotes/domain-core": ^1.24.0
"@standardnotes/domain-core": ^1.25.0
"@types/jest": ^29.2.3
"@typescript-eslint/eslint-plugin": "*"
eslint: "*"
@@ -4626,7 +4635,7 @@ __metadata:
resolution: "@standardnotes/models@workspace:packages/models"
dependencies:
"@standardnotes/common": ^1.50.0
"@standardnotes/domain-core": ^1.24.0
"@standardnotes/domain-core": ^1.25.0
"@standardnotes/features": "workspace:*"
"@standardnotes/responses": "workspace:*"
"@standardnotes/sncrypto-common": "workspace:^"
@@ -4712,7 +4721,7 @@ __metadata:
dependencies:
"@standardnotes/api": "workspace:^"
"@standardnotes/common": ^1.50.0
"@standardnotes/domain-core": ^1.24.0
"@standardnotes/domain-core": ^1.25.0
"@standardnotes/encryption": "workspace:^"
"@standardnotes/features": "workspace:^"
"@standardnotes/files": "workspace:^"
@@ -4811,7 +4820,7 @@ __metadata:
"@babel/preset-env": "*"
"@standardnotes/api": "workspace:*"
"@standardnotes/common": ^1.50.0
"@standardnotes/domain-core": ^1.24.0
"@standardnotes/domain-core": ^1.25.0
"@standardnotes/domain-events": ^2.108.1
"@standardnotes/encryption": "workspace:*"
"@standardnotes/features": "workspace:*"
@@ -4936,7 +4945,7 @@ __metadata:
resolution: "@standardnotes/ui-services@workspace:packages/ui-services"
dependencies:
"@standardnotes/common": ^1.50.0
"@standardnotes/domain-core": ^1.24.0
"@standardnotes/domain-core": ^1.25.0
"@standardnotes/features": "workspace:^"
"@standardnotes/filepicker": "workspace:^"
"@standardnotes/models": "workspace:^"