Compare commits

..

10 Commits

Author SHA1 Message Date
standardci
9313e6b568 chore(release): publish new version
- @standardnotes/api-gateway@1.20.0
 - @standardnotes/auth-server@1.30.0
 - @standardnotes/domain-events-infra@1.8.12
 - @standardnotes/domain-events@2.60.6
 - @standardnotes/event-store@1.3.17
 - @standardnotes/files-server@1.6.3
 - @standardnotes/scheduler-server@1.10.31
 - @standardnotes/security@1.4.0
 - @standardnotes/syncing-server@1.8.8
2022-09-21 09:00:32 +00:00
Karol Sójko
8033177f48 feat(auth): add creating web socket connection tokens 2022-09-21 10:58:39 +02:00
standardci
11011fa15d chore(release): publish new version
- @standardnotes/syncing-server@1.8.7
2022-09-20 08:01:52 +00:00
Karol Sójko
c2e9f3e72b fix(syncing-server): content size calculation and add syncing upper bound for limit paramter 2022-09-20 09:59:40 +02:00
standardci
f0fb7fd1cd chore(release): publish new version
- @standardnotes/files-server@1.6.2
2022-09-19 11:55:08 +00:00
Karol Sójko
15e342fd51 Merge pull request #224 from standardnotes/fs_dos
fix: add upper bound for FS file chunk upload
2022-09-19 13:53:39 +02:00
Karol Sójko
dfa7e06f87 fix: add upper bound for FS file chunk upload 2022-09-19 13:44:37 +02:00
standardci
a9aef5521b chore(release): publish new version
- @standardnotes/auth-server@1.29.1
 - @standardnotes/files-server@1.6.1
2022-09-19 07:59:14 +00:00
Karol Sójko
a628bdc44e fix(files): uuid validator binding 2022-09-19 09:57:17 +02:00
Karol Sójko
db6f966045 fix(auth): uuid validator binding 2022-09-19 09:57:10 +02:00
50 changed files with 450 additions and 171 deletions

38
.pnp.cjs generated
View File

@@ -2484,14 +2484,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/api", [\
["npm:1.7.2", {\
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.7.2-e68e7d4e63-bdfc414e6d.zip/node_modules/@standardnotes/api/",\
["npm:1.8.1", {\
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.8.1-15c2e051d4-76c5d1a2d2.zip/node_modules/@standardnotes/api/",\
"packageDependencies": [\
["@standardnotes/api", "npm:1.7.2"],\
["@standardnotes/api", "npm:1.8.1"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/encryption", "npm:1.15.2"],\
["@standardnotes/models", "npm:1.18.2"],\
["@standardnotes/responses", "npm:1.10.1"],\
["@standardnotes/encryption", "npm:1.15.3"],\
["@standardnotes/models", "npm:1.18.3"],\
["@standardnotes/responses", "npm:1.10.2"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/utils", "npm:1.9.0"],\
["reflect-metadata", "npm:0.1.13"]\
@@ -2563,7 +2563,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
["@sentry/node", "npm:7.5.0"],\
["@standardnotes/analytics", "workspace:packages/analytics"],\
["@standardnotes/api", "npm:1.7.2"],\
["@standardnotes/api", "npm:1.8.1"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
@@ -2688,13 +2688,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/encryption", [\
["npm:1.15.2", {\
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.15.2-ef86a8281d-6e8336f1e7.zip/node_modules/@standardnotes/encryption/",\
["npm:1.15.3", {\
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.15.3-3580c52c1f-1a7863299f.zip/node_modules/@standardnotes/encryption/",\
"packageDependencies": [\
["@standardnotes/encryption", "npm:1.15.2"],\
["@standardnotes/encryption", "npm:1.15.3"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/models", "npm:1.18.2"],\
["@standardnotes/responses", "npm:1.10.1"],\
["@standardnotes/models", "npm:1.18.3"],\
["@standardnotes/responses", "npm:1.10.2"],\
["@standardnotes/sncrypto-common", "npm:1.11.1"],\
["@standardnotes/utils", "npm:1.9.0"],\
["reflect-metadata", "npm:0.1.13"]\
@@ -2808,13 +2808,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/models", [\
["npm:1.18.2", {\
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.18.2-56f35bb72d-88180a93e5.zip/node_modules/@standardnotes/models/",\
["npm:1.18.3", {\
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.18.3-6c65a62f30-21830c805f.zip/node_modules/@standardnotes/models/",\
"packageDependencies": [\
["@standardnotes/models", "npm:1.18.2"],\
["@standardnotes/models", "npm:1.18.3"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/features", "npm:1.52.0"],\
["@standardnotes/responses", "npm:1.10.1"],\
["@standardnotes/responses", "npm:1.10.2"],\
["@standardnotes/utils", "npm:1.9.0"],\
["lodash", "npm:4.17.21"],\
["reflect-metadata", "npm:0.1.13"]\
@@ -2851,10 +2851,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/responses", [\
["npm:1.10.1", {\
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.10.1-9f82fff6c1-b84fb3f71c.zip/node_modules/@standardnotes/responses/",\
["npm:1.10.2", {\
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.10.2-39d2d1f9b5-364724b5c7.zip/node_modules/@standardnotes/responses/",\
"packageDependencies": [\
["@standardnotes/responses", "npm:1.10.1"],\
["@standardnotes/responses", "npm:1.10.2"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/features", "npm:1.52.0"],\
["@standardnotes/security", "workspace:packages/security"],\

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.20.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.6...@standardnotes/api-gateway@1.20.0) (2022-09-21)
### Features
* **auth:** add creating web socket connection tokens ([8033177](https://github.com/standardnotes/api-gateway/commit/8033177f48dc961194f24fb7daa1073b8b697b74))
## [1.19.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.5...@standardnotes/api-gateway@1.19.6) (2022-09-19)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.19.6",
"version": "1.20.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -15,6 +15,11 @@ export class WebSocketsController extends BaseHttpController {
super()
}
@httpPost('/tokens', TYPES.AuthMiddleware)
async createWebSocketConnectionToken(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'sockets/tokens', request.body)
}
@httpPost('/', TYPES.AuthMiddleware)
async createWebSocketConnection(request: Request, response: Response): Promise<void> {
if (!request.headers.connectionid) {

View File

@@ -66,5 +66,8 @@ SENTRY_ENVIRONMENT=
VALET_TOKEN_SECRET=
VALET_TOKEN_TTL=
WEB_SOCKET_CONNECTION_TOKEN_SECRET=
WEB_SOCKET_CONNECTION_TOKEN_TTL=
# (Optional) Analytics
ANALYTICS_ENABLED=false

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.30.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.29.1...@standardnotes/auth-server@1.30.0) (2022-09-21)
### Features
* **auth:** add creating web socket connection tokens ([8033177](https://github.com/standardnotes/server/commit/8033177f48dc961194f24fb7daa1073b8b697b74))
## [1.29.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.29.0...@standardnotes/auth-server@1.29.1) (2022-09-19)
### Bug Fixes
* **auth:** uuid validator binding ([db6f966](https://github.com/standardnotes/server/commit/db6f966045d51e59555740c9e009bf66b629673c))
# [1.29.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.28.4...@standardnotes/auth-server@1.29.0) (2022-09-19)
### Features

View File

@@ -10,7 +10,6 @@ import '../src/Controller/SessionsController'
import '../src/Controller/UsersController'
import '../src/Controller/SettingsController'
import '../src/Controller/FeaturesController'
import '../src/Controller/WebSocketsController'
import '../src/Controller/AdminController'
import '../src/Controller/InternalController'
import '../src/Controller/SubscriptionTokensController'
@@ -21,6 +20,7 @@ import '../src/Controller/SubscriptionSettingsController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressAuthController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionInvitesController'
import '../src/Infra/InversifyExpressUtils/InversifyExpressWebSocketsController'
import * as cors from 'cors'
import { urlencoded, json, Request, Response, NextFunction, RequestHandler, ErrorRequestHandler } from 'express'

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.29.0",
"version": "1.30.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -34,7 +34,7 @@
"@newrelic/winston-enricher": "^4.0.0",
"@sentry/node": "^7.3.0",
"@standardnotes/analytics": "workspace:*",
"@standardnotes/api": "^1.7.2",
"@standardnotes/api": "^1.8.1",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",

View File

@@ -156,6 +156,7 @@ import {
TokenEncoder,
TokenEncoderInterface,
ValetTokenData,
WebSocketConnectionTokenData,
} from '@standardnotes/security'
import { FileUploadedEventHandler } from '../Domain/Handler/FileUploadedEventHandler'
import { CreateValetToken } from '../Domain/UseCase/CreateValetToken/CreateValetToken'
@@ -208,6 +209,9 @@ import { PaymentFailedEventHandler } from '../Domain/Handler/PaymentFailedEventH
import { PaymentSuccessEventHandler } from '../Domain/Handler/PaymentSuccessEventHandler'
import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEventHandler'
import { SubscriptionInvitesController } from '../Controller/SubscriptionInvitesController'
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
import { WebSocketsController } from '../Controller/WebSocketsController'
import { WebSocketServerInterface } from '@standardnotes/api'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -271,6 +275,7 @@ export class ContainerConfigLoader {
// Controller
container.bind<AuthController>(TYPES.AuthController).to(AuthController)
container.bind<SubscriptionInvitesController>(TYPES.SubscriptionInvitesController).to(SubscriptionInvitesController)
container.bind<WebSocketServerInterface>(TYPES.WebSocketsController).to(WebSocketsController)
// Repositories
container.bind<SessionRepositoryInterface>(TYPES.SessionRepository).to(MySQLSessionRepository)
@@ -369,6 +374,12 @@ export class ContainerConfigLoader {
container.bind(TYPES.AUTH_JWT_TTL).toConstantValue(+env.get('AUTH_JWT_TTL'))
container.bind(TYPES.VALET_TOKEN_SECRET).toConstantValue(env.get('VALET_TOKEN_SECRET', true))
container.bind(TYPES.VALET_TOKEN_TTL).toConstantValue(+env.get('VALET_TOKEN_TTL', true))
container
.bind(TYPES.WEB_SOCKET_CONNECTION_TOKEN_SECRET)
.toConstantValue(env.get('WEB_SOCKET_CONNECTION_TOKEN_SECRET', true))
container
.bind(TYPES.WEB_SOCKET_CONNECTION_TOKEN_TTL)
.toConstantValue(+env.get('WEB_SOCKET_CONNECTION_TOKEN_TTL', true))
container.bind(TYPES.ENCRYPTION_SERVER_KEY).toConstantValue(env.get('ENCRYPTION_SERVER_KEY'))
container.bind(TYPES.ACCESS_TOKEN_AGE).toConstantValue(env.get('ACCESS_TOKEN_AGE'))
container.bind(TYPES.REFRESH_TOKEN_AGE).toConstantValue(env.get('REFRESH_TOKEN_AGE'))
@@ -448,6 +459,9 @@ export class ContainerConfigLoader {
container.bind<GetSubscriptionSetting>(TYPES.GetSubscriptionSetting).to(GetSubscriptionSetting)
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
container.bind<VerifyPredicate>(TYPES.VerifyPredicate).to(VerifyPredicate)
container
.bind<CreateWebSocketConnectionToken>(TYPES.CreateWebSocketConnectionToken)
.to(CreateWebSocketConnectionToken)
// Handlers
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
@@ -532,6 +546,11 @@ export class ContainerConfigLoader {
container
.bind<TokenEncoderInterface<ValetTokenData>>(TYPES.ValetTokenEncoder)
.toConstantValue(new TokenEncoder<ValetTokenData>(container.get(TYPES.VALET_TOKEN_SECRET)))
container
.bind<TokenEncoderInterface<WebSocketConnectionTokenData>>(TYPES.WebSocketConnectionTokenEncoder)
.toConstantValue(
new TokenEncoder<WebSocketConnectionTokenData>(container.get(TYPES.WEB_SOCKET_CONNECTION_TOKEN_SECRET)),
)
container.bind<AuthenticationMethodResolver>(TYPES.AuthenticationMethodResolver).to(AuthenticationMethodResolver)
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
container.bind<AxiosInstance>(TYPES.HTTPClient).toConstantValue(axios.create())
@@ -566,7 +585,7 @@ export class ContainerConfigLoader {
container
.bind<StatisticsStoreInterface>(TYPES.StatisticsStore)
.toConstantValue(new RedisStatisticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
container.bind<ValidatorInterface<Uuid>>(TYPES.UuidValidator).to(UuidValidator)
container.bind<ValidatorInterface<Uuid>>(TYPES.UuidValidator).toConstantValue(new UuidValidator())
if (env.get('SNS_TOPIC_ARN', true)) {
container

View File

@@ -6,6 +6,7 @@ const TYPES = {
// Controller
AuthController: Symbol.for('AuthController'),
SubscriptionInvitesController: Symbol.for('SubscriptionInvitesController'),
WebSocketsController: Symbol.for('WebSocketsController'),
// Repositories
UserRepository: Symbol.for('UserRepository'),
SessionRepository: Symbol.for('SessionRepository'),
@@ -60,6 +61,8 @@ const TYPES = {
AUTH_JWT_TTL: Symbol.for('AUTH_JWT_TTL'),
VALET_TOKEN_SECRET: Symbol.for('VALET_TOKEN_SECRET'),
VALET_TOKEN_TTL: Symbol.for('VALET_TOKEN_TTL'),
WEB_SOCKET_CONNECTION_TOKEN_SECRET: Symbol.for('WEB_SOCKET_CONNECTION_TOKEN_SECRET'),
WEB_SOCKET_CONNECTION_TOKEN_TTL: Symbol.for('WEB_SOCKET_CONNECTION_TOKEN_TTL'),
ENCRYPTION_SERVER_KEY: Symbol.for('ENCRYPTION_SERVER_KEY'),
ACCESS_TOKEN_AGE: Symbol.for('ACCESS_TOKEN_AGE'),
REFRESH_TOKEN_AGE: Symbol.for('REFRESH_TOKEN_AGE'),
@@ -125,6 +128,7 @@ const TYPES = {
GetSubscriptionSetting: Symbol.for('GetSubscriptionSetting'),
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
VerifyPredicate: Symbol.for('VerifyPredicate'),
CreateWebSocketConnectionToken: Symbol.for('CreateWebSocketConnectionToken'),
// Handlers
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
@@ -166,6 +170,7 @@ const TYPES = {
CrossServiceTokenEncoder: Symbol.for('CrossServiceTokenEncoder'),
SessionTokenEncoder: Symbol.for('SessionTokenEncoder'),
ValetTokenEncoder: Symbol.for('ValetTokenEncoder'),
WebSocketConnectionTokenEncoder: Symbol.for('WebSocketConnectionTokenEncoder'),
AuthenticationMethodResolver: Symbol.for('AuthenticationMethodResolver'),
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),

View File

@@ -1,65 +1,28 @@
import 'reflect-metadata'
import * as express from 'express'
import { results } from 'inversify-express-utils'
import { AddWebSocketsConnection } from '../Domain/UseCase/AddWebSocketsConnection/AddWebSocketsConnection'
import { WebSocketsController } from './WebSocketsController'
import { RemoveWebSocketsConnection } from '../Domain/UseCase/RemoveWebSocketsConnection/RemoveWebSocketsConnection'
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
describe('WebSocketsController', () => {
let addWebSocketsConnection: AddWebSocketsConnection
let removeWebSocketsConnection: RemoveWebSocketsConnection
let request: express.Request
let response: express.Response
let createWebSocketConnectionToken: CreateWebSocketConnectionToken
const createController = () => new WebSocketsController(addWebSocketsConnection, removeWebSocketsConnection)
const createController = () => new WebSocketsController(createWebSocketConnectionToken)
beforeEach(() => {
addWebSocketsConnection = {} as jest.Mocked<AddWebSocketsConnection>
addWebSocketsConnection.execute = jest.fn()
removeWebSocketsConnection = {} as jest.Mocked<RemoveWebSocketsConnection>
removeWebSocketsConnection.execute = jest.fn()
request = {
body: {
userUuid: '1-2-3',
},
params: {},
headers: {},
} as jest.Mocked<express.Request>
request.params.connectionId = '2-3-4'
response = {
locals: {},
} as jest.Mocked<express.Response>
response.locals.user = {
uuid: '1-2-3',
}
createWebSocketConnectionToken = {} as jest.Mocked<CreateWebSocketConnectionToken>
createWebSocketConnectionToken.execute = jest.fn().mockReturnValue({ token: 'foobar' })
})
it('should persist an established web sockets connection', async () => {
const httpResponse = await createController().storeWebSocketsConnection(request, response)
it('should create a web sockets connection token', async () => {
const response = await createController().createConnectionToken({ userUuid: '1-2-3' })
expect(httpResponse).toBeInstanceOf(results.JsonResult)
expect((<results.JsonResult>httpResponse).statusCode).toEqual(200)
expect(addWebSocketsConnection.execute).toHaveBeenCalledWith({
userUuid: '1-2-3',
connectionId: '2-3-4',
expect(response).toEqual({
status: 200,
data: { token: 'foobar' },
})
})
it('should remove a disconnected web sockets connection', async () => {
const httpResponse = await createController().deleteWebSocketsConnection(request)
expect(httpResponse).toBeInstanceOf(results.JsonResult)
expect((<results.JsonResult>httpResponse).statusCode).toEqual(200)
expect(removeWebSocketsConnection.execute).toHaveBeenCalledWith({
connectionId: '2-3-4',
expect(createWebSocketConnectionToken.execute).toHaveBeenCalledWith({
userUuid: '1-2-3',
})
})
})

View File

@@ -1,45 +1,29 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import {
BaseHttpController,
controller,
httpDelete,
httpPost,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
results,
} from 'inversify-express-utils'
HttpStatusCode,
WebSocketConnectionTokenRequestParams,
WebSocketConnectionTokenResponse,
WebSocketServerInterface,
} from '@standardnotes/api'
import { inject, injectable } from 'inversify'
import TYPES from '../Bootstrap/Types'
import { AddWebSocketsConnection } from '../Domain/UseCase/AddWebSocketsConnection/AddWebSocketsConnection'
import { RemoveWebSocketsConnection } from '../Domain/UseCase/RemoveWebSocketsConnection/RemoveWebSocketsConnection'
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
@controller('/sockets')
export class WebSocketsController extends BaseHttpController {
@injectable()
export class WebSocketsController implements WebSocketServerInterface {
constructor(
@inject(TYPES.AddWebSocketsConnection) private addWebSocketsConnection: AddWebSocketsConnection,
@inject(TYPES.RemoveWebSocketsConnection) private removeWebSocketsConnection: RemoveWebSocketsConnection,
) {
super()
}
@inject(TYPES.CreateWebSocketConnectionToken)
private createWebSocketConnectionToken: CreateWebSocketConnectionToken,
) {}
@httpPost('/:connectionId', TYPES.ApiGatewayAuthMiddleware)
async storeWebSocketsConnection(
request: Request,
response: Response,
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
await this.addWebSocketsConnection.execute({
userUuid: response.locals.user.uuid,
connectionId: request.params.connectionId,
})
async createConnectionToken(
params: WebSocketConnectionTokenRequestParams,
): Promise<WebSocketConnectionTokenResponse> {
const result = await this.createWebSocketConnectionToken.execute({ userUuid: params.userUuid as string })
return this.json({ success: true })
}
@httpDelete('/:connectionId')
async deleteWebSocketsConnection(
request: Request,
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
await this.removeWebSocketsConnection.execute({ connectionId: request.params.connectionId })
return this.json({ success: true })
return {
status: HttpStatusCode.Success,
data: result,
}
}
}

View File

@@ -0,0 +1,25 @@
import 'reflect-metadata'
import { TokenEncoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security'
import { CreateWebSocketConnectionToken } from './CreateWebSocketConnectionToken'
describe('CreateWebSocketConnection', () => {
let tokenEncoder: TokenEncoderInterface<WebSocketConnectionTokenData>
const tokenTTL = 30
const createUseCase = () => new CreateWebSocketConnectionToken(tokenEncoder, tokenTTL)
beforeEach(() => {
tokenEncoder = {} as jest.Mocked<TokenEncoderInterface<WebSocketConnectionTokenData>>
tokenEncoder.encodeExpirableToken = jest.fn().mockReturnValue('foobar')
})
it('should create a web socket connection token', async () => {
const result = await createUseCase().execute({ userUuid: '1-2-3' })
expect(result.token).toEqual('foobar')
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith({ userUuid: '1-2-3' }, 30)
})
})

View File

@@ -0,0 +1,3 @@
export type CreateWebSocketConnectionDTO = {
userUuid: string
}

View File

@@ -0,0 +1,3 @@
export type CreateWebSocketConnectionResponse = {
token: string
}

View File

@@ -0,0 +1,25 @@
import { TokenEncoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security'
import { inject } from 'inversify'
import TYPES from '../../../Bootstrap/Types'
import { UseCaseInterface } from '../UseCaseInterface'
import { CreateWebSocketConnectionDTO } from './CreateWebSocketConnectionDTO'
import { CreateWebSocketConnectionResponse } from './CreateWebSocketConnectionResponse'
export class CreateWebSocketConnectionToken implements UseCaseInterface {
constructor(
@inject(TYPES.WebSocketConnectionTokenEncoder)
private tokenEncoder: TokenEncoderInterface<WebSocketConnectionTokenData>,
@inject(TYPES.WEB_SOCKET_CONNECTION_TOKEN_TTL) private tokenTTL: number,
) {}
async execute(dto: CreateWebSocketConnectionDTO): Promise<CreateWebSocketConnectionResponse> {
const data: WebSocketConnectionTokenData = {
userUuid: dto.userUuid,
}
return {
token: this.tokenEncoder.encodeExpirableToken(data, this.tokenTTL),
}
}
}

View File

@@ -0,0 +1,56 @@
import { WebSocketServerInterface } from '@standardnotes/api'
import { Request, Response } from 'express'
import { inject } from 'inversify'
import {
BaseHttpController,
controller,
httpDelete,
httpPost,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
results,
} from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { AddWebSocketsConnection } from '../../Domain/UseCase/AddWebSocketsConnection/AddWebSocketsConnection'
import { RemoveWebSocketsConnection } from '../../Domain/UseCase/RemoveWebSocketsConnection/RemoveWebSocketsConnection'
@controller('/sockets')
export class InversifyExpressWebSocketsController extends BaseHttpController {
constructor(
@inject(TYPES.AddWebSocketsConnection) private addWebSocketsConnection: AddWebSocketsConnection,
@inject(TYPES.RemoveWebSocketsConnection) private removeWebSocketsConnection: RemoveWebSocketsConnection,
@inject(TYPES.WebSocketsController) private webSocketsController: WebSocketServerInterface,
) {
super()
}
@httpPost('/:connectionId', TYPES.ApiGatewayAuthMiddleware)
async storeWebSocketsConnection(
request: Request,
response: Response,
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
await this.addWebSocketsConnection.execute({
userUuid: response.locals.user.uuid,
connectionId: request.params.connectionId,
})
return this.json({ success: true })
}
@httpDelete('/:connectionId')
async deleteWebSocketsConnection(
request: Request,
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
await this.removeWebSocketsConnection.execute({ connectionId: request.params.connectionId })
return this.json({ success: true })
}
@httpPost('/tokens', TYPES.ApiGatewayAuthMiddleware)
async createConnectionToken(_request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.webSocketsController.createConnectionToken({
userUuid: response.locals.user.uuid,
})
return this.json(result)
}
}

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.8.12](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.11...@standardnotes/domain-events-infra@1.8.12) (2022-09-21)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.11](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.10...@standardnotes/domain-events-infra@1.8.11) (2022-09-19)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.8.11",
"version": "1.8.12",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

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.
## [2.60.6](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.60.5...@standardnotes/domain-events@2.60.6) (2022-09-21)
**Note:** Version bump only for package @standardnotes/domain-events
## [2.60.5](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.60.4...@standardnotes/domain-events@2.60.5) (2022-09-19)
**Note:** Version bump only for package @standardnotes/domain-events

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.60.5",
"version": "2.60.6",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

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.3.17](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.16...@standardnotes/event-store@1.3.17) (2022-09-21)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.16](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.15...@standardnotes/event-store@1.3.16) (2022-09-19)
**Note:** Version bump only for package @standardnotes/event-store

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/event-store",
"version": "1.3.16",
"version": "1.3.17",
"description": "Event Store Service",
"private": true,
"main": "dist/src/index.js",

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.6.3](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.2...@standardnotes/files-server@1.6.3) (2022-09-21)
**Note:** Version bump only for package @standardnotes/files-server
## [1.6.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.1...@standardnotes/files-server@1.6.2) (2022-09-19)
### Bug Fixes
* add upper bound for FS file chunk upload ([dfa7e06](https://github.com/standardnotes/files/commit/dfa7e06f8780bec21893ec77ab4a0945a6681545))
## [1.6.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.0...@standardnotes/files-server@1.6.1) (2022-09-19)
### Bug Fixes
* **files:** uuid validator binding ([a628bdc](https://github.com/standardnotes/files/commit/a628bdc44e97935b8a79460b74c30c0d29ef83bf))
# [1.6.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.52...@standardnotes/files-server@1.6.0) (2022-09-19)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.6.0",
"version": "1.6.3",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -108,7 +108,7 @@ export class ContainerConfigLoader {
.toConstantValue(new FSFileUploader(container.get(TYPES.FILE_UPLOAD_PATH), container.get(TYPES.Logger)))
container.bind<FileRemoverInterface>(TYPES.FileRemover).to(FSFileRemover)
}
container.bind<ValidatorInterface<Uuid>>(TYPES.UuidValidator).to(UuidValidator)
container.bind<ValidatorInterface<Uuid>>(TYPES.UuidValidator).toConstantValue(new UuidValidator())
if (env.get('SNS_AWS_REGION', true)) {
container.bind<AWS.SNS>(TYPES.SNS).toConstantValue(

View File

@@ -298,6 +298,7 @@ describe('FilesController', () => {
chunkId: 2,
data: Buffer.from([123]),
resourceRemoteIdentifier: '2-3-4',
resourceUnencryptedFileSize: 123,
userUuid: '1-2-3',
})
})

View File

@@ -63,6 +63,7 @@ export class FilesController extends BaseHttpController {
const result = await this.uploadFileChunk.execute({
userUuid: response.locals.userUuid,
resourceRemoteIdentifier: response.locals.permittedResources[0].remoteIdentifier,
resourceUnencryptedFileSize: response.locals.permittedResources[0].unencryptedFileSize,
chunkId,
data: request.body,
})

View File

@@ -4,6 +4,12 @@ import { UploadId } from '../Upload/UploadId'
export interface FileUploaderInterface {
createUploadSession(filePath: string): Promise<UploadId>
uploadFileChunk(dto: { uploadId: string; data: Uint8Array; filePath: string; chunkId: ChunkId }): Promise<string>
uploadFileChunk(dto: {
uploadId: string
data: Uint8Array
filePath: string
chunkId: ChunkId
unencryptedFileSize: number
}): Promise<string>
finishUploadSession(uploadId: string, filePath: string, uploadChunkResults: Array<UploadChunkResult>): Promise<void>
}

View File

@@ -33,6 +33,7 @@ describe('UploadFileChunk', () => {
chunkId: 2,
data: new Uint8Array([123]),
resourceRemoteIdentifier: '2-3-4',
resourceUnencryptedFileSize: 123,
userUuid: '1-2-3',
})
@@ -50,6 +51,7 @@ describe('UploadFileChunk', () => {
chunkId: 2,
data: new Uint8Array([123]),
resourceRemoteIdentifier: '2-3-4',
resourceUnencryptedFileSize: 123,
userUuid: '1-2-3',
}),
).toEqual({
@@ -66,6 +68,7 @@ describe('UploadFileChunk', () => {
chunkId: 2,
data: new Uint8Array([123]),
resourceRemoteIdentifier: '2-3-4',
resourceUnencryptedFileSize: 123,
userUuid: '1-2-3',
})
@@ -74,6 +77,7 @@ describe('UploadFileChunk', () => {
data: new Uint8Array([123]),
filePath: '1-2-3/2-3-4',
uploadId: '123',
unencryptedFileSize: 123,
})
expect(uploadRepository.storeUploadChunkResult).toHaveBeenCalledWith('123', {
tag: 'ETag123',

View File

@@ -39,6 +39,7 @@ export class UploadFileChunk implements UseCaseInterface {
data: dto.data,
chunkId: dto.chunkId,
filePath,
unencryptedFileSize: dto.resourceUnencryptedFileSize,
})
await this.uploadRepository.storeUploadChunkResult(uploadId, {

View File

@@ -5,4 +5,5 @@ export type UploadFileChunkDTO = {
chunkId: ChunkId
userUuid: string
resourceRemoteIdentifier: string
resourceUnencryptedFileSize: number
}

View File

@@ -1,11 +1,12 @@
import { promises } from 'fs'
import { dirname } from 'path'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import { FileUploaderInterface } from '../../Domain/Services/FileUploaderInterface'
import { UploadChunkResult } from '../../Domain/Upload/UploadChunkResult'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { ChunkId } from '../../Domain/Upload/ChunkId'
@injectable()
export class FSFileUploader implements FileUploaderInterface {
@@ -22,7 +23,8 @@ export class FSFileUploader implements FileUploaderInterface {
uploadId: string
data: Uint8Array
filePath: string
chunkId: number
chunkId: ChunkId
unencryptedFileSize: number
}): Promise<string> {
if (!this.inMemoryChunks.has(dto.uploadId)) {
this.inMemoryChunks.set(dto.uploadId, new Map<number, Uint8Array>())
@@ -30,6 +32,13 @@ export class FSFileUploader implements FileUploaderInterface {
const fileChunks = this.inMemoryChunks.get(dto.uploadId) as Map<number, Uint8Array>
const alreadyStoredBytes = this.accumulatedEncryptedFileSize(fileChunks)
if (alreadyStoredBytes >= dto.unencryptedFileSize) {
throw new Error(
`Could not finish chunk upload. Accumulated encrypted file size (${alreadyStoredBytes}B) already exceeds the unecrypted file size: ${dto.unencryptedFileSize}`,
)
}
this.logger.debug(`FS storing file chunk ${dto.chunkId} in memory for ${dto.uploadId}`)
fileChunks.set(dto.chunkId, dto.data)
@@ -64,4 +73,14 @@ export class FSFileUploader implements FileUploaderInterface {
return fullPath
}
private accumulatedEncryptedFileSize(fileChunks: Map<number, Uint8Array>): number {
let accumulatedSize = 0
for (const value of fileChunks.values()) {
accumulatedSize += value.byteLength
}
return accumulatedSize
}
}

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.10.31](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.30...@standardnotes/scheduler-server@1.10.31) (2022-09-21)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.30](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.29...@standardnotes/scheduler-server@1.10.30) (2022-09-19)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.10.30",
"version": "1.10.31",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.4.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.3.3...@standardnotes/security@1.4.0) (2022-09-21)
### Features
* **auth:** add creating web socket connection tokens ([8033177](https://github.com/standardnotes/server/commit/8033177f48dc961194f24fb7daa1073b8b697b74))
## [1.3.3](https://github.com/standardnotes/server/compare/@standardnotes/security@1.3.2...@standardnotes/security@1.3.3) (2022-09-19)
**Note:** Version bump only for package @standardnotes/security

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/security",
"version": "1.3.3",
"version": "1.4.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -0,0 +1,5 @@
import { Uuid } from '@standardnotes/common'
export type WebSocketConnectionTokenData = {
userUuid: Uuid
}

View File

@@ -12,3 +12,4 @@ export * from './Token/OfflineUserTokenData'
export * from './Token/SessionTokenData'
export * from './Token/ValetTokenData'
export * from './Token/ValetTokenOperation'
export * from './Token/WebSocketConnectionToken'

View File

@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.8](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.8.7...@standardnotes/syncing-server@1.8.8) (2022-09-21)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.8.7](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.8.6...@standardnotes/syncing-server@1.8.7) (2022-09-20)
### Bug Fixes
* **syncing-server:** content size calculation and add syncing upper bound for limit paramter ([c2e9f3e](https://github.com/standardnotes/syncing-server-js/commit/c2e9f3e72b87c445a6f4d61cbf59621954187d21))
## [1.8.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.8.5...@standardnotes/syncing-server@1.8.6) (2022-09-19)
**Note:** Version bump only for package @standardnotes/syncing-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.8.6",
"version": "1.8.8",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -5,12 +5,16 @@ import { ContentType } from '@standardnotes/common'
import { ItemFactory } from './ItemFactory'
import { ItemHash } from './ItemHash'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { ItemProjection } from '../../Projection/ItemProjection'
import { Item } from './Item'
describe('ItemFactory', () => {
let timer: TimerInterface
let itemProjector: ProjectorInterface<Item, ItemProjection>
let timeHelper: Timer
const createFactory = () => new ItemFactory(timer)
const createFactory = () => new ItemFactory(timer, itemProjector)
beforeEach(() => {
timeHelper = new Timer()
@@ -26,6 +30,23 @@ describe('ItemFactory', () => {
timer.convertStringDateToDate = jest
.fn()
.mockImplementation((date: string) => timeHelper.convertStringDateToDate(date))
itemProjector = {} as jest.Mocked<ProjectorInterface<Item, ItemProjection>>
itemProjector.projectFull = jest.fn().mockReturnValue({
uuid: '1-2-3',
items_key_id: 'foobar',
duplicate_of: null,
enc_item_key: 'foobar',
content: 'foobar',
content_type: ContentType.Note,
auth_hash: 'foobar',
deleted: false,
created_at: '2022-09-01 10:00:00',
created_at_timestamp: 123123123123123,
updated_at: '2022-09-01 10:00:00',
updated_at_timestamp: 123123123123123,
updated_with_session: '2-4-5',
})
})
it('should create an item based on item hash', () => {
@@ -43,7 +64,7 @@ describe('ItemFactory', () => {
updatedAtTimestamp: 1616164633241568,
userUuid: 'a-b-c',
uuid: '1-2-3',
contentSize: 0,
contentSize: 341,
})
})
@@ -64,7 +85,7 @@ describe('ItemFactory', () => {
userUuid: 'a-b-c',
uuid: '1-2-3',
content: null,
contentSize: 0,
contentSize: 341,
})
})
@@ -86,7 +107,7 @@ describe('ItemFactory', () => {
userUuid: 'a-b-c',
uuid: '1-2-3',
content: 'foobar',
contentSize: 6,
contentSize: 341,
})
})
@@ -106,7 +127,7 @@ describe('ItemFactory', () => {
userUuid: 'a-b-c',
uuid: '1-2-3',
content: null,
contentSize: 0,
contentSize: 341,
})
})
@@ -128,7 +149,7 @@ describe('ItemFactory', () => {
expect(item).toEqual({
content: 'asdqwe1',
contentSize: 7,
contentSize: 341,
contentType: 'Note',
createdAt: expect.any(Date),
updatedWithSession: '1-2-3',
@@ -161,7 +182,7 @@ describe('ItemFactory', () => {
expect(item).toEqual({
content: 'asdqwe1',
contentSize: 7,
contentSize: 341,
contentType: 'Note',
createdAt: expect.any(Date),
updatedWithSession: '1-2-3',

View File

@@ -3,13 +3,18 @@ import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
import TYPES from '../../Bootstrap/Types'
import { ItemProjection } from '../../Projection/ItemProjection'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { Item } from './Item'
import { ItemFactoryInterface } from './ItemFactoryInterface'
import { ItemHash } from './ItemHash'
@injectable()
export class ItemFactory implements ItemFactoryInterface {
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
constructor(
@inject(TYPES.Timer) private timer: TimerInterface,
@inject(TYPES.ItemProjector) private itemProjector: ProjectorInterface<Item, ItemProjection>,
) {}
createStub(dto: { userUuid: string; itemHash: ItemHash; sessionUuid: Uuid | null }): Item {
const item = this.create(dto)
@@ -36,7 +41,6 @@ export class ItemFactory implements ItemFactoryInterface {
newItem.contentSize = 0
if (dto.itemHash.content) {
newItem.content = dto.itemHash.content
newItem.contentSize = Buffer.byteLength(dto.itemHash.content)
}
newItem.userUuid = dto.userUuid
if (dto.itemHash.content_type) {
@@ -75,6 +79,8 @@ export class ItemFactory implements ItemFactoryInterface {
newItem.createdAt = this.timer.convertStringDateToDate(dto.itemHash.created_at)
}
newItem.contentSize = Buffer.byteLength(JSON.stringify(this.itemProjector.projectFull(newItem)))
return newItem
}
}

View File

@@ -16,6 +16,8 @@ import { ItemSaveValidatorInterface } from './SaveValidator/ItemSaveValidatorInt
import { ItemFactoryInterface } from './ItemFactoryInterface'
import { ItemConflict } from './ItemConflict'
import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { ItemProjection } from '../../Projection/ItemProjection'
describe('ItemService', () => {
let itemRepository: ItemRepositoryInterface
@@ -37,6 +39,7 @@ describe('ItemService', () => {
let itemFactory: ItemFactoryInterface
let timeHelper: Timer
let itemTransferCalculator: ItemTransferCalculatorInterface
let itemProjector: ProjectorInterface<Item, ItemProjection>
const createService = () =>
new ItemService(
@@ -50,6 +53,7 @@ describe('ItemService', () => {
contentSizeTransferLimit,
itemTransferCalculator,
timer,
itemProjector,
logger,
)
@@ -156,6 +160,24 @@ describe('ItemService', () => {
itemFactory = {} as jest.Mocked<ItemFactoryInterface>
itemFactory.create = jest.fn().mockReturnValue(newItem)
itemFactory.createStub = jest.fn().mockReturnValue(newItem)
itemProjector = {} as jest.Mocked<ProjectorInterface<Item, ItemProjection>>
itemProjector.projectFull = jest.fn().mockReturnValue({
uuid: '1-2-3',
items_key_id: 'foobar',
duplicate_of: null,
enc_item_key: 'foobar',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed viverra tellus in hac habitasse. Tortor posuere ac ut consequat semper. Ut diam quam nulla porttitor. Sapien pellentesque habitant morbi tristique senectus et netus et malesuada. Dapibus ultrices in iaculis nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames. Faucibus et molestie ac feugiat sed lectus vestibulum mattis. Eu consequat ac felis donec. Eget velit aliquet sagittis id. Nullam eget felis eget nunc. Turpis in eu mi bibendum neque egestas congue.',
content_type: ContentType.Note,
auth_hash: 'foobar',
deleted: false,
created_at: '2022-09-01 10:00:00',
created_at_timestamp: 123123123123123,
updated_at: '2022-09-01 10:00:00',
updated_at_timestamp: 123123123123123,
updated_with_session: '2-4-5',
})
})
it('should retrieve all items for a user from last sync with sync token version 1', async () => {
@@ -214,6 +236,34 @@ describe('ItemService', () => {
})
})
it('should retrieve all items for a user from last sync with upper bound items limit', async () => {
expect(
await createService().getItems({
userUuid: '1-2-3',
syncToken,
contentType: ContentType.Note,
limit: 1000,
}),
).toEqual({
items: [item1, item2],
})
expect(itemRepository.countAll).toHaveBeenCalledWith({
contentType: 'Note',
lastSyncTime: 1616164633241564,
syncTimeComparison: '>',
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
userUuid: '1-2-3',
limit: 300,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['1-2-3', '2-3-4'],
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
})
it('should retrieve no items for a user if there are none from last sync', async () => {
itemTransferCalculator.computeItemUuidsToFetch = jest.fn().mockReturnValue([])
@@ -589,7 +639,7 @@ describe('ItemService', () => {
savedItems: [
{
content: 'asdqwe1',
contentSize: 7,
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
@@ -625,7 +675,7 @@ describe('ItemService', () => {
savedItems: [
{
content: 'asdqwe1',
contentSize: 7,
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
@@ -660,7 +710,7 @@ describe('ItemService', () => {
savedItems: [
{
content: 'asdqwe1',
contentSize: 7,
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: 123,
createdAt: expect.any(Date),
@@ -696,7 +746,7 @@ describe('ItemService', () => {
conflicts: [],
savedItems: [
{
contentSize: 0,
contentSize: 950,
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
userUuid: '1-2-3',
@@ -726,7 +776,7 @@ describe('ItemService', () => {
savedItems: [
{
content: 'asdqwe1',
contentSize: 7,
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
@@ -759,7 +809,7 @@ describe('ItemService', () => {
savedItems: [
{
content: 'asdqwe1',
contentSize: 7,
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
@@ -794,7 +844,7 @@ describe('ItemService', () => {
savedItems: [
{
content: 'asdqwe1',
contentSize: 7,
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
@@ -865,7 +915,7 @@ describe('ItemService', () => {
savedItems: [
{
content: 'asdqwe1',
contentSize: 7,
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),

View File

@@ -21,10 +21,13 @@ import { SaveItemsResult } from './SaveItemsResult'
import { ItemSaveValidatorInterface } from './SaveValidator/ItemSaveValidatorInterface'
import { ConflictType } from '@standardnotes/responses'
import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { ItemProjection } from '../../Projection/ItemProjection'
@injectable()
export class ItemService implements ItemServiceInterface {
private readonly DEFAULT_ITEMS_LIMIT = 150
private readonly MAX_ITEMS_LIMIT = 300
private readonly SYNC_TOKEN_VERSION = 2
constructor(
@@ -38,6 +41,7 @@ export class ItemService implements ItemServiceInterface {
@inject(TYPES.CONTENT_SIZE_TRANSFER_LIMIT) private contentSizeTransferLimit: number,
@inject(TYPES.ItemTransferCalculator) private itemTransferCalculator: ItemTransferCalculatorInterface,
@inject(TYPES.Timer) private timer: TimerInterface,
@inject(TYPES.ItemProjector) private itemProjector: ProjectorInterface<Item, ItemProjection>,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -54,7 +58,7 @@ export class ItemService implements ItemServiceInterface {
deleted: lastSyncTime ? undefined : false,
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
limit,
limit: limit < this.MAX_ITEMS_LIMIT ? limit : this.MAX_ITEMS_LIMIT,
}
const itemUuidsToFetch = await this.itemTransferCalculator.computeItemUuidsToFetch(
@@ -196,7 +200,6 @@ export class ItemService implements ItemServiceInterface {
dto.existingItem.contentSize = 0
if (dto.itemHash.content) {
dto.existingItem.content = dto.itemHash.content
dto.existingItem.contentSize = Buffer.byteLength(dto.itemHash.content)
}
if (dto.itemHash.content_type) {
dto.existingItem.contentType = dto.itemHash.content_type
@@ -219,14 +222,6 @@ export class ItemService implements ItemServiceInterface {
dto.existingItem.itemsKeyId = dto.itemHash.items_key_id
}
if (dto.itemHash.deleted === true) {
dto.existingItem.deleted = true
dto.existingItem.content = null
;(dto.existingItem.contentSize = 0), (dto.existingItem.encItemKey = null)
dto.existingItem.authHash = null
dto.existingItem.itemsKeyId = null
}
const updatedAt = this.timer.getTimestampInMicroseconds()
const secondsFromLastUpdate = this.timer.convertMicrosecondsToSeconds(
updatedAt - dto.existingItem.updatedAtTimestamp,
@@ -243,6 +238,17 @@ export class ItemService implements ItemServiceInterface {
dto.existingItem.updatedAtTimestamp = updatedAt
dto.existingItem.updatedAt = this.timer.convertMicrosecondsToDate(updatedAt)
dto.existingItem.contentSize = Buffer.byteLength(JSON.stringify(this.itemProjector.projectFull(dto.existingItem)))
if (dto.itemHash.deleted === true) {
dto.existingItem.deleted = true
dto.existingItem.content = null
dto.existingItem.contentSize = 0
dto.existingItem.encItemKey = null
dto.existingItem.authHash = null
dto.existingItem.itemsKeyId = null
}
const savedItem = await this.itemRepository.save(dto.existingItem)
if (secondsFromLastUpdate >= this.revisionFrequency) {

View File

@@ -1803,18 +1803,18 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/api@npm:^1.7.2":
version: 1.7.2
resolution: "@standardnotes/api@npm:1.7.2"
"@standardnotes/api@npm:^1.8.1":
version: 1.8.1
resolution: "@standardnotes/api@npm:1.8.1"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/encryption": 1.15.2
"@standardnotes/models": 1.18.2
"@standardnotes/responses": 1.10.1
"@standardnotes/encryption": 1.15.3
"@standardnotes/models": 1.18.3
"@standardnotes/responses": 1.10.2
"@standardnotes/security": ^1.1.0
"@standardnotes/utils": 1.9.0
reflect-metadata: ^0.1.13
checksum: bdfc414e6d01620fd047979255a43eb447afbb69d1bb694015b162ad236431273cd234bba4129d13ba94791271aaff71895d726357491d6ab984c7d5a7a8a3f7
checksum: 76c5d1a2d29cf7f407813246febf54fe02c5d7cacedcfd1bf5f6ee6630d847f58cae0b5827fbba1c5c5d5a30e56095833c9eff8b413111f8aae9cc17802ffa63
languageName: node
linkType: hard
@@ -1825,7 +1825,7 @@ __metadata:
"@newrelic/winston-enricher": ^4.0.0
"@sentry/node": ^7.3.0
"@standardnotes/analytics": "workspace:*"
"@standardnotes/api": ^1.7.2
"@standardnotes/api": ^1.8.1
"@standardnotes/common": "workspace:*"
"@standardnotes/domain-events": "workspace:*"
"@standardnotes/domain-events-infra": "workspace:*"
@@ -1951,17 +1951,17 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/encryption@npm:1.15.2":
version: 1.15.2
resolution: "@standardnotes/encryption@npm:1.15.2"
"@standardnotes/encryption@npm:1.15.3":
version: 1.15.3
resolution: "@standardnotes/encryption@npm:1.15.3"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/models": 1.18.2
"@standardnotes/responses": 1.10.1
"@standardnotes/models": 1.18.3
"@standardnotes/responses": 1.10.2
"@standardnotes/sncrypto-common": 1.11.1
"@standardnotes/utils": 1.9.0
reflect-metadata: ^0.1.13
checksum: 6e8336f1e7e961fbd42c4890458dca877da62dcc1987f7e9a7fb6ca230821276fce6a33652669bcc1752a80ffc55e4cf82b8631f7902d9714f4a07a7956092b0
checksum: 1a7863299f86ee28de1640b93277f8b3e206bec2b34a205eb6e6fd6c6899a4908623acd9f2452d71a83c542b1f181408e7743386e5e1079239c6c0fa384242c9
languageName: node
linkType: hard
@@ -2066,17 +2066,17 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/models@npm:1.18.2":
version: 1.18.2
resolution: "@standardnotes/models@npm:1.18.2"
"@standardnotes/models@npm:1.18.3":
version: 1.18.3
resolution: "@standardnotes/models@npm:1.18.3"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/features": 1.52.0
"@standardnotes/responses": 1.10.1
"@standardnotes/responses": 1.10.2
"@standardnotes/utils": 1.9.0
lodash: ^4.17.21
reflect-metadata: ^0.1.13
checksum: 88180a93e5acdc349e1f96159c40610d7f52d49f0566386d9d6db8767d5ac4ba73af3131c8e433afa253557349e3f96238f6b2060e94df51ceedb5d378b3dd1f
checksum: 21830c805ffa1ac2184c64903f88915b7b439eb4eb80ac0686c4920a9a4c86cc6c71a3daeb1ede8f3fe6cf0ce106f7ba396f994165306c1c59c05902a7ec075a
languageName: node
linkType: hard
@@ -2105,15 +2105,15 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/responses@npm:1.10.1":
version: 1.10.1
resolution: "@standardnotes/responses@npm:1.10.1"
"@standardnotes/responses@npm:1.10.2":
version: 1.10.2
resolution: "@standardnotes/responses@npm:1.10.2"
dependencies:
"@standardnotes/common": ^1.32.0
"@standardnotes/features": 1.52.0
"@standardnotes/security": ^1.1.0
reflect-metadata: ^0.1.13
checksum: b84fb3f71cc32286fc757280e01c2da7fd0576e96455bfd53c5e55f807875d7201a23e727a7c702277b90f1959837a9a0cbda94ca6a4f4ad6a4896e306ed851c
checksum: 364724b5c7efa06948a240da82320817bce58049d6ea226cc2e828e86144ba4d19e5ae6fa437311438008b16aff7bd8cbe7e3f7475a2e9c89cf47650e1e1e9b0
languageName: node
linkType: hard