mirror of
https://github.com/standardnotes/app
synced 2026-09-13 18:46:08 -04:00
Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e67f694c85 | ||
|
|
a77535456c | ||
|
|
e698b1c990 | ||
|
|
ab2e88a710 | ||
|
|
68db05581b | ||
|
|
7acf23033d | ||
|
|
78b87672de | ||
|
|
2b0df30ca2 | ||
|
|
58cf0e542f | ||
|
|
edead7e251 | ||
|
|
166b4f3a5d | ||
|
|
1566c862a5 | ||
|
|
7df9873871 | ||
|
|
071b345e93 | ||
|
|
072e9f803b | ||
|
|
8ceb6938b3 | ||
|
|
e7d805b91a | ||
|
|
eba1289ab9 | ||
|
|
7fb33c400c | ||
|
|
a56e1f5398 | ||
|
|
bf115b5eca | ||
|
|
5932eb814c | ||
|
|
e1f1be96cb | ||
|
|
e86dff4e58 | ||
|
|
bac665fe1e | ||
|
|
10cddeee25 | ||
|
|
bd7b5a2b99 | ||
|
|
f64a9b66ca | ||
|
|
2662f5abbb | ||
|
|
526c70f623 | ||
|
|
af8aca612a | ||
|
|
a890f44cbe | ||
|
|
415757cc30 |
@@ -154,13 +154,14 @@ jobs:
|
||||
|
||||
Publish:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
||||
if: ${{ always() }}
|
||||
needs: [Windows, Mac, Linux]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/desktop
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
@@ -209,6 +210,5 @@ jobs:
|
||||
continue-on-error: true
|
||||
run: |
|
||||
sudo snap install snapcraft --classic
|
||||
echo "${{ secrets.SNAPCRAFT_LOGIN_FILE }}" >> snapauth.txt
|
||||
snapcraft login --with=snapauth.txt
|
||||
snapcraft login
|
||||
snapcraft upload dist/standard-notes-${{ env.APP_VERSION }}-linux-amd64.snap --release stable,candidate,beta,edge
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
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
|
||||
|
||||
* fixes issue where some accounts were not correctly loading subscription info ([#2361](https://github.com/standardnotes/app/issues/2361)) ([eba1289](https://github.com/standardnotes/app/commit/eba1289ab9da10a545b58572bcf0dde8c050de61))
|
||||
|
||||
## [1.26.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.26.28",
|
||||
"version": "1.26.30",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -39,7 +39,6 @@
|
||||
"@standardnotes/domain-core": "^1.22.0",
|
||||
"@standardnotes/models": "workspace:*",
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
"@standardnotes/security": "^1.7.6",
|
||||
"@standardnotes/utils": "workspace:*",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
}
|
||||
|
||||
@@ -65,13 +65,26 @@ export class FetchRequestHandler implements RequestHandlerInterface {
|
||||
}
|
||||
|
||||
private async runRequest<T>(request: Request, body?: string | Uint8Array | undefined): Promise<HttpResponse<T>> {
|
||||
const fetchResponse = await fetch(request, {
|
||||
body,
|
||||
})
|
||||
try {
|
||||
const fetchResponse = await fetch(request, {
|
||||
body,
|
||||
})
|
||||
|
||||
const response = await this.handleFetchResponse<T>(fetchResponse)
|
||||
const response = await this.handleFetchResponse<T>(fetchResponse)
|
||||
|
||||
return response
|
||||
return response
|
||||
} catch (error) {
|
||||
return {
|
||||
status: HttpStatusCode.InternalServerError,
|
||||
headers: new Map<string, string | null>(),
|
||||
data: {
|
||||
error: {
|
||||
message:
|
||||
'message' in (error as { message: string }) ? (error as { message: string }).message : 'Unknown error',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async handleFetchResponse<T>(fetchResponse: Response): Promise<HttpResponse<T>> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { joinPaths, sleep } from '@standardnotes/utils'
|
||||
import { Environment } from '@standardnotes/models'
|
||||
import { Session, SessionToken } from '@standardnotes/domain-core'
|
||||
import { LegacySession, Session, SessionToken } from '@standardnotes/domain-core'
|
||||
import {
|
||||
HttpStatusCode,
|
||||
HttpRequestParams,
|
||||
@@ -18,7 +18,7 @@ import { FetchRequestHandler } from './FetchRequestHandler'
|
||||
import { RequestHandlerInterface } from './RequestHandlerInterface'
|
||||
|
||||
export class HttpService implements HttpServiceInterface {
|
||||
private session: Session | null
|
||||
private session?: Session | LegacySession
|
||||
private __latencySimulatorMs?: number
|
||||
private declare host: string
|
||||
|
||||
@@ -29,7 +29,6 @@ export class HttpService implements HttpServiceInterface {
|
||||
private requestHandler: RequestHandlerInterface
|
||||
|
||||
constructor(private environment: Environment, private appVersion: string, private snjsVersion: string) {
|
||||
this.session = null
|
||||
this.requestHandler = new FetchRequestHandler(this.snjsVersion, this.appVersion, this.environment)
|
||||
}
|
||||
|
||||
@@ -42,12 +41,12 @@ export class HttpService implements HttpServiceInterface {
|
||||
}
|
||||
|
||||
public deinit(): void {
|
||||
this.session = null
|
||||
;(this.session as unknown) = undefined
|
||||
;(this.updateMetaCallback as unknown) = undefined
|
||||
;(this.refreshSessionCallback as unknown) = undefined
|
||||
}
|
||||
|
||||
setSession(session: Session): void {
|
||||
setSession(session: Session | LegacySession): void {
|
||||
this.session = session
|
||||
}
|
||||
|
||||
@@ -59,6 +58,18 @@ export class HttpService implements HttpServiceInterface {
|
||||
return this.host
|
||||
}
|
||||
|
||||
private getSessionAccessToken(): string | undefined {
|
||||
if (!this.session) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (this.session instanceof Session) {
|
||||
return this.session.accessToken.value
|
||||
} else {
|
||||
return this.session.accessToken
|
||||
}
|
||||
}
|
||||
|
||||
async get<T>(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse<T>> {
|
||||
if (!this.host) {
|
||||
throw new Error('Attempting to make network request before host is set')
|
||||
@@ -68,7 +79,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
url: joinPaths(this.host, path),
|
||||
params,
|
||||
verb: HttpVerb.Get,
|
||||
authentication: authentication ?? this.session?.accessToken.value,
|
||||
authentication: authentication ?? this.getSessionAccessToken(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -90,7 +101,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
url: joinPaths(this.host, path),
|
||||
params,
|
||||
verb: HttpVerb.Post,
|
||||
authentication: authentication ?? this.session?.accessToken.value,
|
||||
authentication: authentication ?? this.getSessionAccessToken(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,7 +110,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
url: joinPaths(this.host, path),
|
||||
params,
|
||||
verb: HttpVerb.Put,
|
||||
authentication: authentication ?? this.session?.accessToken.value,
|
||||
authentication: authentication ?? this.getSessionAccessToken(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -108,7 +119,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
url: joinPaths(this.host, path),
|
||||
params,
|
||||
verb: HttpVerb.Patch,
|
||||
authentication: authentication ?? this.session?.accessToken.value,
|
||||
authentication: authentication ?? this.getSessionAccessToken(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -117,7 +128,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
url: joinPaths(this.host, path),
|
||||
params,
|
||||
verb: HttpVerb.Delete,
|
||||
authentication: authentication ?? this.session?.accessToken.value,
|
||||
authentication: authentication ?? this.getSessionAccessToken(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -130,7 +141,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
if (this.inProgressRefreshSessionPromise && !isRefreshRequest) {
|
||||
await this.inProgressRefreshSessionPromise
|
||||
|
||||
httpRequest.authentication = this.session?.accessToken.value
|
||||
httpRequest.authentication = this.getSessionAccessToken()
|
||||
}
|
||||
|
||||
const response = await this.requestHandler.handleRequest<T>(httpRequest)
|
||||
@@ -152,7 +163,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
}
|
||||
}
|
||||
|
||||
httpRequest.authentication = this.session?.accessToken.value
|
||||
httpRequest.authentication = this.getSessionAccessToken()
|
||||
|
||||
return this.runHttp(httpRequest)
|
||||
}
|
||||
@@ -161,7 +172,11 @@ export class HttpService implements HttpServiceInterface {
|
||||
}
|
||||
|
||||
private async refreshSession(): Promise<boolean> {
|
||||
if (this.session === null) {
|
||||
if (!this.session) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.session instanceof LegacySession) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Session } from '@standardnotes/domain-core'
|
||||
import { LegacySession, Session } from '@standardnotes/domain-core'
|
||||
import { HttpRequest, HttpRequestParams, HttpResponse, HttpResponseMeta } from '@standardnotes/responses'
|
||||
|
||||
export interface HttpServiceInterface {
|
||||
setHost(host: string): void
|
||||
getHost(): string
|
||||
setSession(session: Session): void
|
||||
|
||||
get<T>(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse<T>>
|
||||
getExternal<T>(url: string, params?: HttpRequestParams): Promise<HttpResponse<T>>
|
||||
post<T>(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse<T>>
|
||||
@@ -12,9 +12,12 @@ export interface HttpServiceInterface {
|
||||
patch<T>(path: string, params: HttpRequestParams, authentication?: string): Promise<HttpResponse<T>>
|
||||
delete<T>(path: string, params?: HttpRequestParams, authentication?: string): Promise<HttpResponse<T>>
|
||||
runHttp<T>(httpRequest: HttpRequest): Promise<HttpResponse<T>>
|
||||
|
||||
setSession(session: Session | LegacySession): void
|
||||
setCallbacks(
|
||||
updateMetaCallback: (meta: HttpResponseMeta) => void,
|
||||
refreshSessionCallback: (session: Session) => void,
|
||||
): void
|
||||
|
||||
deinit(): void
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ValetTokenOperation } from '@standardnotes/responses'
|
||||
import { SharedVaultMoveType } from './SharedVaultMoveType'
|
||||
import { SharedVaultMoveType, ValetTokenOperation } from '@standardnotes/responses'
|
||||
|
||||
export type CreateSharedVaultValetTokenParams = {
|
||||
sharedVaultUuid: string
|
||||
|
||||
@@ -18,4 +18,3 @@ export * from './User/UserRegistrationRequestParams'
|
||||
export * from './User/UserUpdateRequestParams'
|
||||
export * from './UserRequest/UserRequestRequestParams'
|
||||
export * from './WebSocket/WebSocketConnectionTokenRequestParams'
|
||||
export * from './SharedVault/SharedVaultMoveType'
|
||||
|
||||
@@ -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 }>>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,54 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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.101](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.100](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.99](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.98](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.97](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.96](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.95](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.94](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/clipper",
|
||||
"description": "Web clipper browser extension for Standard Notes",
|
||||
"version": "1.1.94",
|
||||
"version": "1.1.106",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
|
||||
|
||||
@@ -3,6 +3,54 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
## [3.108.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.108.25",
|
||||
"version": "3.108.37",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
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.21.51](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.21.51",
|
||||
"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)
|
||||
|
||||
+5
-5
@@ -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) {}
|
||||
|
||||
+51
@@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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 = {
|
||||
|
||||
+1
-1
@@ -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) {}
|
||||
|
||||
+20
@@ -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
-1
@@ -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
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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.59.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
## [1.59.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/features",
|
||||
"version": "1.59.9",
|
||||
"version": "1.59.10",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -27,7 +27,6 @@
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.50.0",
|
||||
"@standardnotes/domain-core": "^1.22.0",
|
||||
"@standardnotes/security": "^1.7.6",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
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.28.60](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.28.60",
|
||||
"version": "1.28.62",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
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.16.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.16.6",
|
||||
"version": "1.16.8",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,68 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
## [3.56.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
@@ -2,7 +2,7 @@ storage_mode("git")
|
||||
|
||||
# type("development") # The default type, can be: appstore, adhoc, enterprise or development
|
||||
|
||||
app_identifier(["com.standardnotes.standardnotes"])
|
||||
app_identifier(["com.standardnotes.standardnotes", "com.standardnotes.standardnotes.Share-To-SN"])
|
||||
|
||||
# For all available options run `fastlane match --help`
|
||||
# Remove the # in the beginning of the line to enable the other options
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.standardnotes.standardnotes</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -67,6 +67,7 @@
|
||||
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
00E356F21AD99517003FC87E /* StandardNotesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StandardNotesTests.m; sourceTree = "<group>"; };
|
||||
04FCB5A3A3387CA3CFC82AA3 /* libPods-StandardNotes-StandardNotesTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-StandardNotes-StandardNotesTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0780C8AC2A63ECF400DDE846 /* Share To SNRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Share To SNRelease.entitlements"; sourceTree = "<group>"; };
|
||||
07CAB75B2A618128008FE1EF /* Share To SN.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Share To SN.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
07CAB75D2A618128008FE1EF /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||
07CAB7602A618128008FE1EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
@@ -158,6 +159,7 @@
|
||||
07CAB75C2A618128008FE1EF /* Share To SN */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0780C8AC2A63ECF400DDE846 /* Share To SNRelease.entitlements */,
|
||||
07CAB76B2A6182D6008FE1EF /* Share To SNDebug.entitlements */,
|
||||
07CAB75D2A618128008FE1EF /* ShareViewController.swift */,
|
||||
07CAB75F2A618128008FE1EF /* MainInterface.storyboard */,
|
||||
@@ -818,12 +820,14 @@
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = "Share To SN/Share To SNRelease.entitlements";
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKF9BXSN95;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Share To SN/Info.plist";
|
||||
@@ -839,7 +843,8 @@
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.standardnotes.standardnotes.Share-To-SN";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.standardnotes.standardnotes.Share-To-SN";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.standardnotes.standardnotes.Share-To-SN";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
|
||||
@@ -8,5 +8,9 @@
|
||||
</array>
|
||||
<key>com.apple.developer.default-data-protection</key>
|
||||
<string>NSFileProtectionComplete</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.standardnotes.standardnotes</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.56.1",
|
||||
"version": "3.56.16",
|
||||
"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()
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
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.46.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.46.10",
|
||||
"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.
|
||||
|
||||
+8
-2
@@ -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
-1
@@ -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
|
||||
+93
@@ -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
|
||||
}
|
||||
}
|
||||
+35
-36
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+5
-6
@@ -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
|
||||
}
|
||||
|
||||
-1
@@ -2,6 +2,5 @@ export interface ContactPublicKeySetJsonInterface {
|
||||
encryption: string
|
||||
signing: string
|
||||
timestamp: Date
|
||||
isRevoked: boolean
|
||||
previousKeySet?: ContactPublicKeySetJsonInterface
|
||||
}
|
||||
|
||||
-8
@@ -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
-1
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -3,6 +3,66 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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.4.383](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.382](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.381](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.380](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-17)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.379](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.378](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.377](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.376](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.375](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.374](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.373](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.4.373",
|
||||
"version": "1.4.388",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
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.13.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/responses",
|
||||
"version": "1.13.29",
|
||||
"version": "1.13.31",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -35,7 +35,6 @@
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "^1.50.0",
|
||||
"@standardnotes/features": "workspace:*",
|
||||
"@standardnotes/security": "^1.7.6",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ValetTokenOperation } from './ValetTokenOperation'
|
||||
import { ValetTokenOperation } from '../Temp/ValetTokenOperation'
|
||||
|
||||
export type CreateValetTokenPayload = {
|
||||
operation: ValetTokenOperation
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export type ValetTokenOperation = 'read' | 'write' | 'delete' | 'move'
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Role } from '@standardnotes/security'
|
||||
import { Role } from '../Temp/Role'
|
||||
|
||||
export type DeprecatedResponseMeta = {
|
||||
auth: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Role } from '@standardnotes/security'
|
||||
import { Role } from '../Temp/Role'
|
||||
|
||||
export type HttpResponseMeta = {
|
||||
auth: {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export type Role = {
|
||||
uuid: string
|
||||
name: string
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export type Subscription = {
|
||||
planName: string
|
||||
endsAt: number
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
cancelled: boolean
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export enum ValetTokenOperation {
|
||||
Read = 'read',
|
||||
Write = 'write',
|
||||
Delete = 'delete',
|
||||
Move = 'move',
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Subscription } from '@standardnotes/security'
|
||||
import { Subscription } from '../Temp/Subscription'
|
||||
|
||||
export type GetSubscriptionResponse = {
|
||||
subscription?: Subscription
|
||||
|
||||
@@ -14,6 +14,12 @@ export * from './Auth/SignInResponse'
|
||||
export * from './Auth/SignOutResponse'
|
||||
export * from './Auth/User'
|
||||
|
||||
/** Temps are awaiting final publish state on server repo */
|
||||
export * from './Temp/SharedVaultMoveType'
|
||||
export * from './Temp/Subscription'
|
||||
export * from './Temp/ValetTokenOperation'
|
||||
export * from './Temp/Role'
|
||||
|
||||
export * from './Error/ClientDisplayableError'
|
||||
|
||||
export * from './Files/CloseUploadSessionResponse'
|
||||
@@ -24,7 +30,6 @@ export * from './Files/DownloadFileChunkResponse'
|
||||
export * from './Files/StartUploadSessionResponse'
|
||||
export * from './Files/UploadFileChunkResponse'
|
||||
export * from './Files/MoveFileResponse'
|
||||
export * from './Files/ValetTokenOperation'
|
||||
|
||||
export * from './Http'
|
||||
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
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.63.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.63.4",
|
||||
"version": "1.63.6",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -24,7 +24,6 @@
|
||||
"@standardnotes/files": "workspace:^",
|
||||
"@standardnotes/models": "workspace:^",
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
"@standardnotes/security": "^1.7.5",
|
||||
"@standardnotes/sncrypto-common": "workspace:^",
|
||||
"@standardnotes/utils": "workspace:*",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
|
||||
+3
-1
@@ -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,4 +1,4 @@
|
||||
import { Role } from '@standardnotes/security'
|
||||
import { Role } from '@standardnotes/responses'
|
||||
|
||||
export type MetaReceivedData = {
|
||||
userUuid: string
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
-23
@@ -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
|
||||
}
|
||||
}
|
||||
-17
@@ -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
-1
@@ -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
-1
@@ -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
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -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
|
||||
})
|
||||
}
|
||||
+17
-24
@@ -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,
|
||||
)
|
||||
|
||||
+4
-8
@@ -1,4 +1,3 @@
|
||||
import { ContactServiceInterface } from './../../Contacts/ContactServiceInterface'
|
||||
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
|
||||
import {
|
||||
KeySystemRootKeyInterface,
|
||||
@@ -11,12 +10,13 @@ import {
|
||||
} from '@standardnotes/models'
|
||||
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { CreateOrEditContact } from '../../Contacts/UseCase/CreateOrEditContact'
|
||||
|
||||
export class HandleTrustedSharedVaultInviteMessage {
|
||||
export class ProcessAcceptedVaultInvite {
|
||||
constructor(
|
||||
private mutator: MutatorClientInterface,
|
||||
private sync: SyncServiceInterface,
|
||||
private contacts: ContactServiceInterface,
|
||||
private createOrEditContact: CreateOrEditContact,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
@@ -47,11 +47,7 @@ export class HandleTrustedSharedVaultInviteMessage {
|
||||
await this.mutator.createItem(ContentType.TYPES.VaultListing, FillItemContentSpecialized(content), true)
|
||||
|
||||
for (const contact of trustedContacts) {
|
||||
if (contact.isMe) {
|
||||
throw new Error('Should not receive isMe contact from invite')
|
||||
}
|
||||
|
||||
await this.contacts.createOrEditTrustedContact({
|
||||
await this.createOrEditContact.execute({
|
||||
name: contact.name,
|
||||
contactUuid: contact.contactUuid,
|
||||
publicKey: contact.publicKeySet.encryption,
|
||||
@@ -0,0 +1,58 @@
|
||||
import { AsymmetricMessageServerHash, isErrorResponse } from '@standardnotes/responses'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { AsymmetricMessageServerInterface } from '@standardnotes/api'
|
||||
import { ResendMessage } from './ResendMessage'
|
||||
import { FindContact } from '../../Contacts/UseCase/FindContact'
|
||||
|
||||
export class ResendAllMessages implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private resendMessage: ResendMessage,
|
||||
private messageServer: AsymmetricMessageServerInterface,
|
||||
private findContact: FindContact,
|
||||
) {}
|
||||
|
||||
async execute(params: {
|
||||
keys: {
|
||||
encryption: PkcKeyPair
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
previousKeys?: {
|
||||
encryption: PkcKeyPair
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
}): Promise<Result<AsymmetricMessageServerHash[]>> {
|
||||
const messages = await this.messageServer.getOutboundUserMessages()
|
||||
|
||||
if (isErrorResponse(messages)) {
|
||||
return Result.fail('Failed to get outbound user messages')
|
||||
}
|
||||
|
||||
const errors: string[] = []
|
||||
|
||||
for (const message of messages.data.messages) {
|
||||
const recipient = this.findContact.execute({ userUuid: message.user_uuid })
|
||||
if (recipient) {
|
||||
errors.push(`Contact not found for invite ${message.user_uuid}`)
|
||||
continue
|
||||
}
|
||||
|
||||
await this.resendMessage.execute({
|
||||
keys: params.keys,
|
||||
previousKeys: params.previousKeys,
|
||||
message: message,
|
||||
recipient,
|
||||
})
|
||||
|
||||
await this.messageServer.deleteMessage({
|
||||
messageUuid: message.uuid,
|
||||
})
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Result.fail(errors.join(', '))
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user