Compare commits

...
Author SHA1 Message Date
StandardNotes CI f2d089ab24 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
2023-07-24 11:04:03 +00:00
Aman Harwara a91ab4abbf chore: dont show ios keyboard button when editing link in super 2023-07-24 16:04:37 +05:30
Aman Harwara 0af41cbcad chore: fix link editor dismiss 2023-07-24 16:00:45 +05:30
Mo 1adb21a06e chore: update env.sample [no ci] 2023-07-24 05:00:10 -05:00
StandardNotes CI 412b192b85 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
2023-07-24 09:28:48 +00:00
Aman Harwara 8ad9b8ae2a chore: combine super link editor with mobile toolbar 2023-07-24 14:27:16 +05:30
StandardNotes CI c52d8e7309 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
2023-07-24 07:14:21 +00:00
Aman Harwara 406abb4929 chore: fix links container toggle logic 2023-07-24 12:07:47 +05:30
Aman Harwara 814a156e1b chore: make linked items container collapsed by default 2023-07-24 12:07:44 +05:30
StandardNotes CI e67f694c85 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].4
2023-07-23 21:46:48 +00:00
Mo a77535456c refactor: application dependency management (#2363) 2023-07-23 15:54:31 -05:00
StandardNotes CI e698b1c990 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].3
2023-07-21 10:54:22 +00:00
Aman Harwara ab2e88a710 fix: Shared image links are now correctly handled on iOS 2023-07-21 15:53:36 +05:30
StandardNotes CI 68db05581b chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].2
2023-07-20 15:26:51 +00:00
Aman Harwara 7acf23033d chore: fix blank screen issue on iOS web app 2023-07-20 20:23:31 +05:30
StandardNotes CI 78b87672de chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2023-07-19 14:36:29 +00:00
Aman Harwara 2b0df30ca2 fix: Fixed issue where trying to create new folder on mobile would close navigation pane 2023-07-19 19:35:10 +05:30
369 changed files with 8110 additions and 5152 deletions
-3
View File
@@ -6,6 +6,3 @@ DEFAULT_SYNC_SERVER=https://api.standardnotes.com
DASHBOARD_URL=http://standardnotes.com/dashboard
PLANS_URL=https://standardnotes.com/plans
PURCHASE_URL=https://standardnotes.com/purchase
# Used by Rails internals and not Standard Notes related
SECRET_KEY_BASE=<enter a secret string here>
Binary file not shown.
Binary file not shown.
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
**Note:** Version bump only for package @standardnotes/api
## [1.26.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-17)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.26.29",
"version": "1.26.30",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -13,4 +13,5 @@ export const SharedVaultInvitesPaths = {
`/v1/shared-vaults/${sharedVaultUuid}/invites/${inviteUuid}`,
deleteAllSharedVaultInvites: (sharedVaultUuid: string) => `/v1/shared-vaults/${sharedVaultUuid}/invites`,
deleteAllInboundInvites: '/v1/shared-vaults/invites/inbound',
deleteAllOutboundInvites: '/v1/shared-vaults/invites/outbound',
}
@@ -72,4 +72,8 @@ export class SharedVaultInvitesServer implements SharedVaultInvitesServerInterfa
deleteAllInboundInvites(): Promise<HttpResponse<{ success: boolean }>> {
return this.httpService.delete(SharedVaultInvitesPaths.deleteAllInboundInvites)
}
deleteAllOutboundInvites(): Promise<HttpResponse<{ success: boolean }>> {
return this.httpService.delete(SharedVaultInvitesPaths.deleteAllOutboundInvites)
}
}
@@ -32,4 +32,5 @@ export interface SharedVaultInvitesServerInterface {
): Promise<HttpResponse<DeleteAllSharedVaultInvitesResponse>>
deleteInvite(params: DeleteInviteRequestParams): Promise<HttpResponse<DeleteInviteResponse>>
deleteAllInboundInvites(): Promise<HttpResponse<{ success: boolean }>>
deleteAllOutboundInvites(): Promise<HttpResponse<{ success: boolean }>>
}
+28
View File
@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.109](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.108](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.107](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.106](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.105](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-21)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.104](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-20)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.103](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-19)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.102](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-19)
**Note:** Version bump only for package @standardnotes/clipper
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/clipper",
"description": "Web clipper browser extension for Standard Notes",
"version": "1.1.102",
"version": "1.1.109",
"private": true,
"scripts": {
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
+28
View File
@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.108.40](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.39](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.38](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.37](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.36](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-21)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-20)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.34](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-19)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-19)
**Note:** Version bump only for package @standardnotes/desktop
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.108.33",
"version": "3.108.40",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.53](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.52](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-17)
**Note:** Version bump only for package @standardnotes/encryption
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.21.52",
"version": "1.21.53",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -13,6 +13,7 @@ import {
KeySystemRootKeyInterface,
RootKeyInterface,
KeySystemRootKeyParamsInterface,
PortablePublicKeySet,
} from '@standardnotes/models'
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { firstHalfOfString, secondHalfOfString, splitString, UuidGenerator } from '@standardnotes/utils'
@@ -28,11 +29,12 @@ import { ItemAuthenticatedData } from '../../Types/ItemAuthenticatedData'
import { LegacyAttachedData } from '../../Types/LegacyAttachedData'
import { RootKeyEncryptedAuthenticatedData } from '../../Types/RootKeyEncryptedAuthenticatedData'
import { OperatorInterface } from '../OperatorInterface/OperatorInterface'
import { PublicKeySet } from '../Types/PublicKeySet'
import { AsymmetricDecryptResult } from '../Types/AsymmetricDecryptResult'
import { AsymmetricSignatureVerificationDetachedResult } from '../Types/AsymmetricSignatureVerificationDetachedResult'
import { AsyncOperatorInterface } from '../OperatorInterface/AsyncOperatorInterface'
import { ContentType } from '@standardnotes/domain-core'
import { ContentType, Result } from '@standardnotes/domain-core'
import { AsymmetricItemAdditionalData } from '../../Types/EncryptionAdditionalData'
const NO_IV = '00000000000000000000000000000000'
@@ -272,11 +274,23 @@ export class SNProtocolOperator001 implements OperatorInterface, AsyncOperatorIn
throw new Error('Method not implemented.')
}
asymmetricDecryptOwnMessage(_dto: {
message: string
ownPrivateKey: string
recipientPublicKey: string
}): Result<AsymmetricDecryptResult> {
throw new Error('Method not implemented.')
}
asymmetricSignatureVerifyDetached(_encryptedString: string): AsymmetricSignatureVerificationDetachedResult {
throw new Error('Method not implemented.')
}
getSenderPublicKeySetFromAsymmetricallyEncryptedString(_string: string): PublicKeySet {
asymmetricStringGetAdditionalData(_dto: { encryptedString: string }): Result<AsymmetricItemAdditionalData> {
throw new Error('Method not implemented.')
}
getSenderPublicKeySetFromAsymmetricallyEncryptedString(_string: string): PortablePublicKeySet {
throw new Error('Method not implemented.')
}
}
@@ -12,6 +12,7 @@ import {
KeySystemIdentifier,
RootKeyInterface,
KeySystemRootKeyParamsInterface,
PortablePublicKeySet,
} from '@standardnotes/models'
import { KeyParamsOrigination, ProtocolVersion } from '@standardnotes/common'
import { HexString, PkcKeyPair, PureCryptoInterface, Utf8String } from '@standardnotes/sncrypto-common'
@@ -30,9 +31,9 @@ import { OperatorInterface } from '../OperatorInterface/OperatorInterface'
import { AsymmetricallyEncryptedString } from '../Types/Types'
import { AsymmetricItemAdditionalData } from '../../Types/EncryptionAdditionalData'
import { V004AsymmetricStringComponents } from './V004AlgorithmTypes'
import { AsymmetricEncryptUseCase } from './UseCase/Asymmetric/AsymmetricEncrypt'
import { AsymmetricEncrypt004 } from './UseCase/Asymmetric/AsymmetricEncrypt'
import { ParseConsistentBase64JsonPayloadUseCase } from './UseCase/Utils/ParseConsistentBase64JsonPayload'
import { AsymmetricDecryptUseCase } from './UseCase/Asymmetric/AsymmetricDecrypt'
import { AsymmetricDecrypt004 } from './UseCase/Asymmetric/AsymmetricDecrypt'
import { GenerateDecryptedParametersUseCase } from './UseCase/Symmetric/GenerateDecryptedParameters'
import { GenerateEncryptedParametersUseCase } from './UseCase/Symmetric/GenerateEncryptedParameters'
import { DeriveRootKeyUseCase } from './UseCase/RootKey/DeriveRootKey'
@@ -41,14 +42,15 @@ import { CreateRootKeyUseCase } from './UseCase/RootKey/CreateRootKey'
import { UuidGenerator } from '@standardnotes/utils'
import { CreateKeySystemItemsKeyUseCase } from './UseCase/KeySystem/CreateKeySystemItemsKey'
import { AsymmetricDecryptResult } from '../Types/AsymmetricDecryptResult'
import { PublicKeySet } from '../Types/PublicKeySet'
import { CreateRandomKeySystemRootKey } from './UseCase/KeySystem/CreateRandomKeySystemRootKey'
import { CreateUserInputKeySystemRootKey } from './UseCase/KeySystem/CreateUserInputKeySystemRootKey'
import { AsymmetricSignatureVerificationDetachedResult } from '../Types/AsymmetricSignatureVerificationDetachedResult'
import { AsymmetricSignatureVerificationDetachedUseCase } from './UseCase/Asymmetric/AsymmetricSignatureVerificationDetached'
import { AsymmetricSignatureVerificationDetached004 } from './UseCase/Asymmetric/AsymmetricSignatureVerificationDetached'
import { DeriveKeySystemRootKeyUseCase } from './UseCase/KeySystem/DeriveKeySystemRootKey'
import { SyncOperatorInterface } from '../OperatorInterface/SyncOperatorInterface'
import { ContentType } from '@standardnotes/domain-core'
import { ContentType, Result } from '@standardnotes/domain-core'
import { AsymmetricStringGetAdditionalData004 } from './UseCase/Asymmetric/AsymmetricStringGetAdditionalData'
import { AsymmetricDecryptOwnMessage004 } from './UseCase/Asymmetric/AsymmetricDecryptOwnMessage'
export class SNProtocolOperator004 implements OperatorInterface, SyncOperatorInterface {
constructor(protected readonly crypto: PureCryptoInterface) {}
@@ -167,7 +169,7 @@ export class SNProtocolOperator004 implements OperatorInterface, SyncOperatorInt
senderSigningKeyPair: PkcKeyPair
recipientPublicKey: HexString
}): AsymmetricallyEncryptedString {
const usecase = new AsymmetricEncryptUseCase(this.crypto)
const usecase = new AsymmetricEncrypt004(this.crypto)
return usecase.execute(dto)
}
@@ -175,18 +177,34 @@ export class SNProtocolOperator004 implements OperatorInterface, SyncOperatorInt
stringToDecrypt: AsymmetricallyEncryptedString
recipientSecretKey: HexString
}): AsymmetricDecryptResult | null {
const usecase = new AsymmetricDecryptUseCase(this.crypto)
const usecase = new AsymmetricDecrypt004(this.crypto)
return usecase.execute(dto)
}
asymmetricDecryptOwnMessage(dto: {
message: AsymmetricallyEncryptedString
ownPrivateKey: HexString
recipientPublicKey: HexString
}): Result<AsymmetricDecryptResult> {
const usecase = new AsymmetricDecryptOwnMessage004(this.crypto)
return usecase.execute(dto)
}
asymmetricSignatureVerifyDetached(
encryptedString: AsymmetricallyEncryptedString,
): AsymmetricSignatureVerificationDetachedResult {
const usecase = new AsymmetricSignatureVerificationDetachedUseCase(this.crypto)
const usecase = new AsymmetricSignatureVerificationDetached004(this.crypto)
return usecase.execute({ encryptedString })
}
getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PublicKeySet {
asymmetricStringGetAdditionalData(dto: {
encryptedString: AsymmetricallyEncryptedString
}): Result<AsymmetricItemAdditionalData> {
const usecase = new AsymmetricStringGetAdditionalData004(this.crypto)
return usecase.execute(dto)
}
getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PortablePublicKeySet {
const [_, __, ___, additionalDataString] = <V004AsymmetricStringComponents>string.split(':')
const parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(this.crypto)
const additionalData = parseBase64Usecase.execute<AsymmetricItemAdditionalData>(additionalDataString)
@@ -1,27 +1,27 @@
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { getMockedCrypto } from '../../MockedCrypto'
import { AsymmetricDecryptUseCase } from './AsymmetricDecrypt'
import { AsymmetricEncryptUseCase } from './AsymmetricEncrypt'
import { AsymmetricDecrypt004 } from './AsymmetricDecrypt'
import { AsymmetricEncrypt004 } from './AsymmetricEncrypt'
import { V004AsymmetricStringComponents } from '../../V004AlgorithmTypes'
import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData'
describe('asymmetric decrypt use case', () => {
let crypto: PureCryptoInterface
let usecase: AsymmetricDecryptUseCase
let usecase: AsymmetricDecrypt004
let recipientKeyPair: PkcKeyPair
let senderKeyPair: PkcKeyPair
let senderSigningKeyPair: PkcKeyPair
beforeEach(() => {
crypto = getMockedCrypto()
usecase = new AsymmetricDecryptUseCase(crypto)
usecase = new AsymmetricDecrypt004(crypto)
recipientKeyPair = crypto.sodiumCryptoBoxSeedKeypair('recipient-seedling')
senderKeyPair = crypto.sodiumCryptoBoxSeedKeypair('sender-seedling')
senderSigningKeyPair = crypto.sodiumCryptoSignSeedKeypair('sender-signing-seedling')
})
const getEncryptedString = () => {
const encryptUsecase = new AsymmetricEncryptUseCase(crypto)
const encryptUsecase = new AsymmetricEncrypt004(crypto)
const result = encryptUsecase.execute({
stringToEncrypt: 'foobar',
@@ -5,7 +5,7 @@ import { ParseConsistentBase64JsonPayloadUseCase } from '../Utils/ParseConsisten
import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData'
import { AsymmetricDecryptResult } from '../../../Types/AsymmetricDecryptResult'
export class AsymmetricDecryptUseCase {
export class AsymmetricDecrypt004 {
private parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(this.crypto)
constructor(private readonly crypto: PureCryptoInterface) {}
@@ -0,0 +1,51 @@
import { HexString, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { AsymmetricallyEncryptedString } from '../../../Types/Types'
import { V004AsymmetricStringComponents } from '../../V004AlgorithmTypes'
import { ParseConsistentBase64JsonPayloadUseCase } from '../Utils/ParseConsistentBase64JsonPayload'
import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData'
import { AsymmetricDecryptResult } from '../../../Types/AsymmetricDecryptResult'
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
export class AsymmetricDecryptOwnMessage004 implements SyncUseCaseInterface<AsymmetricDecryptResult> {
private parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(this.crypto)
constructor(private readonly crypto: PureCryptoInterface) {}
execute(dto: {
message: AsymmetricallyEncryptedString
ownPrivateKey: HexString
recipientPublicKey: HexString
}): Result<AsymmetricDecryptResult> {
const [_, nonce, ciphertext, additionalDataString] = <V004AsymmetricStringComponents>dto.message.split(':')
const additionalData = this.parseBase64Usecase.execute<AsymmetricItemAdditionalData>(additionalDataString)
try {
const plaintext = this.crypto.sodiumCryptoBoxEasyDecrypt(
ciphertext,
nonce,
dto.recipientPublicKey,
dto.ownPrivateKey,
)
if (!plaintext) {
return Result.fail('Could not decrypt message')
}
const signatureVerified = this.crypto.sodiumCryptoSignVerify(
ciphertext,
additionalData.signingData.signature,
additionalData.signingData.publicKey,
)
return Result.ok({
plaintext,
signatureVerified,
signaturePublicKey: additionalData.signingData.publicKey,
senderPublicKey: additionalData.senderPublicKey,
})
} catch (error) {
return Result.fail('Could not decrypt message')
}
}
}
@@ -1,20 +1,20 @@
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { getMockedCrypto } from '../../MockedCrypto'
import { AsymmetricEncryptUseCase } from './AsymmetricEncrypt'
import { AsymmetricEncrypt004 } from './AsymmetricEncrypt'
import { V004AsymmetricStringComponents } from '../../V004AlgorithmTypes'
import { ParseConsistentBase64JsonPayloadUseCase } from '../Utils/ParseConsistentBase64JsonPayload'
import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData'
describe('asymmetric encrypt use case', () => {
let crypto: PureCryptoInterface
let usecase: AsymmetricEncryptUseCase
let usecase: AsymmetricEncrypt004
let encryptionKeyPair: PkcKeyPair
let signingKeyPair: PkcKeyPair
let parseBase64Usecase: ParseConsistentBase64JsonPayloadUseCase
beforeEach(() => {
crypto = getMockedCrypto()
usecase = new AsymmetricEncryptUseCase(crypto)
usecase = new AsymmetricEncrypt004(crypto)
encryptionKeyPair = crypto.sodiumCryptoBoxSeedKeypair('seedling')
signingKeyPair = crypto.sodiumCryptoSignSeedKeypair('seedling')
parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(crypto)
@@ -5,7 +5,7 @@ import { V004AsymmetricCiphertextPrefix, V004AsymmetricStringComponents } from '
import { CreateConsistentBase64JsonPayloadUseCase } from '../Utils/CreateConsistentBase64JsonPayload'
import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData'
export class AsymmetricEncryptUseCase {
export class AsymmetricEncrypt004 {
private base64DataUsecase = new CreateConsistentBase64JsonPayloadUseCase(this.crypto)
constructor(private readonly crypto: PureCryptoInterface) {}
@@ -21,8 +21,8 @@ export class AsymmetricEncryptUseCase {
const ciphertext = this.crypto.sodiumCryptoBoxEasyEncrypt(
dto.stringToEncrypt,
nonce,
dto.senderKeyPair.privateKey,
dto.recipientPublicKey,
dto.senderKeyPair.privateKey,
)
const additionalData: AsymmetricItemAdditionalData = {
@@ -5,7 +5,7 @@ import { ParseConsistentBase64JsonPayloadUseCase } from '../Utils/ParseConsisten
import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData'
import { AsymmetricSignatureVerificationDetachedResult } from '../../../Types/AsymmetricSignatureVerificationDetachedResult'
export class AsymmetricSignatureVerificationDetachedUseCase {
export class AsymmetricSignatureVerificationDetached004 {
private parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(this.crypto)
constructor(private readonly crypto: PureCryptoInterface) {}
@@ -0,0 +1,20 @@
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { AsymmetricallyEncryptedString } from '../../../Types/Types'
import { V004AsymmetricStringComponents } from '../../V004AlgorithmTypes'
import { ParseConsistentBase64JsonPayloadUseCase } from '../Utils/ParseConsistentBase64JsonPayload'
import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData'
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
export class AsymmetricStringGetAdditionalData004 implements SyncUseCaseInterface<AsymmetricItemAdditionalData> {
private parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(this.crypto)
constructor(private readonly crypto: PureCryptoInterface) {}
execute(dto: { encryptedString: AsymmetricallyEncryptedString }): Result<AsymmetricItemAdditionalData> {
const [_, __, ___, additionalDataString] = <V004AsymmetricStringComponents>dto.encryptedString.split(':')
const additionalData = this.parseBase64Usecase.execute<AsymmetricItemAdditionalData>(additionalDataString)
return Result.ok(additionalData)
}
}
@@ -2,8 +2,9 @@ import { ProtocolVersion, ProtocolVersionLatest } from '@standardnotes/common'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { createOperatorForVersion } from './Functions'
import { AnyOperatorInterface } from './OperatorInterface/TypeCheck'
import { EncryptionOperatorsInterface } from './EncryptionOperatorsInterface'
export class OperatorManager {
export class EncryptionOperators implements EncryptionOperatorsInterface {
private operators: Record<string, AnyOperatorInterface> = {}
constructor(private crypto: PureCryptoInterface) {
@@ -0,0 +1,8 @@
import { ProtocolVersion } from '@standardnotes/common'
import { AnyOperatorInterface } from './OperatorInterface/TypeCheck'
export interface EncryptionOperatorsInterface {
operatorForVersion(version: ProtocolVersion): AnyOperatorInterface
defaultOperator(): AnyOperatorInterface
deinit(): void
}
@@ -6,6 +6,7 @@ import {
KeySystemRootKeyInterface,
KeySystemIdentifier,
KeySystemRootKeyParamsInterface,
PortablePublicKeySet,
} from '@standardnotes/models'
import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams'
import { EncryptedOutputParameters } from '../../Types/EncryptedParameters'
@@ -15,8 +16,9 @@ import { RootKeyEncryptedAuthenticatedData } from '../../Types/RootKeyEncryptedA
import { HexString, PkcKeyPair } from '@standardnotes/sncrypto-common'
import { AsymmetricallyEncryptedString } from '../Types/Types'
import { AsymmetricDecryptResult } from '../Types/AsymmetricDecryptResult'
import { PublicKeySet } from '../Types/PublicKeySet'
import { AsymmetricSignatureVerificationDetachedResult } from '../Types/AsymmetricSignatureVerificationDetachedResult'
import { AsymmetricItemAdditionalData } from '../../Types/EncryptionAdditionalData'
import { Result } from '@standardnotes/domain-core'
/**w
* An operator is responsible for performing crypto operations, such as generating keys
@@ -92,11 +94,21 @@ export interface OperatorInterface {
recipientSecretKey: HexString
}): AsymmetricDecryptResult | null
asymmetricDecryptOwnMessage(dto: {
message: AsymmetricallyEncryptedString
ownPrivateKey: HexString
recipientPublicKey: HexString
}): Result<AsymmetricDecryptResult>
asymmetricSignatureVerifyDetached(
encryptedString: AsymmetricallyEncryptedString,
): AsymmetricSignatureVerificationDetachedResult
getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PublicKeySet
asymmetricStringGetAdditionalData(dto: {
encryptedString: AsymmetricallyEncryptedString
}): Result<AsymmetricItemAdditionalData>
getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PortablePublicKeySet
versionForAsymmetricallyEncryptedString(encryptedString: string): ProtocolVersion
}
@@ -13,14 +13,14 @@ import {
ErrorDecryptingParameters,
} from '../Types/EncryptedParameters'
import { DecryptedParameters } from '../Types/DecryptedParameters'
import { OperatorManager } from './OperatorManager'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { isAsyncOperator } from './OperatorInterface/TypeCheck'
import { EncryptionOperatorsInterface } from './EncryptionOperatorsInterface'
export async function encryptPayload(
payload: DecryptedPayloadInterface,
key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface | RootKeyInterface,
operatorManager: OperatorManager,
operatorManager: EncryptionOperatorsInterface,
signingKeyPair: PkcKeyPair | undefined,
): Promise<EncryptedOutputParameters> {
const operator = operatorManager.operatorForVersion(key.keyVersion)
@@ -42,7 +42,7 @@ export async function encryptPayload(
export async function decryptPayload<C extends ItemContent = ItemContent>(
payload: EncryptedPayloadInterface,
key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface | RootKeyInterface,
operatorManager: OperatorManager,
operatorManager: EncryptionOperatorsInterface,
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
const operator = operatorManager.operatorForVersion(payload.version)
@@ -1,30 +0,0 @@
import { ItemsKeyInterface } from '@standardnotes/models'
export function findDefaultItemsKey(itemsKeys: ItemsKeyInterface[]): ItemsKeyInterface | undefined {
if (itemsKeys.length === 1) {
return itemsKeys[0]
}
const defaultKeys = itemsKeys.filter((key) => {
return key.isDefault
})
if (defaultKeys.length === 0) {
return undefined
}
if (defaultKeys.length === 1) {
return defaultKeys[0]
}
/**
* Prioritize one that is synced, as neverSynced keys will likely be deleted after
* DownloadFirst sync.
*/
const syncedKeys = defaultKeys.filter((key) => !key.neverSynced)
if (syncedKeys.length > 0) {
return syncedKeys[0]
}
return undefined
}
+3 -17
View File
@@ -1,42 +1,30 @@
export * from './Algorithm'
export * from './Backups/BackupFileType'
export * from './Keys/ItemsKey/ItemsKey'
export * from './Keys/ItemsKey/ItemsKeyMutator'
export * from './Keys/ItemsKey/Registration'
export * from './Keys/KeySystemItemsKey/KeySystemItemsKey'
export * from './Keys/KeySystemItemsKey/KeySystemItemsKeyMutator'
export * from './Keys/KeySystemItemsKey/Registration'
export * from './Keys/RootKey/Functions'
export * from './Keys/RootKey/KeyParamsFunctions'
export * from './Keys/RootKey/ProtocolVersionForKeyParams'
export * from './Keys/RootKey/RootKey'
export * from './Keys/RootKey/RootKeyParams'
export * from './Keys/RootKey/ValidKeyParamsKeys'
export * from './Keys/Utils/KeyRecoveryStrings'
export * from './Operator/001/Operator001'
export * from './Operator/002/Operator002'
export * from './Operator/003/Operator003'
export * from './Operator/004/Operator004'
export * from './Operator/004/V004AlgorithmHelpers'
export * from './Operator/EncryptionOperators'
export * from './Operator/EncryptionOperatorsInterface'
export * from './Operator/Functions'
export * from './Operator/OperatorInterface/OperatorInterface'
export * from './Operator/OperatorManager'
export * from './Operator/OperatorWrapper'
export * from './Operator/Types/PublicKeySet'
export * from './Operator/Types/AsymmetricSignatureVerificationDetachedResult'
export * from './Operator/Types/Types'
export * from './Service/Encryption/EncryptionProviderInterface'
export * from './Service/KeySystemKeyManagerInterface'
export * from './Service/Functions'
export * from './Service/RootKey/KeyMode'
export * from './Split/AbstractKeySplit'
export * from './Split/EncryptionSplit'
export * from './Split/EncryptionTypeSplit'
@@ -44,11 +32,9 @@ export * from './Split/Functions'
export * from './Split/KeyedDecryptionSplit'
export * from './Split/KeyedEncryptionSplit'
export * from './StandardException'
export * from './Types/EncryptedParameters'
export * from './Types/DecryptedParameters'
export * from './Types/EncryptedParameters'
export * from './Types/ItemAuthenticatedData'
export * from './Types/LegacyAttachedData'
export * from './Types/RootKeyEncryptedAuthenticatedData'
export * from './Username/PrivateUsername'
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.28.62](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.28.61](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-17)
**Note:** Version bump only for package @standardnotes/filepicker
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/filepicker",
"version": "1.28.61",
"version": "1.28.62",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.16.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
**Note:** Version bump only for package @standardnotes/files
## [1.16.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-17)
**Note:** Version bump only for package @standardnotes/files
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files",
"version": "1.16.7",
"version": "1.16.8",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+30
View File
@@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.56.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
**Note:** Version bump only for package @standardnotes/mobile
## [3.56.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
**Note:** Version bump only for package @standardnotes/mobile
## [3.56.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
**Note:** Version bump only for package @standardnotes/mobile
## [3.56.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
**Note:** Version bump only for package @standardnotes/mobile
## [3.56.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-21)
### Bug Fixes
* Shared image links are now correctly handled on iOS ([ab2e88a](https://github.com/standardnotes/app/commit/ab2e88a710c65771312bd9d02cec0b400d352a0e))
## [3.56.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-20)
**Note:** Version bump only for package @standardnotes/mobile
## [3.56.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-19)
**Note:** Version bump only for package @standardnotes/mobile
## [3.56.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-19)
**Note:** Version bump only for package @standardnotes/mobile
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.56.12",
"version": "3.56.19",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0-or-later",
@@ -109,42 +109,16 @@ export class ReceivedSharedItemsHandler {
return
}
if (isReceivedAndroidFile(item)) {
const data = await readFile(item.contentUri, 'base64')
const file = {
name: item.fileName || item.contentUri,
data,
mimeType: item.mimeType,
}
this.webViewRef.current?.postMessage(
JSON.stringify({
reactNativeEvent: ReactNativeToWebEvent.ReceivedFile,
messageType: 'event',
messageData: file,
}),
)
} else if (isReceivedIosFile(item)) {
const data = await readFile(item.path, 'base64')
const file = {
name: item.fileName || item.path,
data,
mimeType: item.mimeType,
}
this.webViewRef.current?.postMessage(
JSON.stringify({
reactNativeEvent: ReactNativeToWebEvent.ReceivedFile,
messageType: 'event',
messageData: file,
}),
)
if (isReceivedAndroidFile(item) || isReceivedIosFile(item)) {
this.sendFileToWebView(item).catch(console.error)
} else if (isReceivedWeblink(item)) {
this.webViewRef.current?.postMessage(
JSON.stringify({
reactNativeEvent: ReactNativeToWebEvent.ReceivedText,
reactNativeEvent: ReactNativeToWebEvent.ReceivedLink,
messageType: 'event',
messageData: {
title: item.subject || item.weblink,
text: item.weblink,
link: item.weblink,
},
}),
)
@@ -163,6 +137,23 @@ export class ReceivedSharedItemsHandler {
this.handleItemsQueue().catch(console.error)
}
sendFileToWebView = async (item: ReceivedAndroidFile | ReceivedIosFile) => {
const path = isReceivedAndroidFile(item) ? item.contentUri : item.path
const data = await readFile(path, 'base64')
const file = {
name: item.fileName || item.path,
data,
mimeType: item.mimeType,
}
this.webViewRef.current?.postMessage(
JSON.stringify({
reactNativeEvent: ReactNativeToWebEvent.ReceivedFile,
messageType: 'event',
messageData: file,
}),
)
}
private addSharedItemsToQueue = async (url?: string) => {
const received =
Platform.OS === 'ios' ? await ReceiveSharingIntent.getFileNames(url) : await ReceiveSharingIntent.getFileNames()
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.46.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
**Note:** Version bump only for package @standardnotes/models
## [1.46.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-17)
**Note:** Version bump only for package @standardnotes/models
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/models",
"version": "1.46.11",
"version": "1.46.12",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -23,7 +23,7 @@ export class DecryptedItemMutator<
this.mutableContent = mutableCopy
}
public override getResult() {
public override getResult(): DecryptedPayloadInterface<C> {
if (this.type === MutationType.NonDirtying) {
return this.immutablePayload.copy({
content: this.mutableContent,
@@ -3,7 +3,6 @@ export enum AsymmetricMessagePayloadType {
SharedVaultRootKeyChanged = 'shared-vault-root-key-changed',
SenderKeypairChanged = 'sender-keypair-changed',
SharedVaultMetadataChanged = 'shared-vault-metadata-changed',
/**
* Shared Vault Invites conform to the asymmetric message protocol, but are sent via the dedicated
* SharedVaultInvite model and not the AsymmetricMessage model on the server side.
@@ -1,13 +1,19 @@
import { KeySystemRootKeyContentSpecialized } from '../../../Syncable/KeySystemRootKey/KeySystemRootKeyContent'
import { TrustedContactContentSpecialized } from '../../../Syncable/TrustedContact/TrustedContactContent'
import { ContactPublicKeySetJsonInterface } from '../../../Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetJsonInterface'
import { AsymmetricMessageDataCommon } from '../AsymmetricMessageDataCommon'
import { AsymmetricMessagePayloadType } from '../AsymmetricMessagePayloadType'
export type VaultInviteDelegatedContact = {
name?: string
contactUuid: string
publicKeySet: ContactPublicKeySetJsonInterface
}
export type AsymmetricMessageSharedVaultInvite = {
type: AsymmetricMessagePayloadType.SharedVaultInvite
data: AsymmetricMessageDataCommon & {
rootKey: KeySystemRootKeyContentSpecialized
trustedContacts: TrustedContactContentSpecialized[]
trustedContacts: VaultInviteDelegatedContact[]
metadata: {
name: string
description?: string
@@ -1,4 +1,4 @@
import { TrustedContactContentSpecialized } from '../../../Syncable/TrustedContact/TrustedContactContent'
import { TrustedContactContentSpecialized } from '../../../Syncable/TrustedContact/Content/TrustedContactContent'
import { AsymmetricMessageDataCommon } from '../AsymmetricMessageDataCommon'
import { AsymmetricMessagePayloadType } from '../AsymmetricMessagePayloadType'
@@ -0,0 +1,11 @@
import { ItemContent } from '../../../Abstract/Content/ItemContent'
import { ContactPublicKeySetJsonInterface } from '../PublicKeySet/ContactPublicKeySetJsonInterface'
export type TrustedContactContentSpecialized = {
name: string
contactUuid: string
publicKeySet: ContactPublicKeySetJsonInterface
isMe: boolean
}
export type TrustedContactContent = TrustedContactContentSpecialized & ItemContent
@@ -0,0 +1,93 @@
import { ContentType } from '@standardnotes/domain-core'
import { DecryptedPayload, PayloadTimestampDefaults } from '../../../Abstract/Payload'
import { TrustedContact } from '../TrustedContact'
import { TrustedContactInterface } from '../TrustedContactInterface'
import { TrustedContactMutator } from './TrustedContactMutator'
import { ContactPublicKeySet } from '../PublicKeySet/ContactPublicKeySet'
import { FillItemContentSpecialized } from '../../../Abstract/Content/ItemContent'
import { TrustedContactContentSpecialized } from '../Content/TrustedContactContent'
import { MutationType } from '../../../Abstract/Item'
import { PortablePublicKeySet } from '../Types/PortablePublicKeySet'
import { ContactPublicKeySetInterface } from '../PublicKeySet/ContactPublicKeySetInterface'
function createMockPublicKeySetChain(): ContactPublicKeySetInterface {
const nMinusOne = new ContactPublicKeySet({
encryption: 'encryption-public-key-n-1',
signing: 'signing-public-key-n-1',
timestamp: new Date(-1),
previousKeySet: undefined,
})
const root = new ContactPublicKeySet({
encryption: 'encryption-public-key',
signing: 'signing-public-key',
timestamp: new Date(),
previousKeySet: nMinusOne,
})
return root
}
describe('TrustedContactMutator', () => {
let contact: TrustedContactInterface
let mutator: TrustedContactMutator
beforeEach(() => {
contact = new TrustedContact(
new DecryptedPayload({
uuid: 'item-uuid',
content_type: ContentType.TYPES.TrustedContact,
...PayloadTimestampDefaults(),
content: FillItemContentSpecialized<TrustedContactContentSpecialized, TrustedContactInterface>({
name: 'test',
contactUuid: 'contact-uuid',
isMe: true,
publicKeySet: createMockPublicKeySetChain(),
}),
}),
)
mutator = new TrustedContactMutator(contact, MutationType.UpdateUserTimestamps)
})
it('should set name', () => {
mutator.name = 'new name'
const result = mutator.getResult()
expect(result.content.name).toEqual('new name')
})
it('should add public key', () => {
const currentKeySet = contact.publicKeySet
const newKeySet: PortablePublicKeySet = {
encryption: 'new-encryption-public-key',
signing: 'new-signing-public-key',
}
mutator.addPublicKey(newKeySet)
const result = new TrustedContact(mutator.getResult())
expect(result.publicKeySet.encryption).toEqual(newKeySet.encryption)
expect(result.publicKeySet.signing).toEqual(newKeySet.signing)
expect(result.publicKeySet.previousKeySet).toEqual(currentKeySet)
})
it('should replace public key set', () => {
const replacement = new ContactPublicKeySet({
encryption: 'encryption-public-key-replacement',
signing: 'signing-public-key-replacement',
timestamp: new Date(),
previousKeySet: undefined,
})
mutator.replacePublicKeySet(replacement.asJson())
const result = new TrustedContact(mutator.getResult())
expect(result.publicKeySet.encryption).toEqual(replacement.encryption)
expect(result.publicKeySet.signing).toEqual(replacement.signing)
})
})
@@ -0,0 +1,27 @@
import { DecryptedItemMutator } from '../../../Abstract/Item'
import { TrustedContactContent } from '../Content/TrustedContactContent'
import { TrustedContactInterface } from '../TrustedContactInterface'
import { ContactPublicKeySet } from '../PublicKeySet/ContactPublicKeySet'
import { PortablePublicKeySet } from '../Types/PortablePublicKeySet'
import { ContactPublicKeySetJsonInterface } from '../PublicKeySet/ContactPublicKeySetJsonInterface'
export class TrustedContactMutator extends DecryptedItemMutator<TrustedContactContent, TrustedContactInterface> {
set name(newName: string) {
this.mutableContent.name = newName
}
addPublicKey(keySet: PortablePublicKeySet): void {
const newKey = new ContactPublicKeySet({
encryption: keySet.encryption,
signing: keySet.signing,
timestamp: new Date(),
previousKeySet: this.immutableItem.publicKeySet,
})
this.mutableContent.publicKeySet = newKey
}
replacePublicKeySet(publicKeySet: ContactPublicKeySetJsonInterface): void {
this.mutableContent.publicKeySet = publicKeySet
}
}
@@ -5,36 +5,18 @@ export class ContactPublicKeySet implements ContactPublicKeySetInterface {
encryption: string
signing: string
timestamp: Date
isRevoked: boolean
previousKeySet?: ContactPublicKeySet
previousKeySet?: ContactPublicKeySetInterface
constructor(
encryption: string,
signing: string,
timestamp: Date,
isRevoked: boolean,
previousKeySet: ContactPublicKeySet | undefined,
) {
this.encryption = encryption
this.signing = signing
this.timestamp = timestamp
this.isRevoked = isRevoked
this.previousKeySet = previousKeySet
}
public findKeySet(params: {
targetEncryptionPublicKey: string
targetSigningPublicKey: string
}): ContactPublicKeySetInterface | undefined {
if (this.encryption === params.targetEncryptionPublicKey && this.signing === params.targetSigningPublicKey) {
return this
}
if (this.previousKeySet) {
return this.previousKeySet.findKeySet(params)
}
return undefined
constructor(dto: {
encryption: string
signing: string
timestamp: Date
previousKeySet: ContactPublicKeySetInterface | undefined
}) {
this.encryption = dto.encryption
this.signing = dto.signing
this.timestamp = dto.timestamp
this.previousKeySet = dto.previousKeySet
}
public findKeySetWithSigningKey(signingKey: string): ContactPublicKeySetInterface | undefined {
@@ -61,13 +43,30 @@ export class ContactPublicKeySet implements ContactPublicKeySetInterface {
return undefined
}
asJson(): ContactPublicKeySetJsonInterface {
return {
encryption: this.encryption,
signing: this.signing,
timestamp: this.timestamp,
previousKeySet: this.previousKeySet ? this.previousKeySet.asJson() : undefined,
}
}
mutableCopy(): ContactPublicKeySetInterface {
return new ContactPublicKeySet({
encryption: this.encryption,
signing: this.signing,
timestamp: this.timestamp,
previousKeySet: this.previousKeySet ? ContactPublicKeySet.FromJson(this.previousKeySet.asJson()) : undefined,
})
}
static FromJson(json: ContactPublicKeySetJsonInterface): ContactPublicKeySetInterface {
return new ContactPublicKeySet(
json.encryption,
json.signing,
new Date(json.timestamp),
json.isRevoked,
json.previousKeySet ? ContactPublicKeySet.FromJson(json.previousKeySet) : undefined,
)
return new ContactPublicKeySet({
encryption: json.encryption,
signing: json.signing,
timestamp: new Date(json.timestamp),
previousKeySet: json.previousKeySet ? ContactPublicKeySet.FromJson(json.previousKeySet) : undefined,
})
}
}
@@ -1,15 +1,14 @@
import { ContactPublicKeySetJsonInterface } from './ContactPublicKeySetJsonInterface'
export interface ContactPublicKeySetInterface {
encryption: string
signing: string
timestamp: Date
isRevoked: boolean
previousKeySet?: ContactPublicKeySetInterface
findKeySet(params: {
targetEncryptionPublicKey: string
targetSigningPublicKey: string
}): ContactPublicKeySetInterface | undefined
findKeySetWithPublicKey(publicKey: string): ContactPublicKeySetInterface | undefined
findKeySetWithSigningKey(signingKey: string): ContactPublicKeySetInterface | undefined
asJson(): ContactPublicKeySetJsonInterface
mutableCopy(): ContactPublicKeySetInterface
}
@@ -2,6 +2,5 @@ export interface ContactPublicKeySetJsonInterface {
encryption: string
signing: string
timestamp: Date
isRevoked: boolean
previousKeySet?: ContactPublicKeySetJsonInterface
}
@@ -1,8 +0,0 @@
import { ContactPublicKeySetInterface } from './ContactPublicKeySetInterface'
export type FindPublicKeySetResult =
| {
publicKeySet: ContactPublicKeySetInterface
current: boolean
}
| undefined
@@ -0,0 +1,109 @@
import { ContentType } from '@standardnotes/domain-core'
import { DecryptedPayload, PayloadTimestampDefaults } from '../../Abstract/Payload'
import { ContactPublicKeySet } from './PublicKeySet/ContactPublicKeySet'
import { ContactPublicKeySetInterface } from './PublicKeySet/ContactPublicKeySetInterface'
import { TrustedContact } from './TrustedContact'
import { TrustedContactInterface } from './TrustedContactInterface'
import { FillItemContentSpecialized } from '../../Abstract/Content/ItemContent'
import { ConflictStrategy } from '../../Abstract/Item'
import { TrustedContactContentSpecialized } from './Content/TrustedContactContent'
import { PublicKeyTrustStatus } from './Types/PublicKeyTrustStatus'
function createMockPublicKeySetChain(): ContactPublicKeySetInterface {
const nMinusOne = new ContactPublicKeySet({
encryption: 'encryption-public-key-n-1',
signing: 'signing-public-key-n-1',
timestamp: new Date(-1),
previousKeySet: undefined,
})
const root = new ContactPublicKeySet({
encryption: 'encryption-public-key',
signing: 'signing-public-key',
timestamp: new Date(),
previousKeySet: nMinusOne,
})
return root
}
function CreateContact(params: {
name?: string
contactUuid?: string
isMe?: boolean
publicKeySet?: ContactPublicKeySetInterface
}): TrustedContactInterface {
return new TrustedContact(
new DecryptedPayload({
uuid: 'item-uuid',
content_type: ContentType.TYPES.TrustedContact,
...PayloadTimestampDefaults(),
content: FillItemContentSpecialized<TrustedContactContentSpecialized, TrustedContactInterface>({
name: params.name ?? 'test',
contactUuid: params.contactUuid ?? 'contact-uuid',
isMe: params.isMe ?? false,
publicKeySet: params.publicKeySet ?? createMockPublicKeySetChain(),
}),
}),
)
}
describe('isSingleton', () => {
it('should be true', () => {
const contact = CreateContact({})
expect(contact.isSingleton).toEqual(true)
})
})
describe('strategyWhenConflictingWithItem', () => {
it('should be KeepBase', () => {
const contact = CreateContact({})
expect(contact.strategyWhenConflictingWithItem({} as unknown as TrustedContactInterface)).toEqual(
ConflictStrategy.KeepBase,
)
})
})
describe('TrustedContact', () => {
describe('getTrustStatusForPublicKey', () => {
it('should be trusted if key set is root', () => {
const contact = CreateContact({ publicKeySet: createMockPublicKeySetChain() })
expect(contact.getTrustStatusForPublicKey('encryption-public-key')).toEqual(PublicKeyTrustStatus.Trusted)
})
it('should be semi-trusted if key set is previous', () => {
const contact = CreateContact({ publicKeySet: createMockPublicKeySetChain() })
expect(contact.getTrustStatusForPublicKey('encryption-public-key-n-1')).toEqual(PublicKeyTrustStatus.Previous)
})
it('should return not trusted if public key is not found', () => {
const contact = CreateContact({ publicKeySet: createMockPublicKeySetChain() })
expect(contact.getTrustStatusForPublicKey('not-found-public-key')).toEqual(PublicKeyTrustStatus.NotTrusted)
})
})
describe('getTrustStatusForSigningPublicKey', () => {
it('should be trusted if key set is root', () => {
const contact = CreateContact({ publicKeySet: createMockPublicKeySetChain() })
expect(contact.getTrustStatusForSigningPublicKey('signing-public-key')).toEqual(PublicKeyTrustStatus.Trusted)
})
it('should be semi-trusted if key set is previous', () => {
const contact = CreateContact({ publicKeySet: createMockPublicKeySetChain() })
expect(contact.getTrustStatusForSigningPublicKey('signing-public-key-n-1')).toEqual(PublicKeyTrustStatus.Previous)
})
it('should return not trusted if public key is not found', () => {
const contact = CreateContact({ publicKeySet: createMockPublicKeySetChain() })
expect(contact.getTrustStatusForSigningPublicKey('not-found-public-key')).toEqual(PublicKeyTrustStatus.NotTrusted)
})
})
})
@@ -1,12 +1,12 @@
import { ConflictStrategy, DecryptedItem, DecryptedItemInterface } from '../../Abstract/Item'
import { DecryptedPayloadInterface } from '../../Abstract/Payload'
import { HistoryEntryInterface } from '../../Runtime/History'
import { TrustedContactContent } from './TrustedContactContent'
import { TrustedContactContent } from './Content/TrustedContactContent'
import { TrustedContactInterface } from './TrustedContactInterface'
import { FindPublicKeySetResult } from './PublicKeySet/FindPublicKeySetResult'
import { ContactPublicKeySet } from './PublicKeySet/ContactPublicKeySet'
import { ContactPublicKeySetInterface } from './PublicKeySet/ContactPublicKeySetInterface'
import { Predicate } from '../../Runtime/Predicate/Predicate'
import { PublicKeyTrustStatus } from './Types/PublicKeyTrustStatus'
export class TrustedContact extends DecryptedItem<TrustedContactContent> implements TrustedContactInterface {
static singletonPredicate = new Predicate<TrustedContact>('isMe', '=', true)
@@ -33,39 +33,36 @@ export class TrustedContact extends DecryptedItem<TrustedContactContent> impleme
return TrustedContact.singletonPredicate
}
public findKeySet(params: {
targetEncryptionPublicKey: string
targetSigningPublicKey: string
}): FindPublicKeySetResult {
const set = this.publicKeySet.findKeySet(params)
if (!set) {
return undefined
}
return {
publicKeySet: set,
current: set === this.publicKeySet,
}
hasCurrentOrPreviousSigningPublicKey(signingPublicKey: string): boolean {
return this.publicKeySet.findKeySetWithSigningKey(signingPublicKey) !== undefined
}
isPublicKeyTrusted(encryptionPublicKey: string): boolean {
const keySet = this.publicKeySet.findKeySetWithPublicKey(encryptionPublicKey)
if (keySet && !keySet.isRevoked) {
return true
getTrustStatusForPublicKey(encryptionPublicKey: string): PublicKeyTrustStatus {
if (this.publicKeySet.encryption === encryptionPublicKey) {
return PublicKeyTrustStatus.Trusted
}
return false
const previous = this.publicKeySet.findKeySetWithPublicKey(encryptionPublicKey)
if (previous) {
return PublicKeyTrustStatus.Previous
}
return PublicKeyTrustStatus.NotTrusted
}
isSigningKeyTrusted(signingKey: string): boolean {
const keySet = this.publicKeySet.findKeySetWithSigningKey(signingKey)
if (keySet && !keySet.isRevoked) {
return true
getTrustStatusForSigningPublicKey(signingPublicKey: string): PublicKeyTrustStatus {
if (this.publicKeySet.signing === signingPublicKey) {
return PublicKeyTrustStatus.Trusted
}
return false
const previous = this.publicKeySet.findKeySetWithSigningKey(signingPublicKey)
if (previous) {
return PublicKeyTrustStatus.Previous
}
return PublicKeyTrustStatus.NotTrusted
}
override strategyWhenConflictingWithItem(
@@ -1,11 +0,0 @@
import { ItemContent } from '../../Abstract/Content/ItemContent'
import { ContactPublicKeySetInterface } from './PublicKeySet/ContactPublicKeySetInterface'
export type TrustedContactContentSpecialized = {
name: string
contactUuid: string
publicKeySet: ContactPublicKeySetInterface
isMe: boolean
}
export type TrustedContactContent = TrustedContactContentSpecialized & ItemContent
@@ -1,7 +1,7 @@
import { DecryptedItemInterface } from '../../Abstract/Item/Interfaces/DecryptedItem'
import { FindPublicKeySetResult } from './PublicKeySet/FindPublicKeySetResult'
import { TrustedContactContent } from './TrustedContactContent'
import { TrustedContactContent } from './Content/TrustedContactContent'
import { ContactPublicKeySetInterface } from './PublicKeySet/ContactPublicKeySetInterface'
import { PublicKeyTrustStatus } from './Types/PublicKeyTrustStatus'
export interface TrustedContactInterface extends DecryptedItemInterface<TrustedContactContent> {
name: string
@@ -9,8 +9,7 @@ export interface TrustedContactInterface extends DecryptedItemInterface<TrustedC
publicKeySet: ContactPublicKeySetInterface
isMe: boolean
findKeySet(params: { targetEncryptionPublicKey: string; targetSigningPublicKey: string }): FindPublicKeySetResult
isPublicKeyTrusted(encryptionPublicKey: string): boolean
isSigningKeyTrusted(signingKey: string): boolean
getTrustStatusForPublicKey(encryptionPublicKey: string): PublicKeyTrustStatus
getTrustStatusForSigningPublicKey(signingPublicKey: string): PublicKeyTrustStatus
hasCurrentOrPreviousSigningPublicKey(signingPublicKey: string): boolean
}
@@ -1,26 +0,0 @@
import { DecryptedItemMutator } from '../../Abstract/Item'
import { TrustedContactContent } from './TrustedContactContent'
import { TrustedContactInterface } from './TrustedContactInterface'
import { ContactPublicKeySet } from './PublicKeySet/ContactPublicKeySet'
export class TrustedContactMutator extends DecryptedItemMutator<TrustedContactContent, TrustedContactInterface> {
set name(newName: string) {
this.mutableContent.name = newName
}
addPublicKey(params: { encryption: string; signing: string }): void {
const newKey = new ContactPublicKeySet(
params.encryption,
params.signing,
new Date(),
false,
this.immutableItem.publicKeySet,
)
this.mutableContent.publicKeySet = newKey
}
replacePublicKeySet(publicKeySet: ContactPublicKeySet): void {
this.mutableContent.publicKeySet = publicKeySet
}
}
@@ -1,4 +1,4 @@
export type PublicKeySet = {
export type PortablePublicKeySet = {
encryption: string
signing: string
}
@@ -0,0 +1,5 @@
export enum PublicKeyTrustStatus {
Trusted = 'Trusted',
Previous = 'Previous',
NotTrusted = 'NotTrusted',
}
@@ -27,7 +27,7 @@ import { EncryptedItemInterface } from '../../Abstract/Item/Interfaces/Encrypted
import { DeletedItemInterface } from '../../Abstract/Item/Interfaces/DeletedItem'
import { SmartViewMutator } from '../../Syncable/SmartView'
import { TrustedContact } from '../../Syncable/TrustedContact/TrustedContact'
import { TrustedContactMutator } from '../../Syncable/TrustedContact/TrustedContactMutator'
import { TrustedContactMutator } from '../../Syncable/TrustedContact/Mutator/TrustedContactMutator'
import { KeySystemRootKey } from '../../Syncable/KeySystemRootKey/KeySystemRootKey'
import { KeySystemRootKeyMutator } from '../../Syncable/KeySystemRootKey/KeySystemRootKeyMutator'
import { VaultListing } from '../../Syncable/VaultListing/VaultListing'
+4 -2
View File
@@ -97,11 +97,13 @@ export * from './Syncable/Theme'
export * from './Syncable/UserPrefs'
export * from './Syncable/TrustedContact/TrustedContact'
export * from './Syncable/TrustedContact/TrustedContactMutator'
export * from './Syncable/TrustedContact/TrustedContactContent'
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/PublicKeySet/ContactPublicKeySet'
export * from './Syncable/TrustedContact/Types/PortablePublicKeySet'
export * from './Syncable/TrustedContact/Types/PublicKeyTrustStatus'
export * from './Syncable/KeySystemRootKey/KeySystemRootKey'
export * from './Syncable/KeySystemRootKey/KeySystemRootKeyMutator'
+28
View File
@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.391](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.390](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.389](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.388](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.387](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-21)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.386](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-20)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.385](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-19)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.384](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-19)
**Note:** Version bump only for package @standardnotes/releases
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/releases",
"version": "1.4.384",
"version": "1.4.391",
"license": "AGPL-3.0-or-later",
"main": "dist/releases.json",
"types": "dist/index.d.ts",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.13.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
**Note:** Version bump only for package @standardnotes/responses
## [1.13.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-17)
**Note:** Version bump only for package @standardnotes/responses
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/responses",
"version": "1.13.30",
"version": "1.13.31",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -2,6 +2,7 @@ export interface AsymmetricMessageServerHash {
uuid: string
user_uuid: string
sender_uuid: string
replaceabilityIdentifier?: string
encrypted_message: string
created_at_timestamp: number
updated_at_timestamp: number
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.63.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
**Note:** Version bump only for package @standardnotes/services
## [1.63.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-17)
**Note:** Version bump only for package @standardnotes/services
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/services",
"version": "1.63.5",
"version": "1.63.6",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -6,7 +6,9 @@ import { SNFeatureRepo } from '@standardnotes/models'
import { ClientDisplayableError, HttpResponse } from '@standardnotes/responses'
import { AnyFeatureDescription } from '@standardnotes/features'
export interface ApiServiceInterface extends AbstractService<ApiServiceEvent, ApiServiceEventData>, FilesApiInterface {
export interface LegacyApiServiceInterface
extends AbstractService<ApiServiceEvent, ApiServiceEventData>,
FilesApiInterface {
isThirdPartyHostUsed(): boolean
downloadOfflineFeaturesFromRepo(
@@ -1,3 +1,5 @@
import { HistoryServiceInterface } from './../History/HistoryServiceInterface'
import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface'
import { PreferenceServiceInterface } from './../Preferences/PreferenceServiceInterface'
import { AsymmetricMessageServiceInterface } from './../AsymmetricMessage/AsymmetricMessageServiceInterface'
import { SyncOptions } from './../Sync/SyncOptions'
@@ -34,6 +36,7 @@ import { UserClientInterface } from '../User/UserClientInterface'
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
import { HomeServerServiceInterface } from '../HomeServer/HomeServerServiceInterface'
import { User } from '@standardnotes/responses'
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
export interface ApplicationInterface {
deinit(mode: DeinitMode, source: DeinitSource): void
@@ -107,9 +110,11 @@ export interface ApplicationInterface {
get alerts(): AlertService
get asymmetric(): AsymmetricMessageServiceInterface
get preferences(): PreferenceServiceInterface
get events(): InternalEventBusInterface
get history(): HistoryServiceInterface
get encryption(): EncryptionProviderInterface
readonly identifier: ApplicationIdentifier
readonly platform: Platform
deviceInterface: DeviceInterface
alertService: AlertService
device: DeviceInterface
}
@@ -1,31 +1,121 @@
import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface'
import { GetUntrustedPayload } from './UseCase/GetUntrustedPayload'
import { GetInboundMessages } from './UseCase/GetInboundMessages'
import { GetOutboundMessages } from './UseCase/GetOutboundMessages'
import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessage'
import { HandleRootKeyChangedMessage } from './UseCase/HandleRootKeyChangedMessage'
import { GetVault } from './../Vaults/UseCase/GetVault'
import { GetTrustedPayload } from './UseCase/GetTrustedPayload'
import { ReplaceContactData } from './../Contacts/UseCase/ReplaceContactData'
import { GetAllContacts } from './../Contacts/UseCase/GetAllContacts'
import { FindContact } from './../Contacts/UseCase/FindContact'
import { CreateOrEditContact } from './../Contacts/UseCase/CreateOrEditContact'
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
import { HttpServiceInterface } from '@standardnotes/api'
import { AsymmetricMessageServer } from '@standardnotes/api'
import { AsymmetricMessageService } from './AsymmetricMessageService'
import { ContactServiceInterface } from './../Contacts/ContactServiceInterface'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
import { AsymmetricMessagePayloadType } from '@standardnotes/models'
import {
AsymmetricMessagePayloadType,
AsymmetricMessageSenderKeypairChanged,
AsymmetricMessageSharedVaultInvite,
AsymmetricMessageSharedVaultMetadataChanged,
AsymmetricMessageSharedVaultRootKeyChanged,
AsymmetricMessageTrustedContactShare,
KeySystemRootKeyContentSpecialized,
TrustedContactInterface,
} from '@standardnotes/models'
describe('AsymmetricMessageService', () => {
let sync: jest.Mocked<SyncServiceInterface>
let mutator: jest.Mocked<MutatorClientInterface>
let encryption: jest.Mocked<EncryptionProviderInterface>
let service: AsymmetricMessageService
beforeEach(() => {
const http = {} as jest.Mocked<HttpServiceInterface>
http.delete = jest.fn()
const messageServer = {} as jest.Mocked<AsymmetricMessageServer>
messageServer.deleteMessage = jest.fn()
const encryption = {} as jest.Mocked<EncryptionProviderInterface>
const contacts = {} as jest.Mocked<ContactServiceInterface>
const items = {} as jest.Mocked<ItemManagerInterface>
const sync = {} as jest.Mocked<SyncServiceInterface>
const mutator = {} as jest.Mocked<MutatorClientInterface>
encryption = {} as jest.Mocked<EncryptionProviderInterface>
const createOrEditContact = {} as jest.Mocked<CreateOrEditContact>
const findContact = {} as jest.Mocked<FindContact>
const getAllContacts = {} as jest.Mocked<GetAllContacts>
const replaceContactData = {} as jest.Mocked<ReplaceContactData>
const getTrustedPayload = {} as jest.Mocked<GetTrustedPayload>
const getVault = {} as jest.Mocked<GetVault>
const handleRootKeyChangedMessage = {} as jest.Mocked<HandleRootKeyChangedMessage>
const sendOwnContactChangedMessage = {} as jest.Mocked<SendOwnContactChangeMessage>
const getOutboundMessagesUseCase = {} as jest.Mocked<GetOutboundMessages>
const getInboundMessagesUseCase = {} as jest.Mocked<GetInboundMessages>
const getUntrustedPayload = {} as jest.Mocked<GetUntrustedPayload>
sync = {} as jest.Mocked<SyncServiceInterface>
sync.sync = jest.fn()
mutator = {} as jest.Mocked<MutatorClientInterface>
mutator.changeItem = jest.fn()
const eventBus = {} as jest.Mocked<InternalEventBusInterface>
eventBus.addEventHandler = jest.fn()
service = new AsymmetricMessageService(http, encryption, contacts, items, mutator, sync, eventBus)
service = new AsymmetricMessageService(
messageServer,
encryption,
mutator,
createOrEditContact,
findContact,
getAllContacts,
replaceContactData,
getTrustedPayload,
getVault,
handleRootKeyChangedMessage,
sendOwnContactChangedMessage,
getOutboundMessagesUseCase,
getInboundMessagesUseCase,
getUntrustedPayload,
eventBus,
)
})
describe('sortServerMessages', () => {
it('should prioritize keypair changed messages over other messages', () => {
const messages: AsymmetricMessageServerHash[] = [
{
uuid: 'keypair-changed-message',
user_uuid: '1',
sender_uuid: '2',
encrypted_message: 'encrypted_message',
created_at_timestamp: 2,
updated_at_timestamp: 2,
},
{
uuid: 'misc-message',
user_uuid: '1',
sender_uuid: '2',
encrypted_message: 'encrypted_message',
created_at_timestamp: 1,
updated_at_timestamp: 1,
},
]
service.getUntrustedMessagePayload = jest.fn()
service.getServerMessageType = jest.fn().mockImplementation((message) => {
if (message.uuid === 'keypair-changed-message') {
return AsymmetricMessagePayloadType.SenderKeypairChanged
} else {
return AsymmetricMessagePayloadType.ContactShare
}
})
const sorted = service.sortServerMessages(messages)
expect(sorted[0].uuid).toEqual('keypair-changed-message')
expect(sorted[1].uuid).toEqual('misc-message')
const reverseSorted = service.sortServerMessages(messages.reverse())
expect(reverseSorted[0].uuid).toEqual('keypair-changed-message')
expect(reverseSorted[1].uuid).toEqual('misc-message')
})
})
it('should process incoming messages oldest first', async () => {
@@ -50,7 +140,9 @@ describe('AsymmetricMessageService', () => {
const trustedPayloadMock = { type: AsymmetricMessagePayloadType.ContactShare, data: { recipientUuid: '1' } }
service.getTrustedMessagePayload = jest.fn().mockReturnValue(trustedPayloadMock)
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
.fn()
.mockReturnValue(trustedPayloadMock)
const handleTrustedContactShareMessageMock = jest.fn()
service.handleTrustedContactShareMessage = handleTrustedContactShareMessageMock
@@ -60,4 +152,174 @@ describe('AsymmetricMessageService', () => {
expect(handleTrustedContactShareMessageMock.mock.calls[0][0]).toEqual(messages[1])
expect(handleTrustedContactShareMessageMock.mock.calls[1][0]).toEqual(messages[0])
})
it('should handle ContactShare message', async () => {
const message: AsymmetricMessageServerHash = {
uuid: 'message',
user_uuid: '1',
sender_uuid: '2',
encrypted_message: 'encrypted_message',
created_at_timestamp: 2,
updated_at_timestamp: 2,
}
const decryptedMessagePayload: AsymmetricMessageTrustedContactShare = {
type: AsymmetricMessagePayloadType.ContactShare,
data: {
recipientUuid: '1',
trustedContact: {} as TrustedContactInterface,
},
}
service.handleTrustedContactShareMessage = jest.fn()
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
.fn()
.mockReturnValue(decryptedMessagePayload)
await service.handleRemoteReceivedAsymmetricMessages([message])
expect(service.handleTrustedContactShareMessage).toHaveBeenCalledWith(message, decryptedMessagePayload)
})
it('should handle SenderKeypairChanged message', async () => {
const message: AsymmetricMessageServerHash = {
uuid: 'message',
user_uuid: '1',
sender_uuid: '2',
encrypted_message: 'encrypted_message',
created_at_timestamp: 2,
updated_at_timestamp: 2,
}
const decryptedMessagePayload: AsymmetricMessageSenderKeypairChanged = {
type: AsymmetricMessagePayloadType.SenderKeypairChanged,
data: {
recipientUuid: '1',
newEncryptionPublicKey: 'new-encryption-public-key',
newSigningPublicKey: 'new-signing-public-key',
},
}
service.handleTrustedSenderKeypairChangedMessage = jest.fn()
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
.fn()
.mockReturnValue(decryptedMessagePayload)
await service.handleRemoteReceivedAsymmetricMessages([message])
expect(service.handleTrustedSenderKeypairChangedMessage).toHaveBeenCalledWith(message, decryptedMessagePayload)
})
it('should handle SharedVaultRootKeyChanged message', async () => {
const message: AsymmetricMessageServerHash = {
uuid: 'message',
user_uuid: '1',
sender_uuid: '2',
encrypted_message: 'encrypted_message',
created_at_timestamp: 2,
updated_at_timestamp: 2,
}
const decryptedMessagePayload: AsymmetricMessageSharedVaultRootKeyChanged = {
type: AsymmetricMessagePayloadType.SharedVaultRootKeyChanged,
data: {
recipientUuid: '1',
rootKey: {} as KeySystemRootKeyContentSpecialized,
},
}
service.handleTrustedSharedVaultRootKeyChangedMessage = jest.fn()
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
.fn()
.mockReturnValue(decryptedMessagePayload)
await service.handleRemoteReceivedAsymmetricMessages([message])
expect(service.handleTrustedSharedVaultRootKeyChangedMessage).toHaveBeenCalledWith(message, decryptedMessagePayload)
})
it('should handle SharedVaultMetadataChanged message', async () => {
const message: AsymmetricMessageServerHash = {
uuid: 'message',
user_uuid: '1',
sender_uuid: '2',
encrypted_message: 'encrypted_message',
created_at_timestamp: 2,
updated_at_timestamp: 2,
}
const decryptedMessagePayload: AsymmetricMessageSharedVaultMetadataChanged = {
type: AsymmetricMessagePayloadType.SharedVaultMetadataChanged,
data: {
recipientUuid: '1',
sharedVaultUuid: 'shared-vault-uuid',
name: 'Vault name',
description: 'Vault description',
},
}
service.handleTrustedVaultMetadataChangedMessage = jest.fn()
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
.fn()
.mockReturnValue(decryptedMessagePayload)
await service.handleRemoteReceivedAsymmetricMessages([message])
expect(service.handleTrustedVaultMetadataChangedMessage).toHaveBeenCalledWith(message, decryptedMessagePayload)
})
it('should throw if message type is SharedVaultInvite', async () => {
const message: AsymmetricMessageServerHash = {
uuid: 'message',
user_uuid: '1',
sender_uuid: '2',
encrypted_message: 'encrypted_message',
created_at_timestamp: 2,
updated_at_timestamp: 2,
}
const decryptedMessagePayload: AsymmetricMessageSharedVaultInvite = {
type: AsymmetricMessagePayloadType.SharedVaultInvite,
data: {
recipientUuid: '1',
},
} as AsymmetricMessageSharedVaultInvite
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
.fn()
.mockReturnValue(decryptedMessagePayload)
await expect(service.handleRemoteReceivedAsymmetricMessages([message])).rejects.toThrow(
'Shared vault invites payloads are not handled as part of asymmetric messages',
)
})
it('should delete message from server after processing', async () => {
const message: AsymmetricMessageServerHash = {
uuid: 'message',
user_uuid: '1',
sender_uuid: '2',
encrypted_message: 'encrypted_message',
created_at_timestamp: 2,
updated_at_timestamp: 2,
}
const decryptedMessagePayload: AsymmetricMessageTrustedContactShare = {
type: AsymmetricMessagePayloadType.ContactShare,
data: {
recipientUuid: '1',
trustedContact: {} as TrustedContactInterface,
},
}
service.deleteMessageAfterProcessing = jest.fn()
service.handleTrustedContactShareMessage = jest.fn()
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
.fn()
.mockReturnValue(decryptedMessagePayload)
await service.handleRemoteReceivedAsymmetricMessages([message])
expect(service.deleteMessageAfterProcessing).toHaveBeenCalled()
})
})
@@ -1,13 +1,11 @@
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
import { ContactServiceInterface } from './../Contacts/ContactServiceInterface'
import { AsymmetricMessageServerHash, ClientDisplayableError, isClientDisplayableError } from '@standardnotes/responses'
import { SyncEvent, SyncEventReceivedAsymmetricMessagesData } from '../Event/SyncEvent'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
import { InternalEventInterface } from '../Internal/InternalEventInterface'
import { AbstractService } from '../Service/AbstractService'
import { GetAsymmetricMessageTrustedPayload } from './UseCase/GetAsymmetricMessageTrustedPayload'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { GetTrustedPayload } from './UseCase/GetTrustedPayload'
import {
AsymmetricMessageSharedVaultRootKeyChanged,
AsymmetricMessagePayloadType,
@@ -16,61 +14,68 @@ import {
AsymmetricMessagePayload,
AsymmetricMessageSharedVaultMetadataChanged,
VaultListingMutator,
MutationType,
PayloadEmitSource,
VaultListingInterface,
} from '@standardnotes/models'
import { HandleTrustedSharedVaultRootKeyChangedMessage } from './UseCase/HandleTrustedSharedVaultRootKeyChangedMessage'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import { HandleRootKeyChangedMessage } from './UseCase/HandleRootKeyChangedMessage'
import { SessionEvent } from '../Session/SessionEvent'
import { AsymmetricMessageServer, HttpServiceInterface } from '@standardnotes/api'
import { AsymmetricMessageServer } from '@standardnotes/api'
import { UserKeyPairChangedEventData } from '../Session/UserKeyPairChangedEventData'
import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessage'
import { GetOutboundAsymmetricMessages } from './UseCase/GetOutboundAsymmetricMessages'
import { GetInboundAsymmetricMessages } from './UseCase/GetInboundAsymmetricMessages'
import { GetVaultUseCase } from '../Vaults/UseCase/GetVault'
import { GetOutboundMessages } from './UseCase/GetOutboundMessages'
import { GetInboundMessages } from './UseCase/GetInboundMessages'
import { GetVault } from '../Vaults/UseCase/GetVault'
import { AsymmetricMessageServiceInterface } from './AsymmetricMessageServiceInterface'
import { GetUntrustedPayload } from './UseCase/GetUntrustedPayload'
import { FindContact } from '../Contacts/UseCase/FindContact'
import { CreateOrEditContact } from '../Contacts/UseCase/CreateOrEditContact'
import { ReplaceContactData } from '../Contacts/UseCase/ReplaceContactData'
import { GetAllContacts } from '../Contacts/UseCase/GetAllContacts'
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
export class AsymmetricMessageService
extends AbstractService
implements AsymmetricMessageServiceInterface, InternalEventHandlerInterface
{
private messageServer: AsymmetricMessageServer
constructor(
http: HttpServiceInterface,
private messageServer: AsymmetricMessageServer,
private encryption: EncryptionProviderInterface,
private contacts: ContactServiceInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private _createOrEditContact: CreateOrEditContact,
private _findContact: FindContact,
private _getAllContacts: GetAllContacts,
private _replaceContactData: ReplaceContactData,
private _getTrustedPayload: GetTrustedPayload,
private _getVault: GetVault,
private _handleRootKeyChangedMessage: HandleRootKeyChangedMessage,
private _sendOwnContactChangedMessage: SendOwnContactChangeMessage,
private _getOutboundMessagesUseCase: GetOutboundMessages,
private _getInboundMessagesUseCase: GetInboundMessages,
private _getUntrustedPayload: GetUntrustedPayload,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
this.messageServer = new AsymmetricMessageServer(http)
eventBus.addEventHandler(this, SyncEvent.ReceivedAsymmetricMessages)
eventBus.addEventHandler(this, SessionEvent.UserKeyPairChanged)
}
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === SessionEvent.UserKeyPairChanged) {
void this.messageServer.deleteAllInboundMessages()
void this.sendOwnContactChangeEventToAllContacts(event.payload as UserKeyPairChangedEventData)
}
if (event.type === SyncEvent.ReceivedAsymmetricMessages) {
void this.handleRemoteReceivedAsymmetricMessages(event.payload as SyncEventReceivedAsymmetricMessagesData)
switch (event.type) {
case SessionEvent.UserKeyPairChanged:
void this.messageServer.deleteAllInboundMessages()
void this.sendOwnContactChangeEventToAllContacts(event.payload as UserKeyPairChangedEventData)
break
case SyncEvent.ReceivedAsymmetricMessages:
void this.handleRemoteReceivedAsymmetricMessages(event.payload as SyncEventReceivedAsymmetricMessagesData)
break
}
}
public async getOutboundMessages(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError> {
const usecase = new GetOutboundAsymmetricMessages(this.messageServer)
return usecase.execute()
return this._getOutboundMessagesUseCase.execute()
}
public async getInboundMessages(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError> {
const usecase = new GetInboundAsymmetricMessages(this.messageServer)
return usecase.execute()
return this._getInboundMessagesUseCase.execute()
}
public async downloadAndProcessInboundMessages(): Promise<void> {
@@ -83,118 +88,223 @@ export class AsymmetricMessageService
}
private async sendOwnContactChangeEventToAllContacts(data: UserKeyPairChangedEventData): Promise<void> {
if (!data.oldKeyPair || !data.oldSigningKeyPair) {
if (!data.previous) {
return
}
const useCase = new SendOwnContactChangeMessage(this.encryption, this.messageServer)
const contacts = this._getAllContacts.execute()
if (contacts.isFailed()) {
return
}
const contacts = this.contacts.getAllContacts()
for (const contact of contacts) {
for (const contact of contacts.getValue()) {
if (contact.isMe) {
continue
}
await useCase.execute({
senderOldKeyPair: data.oldKeyPair,
senderOldSigningKeyPair: data.oldSigningKeyPair,
senderNewKeyPair: data.newKeyPair,
senderNewSigningKeyPair: data.newSigningKeyPair,
await this._sendOwnContactChangedMessage.execute({
senderOldKeyPair: data.previous.encryption,
senderOldSigningKeyPair: data.previous.signing,
senderNewKeyPair: data.current.encryption,
senderNewSigningKeyPair: data.current.signing,
contact,
})
}
}
sortServerMessages(messages: AsymmetricMessageServerHash[]): AsymmetricMessageServerHash[] {
const SortedPriorityTypes = [AsymmetricMessagePayloadType.SenderKeypairChanged]
const priority: AsymmetricMessageServerHash[] = []
const regular: AsymmetricMessageServerHash[] = []
const allMessagesOldestFirst = messages.slice().sort((a, b) => a.created_at_timestamp - b.created_at_timestamp)
const messageTypeMap: Record<string, AsymmetricMessagePayloadType> = {}
for (const message of allMessagesOldestFirst) {
const messageType = this.getServerMessageType(message)
if (!messageType) {
continue
}
messageTypeMap[message.uuid] = messageType
if (SortedPriorityTypes.includes(messageType)) {
priority.push(message)
} else {
regular.push(message)
}
}
const sortedPriority = priority.sort((a, b) => {
const typeA = messageTypeMap[a.uuid]
const typeB = messageTypeMap[b.uuid]
if (typeA !== typeB) {
return SortedPriorityTypes.indexOf(typeA) - SortedPriorityTypes.indexOf(typeB)
}
return a.created_at_timestamp - b.created_at_timestamp
})
const regularMessagesOldestFirst = regular.sort((a, b) => a.created_at_timestamp - b.created_at_timestamp)
return [...sortedPriority, ...regularMessagesOldestFirst]
}
getServerMessageType(message: AsymmetricMessageServerHash): AsymmetricMessagePayloadType | undefined {
const result = this.getUntrustedMessagePayload(message)
if (!result) {
return undefined
}
return result.type
}
async handleRemoteReceivedAsymmetricMessages(messages: AsymmetricMessageServerHash[]): Promise<void> {
if (messages.length === 0) {
return
}
const sortedMessages = messages.slice().sort((a, b) => a.created_at_timestamp - b.created_at_timestamp)
const sortedMessages = this.sortServerMessages(messages)
for (const message of sortedMessages) {
const trustedMessagePayload = this.getTrustedMessagePayload(message)
if (!trustedMessagePayload) {
const trustedPayload = this.getTrustedMessagePayload(message)
if (!trustedPayload) {
continue
}
if (trustedMessagePayload.data.recipientUuid !== message.user_uuid) {
continue
}
if (trustedMessagePayload.type === AsymmetricMessagePayloadType.ContactShare) {
await this.handleTrustedContactShareMessage(message, trustedMessagePayload)
} else if (trustedMessagePayload.type === AsymmetricMessagePayloadType.SenderKeypairChanged) {
await this.handleTrustedSenderKeypairChangedMessage(message, trustedMessagePayload)
} else if (trustedMessagePayload.type === AsymmetricMessagePayloadType.SharedVaultRootKeyChanged) {
await this.handleTrustedSharedVaultRootKeyChangedMessage(message, trustedMessagePayload)
} else if (trustedMessagePayload.type === AsymmetricMessagePayloadType.SharedVaultMetadataChanged) {
await this.handleVaultMetadataChangedMessage(message, trustedMessagePayload)
} else if (trustedMessagePayload.type === AsymmetricMessagePayloadType.SharedVaultInvite) {
throw new Error('Shared vault invites payloads are not handled as part of asymmetric messages')
}
await this.deleteMessageAfterProcessing(message)
await this.handleTrustedMessageResult(message, trustedPayload)
}
}
getTrustedMessagePayload(message: AsymmetricMessageServerHash): AsymmetricMessagePayload | undefined {
const useCase = new GetAsymmetricMessageTrustedPayload(this.encryption, this.contacts)
return useCase.execute({
privateKey: this.encryption.getKeyPair().privateKey,
message,
})
}
private async deleteMessageAfterProcessing(message: AsymmetricMessageServerHash): Promise<void> {
await this.messageServer.deleteMessage({ messageUuid: message.uuid })
}
async handleVaultMetadataChangedMessage(
_message: AsymmetricMessageServerHash,
trustedPayload: AsymmetricMessageSharedVaultMetadataChanged,
private async handleTrustedMessageResult(
message: AsymmetricMessageServerHash,
payload: AsymmetricMessagePayload,
): Promise<void> {
const vault = new GetVaultUseCase(this.items).execute({ sharedVaultUuid: trustedPayload.data.sharedVaultUuid })
if (!vault) {
if (payload.data.recipientUuid !== message.user_uuid) {
return
}
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
mutator.name = trustedPayload.data.name
mutator.description = trustedPayload.data.description
if (payload.type === AsymmetricMessagePayloadType.ContactShare) {
await this.handleTrustedContactShareMessage(message, payload)
} else if (payload.type === AsymmetricMessagePayloadType.SenderKeypairChanged) {
await this.handleTrustedSenderKeypairChangedMessage(message, payload)
} else if (payload.type === AsymmetricMessagePayloadType.SharedVaultRootKeyChanged) {
await this.handleTrustedSharedVaultRootKeyChangedMessage(message, payload)
} else if (payload.type === AsymmetricMessagePayloadType.SharedVaultMetadataChanged) {
await this.handleTrustedVaultMetadataChangedMessage(message, payload)
} else if (payload.type === AsymmetricMessagePayloadType.SharedVaultInvite) {
throw new Error('Shared vault invites payloads are not handled as part of asymmetric messages')
}
await this.deleteMessageAfterProcessing(message)
}
getUntrustedMessagePayload(message: AsymmetricMessageServerHash): AsymmetricMessagePayload | undefined {
const result = this._getUntrustedPayload.execute({
privateKey: this.encryption.getKeyPair().privateKey,
message,
})
if (result.isFailed()) {
return undefined
}
return result.getValue()
}
getTrustedMessagePayload(message: AsymmetricMessageServerHash): AsymmetricMessagePayload | undefined {
const contact = this._findContact.execute({ userUuid: message.sender_uuid })
if (contact.isFailed()) {
return undefined
}
const result = this._getTrustedPayload.execute({
privateKey: this.encryption.getKeyPair().privateKey,
sender: contact.getValue(),
message,
})
if (result.isFailed()) {
return undefined
}
return result.getValue()
}
async deleteMessageAfterProcessing(message: AsymmetricMessageServerHash): Promise<void> {
await this.messageServer.deleteMessage({ messageUuid: message.uuid })
}
async handleTrustedVaultMetadataChangedMessage(
_message: AsymmetricMessageServerHash,
trustedPayload: AsymmetricMessageSharedVaultMetadataChanged,
): Promise<void> {
const vault = this._getVault.execute<VaultListingInterface>({
sharedVaultUuid: trustedPayload.data.sharedVaultUuid,
})
if (vault.isFailed()) {
return
}
await this.mutator.changeItem<VaultListingMutator>(
vault.getValue(),
(mutator) => {
mutator.name = trustedPayload.data.name
mutator.description = trustedPayload.data.description
},
MutationType.UpdateUserTimestamps,
PayloadEmitSource.RemoteRetrieved,
)
}
async handleTrustedContactShareMessage(
_message: AsymmetricMessageServerHash,
trustedPayload: AsymmetricMessageTrustedContactShare,
): Promise<void> {
await this.contacts.createOrUpdateTrustedContactFromContactShare(trustedPayload.data.trustedContact)
if (trustedPayload.data.trustedContact.isMe) {
return
}
await this._replaceContactData.execute(trustedPayload.data.trustedContact)
}
private async handleTrustedSenderKeypairChangedMessage(
async handleTrustedSenderKeypairChangedMessage(
message: AsymmetricMessageServerHash,
trustedPayload: AsymmetricMessageSenderKeypairChanged,
): Promise<void> {
await this.contacts.createOrEditTrustedContact({
await this._createOrEditContact.execute({
contactUuid: message.sender_uuid,
publicKey: trustedPayload.data.newEncryptionPublicKey,
signingPublicKey: trustedPayload.data.newSigningPublicKey,
})
}
private async handleTrustedSharedVaultRootKeyChangedMessage(
async handleTrustedSharedVaultRootKeyChangedMessage(
_message: AsymmetricMessageServerHash,
trustedPayload: AsymmetricMessageSharedVaultRootKeyChanged,
): Promise<void> {
const useCase = new HandleTrustedSharedVaultRootKeyChangedMessage(
this.mutator,
this.items,
this.sync,
this.encryption,
)
await useCase.execute(trustedPayload)
await this._handleRootKeyChangedMessage.execute(trustedPayload)
}
public override deinit(): void {
super.deinit()
;(this.messageServer as unknown) = undefined
;(this.encryption as unknown) = undefined
;(this.mutator as unknown) = undefined
;(this._createOrEditContact as unknown) = undefined
;(this._findContact as unknown) = undefined
;(this._getAllContacts as unknown) = undefined
;(this._replaceContactData as unknown) = undefined
;(this._getTrustedPayload as unknown) = undefined
;(this._getVault as unknown) = undefined
;(this._handleRootKeyChangedMessage as unknown) = undefined
;(this._sendOwnContactChangedMessage as unknown) = undefined
;(this._getOutboundMessagesUseCase as unknown) = undefined
;(this._getInboundMessagesUseCase as unknown) = undefined
;(this._getUntrustedPayload as unknown) = undefined
}
}
@@ -1,23 +0,0 @@
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface'
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
import { AsymmetricMessagePayload } from '@standardnotes/models'
export class GetAsymmetricMessageTrustedPayload<M extends AsymmetricMessagePayload> {
constructor(private encryption: EncryptionProviderInterface, private contacts: ContactServiceInterface) {}
execute(dto: { privateKey: string; message: AsymmetricMessageServerHash }): M | undefined {
const trustedContact = this.contacts.findTrustedContact(dto.message.sender_uuid)
if (!trustedContact) {
return undefined
}
const decryptionResult = this.encryption.asymmetricallyDecryptMessage<M>({
encryptedString: dto.message.encrypted_message,
trustedSender: trustedContact,
privateKey: dto.privateKey,
})
return decryptionResult
}
}
@@ -1,17 +0,0 @@
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
import { AsymmetricMessagePayload } from '@standardnotes/models'
export class GetAsymmetricMessageUntrustedPayload<M extends AsymmetricMessagePayload> {
constructor(private encryption: EncryptionProviderInterface) {}
execute(dto: { privateKey: string; message: AsymmetricMessageServerHash }): M | undefined {
const decryptionResult = this.encryption.asymmetricallyDecryptMessage<M>({
encryptedString: dto.message.encrypted_message,
trustedSender: undefined,
privateKey: dto.privateKey,
})
return decryptionResult
}
}
@@ -1,7 +1,7 @@
import { ClientDisplayableError, isErrorResponse, AsymmetricMessageServerHash } from '@standardnotes/responses'
import { AsymmetricMessageServerInterface } from '@standardnotes/api'
export class GetInboundAsymmetricMessages {
export class GetInboundMessages {
constructor(private messageServer: AsymmetricMessageServerInterface) {}
async execute(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError> {
@@ -1,7 +1,7 @@
import { ClientDisplayableError, isErrorResponse, AsymmetricMessageServerHash } from '@standardnotes/responses'
import { AsymmetricMessageServerInterface } from '@standardnotes/api'
export class GetOutboundAsymmetricMessages {
export class GetOutboundMessages {
constructor(private messageServer: AsymmetricMessageServerInterface) {}
async execute(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError> {
@@ -0,0 +1,17 @@
import { AsymmetricMessagePayloadType } from '@standardnotes/models'
const TypesUsingReplaceableIdentifiers = [
AsymmetricMessagePayloadType.SharedVaultRootKeyChanged,
AsymmetricMessagePayloadType.SharedVaultMetadataChanged,
]
export function GetReplaceabilityIdentifier(
type: AsymmetricMessagePayloadType,
sharedVaultUuid: string,
keySystemIdentifier: string,
): string | undefined {
if (!TypesUsingReplaceableIdentifiers.includes(type)) {
return undefined
}
return [type, sharedVaultUuid, keySystemIdentifier].join(':')
}
@@ -0,0 +1,22 @@
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
import { AsymmetricMessagePayload, TrustedContactInterface } from '@standardnotes/models'
import { DecryptMessage } from '../../Encryption/UseCase/Asymmetric/DecryptMessage'
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
export class GetTrustedPayload implements SyncUseCaseInterface<AsymmetricMessagePayload> {
constructor(private decryptMessage: DecryptMessage) {}
execute<M extends AsymmetricMessagePayload>(dto: {
privateKey: string
message: AsymmetricMessageServerHash
sender: TrustedContactInterface
}): Result<M> {
const result = this.decryptMessage.execute<M>({
message: dto.message.encrypted_message,
sender: dto.sender,
privateKey: dto.privateKey,
})
return result
}
}
@@ -0,0 +1,21 @@
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
import { AsymmetricMessagePayload } from '@standardnotes/models'
import { DecryptMessage } from '../../Encryption/UseCase/Asymmetric/DecryptMessage'
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
export class GetUntrustedPayload implements SyncUseCaseInterface<AsymmetricMessagePayload> {
constructor(private decryptMessage: DecryptMessage) {}
execute<M extends AsymmetricMessagePayload>(dto: {
privateKey: string
message: AsymmetricMessageServerHash
}): Result<M> {
const result = this.decryptMessage.execute<M>({
message: dto.message.encrypted_message,
sender: undefined,
privateKey: dto.privateKey,
})
return result
}
}
@@ -1,5 +1,4 @@
import { ItemManagerInterface } from './../../Item/ItemManagerInterface'
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import {
KeySystemRootKeyInterface,
@@ -7,18 +6,19 @@ import {
FillItemContent,
KeySystemRootKeyContent,
VaultListingMutator,
VaultListingInterface,
} from '@standardnotes/models'
import { ContentType } from '@standardnotes/domain-core'
import { GetVaultUseCase } from '../../Vaults/UseCase/GetVault'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { GetVault } from '../../Vaults/UseCase/GetVault'
import { EncryptionProviderInterface } from '../../Encryption/EncryptionProviderInterface'
export class HandleTrustedSharedVaultRootKeyChangedMessage {
export class HandleRootKeyChangedMessage {
constructor(
private mutator: MutatorClientInterface,
private items: ItemManagerInterface,
private sync: SyncServiceInterface,
private encryption: EncryptionProviderInterface,
private getVault: GetVault,
) {}
async execute(message: AsymmetricMessageSharedVaultRootKeyChanged): Promise<void> {
@@ -30,9 +30,9 @@ export class HandleTrustedSharedVaultRootKeyChangedMessage {
true,
)
const vault = new GetVaultUseCase(this.items).execute({ keySystemIdentifier: rootKeyContent.systemIdentifier })
if (vault) {
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
const vault = this.getVault.execute<VaultListingInterface>({ keySystemIdentifier: rootKeyContent.systemIdentifier })
if (!vault.isFailed()) {
await this.mutator.changeItem<VaultListingMutator>(vault.getValue(), (mutator) => {
mutator.rootKeyParams = rootKeyContent.keyParams
})
}
@@ -1,7 +1,7 @@
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
import { HandleTrustedSharedVaultInviteMessage } from './HandleTrustedSharedVaultInviteMessage'
import { CreateOrEditContact } from './../../Contacts/UseCase/CreateOrEditContact'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
import { ProcessAcceptedVaultInvite } from './ProcessAcceptedVaultInvite'
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface'
import { ContentType } from '@standardnotes/domain-core'
import {
AsymmetricMessagePayloadType,
@@ -9,32 +9,25 @@ import {
KeySystemRootKeyContent,
} from '@standardnotes/models'
describe('HandleTrustedSharedVaultInviteMessage', () => {
let mutatorMock: jest.Mocked<MutatorClientInterface>
let syncServiceMock: jest.Mocked<SyncServiceInterface>
let contactServiceMock: jest.Mocked<ContactServiceInterface>
describe('ProcessAcceptedVaultInvite', () => {
let mutator: jest.Mocked<MutatorClientInterface>
let sync: jest.Mocked<SyncServiceInterface>
let createOrEditContact: jest.Mocked<CreateOrEditContact>
beforeEach(() => {
mutatorMock = {
createItem: jest.fn(),
} as any
mutator = {} as jest.Mocked<MutatorClientInterface>
mutator.createItem = jest.fn()
syncServiceMock = {
sync: jest.fn(),
} as any
sync = {} as jest.Mocked<SyncServiceInterface>
sync.sync = jest.fn()
contactServiceMock = {
createOrEditTrustedContact: jest.fn(),
} as any
createOrEditContact = {} as jest.Mocked<CreateOrEditContact>
createOrEditContact.execute = jest.fn()
})
it('should create root key before creating vault listing so that propagated vault listings do not appear as locked', async () => {
const handleTrustedSharedVaultInviteMessage = new HandleTrustedSharedVaultInviteMessage(
mutatorMock,
syncServiceMock,
contactServiceMock,
)
const handleTrustedSharedVaultInviteMessage = new ProcessAcceptedVaultInvite(mutator, sync, createOrEditContact)
createOrEditContact
const testMessage = {
type: AsymmetricMessagePayloadType.SharedVaultInvite,
data: {
@@ -54,11 +47,11 @@ describe('HandleTrustedSharedVaultInviteMessage', () => {
await handleTrustedSharedVaultInviteMessage.execute(testMessage, sharedVaultUuid, senderUuid)
const keySystemRootKeyCallIndex = mutatorMock.createItem.mock.calls.findIndex(
const keySystemRootKeyCallIndex = mutator.createItem.mock.calls.findIndex(
([contentType]) => contentType === ContentType.TYPES.KeySystemRootKey,
)
const vaultListingCallIndex = mutatorMock.createItem.mock.calls.findIndex(
const vaultListingCallIndex = mutator.createItem.mock.calls.findIndex(
([contentType]) => contentType === ContentType.TYPES.VaultListing,
)

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