Compare commits

...
Author SHA1 Message Date
StandardNotes CI 3a75afc817 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].8
2023-07-10 09:29:39 +00:00
Aman Harwara e4488dcb8c chore: take popover offset into account when fitting inside rect 2023-07-10 14:29:49 +05:30
StandardNotes CI bb997312e8 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].7
2023-07-07 15:19:11 +00:00
Aman Harwara b229a96e58 chore: bump node version 2023-07-07 20:16:21 +05:30
Aman Harwara 7f0bb4e127 refactor: use Fetch API for HttpService (#2349) 2023-07-07 19:26:31 +05:30
StandardNotes CI bf9449b67d chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
2023-07-07 06:37:18 +00:00
Aman Harwara 67359801bb chore: fix PageUp and PageDown not scrolling in preferences view 2023-07-06 22:13:34 +05:30
StandardNotes CI 53e978c30e chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
2023-07-06 15:33:57 +00:00
Aman Harwara ebc9fcb995 chore: fix blank screen issue when moving iOS floating keyboard 2023-07-06 20:30:49 +05:30
StandardNotes CI 7923e9f198 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].4
2023-07-06 14:32:38 +00:00
Aman Harwara 9081607dc7 chore: fix challenge modal automatically closing 2023-07-06 19:27:14 +05:30
Mo c8e52b667c internal: change password preprocessing step (#2347) 2023-07-06 08:51:06 -05:00
StandardNotes CI 5c6ccaf4e1 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].3
2023-07-05 16:57:29 +00:00
Mo 0ab6b5c6fe chore(fix): fixes sync response not correctly identifying all errors (#2346) 2023-07-05 11:28:45 -05:00
StandardNotes CI 2a050fd966 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].2
2023-07-05 16:26:01 +00:00
Mo 40f0d3d5bf chore: merge 2023-07-05 10:54:16 -05:00
Mo c9c93d143a internal: move home server into internal flag 2023-07-05 10:53:10 -05:00
StandardNotes CI a52828cd45 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2023-07-05 15:50:04 +00:00
Aman Harwara 2611c88cee chore: fix modal positioning when using themed desktop titlebar 2023-07-05 20:43:55 +05:30
StandardNotes CI d4db3e055c chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].0
2023-07-04 15:29:47 +00:00
Karol Sójko 05528782a4 feat: New one-click Home Server, now in Labs. Launch your own self-hosted server instance with just 1 click from the Preferences window. (#2345)
* fix(desktop): add home server coming-soon message on windows platform

* fix(desktop): typos and remove feature block

* fix(desktop): instantiate home server only upon starting
2023-07-04 16:55:37 +02:00
StandardNotes CI 9664d31c4e chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].3
2023-07-04 13:02:42 +00:00
Mo b06999d25b refactor: root key manager (#2344) 2023-07-04 07:31:50 -05:00
StandardNotes CI b4a90025c4 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2023-07-04 09:33:32 +00:00
Aman Harwara 31b42553be chore: improve super formatting toolbar positioning 2023-07-04 14:33:24 +05:30
StandardNotes CI 41311801c0 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
2023-07-03 19:20:31 +00:00
Mo ccc9d73b3b chore: make home server internal feature 2023-07-03 13:53:03 -05:00
160 changed files with 3149 additions and 1812 deletions
+1 -1
View File
@@ -1 +1 @@
16.15.1
18.6.0
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/api
## [1.26.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/api
## [1.26.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/api
## [1.26.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/api
## [1.26.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/api
+2 -1
View File
@@ -6,4 +6,5 @@ module.exports = {
transform: {
'^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.json' }],
},
};
coverageThreshold: {},
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.26.19",
"version": "1.26.23",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -0,0 +1,144 @@
import { Environment } from '@standardnotes/models'
import { HttpVerb } from '@standardnotes/responses'
import { FetchRequestHandler } from './FetchRequestHandler'
import { HttpErrorResponseBody, HttpRequest } from '@standardnotes/responses'
import { ErrorMessage } from '../Error'
describe('FetchRequestHandler', () => {
const snjsVersion = 'snjsVersion'
const appVersion = 'appVersion'
const environment = Environment.Web
const requestHandler = new FetchRequestHandler(snjsVersion, appVersion, environment)
it('should create a request', () => {
const httpRequest: HttpRequest = {
url: 'http://localhost:3000/test',
verb: HttpVerb.Get,
external: false,
authentication: 'authentication',
customHeaders: [],
params: {
key: 'value',
},
}
const request = requestHandler['createRequest'](httpRequest)
expect(request).toBeInstanceOf(Request)
expect(request.url).toBe(httpRequest.url)
expect(request.method).toBe(httpRequest.verb)
expect(request.headers.get('X-SNJS-Version')).toBe(snjsVersion)
expect(request.headers.get('X-Application-Version')).toBe(`${Environment[environment]}-${appVersion}`)
expect(request.headers.get('Content-Type')).toBe('application/json')
})
it('should get url for url and params', () => {
const urlWithoutExistingParams = requestHandler['urlForUrlAndParams']('url', { key: 'value' })
expect(urlWithoutExistingParams).toBe('url?key=value')
const urlWithExistingParams = requestHandler['urlForUrlAndParams']('url?key=value', { key2: 'value2' })
expect(urlWithExistingParams).toBe('url?key=value&key2=value2')
})
it('should create request body if not GET', () => {
const body = requestHandler['createRequestBody']({
url: 'url',
verb: HttpVerb.Post,
external: false,
authentication: 'authentication',
customHeaders: [],
params: {
key: 'value',
},
})
expect(body).toBe('{"key":"value"}')
})
it('should not create request body if GET', () => {
const body = requestHandler['createRequestBody']({
url: 'url',
verb: HttpVerb.Get,
external: false,
authentication: 'authentication',
customHeaders: [],
params: {
key: 'value',
},
})
expect(body).toBeUndefined()
})
it('should handle json response', async () => {
const fetchResponse = new Response('{"key":"value"}', {
status: 200,
headers: {
'Content-Type': 'application/json',
},
})
const response = await requestHandler['handleFetchResponse'](fetchResponse)
expect(response).toEqual({
status: 200,
headers: new Map<string, string | null>([['content-type', 'application/json']]),
data: {
key: 'value',
},
key: 'value',
})
})
it('should handle non-json response', async () => {
const fetchResponse = new Response('body', {
status: 200,
headers: {
'Content-Type': 'text/plain',
},
})
const response = await requestHandler['handleFetchResponse'](fetchResponse)
expect(response.status).toBe(200)
expect(response.headers).toEqual(new Map<string, string | null>([['content-type', 'text/plain']]))
expect(response.data).toBeInstanceOf(ArrayBuffer)
})
it('should have ratelimit error when forbidden', async () => {
const fetchResponse = new Response('body', {
status: 403,
headers: {
'Content-Type': 'text/plain',
},
})
const response = await requestHandler['handleFetchResponse'](fetchResponse)
expect(response.status).toBe(403)
expect(response.headers).toEqual(new Map<string, string | null>([['content-type', 'text/plain']]))
expect((response.data as HttpErrorResponseBody).error).toEqual({
message: ErrorMessage.RateLimited,
})
})
describe('should return ErrorResponse when status is not >=200 and <500', () => {
it('should add unknown error message when response has no data', async () => {
const fetchResponse = new Response('', {
status: 599,
headers: {
'Content-Type': 'text/plain',
},
})
const response = await requestHandler['handleFetchResponse'](fetchResponse)
expect(response.status).toBe(599)
expect(response.headers).toEqual(new Map<string, string | null>([['content-type', 'text/plain']]))
expect((response.data as HttpErrorResponseBody).error).toEqual({
message: 'Unknown error',
})
})
})
})
@@ -0,0 +1,178 @@
import {
HttpErrorResponse,
HttpRequest,
HttpRequestParams,
HttpResponse,
HttpStatusCode,
HttpVerb,
isErrorResponse,
} from '@standardnotes/responses'
import { RequestHandlerInterface } from './RequestHandlerInterface'
import { Environment } from '@standardnotes/models'
import { isString } from 'lodash'
import { ErrorMessage } from '../Error'
export class FetchRequestHandler implements RequestHandlerInterface {
constructor(
protected readonly snjsVersion: string,
protected readonly appVersion: string,
protected readonly environment: Environment,
) {}
async handleRequest<T>(httpRequest: HttpRequest): Promise<HttpResponse<T>> {
const request = this.createRequest(httpRequest)
const response = await this.runRequest<T>(request, this.createRequestBody(httpRequest))
return response
}
private createRequest(httpRequest: HttpRequest): Request {
if (httpRequest.params && httpRequest.verb === HttpVerb.Get && Object.keys(httpRequest.params).length > 0) {
httpRequest.url = this.urlForUrlAndParams(httpRequest.url, httpRequest.params)
}
const headers: Record<string, string> = {}
if (!httpRequest.external) {
headers['X-SNJS-Version'] = this.snjsVersion
const appVersionHeaderValue = `${Environment[this.environment]}-${this.appVersion}`
headers['X-Application-Version'] = appVersionHeaderValue
if (httpRequest.authentication) {
headers['Authorization'] = 'Bearer ' + httpRequest.authentication
}
}
let contentTypeIsSet = false
if (httpRequest.customHeaders && httpRequest.customHeaders.length > 0) {
httpRequest.customHeaders.forEach(({ key, value }) => {
headers[key] = value
if (key === 'Content-Type') {
contentTypeIsSet = true
}
})
}
if (!contentTypeIsSet && !httpRequest.external) {
headers['Content-Type'] = 'application/json'
}
return new Request(httpRequest.url, {
method: httpRequest.verb,
headers,
})
}
private async runRequest<T>(request: Request, body?: string | Uint8Array | undefined): Promise<HttpResponse<T>> {
const fetchResponse = await fetch(request, {
body,
})
const response = await this.handleFetchResponse<T>(fetchResponse)
return response
}
private async handleFetchResponse<T>(fetchResponse: Response): Promise<HttpResponse<T>> {
const httpStatus = fetchResponse.status
const response: HttpResponse<T> = {
status: httpStatus,
headers: new Map<string, string | null>(),
data: {} as T,
}
fetchResponse.headers.forEach((value, key) => {
;(<Map<string, string | null>>response.headers).set(key, value)
})
try {
if (httpStatus !== HttpStatusCode.NoContent) {
let body
const contentTypeHeader = response.headers?.get('content-type') || response.headers?.get('Content-Type')
if (contentTypeHeader?.includes('application/json')) {
body = JSON.parse(await fetchResponse.text())
} else {
body = await fetchResponse.arrayBuffer()
}
/**
* v0 APIs do not have a `data` top-level object. In such cases, mimic
* the newer response body style by putting all the top-level
* properties inside a `data` object.
*/
if (!body.data) {
response.data = body
}
if (!isString(body)) {
Object.assign(response, body)
}
}
} catch (error) {
console.error(error)
}
if (httpStatus >= HttpStatusCode.Success && httpStatus < HttpStatusCode.InternalServerError) {
if (httpStatus === HttpStatusCode.Forbidden && isErrorResponse(response)) {
if (!response.data.error) {
response.data.error = {
message: ErrorMessage.RateLimited,
}
} else {
response.data.error.message = ErrorMessage.RateLimited
}
}
return response
} else {
const errorResponse = response as HttpErrorResponse
if (!errorResponse.data) {
errorResponse.data = {
error: {
message: 'Unknown error',
},
}
}
if (isString(errorResponse.data)) {
errorResponse.data = {
error: {
message: errorResponse.data,
},
}
}
if (!errorResponse.data.error) {
errorResponse.data.error = {
message: 'Unknown error',
}
}
return errorResponse
}
}
private urlForUrlAndParams(url: string, params: HttpRequestParams) {
const keyValueString = Object.keys(params as Record<string, unknown>)
.map((key) => {
return key + '=' + encodeURIComponent((params as Record<string, unknown>)[key] as string)
})
.join('&')
if (url.includes('?')) {
return url + '&' + keyValueString
} else {
return url + '?' + keyValueString
}
}
private createRequestBody(httpRequest: HttpRequest): string | Uint8Array | undefined {
if (
httpRequest.params !== undefined &&
[HttpVerb.Post, HttpVerb.Put, HttpVerb.Patch, HttpVerb.Delete].includes(httpRequest.verb)
) {
return JSON.stringify(httpRequest.params)
}
return httpRequest.rawBytes
}
}
+7 -162
View File
@@ -1,4 +1,4 @@
import { isString, joinPaths, sleep } from '@standardnotes/utils'
import { joinPaths, sleep } from '@standardnotes/utils'
import { Environment } from '@standardnotes/models'
import { Session, SessionToken } from '@standardnotes/domain-core'
import {
@@ -8,15 +8,14 @@ import {
HttpRequest,
HttpResponse,
HttpResponseMeta,
HttpErrorResponse,
isErrorResponse,
} from '@standardnotes/responses'
import { HttpServiceInterface } from './HttpServiceInterface'
import { XMLHttpRequestState } from './XMLHttpRequestState'
import { ErrorMessage } from '../Error/ErrorMessage'
import { Paths } from '../Server/Auth/Paths'
import { SessionRefreshResponseBody } from '../Response/Auth/SessionRefreshResponseBody'
import { FetchRequestHandler } from './FetchRequestHandler'
import { RequestHandlerInterface } from './RequestHandlerInterface'
export class HttpService implements HttpServiceInterface {
private session: Session | null
@@ -27,8 +26,11 @@ export class HttpService implements HttpServiceInterface {
private updateMetaCallback!: (meta: HttpResponseMeta) => void
private refreshSessionCallback!: (session: Session) => void
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)
}
setCallbacks(
@@ -131,9 +133,7 @@ export class HttpService implements HttpServiceInterface {
httpRequest.authentication = this.session?.accessToken.value
}
const request = this.createXmlRequest(httpRequest)
const response = await this.runRequest<T>(request, this.createRequestBody(httpRequest))
const response = await this.requestHandler.handleRequest<T>(httpRequest)
if (response.meta && !httpRequest.external) {
this.updateMetaCallback?.(response.meta)
@@ -209,159 +209,4 @@ export class HttpService implements HttpServiceInterface {
return true
}
private createRequestBody(httpRequest: HttpRequest): string | Uint8Array | undefined {
if (
httpRequest.params !== undefined &&
[HttpVerb.Post, HttpVerb.Put, HttpVerb.Patch, HttpVerb.Delete].includes(httpRequest.verb)
) {
return JSON.stringify(httpRequest.params)
}
return httpRequest.rawBytes
}
private createXmlRequest(httpRequest: HttpRequest) {
const request = new XMLHttpRequest()
if (httpRequest.params && httpRequest.verb === HttpVerb.Get && Object.keys(httpRequest.params).length > 0) {
httpRequest.url = this.urlForUrlAndParams(httpRequest.url, httpRequest.params)
}
request.open(httpRequest.verb, httpRequest.url, true)
request.responseType = httpRequest.responseType ?? ''
if (!httpRequest.external) {
request.setRequestHeader('X-SNJS-Version', this.snjsVersion)
const appVersionHeaderValue = `${Environment[this.environment]}-${this.appVersion}`
request.setRequestHeader('X-Application-Version', appVersionHeaderValue)
if (httpRequest.authentication) {
request.setRequestHeader('Authorization', 'Bearer ' + httpRequest.authentication)
}
}
let contenTypeIsSet = false
if (httpRequest.customHeaders && httpRequest.customHeaders.length > 0) {
httpRequest.customHeaders.forEach(({ key, value }) => {
request.setRequestHeader(key, value)
if (key === 'Content-Type') {
contenTypeIsSet = true
}
})
}
if (!contenTypeIsSet && !httpRequest.external) {
request.setRequestHeader('Content-Type', 'application/json')
}
return request
}
private async runRequest<T>(request: XMLHttpRequest, body?: string | Uint8Array): Promise<HttpResponse<T>> {
return new Promise((resolve) => {
request.onreadystatechange = () => {
this.stateChangeHandlerForRequest(request, resolve)
}
request.send(body)
})
}
private stateChangeHandlerForRequest<T>(request: XMLHttpRequest, resolve: (response: HttpResponse<T>) => void) {
if (request.readyState !== XMLHttpRequestState.Completed) {
return
}
const httpStatus = request.status
const response: HttpResponse<T> = {
status: httpStatus,
headers: new Map<string, string | null>(),
data: {} as T,
}
const responseHeaderLines = request
.getAllResponseHeaders()
?.trim()
.split(/[\r\n]+/)
responseHeaderLines?.forEach((responseHeaderLine) => {
const parts = responseHeaderLine.split(': ')
const name = parts.shift() as string
const value = parts.join(': ')
;(<Map<string, string | null>>response.headers).set(name, value)
})
try {
if (httpStatus !== HttpStatusCode.NoContent) {
let body
const contentTypeHeader = response.headers?.get('content-type') || response.headers?.get('Content-Type')
if (contentTypeHeader?.includes('application/json')) {
body = JSON.parse(request.responseText)
} else {
body = request.response
}
/**
* v0 APIs do not have a `data` top-level object. In such cases, mimic
* the newer response body style by putting all the top-level
* properties inside a `data` object.
*/
if (!body.data) {
response.data = body
}
if (!isString(body)) {
Object.assign(response, body)
}
}
} catch (error) {
console.error(error)
}
if (httpStatus >= HttpStatusCode.Success && httpStatus < HttpStatusCode.InternalServerError) {
if (
httpStatus === HttpStatusCode.Forbidden &&
response.data &&
(response as HttpErrorResponse).data.error !== undefined
) {
;(response as HttpErrorResponse).data.error.message = ErrorMessage.RateLimited
}
resolve(response)
} else {
const errorResponse = response as HttpErrorResponse
if (!errorResponse.data) {
errorResponse.data = {
error: {
message: 'Unknown error',
},
}
}
if (isString(errorResponse.data)) {
errorResponse.data = {
error: {
message: errorResponse.data,
},
}
}
if (!errorResponse.data.error) {
errorResponse.data.error = {
message: 'Unknown error',
}
}
resolve(errorResponse)
}
}
private urlForUrlAndParams(url: string, params: HttpRequestParams) {
const keyValueString = Object.keys(params as Record<string, unknown>)
.map((key) => {
return key + '=' + encodeURIComponent((params as Record<string, unknown>)[key] as string)
})
.join('&')
if (url.includes('?')) {
return url + '&' + keyValueString
} else {
return url + '?' + keyValueString
}
}
}
@@ -0,0 +1,5 @@
import { HttpRequest, HttpResponse } from '@standardnotes/responses'
export interface RequestHandlerInterface {
handleRequest<T>(httpRequest: HttpRequest): Promise<HttpResponse<T>>
}
+48
View File
@@ -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.77](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-10)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.76](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.75](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.74](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.73](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.72](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.71](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.70](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.69](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.68](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.67](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.66](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.65](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/clipper
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/clipper",
"description": "Web clipper browser extension for Standard Notes",
"version": "1.1.65",
"version": "1.1.77",
"private": true,
"scripts": {
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
+50
View File
@@ -3,6 +3,56 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.108.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-10)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/desktop
# [3.108.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
### Features
* New one-click Home Server, now in Labs. Launch your own self-hosted server instance with just 1 click from the Preferences window. ([#2345](https://github.com/standardnotes/app/issues/2345)) ([0552878](https://github.com/standardnotes/app/commit/05528782a4e9684dbd86fd12b77eb86727d6c414))
## [3.107.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.107.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.107.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/desktop
# [3.107.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
### Features
+1 -1
View File
@@ -28,6 +28,7 @@ export function initializeApplication(args: { app: Electron.App; ipcMain: Electr
if (isDev()) {
/** Expose the app's state as a global variable. Useful for debugging */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(global as any).appState = state
setTimeout(() => {
@@ -124,7 +125,6 @@ async function finishApplicationInitialization({ app, shell, state }: { app: App
})
if (state.isRunningVersionForFirstTime()) {
console.log('Clearing window cache')
await windowState.window.webContents.session.clearCache()
}
@@ -10,6 +10,7 @@ import { WebContents } from 'electron'
import { MessageToWebApp } from '../../Shared/IpcMessages'
import { FilesManagerInterface } from '../File/FilesManagerInterface'
import { HomeServerConfigurationFile } from './HomeServerConfigurationFile'
import { isWindows } from '../Types/Platforms'
const os = require('os')
@@ -23,11 +24,9 @@ export class HomeServerManager implements HomeServerManagerInterface {
private readonly LOGS_BUFFER_SIZE = 1000
constructor(
private homeServer: HomeServerInterface,
private webContents: WebContents,
private filesManager: FilesManagerInterface,
) {}
private homeServer?: HomeServerInterface
constructor(private webContents: WebContents, private filesManager: FilesManagerInterface) {}
async getHomeServerUrl(): Promise<string | undefined> {
const homeServerConfiguration = await this.getHomeServerConfigurationObject()
@@ -39,7 +38,7 @@ export class HomeServerManager implements HomeServerManagerInterface {
}
async isHomeServerRunning(): Promise<boolean> {
return this.homeServer.isRunning()
return this.homeServer?.isRunning() ?? false
}
async getHomeServerLastErrorMessage(): Promise<string | undefined> {
@@ -47,6 +46,10 @@ export class HomeServerManager implements HomeServerManagerInterface {
}
async activatePremiumFeatures(username: string): Promise<string | undefined> {
if (!this.homeServer) {
return
}
const result = await this.homeServer.activatePremiumFeatures(username)
if (result.isFailed()) {
@@ -111,6 +114,10 @@ export class HomeServerManager implements HomeServerManagerInterface {
}
async stopHomeServer(): Promise<string | undefined> {
if (!this.homeServer) {
return
}
const result = await this.homeServer.stop()
if (result.isFailed()) {
return result.getError()
@@ -120,6 +127,12 @@ export class HomeServerManager implements HomeServerManagerInterface {
}
async startHomeServer(): Promise<string | undefined> {
await this.lazyLoadHomeServerOnApplicablePlatforms()
if (!this.homeServer) {
return
}
try {
this.lastErrorMessage = undefined
this.logs = []
@@ -251,4 +264,18 @@ export class HomeServerManager implements HomeServerManagerInterface {
return configuration
}
private async lazyLoadHomeServerOnApplicablePlatforms(): Promise<void> {
if (isWindows()) {
return
}
if (this.homeServer) {
return
}
const { HomeServer } = await import('@standardnotes/home-server')
this.homeServer = new HomeServer()
}
}
@@ -25,7 +25,6 @@ import { handleTestMessage, send } from './Utils/Testing'
import { isTesting, lowercaseDriveLetter } from './Utils/Utils'
import { initializeZoomManager } from './ZoomManager'
import { HomeServerManager } from './HomeServer/HomeServerManager'
import { HomeServer } from '@standardnotes/home-server'
import { FilesManager } from './File/FilesManager'
import { DirectoryManager } from './Directory/DirectoryManager'
@@ -62,6 +61,7 @@ export async function createWindowState({
const services = await createWindowServices(window, appState, appLocale)
require('@electron/remote/main').enable(window.webContents)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(global as any).RemoteBridge = new RemoteBridge(
window,
Keychain,
@@ -206,11 +206,11 @@ async function createWindowServices(window: Electron.BrowserWindow, appState: Ap
const trayManager = createTrayManager(window, appState.store)
const spellcheckerManager = createSpellcheckerManager(appState.store, window.webContents, appLocale)
const mediaManager = new MediaManager()
const homeServer = new HomeServer()
const filesManager = new FilesManager()
const directoryManager = new DirectoryManager(filesManager)
const homeServerManager = new HomeServerManager(homeServer, window.webContents, filesManager)
const homeServerManager = new HomeServerManager(window.webContents, filesManager)
if (isTesting()) {
handleTestMessage(MessageType.SpellCheckerManager, () => spellcheckerManager)
@@ -22,6 +22,8 @@ process.once('loaded', function () {
setHomeServerStartedHandler: (handler: MainEventHandler) =>
ipcRenderer.on(MessageToWebApp.HomeServerStarted, handler),
setConsoleLogHandler: (handler: MainEventHandler) => ipcRenderer.on(MessageToWebApp.ConsoleLog, handler),
}
contextBridge.exposeInMainWorld('electronMainEvents', mainEvents)
@@ -128,7 +128,7 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
/* Use custom title bar. Take the sn-titlebar-height off of
the app content height so its not overflowing */
sheet.insertRule(
'[role="dialog"] { position: relative; height: calc(100vh - var(--sn-desktop-titlebar-height)) !important; margin-top: var(--sn-desktop-titlebar-height) !important; }',
'[role="dialog"] { height: calc(100vh - var(--sn-desktop-titlebar-height)) !important; top: var(--sn-desktop-titlebar-height); }',
sheet.cssRules.length,
)
sheet.insertRule(
@@ -150,8 +150,13 @@ window.electronMainEvents.setWindowFocusedHandler(() => {
window.webClient.windowGainedFocus()
})
window.electronMainEvents.setInstallComponentCompleteHandler((_: IpcRendererEvent, data: any) => {
void window.webClient.onComponentInstallationComplete(data.component, undefined)
window.electronMainEvents.setConsoleLogHandler((_: IpcRendererEvent, message: unknown) => {
window.webClient.consoleLog(message as string)
})
window.electronMainEvents.setInstallComponentCompleteHandler((_: IpcRendererEvent, data: unknown) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
void window.webClient.onComponentInstallationComplete((data as any).component, undefined)
})
window.electronMainEvents.setWatchedDirectoriesChangeHandler((_: IpcRendererEvent, changes: unknown) => {
@@ -9,4 +9,5 @@ export interface ElectronMainEvents {
setInstallComponentCompleteHandler(handler: MainEventHandler): void
setWatchedDirectoriesChangeHandler(handler: MainEventHandler): void
setHomeServerStartedHandler(handler: MainEventHandler): void
setConsoleLogHandler(handler: MainEventHandler): void
}
@@ -5,6 +5,7 @@ export enum MessageToWebApp {
InstallComponentComplete = 'install-component-complete',
WatchedDirectoriesChanges = 'watched-directories-changes',
HomeServerStarted = 'home-server-started',
ConsoleLog = 'console-log',
}
export enum MessageToMainProcess {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.107.0",
"version": "3.108.8",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
+1
View File
@@ -2,6 +2,7 @@
"compilerOptions": {
"target": "ES2019",
"moduleResolution": "node",
"module": "es2020",
"types": ["node"],
"allowJs": true,
"noEmit": false,
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.48](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.47](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.46](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.45](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/encryption
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.21.45",
"version": "1.21.48",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -1,3 +0,0 @@
export enum RootKeyServiceEvent {
RootKeyStatusChanged = 'RootKeyStatusChanged',
}
-1
View File
@@ -36,7 +36,6 @@ export * from './Service/Encryption/EncryptionProviderInterface'
export * from './Service/KeySystemKeyManagerInterface'
export * from './Service/Functions'
export * from './Service/RootKey/KeyMode'
export * from './Service/RootKey/RootKeyServiceEvent'
export * from './Split/AbstractKeySplit'
export * from './Split/EncryptionSplit'
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.28.57](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.28.56](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.28.55](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.28.54](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/filepicker
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/filepicker",
"version": "1.28.54",
"version": "1.28.57",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.16.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/files
## [1.16.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/files
## [1.16.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/files
# [1.16.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files",
"version": "1.16.0",
"version": "1.16.3",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+48
View File
@@ -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.54.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-10)
**Note:** Version bump only for package @standardnotes/mobile
## [3.54.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/mobile
## [3.54.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/mobile
## [3.54.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/mobile
## [3.54.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/mobile
## [3.54.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/mobile
## [3.54.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/mobile
## [3.54.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/mobile
## [3.54.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.54.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.54.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.54.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/mobile
# [3.54.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
### Features
+2 -2
View File
@@ -637,7 +637,7 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
boost: a7c83b31436843459a1961bfd74b96033dc77234
boost: 57d2868c099736d80fcd648bf211b4431e51a558
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: 60195509584153283780abdac5569feffb8f08cc
@@ -658,7 +658,7 @@ SPEC CHECKSUMS:
MMKV: 9c4663aa7ca255d478ff10f2f5cb7d17c1651ccd
MMKVCore: 89f5c8a66bba2dcd551779dea4d412eeec8ff5bb
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCTRequired: bec48f07daf7bcdc2655a0cde84e07d24d2a9e2a
RCTTypeSafety: 171394eebacf71e1cfad79dbfae7ee8fc16ca80a
React: d7433ccb6a8c36e4cbed59a73c0700fc83c3e98a
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.54.0",
"version": "3.54.12",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0-or-later",
+8
View File
@@ -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.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/models
## [1.46.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/models
## [1.46.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-06-30)
**Note:** Version bump only for package @standardnotes/models
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/models",
"version": "1.46.5",
"version": "1.46.7",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -2,6 +2,11 @@ export enum AsymmetricMessagePayloadType {
ContactShare = 'contact-share',
SharedVaultRootKeyChanged = 'shared-vault-root-key-changed',
SenderKeypairChanged = 'sender-keypair-changed',
SharedVaultInvite = 'shared-vault-invite',
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.
*/
SharedVaultInvite = 'shared-vault-invite',
}
+48
View File
@@ -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.4.354](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-10)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.353](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.352](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.351](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.350](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.349](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.348](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.347](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.346](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.345](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.344](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.343](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.342](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/releases
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/releases",
"version": "1.4.342",
"version": "1.4.354",
"license": "AGPL-3.0-or-later",
"main": "dist/releases.json",
"types": "dist/index.d.ts",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.13.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/responses
## [1.13.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-06-30)
**Note:** Version bump only for package @standardnotes/responses
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/responses",
"version": "1.13.25",
"version": "1.13.26",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -1,4 +1,4 @@
import { HttpErrorResponse } from '../Http'
import { HttpErrorResponse, getErrorFromErrorResponse } from '../Http'
export class ClientDisplayableError {
constructor(public text: string, public title?: string, public tag?: string) {
@@ -14,7 +14,7 @@ export class ClientDisplayableError {
}
static FromNetworkError(error: HttpErrorResponse) {
return new ClientDisplayableError(error.data.error.message)
return new ClientDisplayableError(getErrorFromErrorResponse(error).message)
}
}
@@ -1,5 +1,5 @@
import { HttpError } from './HttpError'
export type HttpErrorResponseBody = {
error: HttpError
error?: HttpError
}
@@ -2,6 +2,7 @@ import { HttpErrorResponseBody } from './HttpErrorResponseBody'
import { HttpResponseMeta } from './HttpResponseMeta'
import { HttpHeaders } from './HttpHeaders'
import { HttpStatusCode } from './HttpStatusCode'
import { HttpError } from './HttpError'
type AnySuccessRecord = Record<string, unknown> & { error?: never }
@@ -24,3 +25,14 @@ export type HttpResponse<T = AnySuccessRecord> = HttpErrorResponse | HttpSuccess
export function isErrorResponse<T>(response: HttpResponse<T>): response is HttpErrorResponse {
return (response.data as HttpErrorResponseBody)?.error != undefined || response.status >= 400
}
export function getErrorFromErrorResponse(response: HttpErrorResponse): HttpError {
const embeddedError = response.data.error
if (embeddedError) {
return embeddedError
}
return {
message: 'Unknown error',
}
}
+30
View File
@@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.62.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/services
## [1.62.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/services
## [1.62.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/services
## [1.62.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/services
# [1.62.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
### Features
* New one-click Home Server, now in Labs. Launch your own self-hosted server instance with just 1 click from the Preferences window. ([#2345](https://github.com/standardnotes/app/issues/2345)) ([0552878](https://github.com/standardnotes/app/commit/05528782a4e9684dbd86fd12b77eb86727d6c414))
## [1.61.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/services
## [1.61.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/services
# [1.61.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/services",
"version": "1.61.0",
"version": "1.62.4",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -1,3 +1,4 @@
import { AsymmetricMessageServiceInterface } from './../AsymmetricMessage/AsymmetricMessageServiceInterface'
import { SyncOptions } from './../Sync/SyncOptions'
import { ImportDataReturnType } from './../Mutator/ImportDataUseCase'
import { ChallengeServiceInterface } from './../Challenge/ChallengeServiceInterface'
@@ -102,6 +103,8 @@ export interface ApplicationInterface {
get vaults(): VaultServiceInterface
get challenges(): ChallengeServiceInterface
get alerts(): AlertService
get asymmetric(): AsymmetricMessageServiceInterface
readonly identifier: ApplicationIdentifier
readonly platform: Platform
deviceInterface: DeviceInterface
@@ -1,6 +1,6 @@
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
import { ContactServiceInterface } from './../Contacts/ContactServiceInterface'
import { AsymmetricMessageServerHash, ClientDisplayableError } from '@standardnotes/responses'
import { AsymmetricMessageServerHash, ClientDisplayableError, isClientDisplayableError } from '@standardnotes/responses'
import { SyncEvent, SyncEventReceivedAsymmetricMessagesData } from '../Event/SyncEvent'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
@@ -27,8 +27,12 @@ import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessa
import { GetOutboundAsymmetricMessages } from './UseCase/GetOutboundAsymmetricMessages'
import { GetInboundAsymmetricMessages } from './UseCase/GetInboundAsymmetricMessages'
import { GetVaultUseCase } from '../Vaults/UseCase/GetVault'
import { AsymmetricMessageServiceInterface } from './AsymmetricMessageServiceInterface'
export class AsymmetricMessageService extends AbstractService implements InternalEventHandlerInterface {
export class AsymmetricMessageService
extends AbstractService
implements AsymmetricMessageServiceInterface, InternalEventHandlerInterface
{
private messageServer: AsymmetricMessageServer
constructor(
@@ -69,7 +73,16 @@ export class AsymmetricMessageService extends AbstractService implements Interna
return usecase.execute()
}
async sendOwnContactChangeEventToAllContacts(data: UserKeyPairChangedEventData): Promise<void> {
public async downloadAndProcessInboundMessages(): Promise<void> {
const messages = await this.getInboundMessages()
if (isClientDisplayableError(messages)) {
return
}
await this.handleRemoteReceivedAsymmetricMessages(messages)
}
private async sendOwnContactChangeEventToAllContacts(data: UserKeyPairChangedEventData): Promise<void> {
if (!data.oldKeyPair || !data.oldSigningKeyPair) {
return
}
@@ -0,0 +1,7 @@
import { AsymmetricMessageServerHash, ClientDisplayableError } from '@standardnotes/responses'
export interface AsymmetricMessageServiceInterface {
getOutboundMessages(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError>
getInboundMessages(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError>
downloadAndProcessInboundMessages(): Promise<void>
}
@@ -8,7 +8,7 @@ export class GetInboundAsymmetricMessages {
const response = await this.messageServer.getMessages()
if (isErrorResponse(response)) {
return ClientDisplayableError.FromError(response.data.error)
return ClientDisplayableError.FromNetworkError(response)
}
return response.data.messages
@@ -8,7 +8,7 @@ export class GetOutboundAsymmetricMessages {
const response = await this.messageServer.getOutboundUserMessages()
if (isErrorResponse(response)) {
return ClientDisplayableError.FromError(response.data.error)
return ClientDisplayableError.FromNetworkError(response)
}
return response.data.messages
@@ -16,7 +16,7 @@ export class SendAsymmetricMessageUseCase {
})
if (isErrorResponse(response)) {
return ClientDisplayableError.FromError(response.data.error)
return ClientDisplayableError.FromNetworkError(response)
}
return response.data.message
@@ -237,7 +237,7 @@ export class ContactService
}
findTrustedContactForInvite(invite: SharedVaultInviteServerHash): TrustedContactInterface | undefined {
return this.findTrustedContact(invite.user_uuid)
return this.findTrustedContact(invite.sender_uuid)
}
getCollaborationIDForTrustedContact(contact: TrustedContactInterface): string {
@@ -21,7 +21,7 @@ import { PublicKeySet } from '@standardnotes/encryption'
export class SelfContactManager {
public selfContact?: TrustedContactInterface
private shouldReloadSelfContact = true
private isReloadingSelfContact = false
private eventDisposers: (() => void)[] = []
@@ -32,16 +32,14 @@ export class SelfContactManager {
private session: SessionsClientInterface,
private singletons: SingletonManagerInterface,
) {
this.eventDisposers.push(
items.addObserver(ContentType.TrustedContact, () => {
this.shouldReloadSelfContact = true
}),
)
this.eventDisposers.push(
sync.addEventObserver((event) => {
if (event === SyncEvent.SyncCompletedWithAllItemsUploaded || event === SyncEvent.LocalDataIncrementalLoad) {
void this.reloadSelfContact()
if (event === SyncEvent.LocalDataIncrementalLoad) {
this.loadSelfContactFromDatabase()
}
if (event === SyncEvent.SyncCompletedWithAllItemsUploaded) {
void this.reloadSelfContactAndCreateIfNecessary()
}
}),
)
@@ -49,13 +47,21 @@ export class SelfContactManager {
public async handleApplicationStage(stage: ApplicationStage): Promise<void> {
if (stage === ApplicationStage.LoadedDatabase_12) {
this.selfContact = this.singletons.findSingleton<TrustedContactInterface>(
ContentType.UserPrefs,
TrustedContact.singletonPredicate,
)
this.loadSelfContactFromDatabase()
}
}
private loadSelfContactFromDatabase(): void {
if (this.selfContact) {
return
}
this.selfContact = this.singletons.findSingleton<TrustedContactInterface>(
ContentType.TrustedContact,
TrustedContact.singletonPredicate,
)
}
public async updateWithNewPublicKeySet(publicKeySet: PublicKeySet) {
if (!InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) {
return
@@ -74,12 +80,16 @@ export class SelfContactManager {
})
}
private async reloadSelfContact() {
private async reloadSelfContactAndCreateIfNecessary() {
if (!InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) {
return
}
if (!this.shouldReloadSelfContact || this.isReloadingSelfContact) {
if (this.selfContact) {
return
}
if (this.isReloadingSelfContact) {
return
}
@@ -105,17 +115,13 @@ export class SelfContactManager {
}),
}
try {
this.selfContact = await this.singletons.findOrCreateSingleton<TrustedContactContent, TrustedContact>(
TrustedContact.singletonPredicate,
ContentType.TrustedContact,
FillItemContent<TrustedContactContent>(content),
)
this.selfContact = await this.singletons.findOrCreateSingleton<TrustedContactContent, TrustedContact>(
TrustedContact.singletonPredicate,
ContentType.TrustedContact,
FillItemContent<TrustedContactContent>(content),
)
this.shouldReloadSelfContact = false
} finally {
this.isReloadingSelfContact = false
}
this.isReloadingSelfContact = false
}
deinit() {
@@ -18,6 +18,8 @@ export interface DesktopClientRequiresWebMethods {
windowLostFocus(): void
consoleLog(message: string): void
onComponentInstallationComplete(componentData: DecryptedTransferPayload, error: unknown): Promise<void>
handleWatchedDirectoriesChanges(changes: DesktopWatchedDirectoriesChanges): Promise<void>
@@ -1,3 +1,5 @@
import { InternalEventInterface } from './../Internal/InternalEventInterface'
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
import {
CreateAnyKeyParams,
@@ -16,9 +18,6 @@ import {
LegacyAttachedData,
OperatorManager,
RootKeyEncryptedAuthenticatedData,
RootKeyServiceEvent,
SNRootKey,
SNRootKeyParams,
SplitPayloadsByEncryptionType,
V001Algorithm,
V002Algorithm,
@@ -47,6 +46,7 @@ import {
KeySystemRootKeyInterface,
KeySystemRootKeyParamsInterface,
TrustedContactInterface,
RootKeyParamsInterface,
} from '@standardnotes/models'
import { ClientDisplayableError } from '@standardnotes/responses'
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
@@ -78,10 +78,20 @@ import { DeviceInterface } from '../Device/DeviceInterface'
import { StorageServiceInterface } from '../Storage/StorageServiceInterface'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { SyncEvent } from '../Event/SyncEvent'
import { RootKeyEncryptionService } from './RootKeyEncryption'
import { DecryptBackupFileUseCase } from './DecryptBackupFileUseCase'
import { EncryptionServiceEvent } from './EncryptionServiceEvent'
import { DecryptedParameters } from '@standardnotes/encryption/src/Domain/Types/DecryptedParameters'
import { RootKeyManager } from './RootKey/RootKeyManager'
import { RootKeyManagerEvent } from './RootKey/RootKeyManagerEvent'
import { CreateNewItemsKeyWithRollbackUseCase } from './UseCase/ItemsKey/CreateNewItemsKeyWithRollback'
import { DecryptErroredRootPayloadsUseCase } from './UseCase/RootEncryption/DecryptErroredPayloads'
import { CreateNewDefaultItemsKeyUseCase } from './UseCase/ItemsKey/CreateNewDefaultItemsKey'
import { RootKeyDecryptPayloadUseCase } from './UseCase/RootEncryption/DecryptPayload'
import { RootKeyDecryptPayloadWithKeyLookupUseCase } from './UseCase/RootEncryption/DecryptPayloadWithKeyLookup'
import { RootKeyEncryptPayloadWithKeyLookupUseCase } from './UseCase/RootEncryption/EncryptPayloadWithKeyLookup'
import { RootKeyEncryptPayloadUseCase } from './UseCase/RootEncryption/EncryptPayload'
import { ValidateAccountPasswordResult } from './RootKey/ValidateAccountPasswordResult'
import { ValidatePasscodeResult } from './RootKey/ValidatePasscodeResult'
/**
* The encryption service is responsible for the encryption and decryption of payloads, and
@@ -110,75 +120,73 @@ import { DecryptedParameters } from '@standardnotes/encryption/src/Domain/Types/
* It also exposes public methods that allows consumers to retrieve an items key
* for a particular payload, and also retrieve all available items keys.
*/
export class EncryptionService extends AbstractService<EncryptionServiceEvent> implements EncryptionProviderInterface {
private operatorManager: OperatorManager
export class EncryptionService
extends AbstractService<EncryptionServiceEvent>
implements EncryptionProviderInterface, InternalEventHandlerInterface
{
private operators: OperatorManager
private readonly itemsEncryption: ItemsEncryptionService
private readonly rootKeyEncryption: RootKeyEncryptionService
private rootKeyObserverDisposer: () => void
private readonly rootKeyManager: RootKeyManager
constructor(
private itemManager: ItemManagerInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private payloadManager: PayloadManagerInterface,
public deviceInterface: DeviceInterface,
private storageService: StorageServiceInterface,
private payloads: PayloadManagerInterface,
public device: DeviceInterface,
private storage: StorageServiceInterface,
public readonly keys: KeySystemKeyManagerInterface,
private identifier: ApplicationIdentifier,
identifier: ApplicationIdentifier,
public crypto: PureCryptoInterface,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
this.crypto = crypto
this.operatorManager = new OperatorManager(crypto)
this.operators = new OperatorManager(crypto)
this.itemsEncryption = new ItemsEncryptionService(
itemManager,
payloadManager,
storageService,
this.operatorManager,
keys,
this.rootKeyManager = new RootKeyManager(
device,
storage,
items,
mutator,
this.operators,
identifier,
internalEventBus,
)
this.rootKeyEncryption = new RootKeyEncryptionService(
this.itemManager,
this.mutator,
this.operatorManager,
this.deviceInterface,
this.storageService,
this.payloadManager,
keys,
this.identifier,
this.internalEventBus,
)
internalEventBus.addEventHandler(this, RootKeyManagerEvent.RootKeyManagerKeyStatusChanged)
this.rootKeyObserverDisposer = this.rootKeyEncryption.addEventObserver((event) => {
this.itemsEncryption.userVersion = this.getUserVersion()
if (event === RootKeyServiceEvent.RootKeyStatusChanged) {
void this.notifyEvent(EncryptionServiceEvent.RootKeyStatusChanged)
}
})
this.itemsEncryption = new ItemsEncryptionService(items, payloads, storage, this.operators, keys, internalEventBus)
UuidGenerator.SetGenerator(this.crypto.generateUUID)
}
public override deinit(): void {
;(this.itemManager as unknown) = undefined
;(this.payloadManager as unknown) = undefined
;(this.deviceInterface as unknown) = undefined
;(this.storageService as unknown) = undefined
;(this.crypto as unknown) = undefined
;(this.operatorManager as unknown) = undefined
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === RootKeyManagerEvent.RootKeyManagerKeyStatusChanged) {
this.itemsEncryption.userVersion = this.getUserVersion()
void this.notifyEvent(EncryptionServiceEvent.RootKeyStatusChanged)
}
}
this.rootKeyObserverDisposer()
;(this.rootKeyObserverDisposer as unknown) = undefined
public override async blockDeinit(): Promise<void> {
await Promise.all([this.rootKeyManager.blockDeinit(), this.itemsEncryption.blockDeinit()])
return super.blockDeinit()
}
public override deinit(): void {
;(this.items as unknown) = undefined
;(this.payloads as unknown) = undefined
;(this.device as unknown) = undefined
;(this.storage as unknown) = undefined
;(this.crypto as unknown) = undefined
;(this.operators as unknown) = undefined
this.itemsEncryption.deinit()
;(this.itemsEncryption as unknown) = undefined
this.rootKeyEncryption.deinit()
;(this.rootKeyEncryption as unknown) = undefined
this.rootKeyManager.deinit()
;(this.rootKeyManager as unknown) = undefined
super.deinit()
}
@@ -210,17 +218,17 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
}
public async initialize() {
await this.rootKeyEncryption.initialize()
await this.rootKeyManager.initialize()
}
/**
* Returns encryption protocol display name for active account/wrapper
*/
public async getEncryptionDisplayName(): Promise<string> {
const version = await this.rootKeyEncryption.getEncryptionSourceVersion()
const version = await this.rootKeyManager.getEncryptionSourceVersion()
if (version) {
return this.operatorManager.operatorForVersion(version).getEncryptionDisplayName()
return this.operators.operatorForVersion(version).getEncryptionDisplayName()
}
throw Error('Attempting to access encryption display name wtihout source')
@@ -231,15 +239,15 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
}
public hasAccount() {
return this.rootKeyEncryption.hasAccount()
return this.rootKeyManager.hasAccount()
}
public hasRootKeyEncryptionSource(): boolean {
return this.rootKeyEncryption.hasRootKeyEncryptionSource()
return this.rootKeyManager.hasRootKeyEncryptionSource()
}
public getUserVersion(): ProtocolVersion | undefined {
return this.rootKeyEncryption.getUserVersion()
return this.rootKeyManager.getUserVersion()
}
public async upgradeAvailable() {
@@ -257,19 +265,35 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
}
public async reencryptApplicableItemsAfterUserRootKeyChange(): Promise<void> {
await this.rootKeyEncryption.reencryptApplicableItemsAfterUserRootKeyChange()
await this.rootKeyManager.reencryptApplicableItemsAfterUserRootKeyChange()
}
public reencryptKeySystemItemsKeysForVault(keySystemIdentifier: KeySystemIdentifier): Promise<void> {
return this.rootKeyEncryption.reencryptKeySystemItemsKeysForVault(keySystemIdentifier)
/**
* When the key system root key changes, we must re-encrypt all vault items keys
* with this new key system root key (by simply re-syncing).
*/
public async reencryptKeySystemItemsKeysForVault(keySystemIdentifier: KeySystemIdentifier): Promise<void> {
const keySystemItemsKeys = this.keys.getKeySystemItemsKeys(keySystemIdentifier)
if (keySystemItemsKeys.length > 0) {
await this.mutator.setItemsDirty(keySystemItemsKeys)
}
}
public async createNewItemsKeyWithRollback(): Promise<() => Promise<void>> {
return this.rootKeyEncryption.createNewItemsKeyWithRollback()
const usecase = new CreateNewItemsKeyWithRollbackUseCase(
this.mutator,
this.items,
this.storage,
this.operators,
this.rootKeyManager,
)
return usecase.execute()
}
public async decryptErroredPayloads(): Promise<void> {
await this.rootKeyEncryption.decryptErroredRootPayloads()
const usecase = new DecryptErroredRootPayloadsUseCase(this.payloads, this.operators, this.keys, this.rootKeyManager)
await usecase.execute()
await this.itemsEncryption.decryptErroredItemPayloads()
}
@@ -302,10 +326,18 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
usesKeySystemRootKeyWithKeyLookup,
} = split
const rootKeyEncryptWithKeyLookupUsecase = new RootKeyEncryptPayloadWithKeyLookupUseCase(
this.operators,
this.keys,
this.rootKeyManager,
)
const rootKeyEncryptUsecase = new RootKeyEncryptPayloadUseCase(this.operators)
const signingKeyPair = this.hasSigningKeyPair() ? this.getSigningKeyPair() : undefined
if (usesRootKey) {
const rootKeyEncrypted = await this.rootKeyEncryption.encryptPayloads(
const rootKeyEncrypted = await rootKeyEncryptUsecase.executeMany(
usesRootKey.items,
usesRootKey.key,
signingKeyPair,
@@ -314,7 +346,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
}
if (usesRootKeyWithKeyLookup) {
const rootKeyEncrypted = await this.rootKeyEncryption.encryptPayloadsWithKeyLookup(
const rootKeyEncrypted = await rootKeyEncryptWithKeyLookupUsecase.executeMany(
usesRootKeyWithKeyLookup.items,
signingKeyPair,
)
@@ -322,7 +354,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
}
if (usesKeySystemRootKey) {
const keySystemRootKeyEncrypted = await this.rootKeyEncryption.encryptPayloads(
const keySystemRootKeyEncrypted = await rootKeyEncryptUsecase.executeMany(
usesKeySystemRootKey.items,
usesKeySystemRootKey.key,
signingKeyPair,
@@ -331,7 +363,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
}
if (usesKeySystemRootKeyWithKeyLookup) {
const keySystemRootKeyEncrypted = await this.rootKeyEncryption.encryptPayloadsWithKeyLookup(
const keySystemRootKeyEncrypted = await rootKeyEncryptWithKeyLookupUsecase.executeMany(
usesKeySystemRootKeyWithKeyLookup.items,
signingKeyPair,
)
@@ -391,26 +423,32 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
usesKeySystemRootKeyWithKeyLookup,
} = split
const rootKeyDecryptUseCase = new RootKeyDecryptPayloadUseCase(this.operators)
const rootKeyDecryptWithKeyLookupUsecase = new RootKeyDecryptPayloadWithKeyLookupUseCase(
this.operators,
this.keys,
this.rootKeyManager,
)
if (usesRootKey) {
const rootKeyDecrypted = await this.rootKeyEncryption.decryptPayloads<C>(usesRootKey.items, usesRootKey.key)
const rootKeyDecrypted = await rootKeyDecryptUseCase.executeMany<C>(usesRootKey.items, usesRootKey.key)
extendArray(resultParams, rootKeyDecrypted)
}
if (usesRootKeyWithKeyLookup) {
const rootKeyDecrypted = await this.rootKeyEncryption.decryptPayloadsWithKeyLookup<C>(
usesRootKeyWithKeyLookup.items,
)
const rootKeyDecrypted = await rootKeyDecryptWithKeyLookupUsecase.executeMany<C>(usesRootKeyWithKeyLookup.items)
extendArray(resultParams, rootKeyDecrypted)
}
if (usesKeySystemRootKey) {
const keySystemRootKeyDecrypted = await this.rootKeyEncryption.decryptPayloads<C>(
const keySystemRootKeyDecrypted = await rootKeyDecryptUseCase.executeMany<C>(
usesKeySystemRootKey.items,
usesKeySystemRootKey.key,
)
extendArray(resultParams, keySystemRootKeyDecrypted)
}
if (usesKeySystemRootKeyWithKeyLookup) {
const keySystemRootKeyDecrypted = await this.rootKeyEncryption.decryptPayloadsWithKeyLookup<C>(
const keySystemRootKeyDecrypted = await rootKeyDecryptWithKeyLookupUsecase.executeMany<C>(
usesKeySystemRootKeyWithKeyLookup.items,
)
extendArray(resultParams, keySystemRootKeyDecrypted)
@@ -492,14 +530,14 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
* Returns true if the user's account protocol version is not equal to the latest version.
*/
public async passcodeUpgradeAvailable(): Promise<boolean> {
return this.rootKeyEncryption.passcodeUpgradeAvailable()
return this.rootKeyManager.passcodeUpgradeAvailable()
}
/**
* Determines whether the current environment is capable of supporting
* key derivation.
*/
public platformSupportsKeyDerivation(keyParams: SNRootKeyParams) {
public platformSupportsKeyDerivation(keyParams: RootKeyParamsInterface) {
/**
* If the version is 003 or lower, key derivation is supported unless the browser is
* IE or Edge (or generally, where WebCrypto is not available) or React Native environment is detected.
@@ -548,8 +586,11 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
* Computes a root key given a password and key params.
* Delegates computation to respective protocol operator.
*/
public async computeRootKey<K extends RootKeyInterface>(password: string, keyParams: SNRootKeyParams): Promise<K> {
return this.rootKeyEncryption.computeRootKey(password, keyParams)
public async computeRootKey<K extends RootKeyInterface>(
password: string,
keyParams: RootKeyParamsInterface,
): Promise<K> {
return this.rootKeyManager.computeRootKey(password, keyParams)
}
/**
@@ -561,7 +602,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
origination: KeyParamsOrigination,
version?: ProtocolVersion,
): Promise<K> {
return this.rootKeyEncryption.createRootKey(identifier, password, origination, version)
return this.rootKeyManager.createRootKey(identifier, password, origination, version)
}
createRandomizedKeySystemRootKey(dto: {
@@ -569,7 +610,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
systemName: string
systemDescription?: string
}): KeySystemRootKeyInterface {
return this.operatorManager.defaultOperator().createRandomizedKeySystemRootKey(dto)
return this.operators.defaultOperator().createRandomizedKeySystemRootKey(dto)
}
createUserInputtedKeySystemRootKey(dto: {
@@ -578,14 +619,14 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
systemDescription?: string
userInputtedPassword: string
}): KeySystemRootKeyInterface {
return this.operatorManager.defaultOperator().createUserInputtedKeySystemRootKey(dto)
return this.operators.defaultOperator().createUserInputtedKeySystemRootKey(dto)
}
deriveUserInputtedKeySystemRootKey(dto: {
keyParams: KeySystemRootKeyParamsInterface
userInputtedPassword: string
}): KeySystemRootKeyInterface {
return this.operatorManager.defaultOperator().deriveUserInputtedKeySystemRootKey(dto)
return this.operators.defaultOperator().deriveUserInputtedKeySystemRootKey(dto)
}
createKeySystemItemsKey(
@@ -594,7 +635,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
sharedVaultUuid: string | undefined,
rootKeyToken: string,
): KeySystemItemsKeyInterface {
return this.operatorManager
return this.operators
.defaultOperator()
.createKeySystemItemsKey(uuid, keySystemIdentifier, sharedVaultUuid, rootKeyToken)
}
@@ -605,7 +646,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
senderSigningKeyPair: PkcKeyPair
recipientPublicKey: string
}): AsymmetricallyEncryptedString {
const operator = this.operatorManager.defaultOperator()
const operator = this.operators.defaultOperator()
const encrypted = operator.asymmetricEncrypt({
stringToEncrypt: JSON.stringify(dto.message),
senderKeyPair: dto.senderKeyPair,
@@ -620,9 +661,9 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
trustedSender: TrustedContactInterface | undefined
privateKey: string
}): M | undefined {
const defaultOperator = this.operatorManager.defaultOperator()
const defaultOperator = this.operators.defaultOperator()
const version = defaultOperator.versionForAsymmetricallyEncryptedString(dto.encryptedString)
const keyOperator = this.operatorManager.operatorForVersion(version)
const keyOperator = this.operators.operatorForVersion(version)
const decryptedResult = keyOperator.asymmetricDecrypt({
stringToDecrypt: dto.encryptedString,
recipientSecretKey: dto.privateKey,
@@ -652,17 +693,17 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
asymmetricSignatureVerifyDetached(
encryptedString: AsymmetricallyEncryptedString,
): AsymmetricSignatureVerificationDetachedResult {
const defaultOperator = this.operatorManager.defaultOperator()
const defaultOperator = this.operators.defaultOperator()
const version = defaultOperator.versionForAsymmetricallyEncryptedString(encryptedString)
const keyOperator = this.operatorManager.operatorForVersion(version)
const keyOperator = this.operators.operatorForVersion(version)
return keyOperator.asymmetricSignatureVerifyDetached(encryptedString)
}
getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PublicKeySet {
const defaultOperator = this.operatorManager.defaultOperator()
const defaultOperator = this.operators.defaultOperator()
const version = defaultOperator.versionForAsymmetricallyEncryptedString(string)
const keyOperator = this.operatorManager.operatorForVersion(version)
const keyOperator = this.operators.operatorForVersion(version)
return keyOperator.getSenderPublicKeySetFromAsymmetricallyEncryptedString(string)
}
@@ -684,7 +725,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
}
public async createEncryptedBackupFile(): Promise<BackupFile> {
const payloads = this.itemManager.items.map((item) => item.payload)
const payloads = this.items.items.map((item) => item.payload)
const split = SplitPayloadsByEncryptionType(payloads)
@@ -705,7 +746,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
}
public createDecryptedBackupFile(): BackupFile {
const payloads = this.payloadManager.nonDeletedItems.filter((item) => item.content_type !== ContentType.ItemsKey)
const payloads = this.payloads.nonDeletedItems.filter((item) => item.content_type !== ContentType.ItemsKey)
const data: BackupFile = {
version: ProtocolVersionLatest,
@@ -725,22 +766,22 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
}
public hasPasscode(): boolean {
return this.rootKeyEncryption.hasPasscode()
return this.rootKeyManager.hasPasscode()
}
/**
* @returns True if the root key has not yet been unwrapped (passcode locked).
*/
public async isPasscodeLocked() {
return (await this.rootKeyEncryption.hasRootKeyWrapper()) && this.rootKeyEncryption.getRootKey() == undefined
return (await this.rootKeyManager.hasRootKeyWrapper()) && this.rootKeyManager.getRootKey() == undefined
}
public getRootKeyParams() {
return this.rootKeyEncryption.getRootKeyParams()
return this.rootKeyManager.getRootKeyParams()
}
public getAccountKeyParams() {
return this.rootKeyEncryption.memoizedRootKeyParams
return this.rootKeyManager.getMemoizedRootKeyParams()
}
/**
@@ -748,7 +789,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
* Wrapping key params are read from disk.
*/
public async computeWrappingKey(passcode: string) {
const keyParams = this.rootKeyEncryption.getSureRootKeyWrapperKeyParams()
const keyParams = this.rootKeyManager.getSureRootKeyWrapperKeyParams()
const key = await this.computeRootKey(passcode, keyParams)
return key
}
@@ -760,7 +801,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
* After unwrapping, the root key is automatically loaded.
*/
public async unwrapRootKey(wrappingKey: RootKeyInterface) {
return this.rootKeyEncryption.unwrapRootKey(wrappingKey)
return this.rootKeyManager.unwrapRootKey(wrappingKey)
}
/**
* Encrypts rootKey and saves it in storage instead of keychain, and then
@@ -768,42 +809,42 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
* payloads in the keychain. If the root key is not wrapped, it is stored
* in plain form in the user's secure keychain.
*/
public async setNewRootKeyWrapper(wrappingKey: SNRootKey) {
return this.rootKeyEncryption.setNewRootKeyWrapper(wrappingKey)
public async setNewRootKeyWrapper(wrappingKey: RootKeyInterface) {
return this.rootKeyManager.setNewRootKeyWrapper(wrappingKey)
}
public async removePasscode(): Promise<void> {
await this.rootKeyEncryption.removeRootKeyWrapper()
await this.rootKeyManager.removeRootKeyWrapper()
}
public async setRootKey(key: RootKeyInterface, wrappingKey?: SNRootKey) {
await this.rootKeyEncryption.setRootKey(key, wrappingKey)
public async setRootKey(key: RootKeyInterface, wrappingKey?: RootKeyInterface) {
await this.rootKeyManager.setRootKey(key, wrappingKey)
}
/**
* Returns the in-memory root key value.
*/
public getRootKey(): RootKeyInterface | undefined {
return this.rootKeyEncryption.getRootKey()
return this.rootKeyManager.getRootKey()
}
public getSureRootKey(): RootKeyInterface {
return this.rootKeyEncryption.getRootKey() as RootKeyInterface
return this.rootKeyManager.getRootKey() as RootKeyInterface
}
/**
* Deletes root key and wrapper from keychain. Used when signing out of application.
*/
public async deleteWorkspaceSpecificKeyStateFromDevice() {
await this.rootKeyEncryption.deleteWorkspaceSpecificKeyStateFromDevice()
await this.rootKeyManager.deleteWorkspaceSpecificKeyStateFromDevice()
}
public async validateAccountPassword(password: string) {
return this.rootKeyEncryption.validateAccountPassword(password)
public async validateAccountPassword(password: string): Promise<ValidateAccountPasswordResult> {
return this.rootKeyManager.validateAccountPassword(password)
}
public async validatePasscode(passcode: string) {
return this.rootKeyEncryption.validatePasscode(passcode)
public async validatePasscode(passcode: string): Promise<ValidatePasscodeResult> {
return this.rootKeyManager.validatePasscode(passcode)
}
public getEmbeddedPayloadAuthenticatedData<D extends ItemAuthenticatedData>(
@@ -814,7 +855,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
return undefined
}
const operator = this.operatorManager.operatorForVersion(version)
const operator = this.operators.operatorForVersion(version)
const authenticatedData = operator.getPayloadAuthenticatedDataForExternalUse(
encryptedInputParametersFromPayload(payload),
@@ -824,7 +865,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
}
/** Returns the key params attached to this key's encrypted payload */
public getKeyEmbeddedKeyParamsFromItemsKey(key: EncryptedPayloadInterface): SNRootKeyParams | undefined {
public getKeyEmbeddedKeyParamsFromItemsKey(key: EncryptedPayloadInterface): RootKeyParamsInterface | undefined {
const authenticatedData = this.getEmbeddedPayloadAuthenticatedData(key)
if (!authenticatedData) {
return undefined
@@ -847,7 +888,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
return false
}
const rootKey = this.rootKeyEncryption.getRootKey()
const rootKey = this.rootKeyManager.getRootKey()
if (!rootKey) {
return false
}
@@ -872,7 +913,8 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
}
public async createNewDefaultItemsKey(): Promise<ItemsKeyInterface> {
return this.rootKeyEncryption.createNewDefaultItemsKey()
const usecase = new CreateNewDefaultItemsKeyUseCase(this.mutator, this.items, this.operators, this.rootKeyManager)
return usecase.execute()
}
public getPasswordCreatedDate(): Date | undefined {
@@ -943,7 +985,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
}
if (this.itemsEncryption.getItemsKeys().length === 0) {
await this.rootKeyEncryption.createNewDefaultItemsKey()
await this.createNewDefaultItemsKey()
}
}
}
@@ -951,7 +993,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
const userVersion = this.getUserVersion()
const accountVersionedKey = this.itemsEncryption.getItemsKeys().find((key) => key.keyVersion === userVersion)
if (isNullOrUndefined(accountVersionedKey)) {
await this.rootKeyEncryption.createNewDefaultItemsKey()
await this.createNewDefaultItemsKey()
}
this.syncUnsyncedItemsKeys()
@@ -961,8 +1003,8 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
/** Always create a new items key after full sync, if no items key is found */
const currentItemsKey = findDefaultItemsKey(this.itemsEncryption.getItemsKeys())
if (!currentItemsKey) {
await this.rootKeyEncryption.createNewDefaultItemsKey()
if (this.rootKeyEncryption.keyMode === KeyMode.WrapperOnly) {
await this.createNewDefaultItemsKey()
if (this.rootKeyManager.getKeyMode() === KeyMode.WrapperOnly) {
return this.itemsEncryption.repersistAllItems()
}
}
@@ -0,0 +1,491 @@
import {
AnyKeyParamsContent,
ApplicationIdentifier,
KeyParamsOrigination,
ProtocolVersion,
ProtocolVersionLatest,
} from '@standardnotes/common'
import {
KeyMode,
CreateNewRootKey,
CreateAnyKeyParams,
SNRootKey,
isErrorDecryptingParameters,
OperatorManager,
} from '@standardnotes/encryption'
import {
ContentTypesUsingRootKeyEncryption,
DecryptedPayload,
DecryptedTransferPayload,
EncryptedPayload,
EncryptedTransferPayload,
FillItemContentSpecialized,
NamespacedRootKeyInKeychain,
RootKeyContent,
RootKeyInterface,
RootKeyParamsInterface,
} from '@standardnotes/models'
import { DeviceInterface } from '../../Device/DeviceInterface'
import { InternalEventBusInterface } from '../../Internal/InternalEventBusInterface'
import { StorageKey } from '../../Storage/StorageKeys'
import { StorageServiceInterface } from '../../Storage/StorageServiceInterface'
import { StorageValueModes } from '../../Storage/StorageTypes'
import { RootKeyEncryptPayloadUseCase } from '../UseCase/RootEncryption/EncryptPayload'
import { RootKeyDecryptPayloadUseCase } from '../UseCase/RootEncryption/DecryptPayload'
import { AbstractService } from '../../Service/AbstractService'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
import { RootKeyManagerEvent } from './RootKeyManagerEvent'
import { ValidatePasscodeResult } from './ValidatePasscodeResult'
import { ValidateAccountPasswordResult } from './ValidateAccountPasswordResult'
export class RootKeyManager extends AbstractService<RootKeyManagerEvent> {
private rootKey?: RootKeyInterface
private keyMode = KeyMode.RootKeyNone
private memoizedRootKeyParams?: RootKeyParamsInterface
constructor(
private device: DeviceInterface,
private storage: StorageServiceInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private operators: OperatorManager,
private identifier: ApplicationIdentifier,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
}
override deinit() {
super.deinit()
this.rootKey = undefined
this.memoizedRootKeyParams = undefined
}
public async initialize(): Promise<void> {
const wrappedRootKey = this.getWrappedRootKey()
const accountKeyParams = this.recomputeAccountKeyParams()
const hasWrapper = await this.hasRootKeyWrapper()
const hasRootKey = wrappedRootKey != undefined || accountKeyParams != undefined
if (hasWrapper && hasRootKey) {
this.keyMode = KeyMode.RootKeyPlusWrapper
} else if (hasWrapper && !hasRootKey) {
this.keyMode = KeyMode.WrapperOnly
} else if (!hasWrapper && hasRootKey) {
this.keyMode = KeyMode.RootKeyOnly
} else if (!hasWrapper && !hasRootKey) {
this.keyMode = KeyMode.RootKeyNone
} else {
throw 'Invalid key mode condition'
}
if (this.keyMode === KeyMode.RootKeyOnly) {
this.setRootKeyInstance(await this.getRootKeyFromKeychain())
await this.handleKeyStatusChange()
}
}
public getMemoizedRootKeyParams(): RootKeyParamsInterface | undefined {
return this.memoizedRootKeyParams
}
public getKeyMode(): KeyMode {
return this.keyMode
}
public async hasRootKeyWrapper(): Promise<boolean> {
const wrapper = this.getRootKeyWrapperKeyParams()
return wrapper != undefined
}
public getRootKeyWrapperKeyParams(): RootKeyParamsInterface | undefined {
const rawKeyParams = this.storage.getValue(StorageKey.RootKeyWrapperKeyParams, StorageValueModes.Nonwrapped)
if (!rawKeyParams) {
return undefined
}
return CreateAnyKeyParams(rawKeyParams as AnyKeyParamsContent)
}
public async passcodeUpgradeAvailable(): Promise<boolean> {
const passcodeParams = this.getRootKeyWrapperKeyParams()
if (!passcodeParams) {
return false
}
return passcodeParams.version !== ProtocolVersionLatest
}
public hasAccount(): boolean {
switch (this.keyMode) {
case KeyMode.RootKeyNone:
case KeyMode.WrapperOnly:
return false
case KeyMode.RootKeyOnly:
case KeyMode.RootKeyPlusWrapper:
return true
default:
throw Error(`Unhandled keyMode value '${this.keyMode}'.`)
}
}
public getUserVersion(): ProtocolVersion | undefined {
const keyParams = this.memoizedRootKeyParams
return keyParams?.version
}
public hasRootKeyEncryptionSource(): boolean {
return this.hasAccount() || this.hasPasscode()
}
public async computeRootKey<K extends RootKeyInterface>(
password: string,
keyParams: RootKeyParamsInterface,
): Promise<K> {
const version = keyParams.version
const operator = this.operators.operatorForVersion(version)
return operator.computeRootKey(password, keyParams)
}
/**
* Deletes root key and wrapper from keychain. Used when signing out of application.
*/
public async deleteWorkspaceSpecificKeyStateFromDevice(): Promise<void> {
await this.device.clearNamespacedKeychainValue(this.identifier)
await this.storage.removeValue(StorageKey.WrappedRootKey, StorageValueModes.Nonwrapped)
await this.storage.removeValue(StorageKey.RootKeyWrapperKeyParams, StorageValueModes.Nonwrapped)
await this.storage.removeValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
this.keyMode = KeyMode.RootKeyNone
this.setRootKeyInstance(undefined)
await this.handleKeyStatusChange()
}
public async createRootKey<K extends RootKeyInterface>(
identifier: string,
password: string,
origination: KeyParamsOrigination,
version?: ProtocolVersion,
): Promise<K> {
const operator = version ? this.operators.operatorForVersion(version) : this.operators.defaultOperator()
return operator.createRootKey(identifier, password, origination)
}
public async validateAccountPassword(password: string): Promise<ValidateAccountPasswordResult> {
const key = await this.computeRootKey(password, this.memoizedRootKeyParams as RootKeyParamsInterface)
const valid = this.getSureRootKey().compare(key)
if (valid) {
return { valid, artifacts: { rootKey: key } }
} else {
return { valid: false }
}
}
public async validatePasscode(passcode: string): Promise<ValidatePasscodeResult> {
const keyParams = this.getSureRootKeyWrapperKeyParams()
const key = await this.computeRootKey(passcode, keyParams)
const valid = await this.validateWrappingKey(key)
if (valid) {
return { valid, artifacts: { wrappingKey: key } }
} else {
return { valid: false }
}
}
public async getEncryptionSourceVersion(): Promise<ProtocolVersion> {
if (this.hasAccount()) {
return this.getSureUserVersion()
} else if (this.hasPasscode()) {
const passcodeParams = this.getSureRootKeyWrapperKeyParams()
return passcodeParams.version
}
throw Error('Attempting to access encryption source version without source')
}
public getSureUserVersion(): ProtocolVersion {
const keyParams = this.memoizedRootKeyParams as RootKeyParamsInterface
return keyParams.version
}
private async handleKeyStatusChange(): Promise<void> {
this.recomputeAccountKeyParams()
void this.notifyEvent(RootKeyManagerEvent.RootKeyManagerKeyStatusChanged)
}
public hasPasscode(): boolean {
return this.keyMode === KeyMode.WrapperOnly || this.keyMode === KeyMode.RootKeyPlusWrapper
}
public recomputeAccountKeyParams(): RootKeyParamsInterface | undefined {
const rawKeyParams = this.storage.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
if (!rawKeyParams) {
return
}
this.memoizedRootKeyParams = CreateAnyKeyParams(rawKeyParams as AnyKeyParamsContent)
return this.memoizedRootKeyParams
}
public getSureRootKeyWrapperKeyParams() {
return this.getRootKeyWrapperKeyParams() as RootKeyParamsInterface
}
/**
* Wraps the current in-memory root key value using the wrappingKey,
* then persists the wrapped value to disk.
*/
public async wrapAndPersistRootKey(wrappingKey: RootKeyInterface): Promise<void> {
const rootKey = this.getSureRootKey()
const value: DecryptedTransferPayload = {
...rootKey.payload.ejected(),
content: FillItemContentSpecialized(rootKey.persistableValueWhenWrapping()),
}
const payload = new DecryptedPayload(value)
const usecase = new RootKeyEncryptPayloadUseCase(this.operators)
const wrappedKey = await usecase.executeOne(payload, wrappingKey)
const wrappedKeyPayload = new EncryptedPayload({
...payload.ejected(),
...wrappedKey,
errorDecrypting: false,
waitingForKey: false,
})
this.storage.setValue(StorageKey.WrappedRootKey, wrappedKeyPayload.ejected(), StorageValueModes.Nonwrapped)
}
public async unwrapRootKey(wrappingKey: RootKeyInterface): Promise<void> {
if (this.keyMode === KeyMode.WrapperOnly) {
this.setRootKeyInstance(wrappingKey)
return
}
if (this.keyMode !== KeyMode.RootKeyPlusWrapper) {
throw 'Invalid key mode condition for unwrapping.'
}
const wrappedKey = this.getWrappedRootKey()
const payload = new EncryptedPayload(wrappedKey)
const usecase = new RootKeyDecryptPayloadUseCase(this.operators)
const decrypted = await usecase.executeOne<RootKeyContent>(payload, wrappingKey)
if (isErrorDecryptingParameters(decrypted)) {
throw Error('Unable to decrypt root key with provided wrapping key.')
} else {
const decryptedPayload = new DecryptedPayload<RootKeyContent>({
...payload.ejected(),
...decrypted,
})
this.setRootKeyInstance(new SNRootKey(decryptedPayload))
await this.handleKeyStatusChange()
}
}
/**
* Encrypts rootKey and saves it in storage instead of keychain, and then
* clears keychain. This is because we don't want to store large encrypted
* payloads in the keychain. If the root key is not wrapped, it is stored
* in plain form in the user's secure keychain.
*/
public async setNewRootKeyWrapper(wrappingKey: RootKeyInterface) {
if (this.keyMode === KeyMode.RootKeyNone) {
this.keyMode = KeyMode.WrapperOnly
} else if (this.keyMode === KeyMode.RootKeyOnly) {
this.keyMode = KeyMode.RootKeyPlusWrapper
} else {
throw Error('Attempting to set wrapper on already wrapped key.')
}
await this.device.clearNamespacedKeychainValue(this.identifier)
if (this.keyMode === KeyMode.WrapperOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
if (this.keyMode === KeyMode.WrapperOnly) {
this.setRootKeyInstance(wrappingKey)
await this.reencryptApplicableItemsAfterUserRootKeyChange()
} else {
await this.wrapAndPersistRootKey(wrappingKey)
}
this.storage.setValue(
StorageKey.RootKeyWrapperKeyParams,
wrappingKey.keyParams.getPortableValue(),
StorageValueModes.Nonwrapped,
)
await this.handleKeyStatusChange()
} else {
throw Error('Invalid keyMode on setNewRootKeyWrapper')
}
}
/**
* Removes root key wrapper from local storage and stores root key bare in secure keychain.
*/
public async removeRootKeyWrapper(): Promise<void> {
if (this.keyMode !== KeyMode.WrapperOnly && this.keyMode !== KeyMode.RootKeyPlusWrapper) {
throw Error('Attempting to remove root key wrapper on unwrapped key.')
}
if (this.keyMode === KeyMode.WrapperOnly) {
this.keyMode = KeyMode.RootKeyNone
this.setRootKeyInstance(undefined)
} else if (this.keyMode === KeyMode.RootKeyPlusWrapper) {
this.keyMode = KeyMode.RootKeyOnly
}
await this.storage.removeValue(StorageKey.WrappedRootKey, StorageValueModes.Nonwrapped)
await this.storage.removeValue(StorageKey.RootKeyWrapperKeyParams, StorageValueModes.Nonwrapped)
if (this.keyMode === KeyMode.RootKeyOnly) {
await this.saveRootKeyToKeychain()
}
await this.handleKeyStatusChange()
}
public async setRootKey(key: RootKeyInterface, wrappingKey?: RootKeyInterface) {
if (!key.keyParams) {
throw Error('keyParams must be supplied if setting root key.')
}
if (this.getRootKey() === key) {
throw Error('Attempting to set root key as same current value.')
}
if (this.keyMode === KeyMode.WrapperOnly) {
this.keyMode = KeyMode.RootKeyPlusWrapper
} else if (this.keyMode === KeyMode.RootKeyNone) {
this.keyMode = KeyMode.RootKeyOnly
} else if (this.keyMode === KeyMode.RootKeyOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
/** Root key is simply changing, mode stays the same */
/** this.keyMode = this.keyMode; */
} else {
throw Error(`Unhandled key mode for setNewRootKey ${this.keyMode}`)
}
this.setRootKeyInstance(key)
this.storage.setValue(StorageKey.RootKeyParams, key.keyParams.getPortableValue(), StorageValueModes.Nonwrapped)
if (this.keyMode === KeyMode.RootKeyOnly) {
await this.saveRootKeyToKeychain()
} else if (this.keyMode === KeyMode.RootKeyPlusWrapper) {
if (!wrappingKey) {
throw Error('wrappingKey must be supplied')
}
await this.wrapAndPersistRootKey(wrappingKey)
}
await this.handleKeyStatusChange()
}
public getRootKeyParams(): RootKeyParamsInterface | undefined {
if (this.keyMode === KeyMode.WrapperOnly) {
return this.getRootKeyWrapperKeyParams()
} else if (this.keyMode === KeyMode.RootKeyOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
return this.recomputeAccountKeyParams()
} else if (this.keyMode === KeyMode.RootKeyNone) {
return undefined
} else {
throw `Unhandled key mode for getRootKeyParams ${this.keyMode}`
}
}
public getSureRootKeyParams(): RootKeyParamsInterface {
return this.getRootKeyParams() as RootKeyParamsInterface
}
public async saveRootKeyToKeychain() {
if (this.getRootKey() == undefined) {
throw 'Attempting to non-existent root key to the keychain.'
}
if (this.keyMode !== KeyMode.RootKeyOnly) {
throw 'Should not be persisting wrapped key to keychain.'
}
const rawKey = this.getSureRootKey().getKeychainValue()
return this.executeCriticalFunction(() => {
return this.device.setNamespacedKeychainValue(rawKey, this.identifier)
})
}
/**
* We know a wrappingKey is correct if it correctly decrypts
* wrapped root key.
*/
public async validateWrappingKey(wrappingKey: RootKeyInterface): Promise<boolean> {
const wrappedRootKey = this.getWrappedRootKey()
/** If wrapper only, storage is encrypted directly with wrappingKey */
if (this.keyMode === KeyMode.WrapperOnly) {
return this.storage.canDecryptWithKey(wrappingKey)
} else if (this.keyMode === KeyMode.RootKeyOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
/**
* In these modes, storage is encrypted with account keys, and
* account keys are encrypted with wrappingKey. Here we validate
* by attempting to decrypt account keys.
*/
const wrappedKeyPayload = new EncryptedPayload(wrappedRootKey)
const usecase = new RootKeyDecryptPayloadUseCase(this.operators)
const decrypted = await usecase.executeOne(wrappedKeyPayload, wrappingKey)
return !isErrorDecryptingParameters(decrypted)
} else {
throw 'Unhandled case in validateWrappingKey'
}
}
private getWrappedRootKey(): EncryptedTransferPayload {
return this.storage.getValue<EncryptedTransferPayload>(StorageKey.WrappedRootKey, StorageValueModes.Nonwrapped)
}
public setRootKeyInstance(rootKey: RootKeyInterface | undefined): void {
this.rootKey = rootKey
}
public getRootKey(): RootKeyInterface | undefined {
return this.rootKey
}
public getSureRootKey(): RootKeyInterface {
return this.rootKey as RootKeyInterface
}
public async getRootKeyFromKeychain(): Promise<RootKeyInterface | undefined> {
const rawKey = (await this.device.getNamespacedKeychainValue(this.identifier)) as
| NamespacedRootKeyInKeychain
| undefined
if (rawKey == undefined) {
return undefined
}
const keyParams = this.getSureRootKeyParams()
return CreateNewRootKey({
...rawKey,
keyParams: keyParams.getPortableValue(),
})
}
/**
* When the root key changes, we must re-encrypt all relevant items with this new root key (by simply re-syncing).
*/
public async reencryptApplicableItemsAfterUserRootKeyChange(): Promise<void> {
const items = this.items.getItems(ContentTypesUsingRootKeyEncryption())
if (items.length > 0) {
/**
* Do not call sync after marking dirty.
* Re-encrypting items keys is called by consumers who have specific flows who
* will sync on their own timing
*/
await this.mutator.setItemsDirty(items)
}
}
}
@@ -0,0 +1,3 @@
export enum RootKeyManagerEvent {
RootKeyManagerKeyStatusChanged = 'RootKeyManagerKeyStatusChanged',
}
@@ -0,0 +1,13 @@
import { RootKeyInterface } from '@standardnotes/models'
export type ValidateAccountPasswordResult =
| {
valid: true
artifacts: {
rootKey: RootKeyInterface
}
}
| {
valid: boolean
artifacts?: undefined
}
@@ -0,0 +1,13 @@
import { RootKeyInterface } from '@standardnotes/models'
export type ValidatePasscodeResult =
| {
valid: true
artifacts: {
wrappingKey: RootKeyInterface
}
}
| {
valid: boolean
artifacts?: undefined
}
@@ -1,716 +0,0 @@
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
import {
ApplicationIdentifier,
ProtocolVersionLatest,
ProtocolVersion,
AnyKeyParamsContent,
KeyParamsOrigination,
compareVersions,
ProtocolVersionLastNonrootItemsKey,
ContentType,
} from '@standardnotes/common'
import {
RootKeyServiceEvent,
KeyMode,
SNRootKeyParams,
OperatorManager,
CreateNewRootKey,
CreateAnyKeyParams,
SNRootKey,
isErrorDecryptingParameters,
ErrorDecryptingParameters,
findDefaultItemsKey,
ItemsKeyMutator,
encryptPayload,
decryptPayload,
EncryptedOutputParameters,
DecryptedParameters,
KeySystemKeyManagerInterface,
} from '@standardnotes/encryption'
import {
ContentTypeUsesKeySystemRootKeyEncryption,
ContentTypesUsingRootKeyEncryption,
ContentTypeUsesRootKeyEncryption,
CreateDecryptedItemFromPayload,
DecryptedPayload,
DecryptedPayloadInterface,
DecryptedTransferPayload,
EncryptedPayload,
EncryptedPayloadInterface,
EncryptedTransferPayload,
FillItemContentSpecialized,
KeySystemRootKeyInterface,
ItemContent,
ItemsKeyContent,
ItemsKeyContentSpecialized,
ItemsKeyInterface,
NamespacedRootKeyInKeychain,
PayloadEmitSource,
PayloadTimestampDefaults,
RootKeyContent,
RootKeyInterface,
SureFindPayload,
KeySystemIdentifier,
} from '@standardnotes/models'
import { UuidGenerator } from '@standardnotes/utils'
import { DeviceInterface } from '../Device/DeviceInterface'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { AbstractService } from '../Service/AbstractService'
import { StorageKey } from '../Storage/StorageKeys'
import { StorageServiceInterface } from '../Storage/StorageServiceInterface'
import { StorageValueModes } from '../Storage/StorageTypes'
import { PayloadManagerInterface } from '../Payloads/PayloadManagerInterface'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEvent> {
private rootKey?: RootKeyInterface
public keyMode = KeyMode.RootKeyNone
public memoizedRootKeyParams?: SNRootKeyParams
constructor(
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private operatorManager: OperatorManager,
public deviceInterface: DeviceInterface,
private storageService: StorageServiceInterface,
private payloadManager: PayloadManagerInterface,
private keys: KeySystemKeyManagerInterface,
private identifier: ApplicationIdentifier,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
}
public override deinit(): void {
;(this.items as unknown) = undefined
;(this.operatorManager as unknown) = undefined
;(this.deviceInterface as unknown) = undefined
;(this.storageService as unknown) = undefined
;(this.payloadManager as unknown) = undefined
;(this.keys as unknown) = undefined
this.rootKey = undefined
this.memoizedRootKeyParams = undefined
super.deinit()
}
public async initialize() {
const wrappedRootKey = this.getWrappedRootKey()
const accountKeyParams = await this.recomputeAccountKeyParams()
const hasWrapper = await this.hasRootKeyWrapper()
const hasRootKey = wrappedRootKey != undefined || accountKeyParams != undefined
if (hasWrapper && hasRootKey) {
this.keyMode = KeyMode.RootKeyPlusWrapper
} else if (hasWrapper && !hasRootKey) {
this.keyMode = KeyMode.WrapperOnly
} else if (!hasWrapper && hasRootKey) {
this.keyMode = KeyMode.RootKeyOnly
} else if (!hasWrapper && !hasRootKey) {
this.keyMode = KeyMode.RootKeyNone
} else {
throw 'Invalid key mode condition'
}
if (this.keyMode === KeyMode.RootKeyOnly) {
this.setRootKeyInstance(await this.getRootKeyFromKeychain())
await this.handleKeyStatusChange()
}
}
private async handleKeyStatusChange() {
await this.recomputeAccountKeyParams()
void this.notifyEvent(RootKeyServiceEvent.RootKeyStatusChanged)
}
public async passcodeUpgradeAvailable() {
const passcodeParams = await this.getRootKeyWrapperKeyParams()
if (!passcodeParams) {
return false
}
return passcodeParams.version !== ProtocolVersionLatest
}
public async hasRootKeyWrapper() {
const wrapper = await this.getRootKeyWrapperKeyParams()
return wrapper != undefined
}
public hasAccount() {
switch (this.keyMode) {
case KeyMode.RootKeyNone:
case KeyMode.WrapperOnly:
return false
case KeyMode.RootKeyOnly:
case KeyMode.RootKeyPlusWrapper:
return true
default:
throw Error(`Unhandled keyMode value '${this.keyMode}'.`)
}
}
public hasRootKeyEncryptionSource(): boolean {
return this.hasAccount() || this.hasPasscode()
}
public hasPasscode() {
return this.keyMode === KeyMode.WrapperOnly || this.keyMode === KeyMode.RootKeyPlusWrapper
}
public async getEncryptionSourceVersion(): Promise<ProtocolVersion> {
if (this.hasAccount()) {
return this.getSureUserVersion()
} else if (this.hasPasscode()) {
const passcodeParams = this.getSureRootKeyWrapperKeyParams()
return passcodeParams.version
}
throw Error('Attempting to access encryption source version without source')
}
public getUserVersion(): ProtocolVersion | undefined {
const keyParams = this.memoizedRootKeyParams
return keyParams?.version
}
private getSureUserVersion(): ProtocolVersion {
const keyParams = this.memoizedRootKeyParams as SNRootKeyParams
return keyParams.version
}
private async getRootKeyFromKeychain() {
const rawKey = (await this.deviceInterface.getNamespacedKeychainValue(this.identifier)) as
| NamespacedRootKeyInKeychain
| undefined
if (rawKey == undefined) {
return undefined
}
const keyParams = this.getSureRootKeyParams()
return CreateNewRootKey({
...rawKey,
keyParams: keyParams.getPortableValue(),
})
}
private async saveRootKeyToKeychain() {
if (this.getRootKey() == undefined) {
throw 'Attempting to non-existent root key to the keychain.'
}
if (this.keyMode !== KeyMode.RootKeyOnly) {
throw 'Should not be persisting wrapped key to keychain.'
}
const rawKey = this.getSureRootKey().getKeychainValue()
return this.executeCriticalFunction(() => {
return this.deviceInterface.setNamespacedKeychainValue(rawKey, this.identifier)
})
}
public getRootKeyWrapperKeyParams(): SNRootKeyParams | undefined {
const rawKeyParams = this.storageService.getValue(StorageKey.RootKeyWrapperKeyParams, StorageValueModes.Nonwrapped)
if (!rawKeyParams) {
return undefined
}
return CreateAnyKeyParams(rawKeyParams as AnyKeyParamsContent)
}
public getSureRootKeyWrapperKeyParams() {
return this.getRootKeyWrapperKeyParams() as SNRootKeyParams
}
public getRootKeyParams(): SNRootKeyParams | undefined {
if (this.keyMode === KeyMode.WrapperOnly) {
return this.getRootKeyWrapperKeyParams()
} else if (this.keyMode === KeyMode.RootKeyOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
return this.recomputeAccountKeyParams()
} else if (this.keyMode === KeyMode.RootKeyNone) {
return undefined
} else {
throw `Unhandled key mode for getRootKeyParams ${this.keyMode}`
}
}
public getSureRootKeyParams(): SNRootKeyParams {
return this.getRootKeyParams() as SNRootKeyParams
}
public async computeRootKey<K extends RootKeyInterface>(password: string, keyParams: SNRootKeyParams): Promise<K> {
const version = keyParams.version
const operator = this.operatorManager.operatorForVersion(version)
return operator.computeRootKey(password, keyParams)
}
public async createRootKey<K extends RootKeyInterface>(
identifier: string,
password: string,
origination: KeyParamsOrigination,
version?: ProtocolVersion,
): Promise<K> {
const operator = version ? this.operatorManager.operatorForVersion(version) : this.operatorManager.defaultOperator()
return operator.createRootKey(identifier, password, origination)
}
private getSureMemoizedRootKeyParams(): SNRootKeyParams {
return this.memoizedRootKeyParams as SNRootKeyParams
}
public async validateAccountPassword(password: string) {
const key = await this.computeRootKey(password, this.getSureMemoizedRootKeyParams())
const valid = this.getSureRootKey().compare(key)
if (valid) {
return { valid, artifacts: { rootKey: key } }
} else {
return { valid: false }
}
}
public async validatePasscode(passcode: string) {
const keyParams = await this.getSureRootKeyWrapperKeyParams()
const key = await this.computeRootKey(passcode, keyParams)
const valid = await this.validateWrappingKey(key)
if (valid) {
return { valid, artifacts: { wrappingKey: key } }
} else {
return { valid: false }
}
}
/**
* We know a wrappingKey is correct if it correctly decrypts
* wrapped root key.
*/
public async validateWrappingKey(wrappingKey: SNRootKey) {
const wrappedRootKey = this.getWrappedRootKey()
/** If wrapper only, storage is encrypted directly with wrappingKey */
if (this.keyMode === KeyMode.WrapperOnly) {
return this.storageService.canDecryptWithKey(wrappingKey)
} else if (this.keyMode === KeyMode.RootKeyOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
/**
* In these modes, storage is encrypted with account keys, and
* account keys are encrypted with wrappingKey. Here we validate
* by attempting to decrypt account keys.
*/
const wrappedKeyPayload = new EncryptedPayload(wrappedRootKey)
const decrypted = await this.decryptPayload(wrappedKeyPayload, wrappingKey)
return !isErrorDecryptingParameters(decrypted)
} else {
throw 'Unhandled case in validateWrappingKey'
}
}
private recomputeAccountKeyParams(): SNRootKeyParams | undefined {
const rawKeyParams = this.storageService.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
if (!rawKeyParams) {
return
}
this.memoizedRootKeyParams = CreateAnyKeyParams(rawKeyParams as AnyKeyParamsContent)
return this.memoizedRootKeyParams
}
/**
* Wraps the current in-memory root key value using the wrappingKey,
* then persists the wrapped value to disk.
*/
private async wrapAndPersistRootKey(wrappingKey: SNRootKey) {
const rootKey = this.getSureRootKey()
const value: DecryptedTransferPayload = {
...rootKey.payload.ejected(),
content: FillItemContentSpecialized(rootKey.persistableValueWhenWrapping()),
}
const payload = new DecryptedPayload(value)
const wrappedKey = await this.encryptPayload(payload, wrappingKey)
const wrappedKeyPayload = new EncryptedPayload({
...payload.ejected(),
...wrappedKey,
errorDecrypting: false,
waitingForKey: false,
})
this.storageService.setValue(StorageKey.WrappedRootKey, wrappedKeyPayload.ejected(), StorageValueModes.Nonwrapped)
}
public async unwrapRootKey(wrappingKey: RootKeyInterface) {
if (this.keyMode === KeyMode.WrapperOnly) {
this.setRootKeyInstance(wrappingKey)
return
}
if (this.keyMode !== KeyMode.RootKeyPlusWrapper) {
throw 'Invalid key mode condition for unwrapping.'
}
const wrappedKey = this.getWrappedRootKey()
const payload = new EncryptedPayload(wrappedKey)
const decrypted = await this.decryptPayload<RootKeyContent>(payload, wrappingKey)
if (isErrorDecryptingParameters(decrypted)) {
throw Error('Unable to decrypt root key with provided wrapping key.')
} else {
const decryptedPayload = new DecryptedPayload<RootKeyContent>({
...payload.ejected(),
...decrypted,
})
this.setRootKeyInstance(new SNRootKey(decryptedPayload))
await this.handleKeyStatusChange()
}
}
/**
* Encrypts rootKey and saves it in storage instead of keychain, and then
* clears keychain. This is because we don't want to store large encrypted
* payloads in the keychain. If the root key is not wrapped, it is stored
* in plain form in the user's secure keychain.
*/
public async setNewRootKeyWrapper(wrappingKey: SNRootKey) {
if (this.keyMode === KeyMode.RootKeyNone) {
this.keyMode = KeyMode.WrapperOnly
} else if (this.keyMode === KeyMode.RootKeyOnly) {
this.keyMode = KeyMode.RootKeyPlusWrapper
} else {
throw Error('Attempting to set wrapper on already wrapped key.')
}
await this.deviceInterface.clearNamespacedKeychainValue(this.identifier)
if (this.keyMode === KeyMode.WrapperOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
if (this.keyMode === KeyMode.WrapperOnly) {
this.setRootKeyInstance(wrappingKey)
await this.reencryptApplicableItemsAfterUserRootKeyChange()
} else {
await this.wrapAndPersistRootKey(wrappingKey)
}
this.storageService.setValue(
StorageKey.RootKeyWrapperKeyParams,
wrappingKey.keyParams.getPortableValue(),
StorageValueModes.Nonwrapped,
)
await this.handleKeyStatusChange()
} else {
throw Error('Invalid keyMode on setNewRootKeyWrapper')
}
}
/**
* Removes root key wrapper from local storage and stores root key bare in secure keychain.
*/
public async removeRootKeyWrapper(): Promise<void> {
if (this.keyMode !== KeyMode.WrapperOnly && this.keyMode !== KeyMode.RootKeyPlusWrapper) {
throw Error('Attempting to remove root key wrapper on unwrapped key.')
}
if (this.keyMode === KeyMode.WrapperOnly) {
this.keyMode = KeyMode.RootKeyNone
this.setRootKeyInstance(undefined)
} else if (this.keyMode === KeyMode.RootKeyPlusWrapper) {
this.keyMode = KeyMode.RootKeyOnly
}
await this.storageService.removeValue(StorageKey.WrappedRootKey, StorageValueModes.Nonwrapped)
await this.storageService.removeValue(StorageKey.RootKeyWrapperKeyParams, StorageValueModes.Nonwrapped)
if (this.keyMode === KeyMode.RootKeyOnly) {
await this.saveRootKeyToKeychain()
}
await this.handleKeyStatusChange()
}
public async setRootKey(key: SNRootKey, wrappingKey?: SNRootKey) {
if (!key.keyParams) {
throw Error('keyParams must be supplied if setting root key.')
}
if (this.getRootKey() === key) {
throw Error('Attempting to set root key as same current value.')
}
if (this.keyMode === KeyMode.WrapperOnly) {
this.keyMode = KeyMode.RootKeyPlusWrapper
} else if (this.keyMode === KeyMode.RootKeyNone) {
this.keyMode = KeyMode.RootKeyOnly
} else if (this.keyMode === KeyMode.RootKeyOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
/** Root key is simply changing, mode stays the same */
/** this.keyMode = this.keyMode; */
} else {
throw Error(`Unhandled key mode for setNewRootKey ${this.keyMode}`)
}
this.setRootKeyInstance(key)
this.storageService.setValue(
StorageKey.RootKeyParams,
key.keyParams.getPortableValue(),
StorageValueModes.Nonwrapped,
)
if (this.keyMode === KeyMode.RootKeyOnly) {
await this.saveRootKeyToKeychain()
} else if (this.keyMode === KeyMode.RootKeyPlusWrapper) {
if (!wrappingKey) {
throw Error('wrappingKey must be supplied')
}
await this.wrapAndPersistRootKey(wrappingKey)
}
await this.handleKeyStatusChange()
}
/**
* Deletes root key and wrapper from keychain. Used when signing out of application.
*/
public async deleteWorkspaceSpecificKeyStateFromDevice() {
await this.deviceInterface.clearNamespacedKeychainValue(this.identifier)
await this.storageService.removeValue(StorageKey.WrappedRootKey, StorageValueModes.Nonwrapped)
await this.storageService.removeValue(StorageKey.RootKeyWrapperKeyParams, StorageValueModes.Nonwrapped)
await this.storageService.removeValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
this.keyMode = KeyMode.RootKeyNone
this.setRootKeyInstance(undefined)
await this.handleKeyStatusChange()
}
private getWrappedRootKey() {
return this.storageService.getValue<EncryptedTransferPayload>(
StorageKey.WrappedRootKey,
StorageValueModes.Nonwrapped,
)
}
public setRootKeyInstance(rootKey: RootKeyInterface | undefined): void {
this.rootKey = rootKey
}
public getRootKey(): RootKeyInterface | undefined {
return this.rootKey
}
private getSureRootKey(): RootKeyInterface {
return this.rootKey as RootKeyInterface
}
private getItemsKeys() {
return this.items.getDisplayableItemsKeys()
}
private async encryptPayloadWithKeyLookup(
payload: DecryptedPayloadInterface,
signingKeyPair?: PkcKeyPair,
): Promise<EncryptedOutputParameters> {
let key: RootKeyInterface | KeySystemRootKeyInterface | undefined
if (ContentTypeUsesKeySystemRootKeyEncryption(payload.content_type)) {
if (!payload.key_system_identifier) {
throw Error(`Key system-encrypted payload ${payload.content_type}is missing a key_system_identifier`)
}
key = this.keys.getPrimaryKeySystemRootKey(payload.key_system_identifier)
} else {
key = this.getRootKey()
}
if (key == undefined) {
throw Error('Attempting root key encryption with no root key')
}
return this.encryptPayload(payload, key, signingKeyPair)
}
public async encryptPayloadsWithKeyLookup(
payloads: DecryptedPayloadInterface[],
signingKeyPair?: PkcKeyPair,
): Promise<EncryptedOutputParameters[]> {
return Promise.all(payloads.map((payload) => this.encryptPayloadWithKeyLookup(payload, signingKeyPair)))
}
public async encryptPayload(
payload: DecryptedPayloadInterface,
key: RootKeyInterface | KeySystemRootKeyInterface,
signingKeyPair?: PkcKeyPair,
): Promise<EncryptedOutputParameters> {
return encryptPayload(payload, key, this.operatorManager, signingKeyPair)
}
public async encryptPayloads(
payloads: DecryptedPayloadInterface[],
key: RootKeyInterface | KeySystemRootKeyInterface,
signingKeyPair?: PkcKeyPair,
) {
return Promise.all(payloads.map((payload) => this.encryptPayload(payload, key, signingKeyPair)))
}
public async decryptPayloadWithKeyLookup<C extends ItemContent = ItemContent>(
payload: EncryptedPayloadInterface,
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
let key: RootKeyInterface | KeySystemRootKeyInterface | undefined
if (ContentTypeUsesKeySystemRootKeyEncryption(payload.content_type)) {
if (!payload.key_system_identifier) {
throw Error('Key system root key encrypted payload is missing key_system_identifier')
}
key = this.keys.getPrimaryKeySystemRootKey(payload.key_system_identifier)
} else {
key = this.getRootKey()
}
if (key == undefined) {
return {
uuid: payload.uuid,
errorDecrypting: true,
waitingForKey: true,
}
}
return this.decryptPayload(payload, key)
}
public async decryptPayload<C extends ItemContent = ItemContent>(
payload: EncryptedPayloadInterface,
key: RootKeyInterface | KeySystemRootKeyInterface,
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
return decryptPayload(payload, key, this.operatorManager)
}
public async decryptPayloadsWithKeyLookup<C extends ItemContent = ItemContent>(
payloads: EncryptedPayloadInterface[],
): Promise<(DecryptedParameters<C> | ErrorDecryptingParameters)[]> {
return Promise.all(payloads.map((payload) => this.decryptPayloadWithKeyLookup<C>(payload)))
}
public async decryptPayloads<C extends ItemContent = ItemContent>(
payloads: EncryptedPayloadInterface[],
key: RootKeyInterface | KeySystemRootKeyInterface,
): Promise<(DecryptedParameters<C> | ErrorDecryptingParameters)[]> {
return Promise.all(payloads.map((payload) => this.decryptPayload<C>(payload, key)))
}
public async decryptErroredRootPayloads(): Promise<void> {
const erroredRootPayloads = this.payloadManager.invalidPayloads.filter(
(i) =>
ContentTypeUsesRootKeyEncryption(i.content_type) || ContentTypeUsesKeySystemRootKeyEncryption(i.content_type),
)
if (erroredRootPayloads.length === 0) {
return
}
const resultParams = await this.decryptPayloadsWithKeyLookup(erroredRootPayloads)
const decryptedPayloads = resultParams.map((params) => {
const original = SureFindPayload(erroredRootPayloads, params.uuid)
if (isErrorDecryptingParameters(params)) {
return new EncryptedPayload({
...original.ejected(),
...params,
})
} else {
return new DecryptedPayload({
...original.ejected(),
...params,
})
}
})
await this.payloadManager.emitPayloads(decryptedPayloads, PayloadEmitSource.LocalChanged)
}
/**
* When the root key changes, we must re-encrypt all relevant items with this new root key (by simply re-syncing).
*/
public async reencryptApplicableItemsAfterUserRootKeyChange(): Promise<void> {
const items = this.items.getItems(ContentTypesUsingRootKeyEncryption())
if (items.length > 0) {
/**
* Do not call sync after marking dirty.
* Re-encrypting items keys is called by consumers who have specific flows who
* will sync on their own timing
*/
await this.mutator.setItemsDirty(items)
}
}
/**
* When the key system root key changes, we must re-encrypt all vault items keys
* with this new key system root key (by simply re-syncing).
*/
public async reencryptKeySystemItemsKeysForVault(keySystemIdentifier: KeySystemIdentifier): Promise<void> {
const keySystemItemsKeys = this.keys.getKeySystemItemsKeys(keySystemIdentifier)
if (keySystemItemsKeys.length > 0) {
await this.mutator.setItemsDirty(keySystemItemsKeys)
}
}
/**
* Creates a new random items key to use for item encryption, and adds it to model management.
* Consumer must call sync. If the protocol version <= 003, only one items key should be created,
* and its .itemsKey value should be equal to the root key masterKey value.
*/
public async createNewDefaultItemsKey(): Promise<ItemsKeyInterface> {
const rootKey = this.getSureRootKey()
const operatorVersion = rootKey ? rootKey.keyVersion : ProtocolVersionLatest
let itemTemplate: ItemsKeyInterface
if (compareVersions(operatorVersion, ProtocolVersionLastNonrootItemsKey) <= 0) {
/** Create root key based items key */
const payload = new DecryptedPayload<ItemsKeyContent>({
uuid: UuidGenerator.GenerateUuid(),
content_type: ContentType.ItemsKey,
content: FillItemContentSpecialized<ItemsKeyContentSpecialized, ItemsKeyContent>({
itemsKey: rootKey.masterKey,
dataAuthenticationKey: rootKey.dataAuthenticationKey,
version: operatorVersion,
}),
...PayloadTimestampDefaults(),
})
itemTemplate = CreateDecryptedItemFromPayload(payload)
} else {
/** Create independent items key */
itemTemplate = this.operatorManager.operatorForVersion(operatorVersion).createItemsKey()
}
const itemsKeys = this.getItemsKeys()
const defaultKeys = itemsKeys.filter((key) => {
return key.isDefault
})
for (const key of defaultKeys) {
await this.mutator.changeItemsKey(key, (mutator) => {
mutator.isDefault = false
})
}
const itemsKey = await this.mutator.insertItem<ItemsKeyInterface>(itemTemplate)
await this.mutator.changeItemsKey(itemsKey, (mutator) => {
mutator.isDefault = true
})
return itemsKey
}
public async createNewItemsKeyWithRollback(): Promise<() => Promise<void>> {
const currentDefaultItemsKey = findDefaultItemsKey(this.getItemsKeys())
const newDefaultItemsKey = await this.createNewDefaultItemsKey()
const rollback = async () => {
await this.mutator.setItemToBeDeleted(newDefaultItemsKey)
if (currentDefaultItemsKey) {
await this.mutator.changeItem<ItemsKeyMutator>(currentDefaultItemsKey, (mutator) => {
mutator.isDefault = true
})
}
}
return rollback
}
}
@@ -0,0 +1,76 @@
import { OperatorManager } from '@standardnotes/encryption'
import {
ContentType,
ProtocolVersionLastNonrootItemsKey,
ProtocolVersionLatest,
compareVersions,
} from '@standardnotes/common'
import {
CreateDecryptedItemFromPayload,
DecryptedPayload,
FillItemContentSpecialized,
ItemsKeyContent,
ItemsKeyContentSpecialized,
ItemsKeyInterface,
PayloadTimestampDefaults,
} from '@standardnotes/models'
import { UuidGenerator } from '@standardnotes/utils'
import { MutatorClientInterface } from '../../../Mutator/MutatorClientInterface'
import { ItemManagerInterface } from '../../../Item/ItemManagerInterface'
import { RootKeyManager } from '../../RootKey/RootKeyManager'
/**
* Creates a new random items key to use for item encryption, and adds it to model management.
* Consumer must call sync. If the protocol version <= 003, only one items key should be created,
* and its .itemsKey value should be equal to the root key masterKey value.
*/
export class CreateNewDefaultItemsKeyUseCase {
constructor(
private mutator: MutatorClientInterface,
private items: ItemManagerInterface,
private operatorManager: OperatorManager,
private rootKeyManager: RootKeyManager,
) {}
async execute(): Promise<ItemsKeyInterface> {
const rootKey = this.rootKeyManager.getSureRootKey()
const operatorVersion = rootKey ? rootKey.keyVersion : ProtocolVersionLatest
let itemTemplate: ItemsKeyInterface
if (compareVersions(operatorVersion, ProtocolVersionLastNonrootItemsKey) <= 0) {
/** Create root key based items key */
const payload = new DecryptedPayload<ItemsKeyContent>({
uuid: UuidGenerator.GenerateUuid(),
content_type: ContentType.ItemsKey,
content: FillItemContentSpecialized<ItemsKeyContentSpecialized, ItemsKeyContent>({
itemsKey: rootKey.masterKey,
dataAuthenticationKey: rootKey.dataAuthenticationKey,
version: operatorVersion,
}),
...PayloadTimestampDefaults(),
})
itemTemplate = CreateDecryptedItemFromPayload(payload)
} else {
/** Create independent items key */
itemTemplate = this.operatorManager.operatorForVersion(operatorVersion).createItemsKey()
}
const itemsKeys = this.items.getDisplayableItemsKeys()
const defaultKeys = itemsKeys.filter((key) => {
return key.isDefault
})
for (const key of defaultKeys) {
await this.mutator.changeItemsKey(key, (mutator) => {
mutator.isDefault = false
})
}
const itemsKey = await this.mutator.insertItem<ItemsKeyInterface>(itemTemplate)
await this.mutator.changeItemsKey(itemsKey, (mutator) => {
mutator.isDefault = true
})
return itemsKey
}
}
@@ -0,0 +1,43 @@
import { StorageServiceInterface } from './../../../Storage/StorageServiceInterface'
import { ItemsKeyMutator, OperatorManager, findDefaultItemsKey } from '@standardnotes/encryption'
import { MutatorClientInterface } from '../../../Mutator/MutatorClientInterface'
import { ItemManagerInterface } from '../../../Item/ItemManagerInterface'
import { RootKeyManager } from '../../RootKey/RootKeyManager'
import { CreateNewDefaultItemsKeyUseCase } from './CreateNewDefaultItemsKey'
import { RemoveItemsLocallyUseCase } from '../../../UseCase/RemoveItemsLocally'
export class CreateNewItemsKeyWithRollbackUseCase {
private createDefaultItemsKeyUseCase = new CreateNewDefaultItemsKeyUseCase(
this.mutator,
this.items,
this.operatorManager,
this.rootKeyManager,
)
private removeItemsLocallyUsecase = new RemoveItemsLocallyUseCase(this.items, this.storage)
constructor(
private mutator: MutatorClientInterface,
private items: ItemManagerInterface,
private storage: StorageServiceInterface,
private operatorManager: OperatorManager,
private rootKeyManager: RootKeyManager,
) {}
async execute(): Promise<() => Promise<void>> {
const currentDefaultItemsKey = findDefaultItemsKey(this.items.getDisplayableItemsKeys())
const newDefaultItemsKey = await this.createDefaultItemsKeyUseCase.execute()
const rollback = async () => {
await this.removeItemsLocallyUsecase.execute([newDefaultItemsKey])
if (currentDefaultItemsKey) {
await this.mutator.changeItem<ItemsKeyMutator>(currentDefaultItemsKey, (mutator) => {
mutator.isDefault = true
})
}
}
return rollback
}
}
@@ -0,0 +1,55 @@
import {
ContentTypeUsesKeySystemRootKeyEncryption,
ContentTypeUsesRootKeyEncryption,
DecryptedPayload,
EncryptedPayload,
PayloadEmitSource,
SureFindPayload,
} from '@standardnotes/models'
import { PayloadManagerInterface } from './../../../Payloads/PayloadManagerInterface'
import { KeySystemKeyManagerInterface, OperatorManager, isErrorDecryptingParameters } from '@standardnotes/encryption'
import { RootKeyDecryptPayloadWithKeyLookupUseCase } from './DecryptPayloadWithKeyLookup'
import { RootKeyManager } from '../../RootKey/RootKeyManager'
export class DecryptErroredRootPayloadsUseCase {
constructor(
private payloads: PayloadManagerInterface,
private operatorManager: OperatorManager,
private keySystemKeyManager: KeySystemKeyManagerInterface,
private rootKeyManager: RootKeyManager,
) {}
async execute(): Promise<void> {
const erroredRootPayloads = this.payloads.invalidPayloads.filter(
(i) =>
ContentTypeUsesRootKeyEncryption(i.content_type) || ContentTypeUsesKeySystemRootKeyEncryption(i.content_type),
)
if (erroredRootPayloads.length === 0) {
return
}
const usecase = new RootKeyDecryptPayloadWithKeyLookupUseCase(
this.operatorManager,
this.keySystemKeyManager,
this.rootKeyManager,
)
const resultParams = await usecase.executeMany(erroredRootPayloads)
const decryptedPayloads = resultParams.map((params) => {
const original = SureFindPayload(erroredRootPayloads, params.uuid)
if (isErrorDecryptingParameters(params)) {
return new EncryptedPayload({
...original.ejected(),
...params,
})
} else {
return new DecryptedPayload({
...original.ejected(),
...params,
})
}
})
await this.payloads.emitPayloads(decryptedPayloads, PayloadEmitSource.LocalChanged)
}
}
@@ -0,0 +1,30 @@
import {
DecryptedParameters,
ErrorDecryptingParameters,
OperatorManager,
decryptPayload,
} from '@standardnotes/encryption'
import {
EncryptedPayloadInterface,
ItemContent,
KeySystemRootKeyInterface,
RootKeyInterface,
} from '@standardnotes/models'
export class RootKeyDecryptPayloadUseCase {
constructor(private operatorManager: OperatorManager) {}
async executeOne<C extends ItemContent = ItemContent>(
payload: EncryptedPayloadInterface,
key: RootKeyInterface | KeySystemRootKeyInterface,
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
return decryptPayload(payload, key, this.operatorManager)
}
async executeMany<C extends ItemContent = ItemContent>(
payloads: EncryptedPayloadInterface[],
key: RootKeyInterface | KeySystemRootKeyInterface,
): Promise<(DecryptedParameters<C> | ErrorDecryptingParameters)[]> {
return Promise.all(payloads.map((payload) => this.executeOne<C>(payload, key)))
}
}
@@ -0,0 +1,56 @@
import {
DecryptedParameters,
ErrorDecryptingParameters,
KeySystemKeyManagerInterface,
OperatorManager,
} from '@standardnotes/encryption'
import {
ContentTypeUsesKeySystemRootKeyEncryption,
EncryptedPayloadInterface,
ItemContent,
KeySystemRootKeyInterface,
RootKeyInterface,
} from '@standardnotes/models'
import { RootKeyDecryptPayloadUseCase } from './DecryptPayload'
import { RootKeyManager } from '../../RootKey/RootKeyManager'
export class RootKeyDecryptPayloadWithKeyLookupUseCase {
constructor(
private operatorManager: OperatorManager,
private keySystemKeyManager: KeySystemKeyManagerInterface,
private rootKeyManager: RootKeyManager,
) {}
async executeOne<C extends ItemContent = ItemContent>(
payload: EncryptedPayloadInterface,
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
let key: RootKeyInterface | KeySystemRootKeyInterface | undefined
if (ContentTypeUsesKeySystemRootKeyEncryption(payload.content_type)) {
if (!payload.key_system_identifier) {
throw Error('Key system root key encrypted payload is missing key_system_identifier')
}
key = this.keySystemKeyManager.getPrimaryKeySystemRootKey(payload.key_system_identifier)
} else {
key = this.rootKeyManager.getRootKey()
}
if (key == undefined) {
return {
uuid: payload.uuid,
errorDecrypting: true,
waitingForKey: true,
}
}
const usecase = new RootKeyDecryptPayloadUseCase(this.operatorManager)
return usecase.executeOne(payload, key)
}
async executeMany<C extends ItemContent = ItemContent>(
payloads: EncryptedPayloadInterface[],
): Promise<(DecryptedParameters<C> | ErrorDecryptingParameters)[]> {
return Promise.all(payloads.map((payload) => this.executeOne<C>(payload)))
}
}
@@ -0,0 +1,23 @@
import { EncryptedOutputParameters, OperatorManager, encryptPayload } from '@standardnotes/encryption'
import { DecryptedPayloadInterface, KeySystemRootKeyInterface, RootKeyInterface } from '@standardnotes/models'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
export class RootKeyEncryptPayloadUseCase {
constructor(private operatorManager: OperatorManager) {}
async executeOne(
payload: DecryptedPayloadInterface,
key: RootKeyInterface | KeySystemRootKeyInterface,
signingKeyPair?: PkcKeyPair,
): Promise<EncryptedOutputParameters> {
return encryptPayload(payload, key, this.operatorManager, signingKeyPair)
}
async executeMany(
payloads: DecryptedPayloadInterface[],
key: RootKeyInterface | KeySystemRootKeyInterface,
signingKeyPair?: PkcKeyPair,
): Promise<EncryptedOutputParameters[]> {
return Promise.all(payloads.map((payload) => this.executeOne(payload, key, signingKeyPair)))
}
}
@@ -0,0 +1,48 @@
import { EncryptedOutputParameters, KeySystemKeyManagerInterface, OperatorManager } from '@standardnotes/encryption'
import {
ContentTypeUsesKeySystemRootKeyEncryption,
DecryptedPayloadInterface,
KeySystemRootKeyInterface,
RootKeyInterface,
} from '@standardnotes/models'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { RootKeyEncryptPayloadUseCase } from './EncryptPayload'
import { RootKeyManager } from '../../RootKey/RootKeyManager'
export class RootKeyEncryptPayloadWithKeyLookupUseCase {
constructor(
private operatorManager: OperatorManager,
private keySystemKeyManager: KeySystemKeyManagerInterface,
private rootKeyManager: RootKeyManager,
) {}
async executeOne(
payload: DecryptedPayloadInterface,
signingKeyPair?: PkcKeyPair,
): Promise<EncryptedOutputParameters> {
let key: RootKeyInterface | KeySystemRootKeyInterface | undefined
if (ContentTypeUsesKeySystemRootKeyEncryption(payload.content_type)) {
if (!payload.key_system_identifier) {
throw Error(`Key system-encrypted payload ${payload.content_type}is missing a key_system_identifier`)
}
key = this.keySystemKeyManager.getPrimaryKeySystemRootKey(payload.key_system_identifier)
} else {
key = this.rootKeyManager.getRootKey()
}
if (key == undefined) {
throw Error('Attempting root key encryption with no root key')
}
const usecase = new RootKeyEncryptPayloadUseCase(this.operatorManager)
return usecase.executeOne(payload, key, signingKeyPair)
}
async executeMany(
payloads: DecryptedPayloadInterface[],
signingKeyPair?: PkcKeyPair,
): Promise<EncryptedOutputParameters[]> {
return Promise.all(payloads.map((payload) => this.executeOne(payload, signingKeyPair)))
}
}
@@ -386,7 +386,7 @@ export class FileService extends AbstractService implements FilesClientInterface
const result = await this.api.deleteFile(tokenResult, file.shared_vault_uuid ? 'shared-vault' : 'user')
if (result.data?.error) {
if (isErrorResponse(result)) {
const deleteAnyway = await this.alertService.confirm(
spaceSeparatedStrings(
'This file could not be deleted from the server, possibly because you are attempting to delete a file item',
@@ -400,7 +400,7 @@ export class FileService extends AbstractService implements FilesClientInterface
)
if (!deleteAnyway) {
return ClientDisplayableError.FromError(result.data?.error)
return ClientDisplayableError.FromNetworkError(result)
}
}
@@ -1,3 +1,4 @@
export enum InternalFeature {
Vaults = 'vaults',
HomeServer = 'home-server',
}
@@ -1,6 +1,6 @@
import { RevisionApiServiceInterface } from '@standardnotes/api'
import { Uuid } from '@standardnotes/domain-core'
import { isErrorResponse } from '@standardnotes/responses'
import { getErrorFromErrorResponse, isErrorResponse } from '@standardnotes/responses'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { AbstractService } from '../Service/AbstractService'
@@ -21,7 +21,7 @@ export class RevisionManager extends AbstractService implements RevisionClientIn
const result = await this.revisionApiService.listRevisions(itemUuid.value)
if (isErrorResponse(result)) {
throw new Error(result.data.error.message)
throw new Error(getErrorFromErrorResponse(result).message)
}
return result.data.revisions
@@ -31,7 +31,7 @@ export class RevisionManager extends AbstractService implements RevisionClientIn
const result = await this.revisionApiService.deleteRevision(itemUuid.value, revisionUuid.value)
if (isErrorResponse(result)) {
throw new Error(result.data.error.message)
throw new Error(getErrorFromErrorResponse(result).message)
}
return result.data.message
@@ -41,7 +41,7 @@ export class RevisionManager extends AbstractService implements RevisionClientIn
const result = await this.revisionApiService.getRevision(itemUuid.value, revisionUuid.value)
if (isErrorResponse(result)) {
throw new Error(result.data.error.message)
throw new Error(getErrorFromErrorResponse(result).message)
}
return result.data.revision
@@ -111,7 +111,9 @@ export class SharedVaultService
)
this.eventDisposers.push(
items.addObserver<TrustedContactInterface>(ContentType.TrustedContact, ({ changed, inserted, source }) => {
items.addObserver<TrustedContactInterface>(ContentType.TrustedContact, async ({ changed, inserted, source }) => {
await this.reprocessCachedInvitesTrustStatusAfterTrustedContactsChange()
if (source === PayloadEmitSource.LocalChanged && inserted.length > 0) {
void this.handleCreationOfNewTrustedContacts(inserted)
}
@@ -250,8 +252,6 @@ export class SharedVaultService
}
private async handleTrustedContactsChange(contacts: TrustedContactInterface[]): Promise<void> {
await this.reprocessCachedInvitesTrustStatusAfterTrustedContactsChange()
for (const contact of contacts) {
await this.shareContactWithUserAdministeredSharedVaults(contact)
}
@@ -328,28 +328,9 @@ export class SharedVaultService
}
private async reprocessCachedInvitesTrustStatusAfterTrustedContactsChange(): Promise<void> {
const cachedInvites = this.getCachedPendingInviteRecords()
const cachedInvites = this.getCachedPendingInviteRecords().map((record) => record.invite)
for (const record of cachedInvites) {
if (record.trusted) {
continue
}
const trustedMessageUseCase = new GetAsymmetricMessageTrustedPayload<AsymmetricMessageSharedVaultInvite>(
this.encryption,
this.contacts,
)
const trustedMessage = trustedMessageUseCase.execute({
message: record.invite,
privateKey: this.encryption.getKeyPair().privateKey,
})
if (trustedMessage) {
record.message = trustedMessage
record.trusted = true
}
}
await this.processInboundInvites(cachedInvites)
}
private async processInboundInvites(invites: SharedVaultInviteServerHash[]): Promise<void> {
@@ -3,10 +3,12 @@ import { StorageServiceInterface } from '../../Storage/StorageServiceInterface'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { AnyItemInterface, VaultListingInterface } from '@standardnotes/models'
import { Uuids } from '@standardnotes/utils'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
import { RemoveItemsLocallyUseCase } from '../../UseCase/RemoveItemsLocally'
export class DeleteExternalSharedVaultUseCase {
private removeItemsLocallyUsecase = new RemoveItemsLocallyUseCase(this.items, this.storage)
constructor(
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
@@ -28,15 +30,13 @@ export class DeleteExternalSharedVaultUseCase {
* The data will be removed locally without syncing the items
*/
private async deleteDataSharedByVaultUsers(vault: VaultListingInterface): Promise<void> {
const vaultItems = this.items
.allTrackedItems()
.filter((item) => item.key_system_identifier === vault.systemIdentifier)
this.items.removeItemsLocally(vaultItems as AnyItemInterface[])
const vaultItems = <AnyItemInterface[]>(
this.items.allTrackedItems().filter((item) => item.key_system_identifier === vault.systemIdentifier)
)
const itemsKeys = this.encryption.keys.getKeySystemItemsKeys(vault.systemIdentifier)
this.items.removeItemsLocally(itemsKeys)
await this.storage.deletePayloadsWithUuids([...Uuids(vaultItems), ...Uuids(itemsKeys)])
await this.removeItemsLocallyUsecase.execute([...vaultItems, ...itemsKeys])
}
private async deleteDataOwnedByThisUser(vault: VaultListingInterface): Promise<void> {
@@ -23,7 +23,7 @@ export class SendSharedVaultInviteUseCase {
})
if (isErrorResponse(response)) {
return ClientDisplayableError.FromError(response.data.error)
return ClientDisplayableError.FromNetworkError(response)
}
return response.data.invite
@@ -23,7 +23,7 @@ export class UpdateSharedVaultInviteUseCase {
})
if (isErrorResponse(response)) {
return ClientDisplayableError.FromError(response.data.error)
return ClientDisplayableError.FromNetworkError(response)
}
return response.data.invite
@@ -4,7 +4,7 @@ import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface
import { AbstractService } from '../Service/AbstractService'
import { SubscriptionClientInterface } from './SubscriptionClientInterface'
import { AppleIAPReceipt } from './AppleIAPReceipt'
import { isErrorResponse } from '@standardnotes/responses'
import { getErrorFromErrorResponse, isErrorResponse } from '@standardnotes/responses'
export class SubscriptionManager extends AbstractService implements SubscriptionClientInterface {
constructor(
@@ -19,7 +19,7 @@ export class SubscriptionManager extends AbstractService implements Subscription
const result = await this.subscriptionApiService.acceptInvite(inviteUuid)
if (isErrorResponse(result)) {
return { success: false, message: result.data.error.message }
return { success: false, message: getErrorFromErrorResponse(result).message }
}
return result.data
@@ -81,7 +81,7 @@ export class SubscriptionManager extends AbstractService implements Subscription
})
if (isErrorResponse(result)) {
return { success: false, message: result.data.error.message }
return { success: false, message: getErrorFromErrorResponse(result).message }
}
return result.data
@@ -0,0 +1,14 @@
import { StorageServiceInterface } from '../Storage/StorageServiceInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { AnyItemInterface } from '@standardnotes/models'
import { Uuids } from '@standardnotes/utils'
export class RemoveItemsLocallyUseCase {
constructor(private readonly items: ItemManagerInterface, private readonly storage: StorageServiceInterface) {}
async execute(items: AnyItemInterface[]): Promise<void> {
this.items.removeItemsLocally(items)
await this.storage.deletePayloadsWithUuids(Uuids(items))
}
}
@@ -20,7 +20,7 @@ describe('UserService', () => {
let syncService: SyncServiceInterface
let storageService: StorageServiceInterface
let itemManager: ItemManagerInterface
let protocolService: EncryptionProviderInterface
let encryptionService: EncryptionProviderInterface
let alertService: AlertService
let challengeService: ChallengeServiceInterface
let protectionService: ProtectionsClientInterface
@@ -33,7 +33,7 @@ describe('UserService', () => {
syncService,
storageService,
itemManager,
protocolService,
encryptionService,
alertService,
challengeService,
protectionService,
@@ -51,7 +51,7 @@ describe('UserService', () => {
itemManager = {} as jest.Mocked<ItemManagerInterface>
protocolService = {} as jest.Mocked<EncryptionProviderInterface>
encryptionService = {} as jest.Mocked<EncryptionProviderInterface>
alertService = {} as jest.Mocked<AlertService>
@@ -1,6 +1,12 @@
import { Base64String } from '@standardnotes/sncrypto-common'
import { EncryptionProviderInterface, SNRootKey, SNRootKeyParams } from '@standardnotes/encryption'
import { HttpResponse, SignInResponse, User, isErrorResponse } from '@standardnotes/responses'
import {
HttpResponse,
SignInResponse,
User,
getErrorFromErrorResponse,
isErrorResponse,
} from '@standardnotes/responses'
import { KeyParamsOrigination, UserRequestType } from '@standardnotes/common'
import { UuidGenerator } from '@standardnotes/utils'
import { UserApiServiceInterface, UserRegistrationResponseBody } from '@standardnotes/api'
@@ -49,7 +55,7 @@ export class UserService
private syncService: SyncServiceInterface,
private storageService: StorageServiceInterface,
private itemManager: ItemManagerInterface,
private protocolService: EncryptionProviderInterface,
private encryptionService: EncryptionProviderInterface,
private alertService: AlertService,
private challengeService: ChallengeServiceInterface,
private protectionService: ProtectionsClientInterface,
@@ -84,14 +90,14 @@ export class UserService
})
.then(() => {
if (!payload.awaitSync) {
void this.protocolService.decryptErroredPayloads()
void this.encryptionService.decryptErroredPayloads()
}
})
if (payload.awaitSync) {
await syncPromise
await this.protocolService.decryptErroredPayloads()
await this.encryptionService.decryptErroredPayloads()
}
}
}
@@ -102,7 +108,7 @@ export class UserService
;(this.syncService as unknown) = undefined
;(this.storageService as unknown) = undefined
;(this.itemManager as unknown) = undefined
;(this.protocolService as unknown) = undefined
;(this.encryptionService as unknown) = undefined
;(this.alertService as unknown) = undefined
;(this.challengeService as unknown) = undefined
;(this.protectionService as unknown) = undefined
@@ -123,7 +129,7 @@ export class UserService
ephemeral = false,
mergeLocal = true,
): Promise<UserRegistrationResponseBody> {
if (this.protocolService.hasAccount()) {
if (this.encryptionService.hasAccount()) {
throw Error('Tried to register when an account already exists.')
}
@@ -169,7 +175,7 @@ export class UserService
mergeLocal = true,
awaitSync = false,
): Promise<HttpResponse<SignInResponse>> {
if (this.protocolService.hasAccount()) {
if (this.encryptionService.hasAccount()) {
throw Error('Tried to sign in when an account already exists.')
}
@@ -227,7 +233,7 @@ export class UserService
if (isErrorResponse(response)) {
return {
error: true,
message: response.data.error.message,
message: getErrorFromErrorResponse(response).message,
}
}
@@ -313,7 +319,7 @@ export class UserService
public async signOut(force = false, source = DeinitSource.SignOut): Promise<void> {
const performSignOut = async () => {
await this.sessionManager.signOut()
await this.protocolService.deleteWorkspaceSpecificKeyStateFromDevice()
await this.encryptionService.deleteWorkspaceSpecificKeyStateFromDevice()
await this.storageService.clearAllData()
await this.notifyEvent(AccountEvent.SignedOut, { payload: { source } })
}
@@ -359,8 +365,8 @@ export class UserService
canceled?: true
error?: { message: string }
}> {
const hasPasscode = this.protocolService.hasPasscode()
const hasAccount = this.protocolService.hasAccount()
const hasPasscode = this.encryptionService.hasPasscode()
const hasAccount = this.encryptionService.hasAccount()
const prompts = []
if (hasPasscode) {
prompts.push(
@@ -504,14 +510,14 @@ export class UserService
private async setPasscodeWithoutWarning(passcode: string, origination: KeyParamsOrigination) {
const identifier = UuidGenerator.GenerateUuid()
const key = await this.protocolService.createRootKey(identifier, passcode, origination)
await this.protocolService.setNewRootKeyWrapper(key)
const key = await this.encryptionService.createRootKey(identifier, passcode, origination)
await this.encryptionService.setNewRootKeyWrapper(key)
await this.rewriteItemsKeys()
await this.syncService.sync()
}
private async removePasscodeWithoutWarning() {
await this.protocolService.removePasscode()
await this.encryptionService.removePasscode()
await this.rewriteItemsKeys()
}
@@ -564,7 +570,7 @@ export class UserService
}
}
const accountPasswordValidation = await this.protocolService.validateAccountPassword(parameters.currentPassword)
const accountPasswordValidation = await this.encryptionService.validateAccountPassword(parameters.currentPassword)
if (!accountPasswordValidation.valid) {
return {
error: Error(Messages.INVALID_PASSWORD),
@@ -583,7 +589,6 @@ export class UserService
this.lockSyncing()
/** Now, change the credentials on the server. Roll back on failure */
const { response } = await this.sessionManager.changeCredentials({
currentServerPassword: currentRootKey.serverPassword as string,
newRootKey: newRootKey,
@@ -597,11 +602,11 @@ export class UserService
return { error: Error(response.data.error?.message) }
}
const rollback = await this.protocolService.createNewItemsKeyWithRollback()
await this.protocolService.reencryptApplicableItemsAfterUserRootKeyChange()
const rollback = await this.encryptionService.createNewItemsKeyWithRollback()
await this.encryptionService.reencryptApplicableItemsAfterUserRootKeyChange()
await this.syncService.sync({ awaitAll: true })
const defaultItemsKey = this.protocolService.getSureDefaultItemsKey()
const defaultItemsKey = this.encryptionService.getSureDefaultItemsKey()
const itemsKeyWasSynced = !defaultItemsKey.neverSynced
if (!itemsKeyWasSynced) {
@@ -610,7 +615,7 @@ export class UserService
newRootKey: currentRootKey,
wrappingKey,
})
await this.protocolService.reencryptApplicableItemsAfterUserRootKeyChange()
await this.encryptionService.reencryptApplicableItemsAfterUserRootKeyChange()
await rollback()
await this.syncService.sync({ awaitAll: true })
@@ -627,11 +632,11 @@ export class UserService
newEmail?: string
newPassword?: string
}): Promise<{ currentRootKey: SNRootKey; newRootKey: SNRootKey }> {
const currentRootKey = await this.protocolService.computeRootKey(
const currentRootKey = await this.encryptionService.computeRootKey(
parameters.currentPassword,
(await this.protocolService.getRootKeyParams()) as SNRootKeyParams,
(await this.encryptionService.getRootKeyParams()) as SNRootKeyParams,
)
const newRootKey = await this.protocolService.createRootKey(
const newRootKey = await this.encryptionService.createRootKey(
parameters.newEmail ?? parameters.currentEmail,
parameters.newPassword ?? parameters.currentPassword,
parameters.origination,
+1 -1
View File
@@ -9,6 +9,7 @@ export * from './Application/DeinitMode'
export * from './Application/DeinitSource'
export * from './AsymmetricMessage/AsymmetricMessageService'
export * from './AsymmetricMessage/AsymmetricMessageServiceInterface'
export * from './Auth/AuthClientInterface'
export * from './Auth/AuthManager'
@@ -61,7 +62,6 @@ export * from './Encryption/EncryptionService'
export * from './Encryption/EncryptionServiceEvent'
export * from './Encryption/Functions'
export * from './Encryption/ItemsEncryption'
export * from './Encryption/RootKeyEncryption'
export * from './Event/ApplicationEvent'
export * from './Event/ApplicationEventCallback'
+28
View File
@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.201.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/snjs
## [2.201.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/snjs
## [2.201.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/snjs
## [2.201.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/snjs
## [2.201.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/snjs
## [2.201.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/snjs
## [2.201.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/snjs
# [2.201.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
### Features
+43 -39
View File
@@ -139,7 +139,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
private deprecatedHttpService!: InternalServices.DeprecatedHttpService
private declare httpService: HttpServiceInterface
public payloadManager!: InternalServices.PayloadManager
public protocolService!: EncryptionService
public encryptionService!: EncryptionService
private diskStorageService!: InternalServices.DiskStorageService
private inMemoryStore!: ExternalServices.KeyValueStoreInterface<string>
/**
@@ -387,6 +387,10 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
return this.challengeService
}
public get asymmetric(): ExternalServices.AsymmetricMessageServiceInterface {
return this.asymmetricMessageService
}
get homeServer(): ExternalServices.HomeServerServiceInterface | undefined {
return this.homeServerService
}
@@ -435,7 +439,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
await this.diskStorageService.initializeFromDisk()
await this.notifyEvent(ApplicationEvent.StorageReady)
await this.protocolService.initialize()
await this.encryptionService.initialize()
await this.handleStage(ExternalServices.ApplicationStage.ReadyForLaunch_05)
@@ -547,9 +551,9 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
let wrappingKey = response.artifacts?.wrappingKey
if (!wrappingKey) {
const value = response.getValueForType(ChallengeValidation.LocalPasscode)
wrappingKey = await this.protocolService.computeWrappingKey(value.value as string)
wrappingKey = await this.encryptionService.computeWrappingKey(value.value as string)
}
await this.protocolService.unwrapRootKey(wrappingKey)
await this.encryptionService.unwrapRootKey(wrappingKey)
}
}
@@ -740,22 +744,22 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
}
public getUserPasswordCreationDate(): Date | undefined {
return this.protocolService.getPasswordCreatedDate()
return this.encryptionService.getPasswordCreatedDate()
}
public getProtocolEncryptionDisplayName(): Promise<string | undefined> {
return this.protocolService.getEncryptionDisplayName()
return this.encryptionService.getEncryptionDisplayName()
}
public getUserVersion(): Common.ProtocolVersion | undefined {
return this.protocolService.getUserVersion()
return this.encryptionService.getUserVersion()
}
/**
* Returns true if there is an upgrade available for the account or passcode
*/
public protocolUpgradeAvailable(): Promise<boolean> {
return this.protocolService.upgradeAvailable()
return this.encryptionService.upgradeAvailable()
}
/**
@@ -790,7 +794,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
}
public hasAccount(): boolean {
return this.protocolService.hasAccount()
return this.encryptionService.hasAccount()
}
/**
@@ -840,7 +844,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
}
public async createEncryptedBackupFileForAutomatedDesktopBackups(): Promise<BackupFile | undefined> {
return this.protocolService.createEncryptedBackupFile()
return this.encryptionService.createEncryptedBackupFile()
}
public async createEncryptedBackupFile(): Promise<BackupFile | undefined> {
@@ -848,7 +852,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
return
}
return this.protocolService.createEncryptedBackupFile()
return this.encryptionService.createEncryptedBackupFile()
}
public async createDecryptedBackupFile(): Promise<BackupFile | undefined> {
@@ -856,7 +860,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
return
}
return this.protocolService.createDecryptedBackupFile()
return this.encryptionService.createDecryptedBackupFile()
}
public isEphemeralSession(): boolean {
@@ -1058,7 +1062,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.itemManager,
this.syncService,
this.protectionService,
this.protocolService,
this.encryptionService,
this.payloadManager,
this.challengeService,
this.historyManager,
@@ -1083,7 +1087,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
}
public async validateAccountPassword(password: string): Promise<boolean> {
const { valid } = await this.protocolService.validateAccountPassword(password)
const { valid } = await this.encryptionService.validateAccountPassword(password)
return valid
}
@@ -1096,7 +1100,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
}
public hasPasscode(): boolean {
return this.protocolService.hasPasscode()
return this.encryptionService.hasPasscode()
}
async isLocked(): Promise<boolean> {
@@ -1253,7 +1257,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.createKeySystemKeyManager()
this.createProtocolService()
this.diskStorageService.provideEncryptionProvider(this.protocolService)
this.diskStorageService.provideEncryptionProvider(this.encryptionService)
this.createChallengeService()
this.createLegacyHttpManager()
this.createHttpServiceAndApiService()
@@ -1308,7 +1312,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
;(this.deprecatedHttpService as unknown) = undefined
;(this.httpService as unknown) = undefined
;(this.payloadManager as unknown) = undefined
;(this.protocolService as unknown) = undefined
;(this.encryptionService as unknown) = undefined
;(this.diskStorageService as unknown) = undefined
;(this.inMemoryStore as unknown) = undefined
;(this.apiService as unknown) = undefined
@@ -1392,7 +1396,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
private createAsymmetricMessageService() {
this.asymmetricMessageService = new ExternalServices.AsymmetricMessageService(
this.httpService,
this.protocolService,
this.encryptionService,
this.contacts,
this.itemManager,
this.mutator,
@@ -1410,7 +1414,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.sessionManager,
this.options.crypto,
this.user,
this.protocolService,
this.encryptionService,
this.singletonManager,
this.internalEventBus,
)
@@ -1424,7 +1428,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.syncService,
this.itemManager,
this.mutator,
this.protocolService,
this.encryptionService,
this.sessions,
this.contactService,
this.files,
@@ -1440,7 +1444,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.syncService,
this.itemManager,
this.mutator,
this.protocolService,
this.encryptionService,
this.files,
this.alertService,
this.internalEventBus,
@@ -1468,7 +1472,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.apiService,
this.mutator,
this.syncService,
this.protocolService,
this.encryptionService,
this.challengeService,
this.httpService,
this.alertService,
@@ -1542,7 +1546,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
private createMigrationService() {
this.migrationService = new InternalServices.SNMigrationService({
protocolService: this.protocolService,
encryptionService: this.encryptionService,
deviceInterface: this.deviceInterface,
storageService: this.diskStorageService,
sessionManager: this.sessionManager,
@@ -1567,7 +1571,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.syncService,
this.diskStorageService,
this.itemManager,
this.protocolService,
this.encryptionService,
this.alertService,
this.challengeService,
this.protectionService,
@@ -1720,7 +1724,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
}
private createProtocolService() {
this.protocolService = new EncryptionService(
this.encryptionService = new EncryptionService(
this.itemManager,
this.mutator,
this.payloadManager,
@@ -1732,13 +1736,13 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.internalEventBus,
)
this.serviceObservers.push(
this.protocolService.addEventObserver(async (event) => {
this.encryptionService.addEventObserver(async (event) => {
if (event === EncryptionServiceEvent.RootKeyStatusChanged) {
await this.notifyEvent(ApplicationEvent.KeyStatusChanged)
}
}),
)
this.services.push(this.protocolService)
this.services.push(this.encryptionService)
}
private createKeySystemKeyManager() {
@@ -1757,7 +1761,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.itemManager,
this.payloadManager,
this.apiService,
this.protocolService,
this.encryptionService,
this.challengeService,
this.alertService,
this.diskStorageService,
@@ -1774,7 +1778,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.apiService,
this.userApiService,
this.alertService,
this.protocolService,
this.encryptionService,
this.challengeService,
this.webSocketsService,
this.httpService,
@@ -1789,8 +1793,8 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
case ExternalServices.SessionEvent.Restored: {
void (async () => {
await this.sync.sync({ sourceDescription: 'Session restored pre key creation' })
if (this.protocolService.needsNewRootKeyBasedItemsKey()) {
void this.protocolService.createNewDefaultItemsKey().then(() => {
if (this.encryptionService.needsNewRootKeyBasedItemsKey()) {
void this.encryptionService.createNewDefaultItemsKey().then(() => {
void this.sync.sync({ sourceDescription: 'Session restored post key creation' })
})
}
@@ -1816,7 +1820,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.syncService = new InternalServices.SNSyncService(
this.itemManager,
this.sessionManager,
this.protocolService,
this.encryptionService,
this.diskStorageService,
this.payloadManager,
this.apiService,
@@ -1832,7 +1836,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
const syncEventCallback = async (eventName: ExternalServices.SyncEvent) => {
const appEvent = applicationEventForSyncEvent(eventName)
if (appEvent) {
await this.protocolService.onSyncEvent(eventName)
await this.encryptionService.onSyncEvent(eventName)
await this.notifyEvent(appEvent)
@@ -1852,7 +1856,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
private createChallengeService() {
this.challengeService = new InternalServices.ChallengeService(
this.diskStorageService,
this.protocolService,
this.encryptionService,
this.internalEventBus,
)
this.services.push(this.challengeService)
@@ -1860,7 +1864,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
private createProtectionService() {
this.protectionService = new InternalServices.SNProtectionService(
this.protocolService,
this.encryptionService,
this.mutator,
this.challengeService,
this.diskStorageService,
@@ -1895,7 +1899,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.deviceInterface,
this.deprecatedHttpService,
this.payloadManager,
this.protocolService,
this.encryptionService,
this.syncService,
this.challengeService,
this.listedService,
@@ -1953,7 +1957,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.filesBackupService = new FilesBackupService(
this.itemManager,
this.apiService,
this.protocolService,
this.encryptionService,
device as FileBackupsDevice,
this.statusService,
this.options.crypto,
@@ -2003,7 +2007,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
private createUseCases() {
this._signInWithRecoveryCodes = new SignInWithRecoveryCodes(
this.authManager,
this.protocolService,
this.encryptionService,
this.inMemoryStore,
this.options.crypto,
this.sessionManager,
@@ -2030,7 +2034,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this._listRevisions = new ListRevisions(this.revisionManager)
this._getRevision = new GetRevision(this.revisionManager, this.protocolService)
this._getRevision = new GetRevision(this.revisionManager, this.encryptionService)
this._deleteRevision = new DeleteRevision(this.revisionManager)
}
@@ -17,9 +17,9 @@ import { GetRevision } from './GetRevision'
describe('GetRevision', () => {
let revisionManager: RevisionClientInterface
let protocolService: EncryptionProviderInterface
let encryptionService: EncryptionProviderInterface
const createUseCase = () => new GetRevision(revisionManager, protocolService)
const createUseCase = () => new GetRevision(revisionManager, encryptionService)
beforeEach(() => {
revisionManager = {} as jest.Mocked<RevisionClientInterface>
@@ -35,13 +35,13 @@ describe('GetRevision', () => {
updated_at: '2021-01-01T00:00:00.000Z'
} as jest.Mocked<Revision>)
protocolService = {} as jest.Mocked<EncryptionProviderInterface>
protocolService.getEmbeddedPayloadAuthenticatedData = jest.fn().mockReturnValue({ u: '00000000-0000-0000-0000-000000000000' })
encryptionService = {} as jest.Mocked<EncryptionProviderInterface>
encryptionService.getEmbeddedPayloadAuthenticatedData = jest.fn().mockReturnValue({ u: '00000000-0000-0000-0000-000000000000' })
const encryptedPayload = {
content: 'foobar',
} as jest.Mocked<EncryptedPayloadInterface>
encryptedPayload.copy = jest.fn().mockReturnValue(encryptedPayload)
protocolService.decryptSplitSingle = jest.fn().mockReturnValue(encryptedPayload)
encryptionService.decryptSplitSingle = jest.fn().mockReturnValue(encryptedPayload)
isRemotePayloadAllowed.mockImplementation(() => true)
})
@@ -59,7 +59,7 @@ describe('GetRevision', () => {
})
it('it should get a revision without uuid from embedded params', async () => {
protocolService.getEmbeddedPayloadAuthenticatedData = jest.fn().mockReturnValue({ u: undefined })
encryptionService.getEmbeddedPayloadAuthenticatedData = jest.fn().mockReturnValue({ u: undefined })
const useCase = createUseCase()
@@ -73,7 +73,7 @@ describe('GetRevision', () => {
})
it('it should get a revision without embedded params', async () => {
protocolService.getEmbeddedPayloadAuthenticatedData = jest.fn().mockReturnValue(undefined)
encryptionService.getEmbeddedPayloadAuthenticatedData = jest.fn().mockReturnValue(undefined)
const useCase = createUseCase()
@@ -130,7 +130,7 @@ describe('GetRevision', () => {
errorDecrypting: true,
} as jest.Mocked<EncryptedPayloadInterface>
encryptedPayload.copy = jest.fn().mockReturnValue(encryptedPayload)
protocolService.decryptSplitSingle = jest.fn().mockReturnValue(encryptedPayload)
encryptionService.decryptSplitSingle = jest.fn().mockReturnValue(encryptedPayload)
const useCase = createUseCase()
@@ -15,7 +15,10 @@ import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { GetRevisionDTO } from './GetRevisionDTO'
export class GetRevision implements UseCaseInterface<HistoryEntry> {
constructor(private revisionManager: RevisionClientInterface, private protocolService: EncryptionProviderInterface) {}
constructor(
private revisionManager: RevisionClientInterface,
private encryptionService: EncryptionProviderInterface,
) {}
async execute(dto: GetRevisionDTO): Promise<Result<HistoryEntry>> {
const itemUuidOrError = Uuid.create(dto.itemUuid)
@@ -63,7 +66,7 @@ export class GetRevision implements UseCaseInterface<HistoryEntry> {
* these olders revisions (which have not been mutated after copy) with the source item's
* uuid.
*/
const embeddedParams = this.protocolService.getEmbeddedPayloadAuthenticatedData(serverPayload)
const embeddedParams = this.encryptionService.getEmbeddedPayloadAuthenticatedData(serverPayload)
const sourceItemUuid = embeddedParams?.u as string | undefined
const payload = serverPayload.copy({
@@ -76,7 +79,7 @@ export class GetRevision implements UseCaseInterface<HistoryEntry> {
const encryptedPayload = new EncryptedPayload(payload)
const decryptedPayload = await this.protocolService.decryptSplitSingle<NoteContent>({
const decryptedPayload = await this.encryptionService.decryptSplitSingle<NoteContent>({
usesItemsKeyWithKeyLookup: { items: [encryptedPayload] },
})
@@ -14,7 +14,7 @@ import { SignInWithRecoveryCodes } from './SignInWithRecoveryCodes'
describe('SignInWithRecoveryCodes', () => {
let authManager: AuthClientInterface
let protocolService: EncryptionProviderInterface
let encryptionService: EncryptionProviderInterface
let inMemoryStore: KeyValueStoreInterface<string>
let crypto: PureCryptoInterface
let sessionManager: SessionsClientInterface
@@ -22,7 +22,7 @@ describe('SignInWithRecoveryCodes', () => {
const createUseCase = () => new SignInWithRecoveryCodes(
authManager,
protocolService,
encryptionService,
inMemoryStore,
crypto,
sessionManager,
@@ -50,17 +50,17 @@ describe('SignInWithRecoveryCodes', () => {
})
rootKey.payload = payload
protocolService = {} as jest.Mocked<EncryptionProviderInterface>
protocolService.hasAccount = jest.fn()
protocolService.computeRootKey = jest.fn().mockReturnValue(rootKey)
protocolService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(true)
protocolService.supportedVersions = jest.fn().mockReturnValue([
encryptionService = {} as jest.Mocked<EncryptionProviderInterface>
encryptionService.hasAccount = jest.fn()
encryptionService.computeRootKey = jest.fn().mockReturnValue(rootKey)
encryptionService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(true)
encryptionService.supportedVersions = jest.fn().mockReturnValue([
'001',
'002',
'003',
'004',
])
protocolService.isVersionNewerThanLibraryVersion = jest.fn()
encryptionService.isVersionNewerThanLibraryVersion = jest.fn()
inMemoryStore = {} as jest.Mocked<KeyValueStoreInterface<string>>
inMemoryStore.setValue = jest.fn()
@@ -79,7 +79,7 @@ describe('SignInWithRecoveryCodes', () => {
})
it('should fail if an account already exists', async () => {
protocolService.hasAccount = jest.fn().mockReturnValue(true)
encryptionService.hasAccount = jest.fn().mockReturnValue(true)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
@@ -99,7 +99,7 @@ describe('SignInWithRecoveryCodes', () => {
})
it('should fail if key params has unsupported deriviation', async () => {
protocolService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
encryptionService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
@@ -109,7 +109,7 @@ describe('SignInWithRecoveryCodes', () => {
})
it('should fail if key params has unsupported version', async () => {
protocolService.isVersionNewerThanLibraryVersion = jest.fn().mockReturnValue(true)
encryptionService.isVersionNewerThanLibraryVersion = jest.fn().mockReturnValue(true)
authManager.recoveryKeyParams = jest.fn().mockReturnValue({
identifier: '[email protected]',
@@ -120,7 +120,7 @@ describe('SignInWithRecoveryCodes', () => {
version: '006',
})
protocolService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
encryptionService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
@@ -130,7 +130,7 @@ describe('SignInWithRecoveryCodes', () => {
})
it('should fail if key params has expired version', async () => {
protocolService.isVersionNewerThanLibraryVersion = jest.fn().mockReturnValue(false)
encryptionService.isVersionNewerThanLibraryVersion = jest.fn().mockReturnValue(false)
authManager.recoveryKeyParams = jest.fn().mockReturnValue({
identifier: '[email protected]',
@@ -141,7 +141,7 @@ describe('SignInWithRecoveryCodes', () => {
version: '006',
})
protocolService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
encryptionService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
@@ -20,7 +20,7 @@ import { SignInWithRecoveryCodesDTO } from './SignInWithRecoveryCodesDTO'
export class SignInWithRecoveryCodes implements UseCaseInterface<void> {
constructor(
private authManager: AuthClientInterface,
private protocolService: EncryptionProviderInterface,
private encryptionService: EncryptionProviderInterface,
private inMemoryStore: KeyValueStoreInterface<string>,
private crypto: PureCryptoInterface,
private sessionManager: SessionsClientInterface,
@@ -28,7 +28,7 @@ export class SignInWithRecoveryCodes implements UseCaseInterface<void> {
) {}
async execute(dto: SignInWithRecoveryCodesDTO): Promise<Result<void>> {
if (this.protocolService.hasAccount()) {
if (this.encryptionService.hasAccount()) {
return Result.fail('Tried to sign in when an account already exists.')
}
@@ -48,19 +48,19 @@ export class SignInWithRecoveryCodes implements UseCaseInterface<void> {
const rootKeyParams = CreateAnyKeyParams(recoveryKeyParams)
if (!this.protocolService.supportedVersions().includes(rootKeyParams.version)) {
if (this.protocolService.isVersionNewerThanLibraryVersion(rootKeyParams.version)) {
if (!this.encryptionService.supportedVersions().includes(rootKeyParams.version)) {
if (this.encryptionService.isVersionNewerThanLibraryVersion(rootKeyParams.version)) {
return Result.fail(UNSUPPORTED_PROTOCOL_VERSION)
}
return Result.fail(EXPIRED_PROTOCOL_VERSION)
}
if (!this.protocolService.platformSupportsKeyDerivation(rootKeyParams)) {
if (!this.encryptionService.platformSupportsKeyDerivation(rootKeyParams)) {
return Result.fail(UNSUPPORTED_KEY_DERIVATION)
}
const rootKey = await this.protocolService.computeRootKey(dto.password, rootKeyParams)
const rootKey = await this.encryptionService.computeRootKey(dto.password, rootKeyParams)
const signInResult = await this.authManager.signInWithRecoveryCodes({
codeVerifier,
+3 -3
View File
@@ -207,13 +207,13 @@ export class BaseMigration extends Migration {
this.services.challengeService.addChallengeObserver(challenge, {
onNonvalidatedSubmit: async (challengeResponse) => {
const password = challengeResponse.values[0].value as string
const accountParams = this.services.protocolService.createKeyParams(rawAccountParams)
const rootKey = await this.services.protocolService.computeRootKey(password, accountParams)
const accountParams = this.services.encryptionService.createKeyParams(rawAccountParams)
const rootKey = await this.services.encryptionService.computeRootKey(password, accountParams)
/** TS can't detect we returned early above if itemToDecrypt is null */
assert(itemToDecrypt)
const decryptedPayload = await this.services.protocolService.decryptSplitSingle({
const decryptedPayload = await this.services.encryptionService.decryptSplitSingle({
usesRootKey: {
items: [itemToDecrypt],
key: rootKey,
@@ -13,7 +13,7 @@ import { ChallengeService, SNSingletonManager, SNFeaturesService, DiskStorageSer
import { LegacySession, MapperInterface } from '@standardnotes/domain-core'
export type MigrationServices = {
protocolService: EncryptionService
encryptionService: EncryptionService
deviceInterface: DeviceInterface
storageService: DiskStorageService
challengeService: ChallengeService
@@ -14,8 +14,8 @@ export class Migration2_0_15 extends Migration {
}
private async createNewDefaultItemsKeyIfNecessary() {
if (this.services.protocolService.needsNewRootKeyBasedItemsKey()) {
await this.services.protocolService.createNewDefaultItemsKey()
if (this.services.encryptionService.needsNewRootKeyBasedItemsKey()) {
await this.services.encryptionService.createNewDefaultItemsKey()
}
}
}
@@ -63,7 +63,7 @@ export class SNActionsService extends AbstractService {
public deviceInterface: DeviceInterface,
private httpService: DeprecatedHttpService,
private payloadManager: PayloadManager,
private protocolService: EncryptionService,
private encryptionService: EncryptionService,
private syncService: SNSyncService,
private challengeService: ChallengeService,
private listedService: ListedService,
@@ -81,7 +81,7 @@ export class SNActionsService extends AbstractService {
;(this.payloadManager as unknown) = undefined
;(this.listedService as unknown) = undefined
;(this.challengeService as unknown) = undefined
;(this.protocolService as unknown) = undefined
;(this.encryptionService as unknown) = undefined
;(this.syncService as unknown) = undefined
this.payloadRequestHandlers.length = 0
this.previousPasswords.length = 0
@@ -207,7 +207,7 @@ export class SNActionsService extends AbstractService {
return
}
let decryptedPayload = await this.protocolService.decryptSplitSingle<ActionExtensionContent>({
let decryptedPayload = await this.encryptionService.decryptSplitSingle<ActionExtensionContent>({
usesItemsKeyWithKeyLookup: {
items: [payload],
},
@@ -218,7 +218,7 @@ export class SNActionsService extends AbstractService {
}
if (rootKey) {
decryptedPayload = await this.protocolService.decryptSplitSingle({
decryptedPayload = await this.encryptionService.decryptSplitSingle({
usesRootKey: {
items: [payload],
key: rootKey,
@@ -230,7 +230,7 @@ export class SNActionsService extends AbstractService {
}
for (const itemsKey of this.itemManager.getDisplayableItemsKeys()) {
const decryptedPayload = await this.protocolService.decryptSplitSingle<ActionExtensionContent>({
const decryptedPayload = await this.encryptionService.decryptSplitSingle<ActionExtensionContent>({
usesItemsKey: {
items: [payload],
key: itemsKey,
@@ -256,7 +256,7 @@ export class SNActionsService extends AbstractService {
)
return undefined
}
const keyParams = this.protocolService.createKeyParams(keyParamsData)
const keyParams = this.encryptionService.createKeyParams(keyParamsData)
/* Try previous passwords */
for (const passwordCandidate of this.previousPasswords) {
@@ -266,7 +266,7 @@ export class SNActionsService extends AbstractService {
triedPasswords.push(passwordCandidate)
const key = await this.protocolService.computeRootKey(passwordCandidate, keyParams)
const key = await this.encryptionService.computeRootKey(passwordCandidate, keyParams)
if (!key) {
continue
}
@@ -341,7 +341,7 @@ export class SNActionsService extends AbstractService {
return item.payload.ejected()
}
const encrypted = await this.protocolService.encryptSplitSingle({
const encrypted = await this.encryptionService.encryptSplitSingle({
usesItemsKeyWithKeyLookup: { items: [item.payload] },
})
+1 -1
View File
@@ -664,7 +664,7 @@ export class SNApiService
})
if (isErrorResponse(response)) {
return ClientDisplayableError.FromError(response.data.error)
return ClientDisplayableError.FromNetworkError(response)
}
const data = response.data
return {
@@ -44,7 +44,7 @@ export class ChallengeService extends AbstractService implements ChallengeServic
constructor(
private storageService: DiskStorageService,
private protocolService: EncryptionService,
private encryptionService: EncryptionService,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
@@ -52,7 +52,7 @@ export class ChallengeService extends AbstractService implements ChallengeServic
public override deinit() {
;(this.storageService as unknown) = undefined
;(this.protocolService as unknown) = undefined
;(this.encryptionService as unknown) = undefined
;(this.sendChallenge as unknown) = undefined
;(this.challengeOperations as unknown) = undefined
;(this.challengeObservers as unknown) = undefined
@@ -79,9 +79,9 @@ export class ChallengeService extends AbstractService implements ChallengeServic
public async validateChallengeValue(value: ChallengeValue): Promise<ChallengeValidationResponse> {
switch (value.prompt.validation) {
case ChallengeValidation.LocalPasscode:
return this.protocolService.validatePasscode(value.value as string)
return this.encryptionService.validatePasscode(value.value as string)
case ChallengeValidation.AccountPassword:
return this.protocolService.validateAccountPassword(value.value as string)
return this.encryptionService.validateAccountPassword(value.value as string)
case ChallengeValidation.Biometric:
return { valid: value.value === true }
case ChallengeValidation.Authenticator:
@@ -104,7 +104,7 @@ export class ChallengeService extends AbstractService implements ChallengeServic
}
async promptForAccountPassword(): Promise<string | null> {
if (!this.protocolService.hasAccount()) {
if (!this.encryptionService.hasAccount()) {
throw Error('Requiring account password for challenge with no account')
}
@@ -143,7 +143,7 @@ export class ChallengeService extends AbstractService implements ChallengeServic
canceled?: undefined
}
> {
if (!this.protocolService.hasPasscode()) {
if (!this.encryptionService.hasPasscode()) {
return {}
}
@@ -154,12 +154,12 @@ export class ChallengeService extends AbstractService implements ChallengeServic
}
}
const wrappingKey = await this.protocolService.computeWrappingKey(passcode)
const wrappingKey = await this.encryptionService.computeWrappingKey(passcode)
return { wrappingKey }
}
public isPasscodeLocked() {
return this.protocolService.isPasscodeLocked()
return this.encryptionService.isPasscodeLocked()
}
public addChallengeObserver(challenge: Challenge, observer: ChallengeObserver): () => void {
@@ -11,7 +11,7 @@ export class KeyRecoveryOperation {
constructor(
private queueItem: DecryptionQueueItem,
private itemManager: ItemManager,
private protocolService: EncryptionProviderInterface,
private encryptionService: EncryptionProviderInterface,
private challengeService: ChallengeServiceInterface,
private clientParams: SNRootKeyParams | undefined,
private serverParams: SNRootKeyParams | undefined,
@@ -43,7 +43,7 @@ export class KeyRecoveryOperation {
const decryptionResult = await DecryptItemsKeyByPromptingUser(
this.queueItem.encryptedKey,
this.protocolService,
this.encryptionService,
this.challengeService,
this.queueItem.keyParams,
)
@@ -9,6 +9,7 @@ import {
PayloadEmitSource,
EncryptedItemInterface,
getIncrementedDirtyIndex,
ContentTypeUsesRootKeyEncryption,
} from '@standardnotes/models'
import { SNSyncService } from '../Sync/SyncService'
import { DiskStorageService } from '../Storage/DiskStorageService'
@@ -87,7 +88,7 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
private itemManager: ItemManager,
private payloadManager: PayloadManager,
private apiService: SNApiService,
private protocolService: EncryptionService,
private encryptionService: EncryptionService,
private challengeService: ChallengeService,
private alertService: AlertService,
private storageService: DiskStorageService,
@@ -121,7 +122,7 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
;(this.itemManager as unknown) = undefined
;(this.payloadManager as unknown) = undefined
;(this.apiService as unknown) = undefined
;(this.protocolService as unknown) = undefined
;(this.encryptionService as unknown) = undefined
;(this.challengeService as unknown) = undefined
;(this.alertService as unknown) = undefined
;(this.storageService as unknown) = undefined
@@ -187,6 +188,10 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
}
public canAttemptDecryptionOfItem(item: EncryptedItemInterface): ClientDisplayableError | true {
if (ContentTypeUsesRootKeyEncryption(item.content_type)) {
return true
}
const keyId = item.payload.items_key_id
if (!keyId) {
@@ -251,7 +256,7 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
}
private getClientKeyParams() {
return this.protocolService.getAccountKeyParams()
return this.encryptionService.getAccountKeyParams()
}
private async performServerSignIn(): Promise<SNRootKey | undefined> {
@@ -279,7 +284,7 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
return
}
const rootKey = await this.protocolService.computeRootKey(password, serverParams)
const rootKey = await this.encryptionService.computeRootKey(password, serverParams)
const signInResponse = await this.userService.correctiveSignIn(rootKey)
@@ -295,7 +300,7 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
}
private async getWrappingKeyIfApplicable(): Promise<SNRootKey | undefined> {
if (!this.protocolService.hasPasscode()) {
if (!this.encryptionService.hasPasscode()) {
return undefined
}
const { wrappingKey, canceled } = await this.challengeService.getWrappingKeyIfApplicable()
@@ -312,7 +317,7 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
private addKeysToQueue(keys: EncryptedPayloadInterface[]) {
for (const key of keys) {
const keyParams = this.protocolService.getKeyEmbeddedKeyParamsFromItemsKey(key)
const keyParams = this.encryptionService.getKeyEmbeddedKeyParamsFromItemsKey(key)
if (!keyParams) {
continue
}
@@ -356,12 +361,12 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
serverParams = await this.getLatestKeyParamsFromServer(clientParams.identifier)
}
const deallocedAfterNetworkRequest = this.protocolService == undefined
const deallocedAfterNetworkRequest = this.encryptionService == undefined
if (deallocedAfterNetworkRequest) {
return
}
const credentialsMissing = !this.protocolService.hasAccount() && !this.protocolService.hasPasscode()
const credentialsMissing = !this.encryptionService.hasAccount() && !this.encryptionService.hasPasscode()
if (credentialsMissing) {
const rootKey = await this.performServerSignIn()
@@ -426,7 +431,7 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
const operation = new KeyRecoveryOperation(
queueItem,
this.itemManager,
this.protocolService,
this.encryptionService,
this.challengeService,
clientParams,
serverParams,
@@ -460,7 +465,7 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
if (replacesRootKey) {
const wrappingKey = await this.getWrappingKeyIfApplicable()
await this.protocolService.setRootKey(rootKey, wrappingKey)
await this.encryptionService.setRootKey(rootKey, wrappingKey)
}
const clientKeyParams = this.getClientKeyParams()
@@ -475,7 +480,7 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
: qItem.encryptedKey
})
const matchingResults = await this.protocolService.decryptSplit({
const matchingResults = await this.encryptionService.decryptSplit({
usesRootKey: {
items: matchingKeys,
key: rootKey,
@@ -24,14 +24,14 @@ const setupRandomUuid = () => {
describe('protectionService', () => {
let mutator: MutatorClientInterface
let protocolService: EncryptionService
let encryptionService: EncryptionService
let challengeService: ChallengeService
let storageService: DiskStorageService
let internalEventBus: InternalEventBusInterface
let protectionService: SNProtectionService
const createService = () => {
return new SNProtectionService(protocolService, mutator, challengeService, storageService, internalEventBus)
return new SNProtectionService(encryptionService, mutator, challengeService, storageService, internalEventBus)
}
const createFile = (name: string, isProtected?: boolean) => {
@@ -59,9 +59,9 @@ describe('protectionService', () => {
storageService = {} as jest.Mocked<DiskStorageService>
storageService.getValue = jest.fn()
protocolService = {} as jest.Mocked<EncryptionService>
protocolService.hasAccount = jest.fn().mockReturnValue(true)
protocolService.hasPasscode = jest.fn().mockReturnValue(false)
encryptionService = {} as jest.Mocked<EncryptionService>
encryptionService.hasAccount = jest.fn().mockReturnValue(true)
encryptionService.hasPasscode = jest.fn().mockReturnValue(false)
mutator = {} as jest.Mocked<MutatorClientInterface>
})
@@ -76,7 +76,7 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
private mobileBiometricsTiming: MobileUnlockTiming | undefined = MobileUnlockTiming.OnQuit
constructor(
private protocolService: EncryptionService,
private encryptionService: EncryptionService,
private mutator: MutatorClientInterface,
private challengeService: ChallengeService,
private storageService: DiskStorageService,
@@ -87,7 +87,7 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
public override deinit(): void {
clearTimeout(this.sessionExpiryTimeout)
;(this.protocolService as unknown) = undefined
;(this.encryptionService as unknown) = undefined
;(this.challengeService as unknown) = undefined
;(this.storageService as unknown) = undefined
super.deinit()
@@ -103,7 +103,7 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
}
public hasProtectionSources(): boolean {
return this.protocolService.hasAccount() || this.protocolService.hasPasscode() || this.hasBiometricsEnabled()
return this.encryptionService.hasAccount() || this.encryptionService.hasPasscode() || this.hasBiometricsEnabled()
}
public hasUnprotectedAccessSession(): boolean {
@@ -148,7 +148,7 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
if (this.hasBiometricsEnabled()) {
prompts.push(new ChallengePrompt(ChallengeValidation.Biometric))
}
if (this.protocolService.hasPasscode()) {
if (this.encryptionService.hasPasscode()) {
prompts.push(new ChallengePrompt(ChallengeValidation.LocalPasscode))
}
if (prompts.length > 0) {
@@ -354,19 +354,19 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
prompts.push(new ChallengePrompt(ChallengeValidation.Biometric))
}
if (this.protocolService.hasPasscode()) {
if (this.encryptionService.hasPasscode()) {
prompts.push(new ChallengePrompt(ChallengeValidation.LocalPasscode))
}
if (requireAccountPassword) {
if (!this.protocolService.hasAccount()) {
if (!this.encryptionService.hasAccount()) {
throw Error('Requiring account password for challenge with no account')
}
prompts.push(new ChallengePrompt(ChallengeValidation.AccountPassword))
}
if (prompts.length === 0) {
if (fallBackToAccountPassword && this.protocolService.hasAccount()) {
if (fallBackToAccountPassword && this.encryptionService.hasAccount()) {
prompts.push(new ChallengePrompt(ChallengeValidation.AccountPassword))
} else {
return true
@@ -45,6 +45,7 @@ import {
ChangeCredentialsResponse,
SessionListResponse,
HttpSuccessResponse,
getErrorFromErrorResponse,
} from '@standardnotes/responses'
import { CopyPayloadWithContentOverride, RootKeyWithKeyPairsInterface } from '@standardnotes/models'
import { LegacySession, MapperInterface, Result, Session, SessionToken } from '@standardnotes/domain-core'
@@ -93,7 +94,7 @@ export class SNSessionManager
private apiService: SNApiService,
private userApiService: UserApiServiceInterface,
private alertService: AlertService,
private protocolService: EncryptionService,
private encryptionService: EncryptionService,
private challengeService: ChallengeService,
private webSocketsService: SNWebSocketsService,
private httpService: HttpServiceInterface,
@@ -119,7 +120,7 @@ export class SNSessionManager
}
override deinit(): void {
;(this.protocolService as unknown) = undefined
;(this.encryptionService as unknown) = undefined
;(this.diskStorageService as unknown) = undefined
;(this.apiService as unknown) = undefined
;(this.alertService as unknown) = undefined
@@ -205,11 +206,11 @@ export class SNSessionManager
}
public getPublicKey(): string {
return this.protocolService.getKeyPair().publicKey
return this.encryptionService.getKeyPair().publicKey
}
public getSigningPublicKey(): string {
return this.protocolService.getSigningKeyPair().publicKey
return this.encryptionService.getSigningKeyPair().publicKey
}
public get userUuid(): string {
@@ -285,7 +286,7 @@ export class SNSessionManager
onNonvalidatedSubmit: async (challengeResponse) => {
const email = challengeResponse.values[0].value as string
const password = challengeResponse.values[1].value as string
const currentKeyParams = this.protocolService.getAccountKeyParams()
const currentKeyParams = this.encryptionService.getAccountKeyParams()
const { response } = await this.signIn(
email,
password,
@@ -316,7 +317,7 @@ export class SNSessionManager
const result = await this.apiService.getSubscription(this.getSureUser().uuid)
if (isErrorResponse(result)) {
return ClientDisplayableError.FromError(result.data?.error)
return ClientDisplayableError.FromNetworkError(result)
}
const subscription = result.data.subscription
@@ -328,7 +329,7 @@ export class SNSessionManager
const response = await this.apiService.getAvailableSubscriptions()
if (isErrorResponse(response)) {
return ClientDisplayableError.FromError(response.data.error)
return ClientDisplayableError.FromNetworkError(response)
}
return response.data
@@ -403,7 +404,7 @@ export class SNSessionManager
email = cleanedEmailString(email)
const rootKey = await this.protocolService.createRootKey<RootKeyWithKeyPairsInterface>(
const rootKey = await this.encryptionService.createRootKey<RootKeyWithKeyPairsInterface>(
email,
password,
Common.KeyParamsOrigination.Registration,
@@ -418,8 +419,8 @@ export class SNSessionManager
ephemeral,
})
if ('error' in registerResponse.data) {
throw new ApiCallError(registerResponse.data.error.message)
if (isErrorResponse(registerResponse)) {
throw new ApiCallError(getErrorFromErrorResponse(registerResponse).message)
}
await this.handleAuthentication({
@@ -492,8 +493,8 @@ export class SNSessionManager
const result = await this.performSignIn(email, password, strict, ephemeral, minAllowedVersion)
if (
isErrorResponse(result.response) &&
result.response.data.error.tag !== ErrorTag.ClientValidationError &&
result.response.data.error.tag !== ErrorTag.ClientCanceledMfa
getErrorFromErrorResponse(result.response).tag !== ErrorTag.ClientValidationError &&
getErrorFromErrorResponse(result.response).tag !== ErrorTag.ClientCanceledMfa
) {
const cleanedEmail = cleanedEmailString(email)
if (cleanedEmail !== email) {
@@ -525,8 +526,8 @@ export class SNSessionManager
}
}
const keyParams = paramsResult.keyParams as SNRootKeyParams
if (!this.protocolService.supportedVersions().includes(keyParams.version)) {
if (this.protocolService.isVersionNewerThanLibraryVersion(keyParams.version)) {
if (!this.encryptionService.supportedVersions().includes(keyParams.version)) {
if (this.encryptionService.isVersionNewerThanLibraryVersion(keyParams.version)) {
return {
response: this.apiService.createErrorResponse(UNSUPPORTED_PROTOCOL_VERSION),
}
@@ -539,7 +540,7 @@ export class SNSessionManager
if (Common.isProtocolVersionExpired(keyParams.version)) {
/* Cost minimums only apply to now outdated versions (001 and 002) */
const minimum = this.protocolService.costMinimumForVersion(keyParams.version)
const minimum = this.encryptionService.costMinimumForVersion(keyParams.version)
if (keyParams.content002.pw_cost < minimum) {
return {
response: this.apiService.createErrorResponse(INVALID_PASSWORD_COST),
@@ -560,14 +561,14 @@ export class SNSessionManager
}
}
if (!this.protocolService.platformSupportsKeyDerivation(keyParams)) {
if (!this.encryptionService.platformSupportsKeyDerivation(keyParams)) {
return {
response: this.apiService.createErrorResponse(UNSUPPORTED_KEY_DERIVATION),
}
}
if (strict) {
minAllowedVersion = this.protocolService.getLatestVersion()
minAllowedVersion = this.encryptionService.getLatestVersion()
}
if (minAllowedVersion != undefined) {
@@ -577,7 +578,7 @@ export class SNSessionManager
}
}
}
const rootKey = await this.protocolService.computeRootKey(password, keyParams)
const rootKey = await this.encryptionService.computeRootKey(password, keyParams)
const signInResponse = await this.bypassChecksAndSignInWithRootKey(email, rootKey, ephemeral)
return {
@@ -641,8 +642,8 @@ export class SNSessionManager
let oldSigningKeyPair: PkcKeyPair | undefined
try {
oldKeyPair = this.protocolService.getKeyPair()
oldSigningKeyPair = this.protocolService.getSigningKeyPair()
oldKeyPair = this.encryptionService.getKeyPair()
oldSigningKeyPair = this.encryptionService.getSigningKeyPair()
} catch (error) {
void error
}
@@ -718,7 +719,7 @@ export class SNSessionManager
}
private decodeDemoShareToken(token: Base64String): ShareToken {
const jsonString = this.protocolService.crypto.base64Decode(token)
const jsonString = this.encryptionService.crypto.base64Decode(token)
return JSON.parse(jsonString)
}
@@ -735,7 +736,7 @@ export class SNSessionManager
host: string,
wrappingKey?: SNRootKey,
) {
await this.protocolService.setRootKey(rootKey, wrappingKey)
await this.encryptionService.setRootKey(rootKey, wrappingKey)
this.memoizeUser(user)
this.diskStorageService.setValue(StorageKey.User, user)
@@ -1,7 +1,7 @@
import { SettingsList } from './SettingsList'
import { SettingName } from '@standardnotes/settings'
import { API_MESSAGE_INVALID_SESSION } from '@standardnotes/services'
import { HttpStatusCode, isErrorResponse, User } from '@standardnotes/responses'
import { getErrorFromErrorResponse, HttpStatusCode, isErrorResponse, User } from '@standardnotes/responses'
import { SettingsServerInterface } from './SettingsServerInterface'
/**
@@ -34,7 +34,7 @@ export class SettingsGateway {
const response = await this.settingsApi.listSettings(this.userUuid)
if (isErrorResponse(response)) {
throw new Error(response.data?.error.message)
throw new Error(getErrorFromErrorResponse(response).message)
}
if (response.data == undefined || response.data.settings == undefined) {
@@ -53,7 +53,7 @@ export class SettingsGateway {
}
if (isErrorResponse(response)) {
throw new Error(response.data?.error.message)
throw new Error(getErrorFromErrorResponse(response).message)
}
return response?.data?.setting?.value ?? undefined
@@ -71,7 +71,7 @@ export class SettingsGateway {
}
if (isErrorResponse(response)) {
throw new Error(response.data?.error.message)
throw new Error(getErrorFromErrorResponse(response).message)
}
return response?.data?.setting?.value ?? undefined
@@ -89,7 +89,7 @@ export class SettingsGateway {
}
if (isErrorResponse(response)) {
throw new Error(response.data?.error.message)
throw new Error(getErrorFromErrorResponse(response).message)
}
return response.data?.success ?? false
@@ -98,14 +98,14 @@ export class SettingsGateway {
async updateSetting(name: SettingName, payload: string, sensitive: boolean): Promise<void> {
const response = await this.settingsApi.updateSetting(this.userUuid, name.value, payload, sensitive)
if (isErrorResponse(response)) {
throw new Error(response.data?.error.message)
throw new Error(getErrorFromErrorResponse(response).message)
}
}
async deleteSetting(name: SettingName): Promise<void> {
const response = await this.settingsApi.deleteSetting(this.userUuid, name.value)
if (isErrorResponse(response)) {
throw new Error(response.data?.error.message)
throw new Error(getErrorFromErrorResponse(response).message)
}
}
@@ -9,6 +9,7 @@ import {
RawSyncResponse,
UserEventServerHash,
AsymmetricMessageServerHash,
getErrorFromErrorResponse,
} from '@standardnotes/responses'
import {
FilterDisallowedRemotePayloadsAndMap,
@@ -35,8 +36,6 @@ export class ServerSyncResponse {
private successResponseData: RawSyncResponse | undefined
constructor(public rawResponse: HttpResponse<RawSyncResponse>) {
this.rawResponse = rawResponse
if (!isErrorResponse(rawResponse)) {
this.successResponseData = rawResponse.data
}
@@ -101,7 +100,11 @@ export class ServerSyncResponse {
}
public get error(): HttpError | undefined {
return isErrorResponse(this.rawResponse) ? this.rawResponse.data?.error : undefined
if (isErrorResponse(this.rawResponse)) {
return getErrorFromErrorResponse(this.rawResponse)
} else {
return undefined
}
}
public get status(): number {
@@ -123,6 +126,6 @@ export class ServerSyncResponse {
}
public get hasError(): boolean {
return this.error != undefined
return isErrorResponse(this.rawResponse)
}
}
@@ -153,7 +153,7 @@ export class SNSyncService
constructor(
private itemManager: ItemManager,
private sessionManager: SNSessionManager,
private protocolService: EncryptionService,
private encryptionService: EncryptionService,
private storageService: DiskStorageService,
private payloadManager: PayloadManager,
private apiService: SNApiService,
@@ -189,7 +189,7 @@ export class SNSyncService
this.dealloced = true
;(this.sessionManager as unknown) = undefined
;(this.itemManager as unknown) = undefined
;(this.protocolService as unknown) = undefined
;(this.encryptionService as unknown) = undefined
;(this.payloadManager as unknown) = undefined
;(this.storageService as unknown) = undefined
;(this.apiService as unknown) = undefined
@@ -250,7 +250,7 @@ export class SNSyncService
const encryptionSplit = SplitPayloadsByEncryptionType(encryptedPayloads)
const decryptionSplit = CreateDecryptionSplitWithKeyLookup(encryptionSplit)
const newlyDecryptedPayloads = await this.protocolService.decryptSplit(decryptionSplit)
const newlyDecryptedPayloads = await this.encryptionService.decryptSplit(decryptionSplit)
await this.payloadManager.emitPayloads(
[...alreadyDecryptedPayloads, ...newlyDecryptedPayloads],
@@ -369,7 +369,7 @@ export class SNSyncService
const encryptionSplit = SplitPayloadsByEncryptionType(encrypted)
const decryptionSplit = CreateDecryptionSplitWithKeyLookup(encryptionSplit)
const results = await this.protocolService.decryptSplit(decryptionSplit)
const results = await this.encryptionService.decryptSplit(decryptionSplit)
await this.payloadManager.emitPayloads([...nonencrypted, ...results], PayloadEmitSource.LocalDatabaseLoaded)
@@ -510,7 +510,7 @@ export class SNSyncService
const keyLookupSplit = CreateEncryptionSplitWithKeyLookup(encryptionSplit)
const encryptedResults = await this.protocolService.encryptSplit(keyLookupSplit)
const encryptedResults = await this.encryptionService.encryptSplit(keyLookupSplit)
const contextPayloads = [
...encryptedResults.map(CreateEncryptedServerSyncPushPayload),
@@ -1138,7 +1138,7 @@ export class SNSyncService
},
}
const results = await this.protocolService.decryptSplit<ItemsKeyContent>(rootKeySplit)
const results = await this.encryptionService.decryptSplit<ItemsKeyContent>(rootKeySplit)
results.forEach((result) => {
if (isDecryptedPayload<ItemsKeyContent>(result) && result.content_type === ContentType.ItemsKey) {
@@ -1168,7 +1168,7 @@ export class SNSyncService
},
}
const results = await this.protocolService.decryptSplit<KeySystemItemsKeyContent>(keySystemRootKeySplit)
const results = await this.encryptionService.decryptSplit<KeySystemItemsKeyContent>(keySystemRootKeySplit)
results.forEach((result) => {
if (
@@ -1213,7 +1213,7 @@ export class SNSyncService
}
}
return this.protocolService.decryptSplitSingle(keyedSplit)
return this.encryptionService.decryptSplitSingle(keyedSplit)
}),
)
}
@@ -1399,7 +1399,7 @@ export class SNSyncService
const keyedSplit = CreateDecryptionSplitWithKeyLookup(encryptionSplit)
const decryptionResults = await this.protocolService.decryptSplit(keyedSplit)
const decryptionResults = await this.encryptionService.decryptSplit(keyedSplit)
this.setInSync(false)
+3 -3
View File
@@ -31,7 +31,7 @@ describe('001 protocol operations', () => {
})
it('cost minimum', () => {
expect(application.protocolService.costMinimumForVersion('001')).to.equal(3000)
expect(application.encryptionService.costMinimumForVersion('001')).to.equal(3000)
})
it('generates valid keys for registration', async () => {
@@ -46,7 +46,7 @@ describe('001 protocol operations', () => {
it('generates valid keys from existing params and decrypts', async () => {
const password = 'password'
const keyParams = await application.protocolService.createKeyParams({
const keyParams = await application.encryptionService.createKeyParams({
pw_func: 'pbkdf2',
pw_alg: 'sha512',
pw_key_size: 512,
@@ -67,7 +67,7 @@ describe('001 protocol operations', () => {
'sVuHmG0XAp1PRDE8r8XqFXijjP8Pqdwal9YFRrXK4hKLt1yyq8MwQU+1Z95Tz/b7ajYdidwFE0iDwd8Iu8281VtJsQ4yhh2tJiAzBy6newyHfhA5nH93yZ3iXRJaG87bgNQE9lsXzTV/OHAvqMuQtw/QVSWI3Qy1Pyu1Tn72q7FPKKhRRkzEEZ+Ax0BA1fHg',
uuid: '54001a6f-7c22-4b34-8316-fadf9b1fc255',
})
const decrypted = await application.protocolService.decryptSplitSingle({
const decrypted = await application.encryptionService.decryptSplitSingle({
usesRootKey: {
items: [payload],
key: key,
+3 -3
View File
@@ -30,7 +30,7 @@ describe('002 protocol operations', () => {
})
it('cost minimum', () => {
expect(application.protocolService.costMinimumForVersion('002')).to.equal(3000)
expect(application.encryptionService.costMinimumForVersion('002')).to.equal(3000)
})
it('generates valid keys for registration', async () => {
@@ -46,7 +46,7 @@ describe('002 protocol operations', () => {
it('generates valid keys from existing params and decrypts', async () => {
const password = 'password'
const keyParams = await application.protocolService.createKeyParams({
const keyParams = await application.encryptionService.createKeyParams({
pw_salt: '8d381ef44cdeab1489194f87066b747b46053a833ee24956e846e7b40440f5f4',
pw_cost: 101000,
version: '002',
@@ -64,7 +64,7 @@ describe('002 protocol operations', () => {
'002:24a8e8f7728bbe06605d8209d87ad338d3d15ef81154bb64d3967c77daa01333:959b042a-3892-461e-8c50-477c10c7c40a:f1d294388742dca34f6f266a01483a4e:VdlEDyjhZ35GbJDg8ruSZv3Tp6WtMME3T5LLvcBYLHIMhrMi0RlPK83lK6F0aEaZvY82pZ0ntU+XpAX7JMSEdKdPXsACML7WeFrqKb3z2qHnA7NxgnIC0yVT/Z2mRrvlY3NNrUPGwJbfRcvfS7FVyw87MemT9CSubMZRviXvXETx82t7rsgjV/AIwOOeWhFi',
uuid: '959b042a-3892-461e-8c50-477c10c7c40a',
})
const decrypted = await application.protocolService.decryptSplitSingle({
const decrypted = await application.encryptionService.decryptSplitSingle({
usesRootKey: {
items: [payload],
key: key,
+3 -3
View File
@@ -39,7 +39,7 @@ describe('003 protocol operations', () => {
it('cost minimum should throw', () => {
expect(() => {
sharedApplication.protocolService.costMinimumForVersion('003')
sharedApplication.encryptionService.costMinimumForVersion('003')
}).to.throw('Cost minimums only apply to versions <= 002')
})
@@ -59,7 +59,7 @@ describe('003 protocol operations', () => {
it('computes proper keys for sign in', async () => {
const identifier = '[email protected]'
const password = 'very_secure'
const keyParams = sharedApplication.protocolService.createKeyParams({
const keyParams = sharedApplication.encryptionService.createKeyParams({
pw_nonce: 'baaec0131d677cf993381367eb082fe377cefe70118c1699cb9b38f0bc850e7b',
identifier: identifier,
version: '003',
@@ -73,7 +73,7 @@ describe('003 protocol operations', () => {
it('can decrypt item generated with web version 3.3.6', async () => {
const identifier = '[email protected]'
const password = 'password'
const keyParams = sharedApplication.protocolService.createKeyParams({
const keyParams = sharedApplication.encryptionService.createKeyParams({
pw_nonce: '31107837b44d86179140b7c602a55d694243e2e9ced0c4c914ac21ad90215055',
identifier: identifier,
version: '003',

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