mirror of
https://github.com/standardnotes/server
synced 2026-05-07 18:57:31 -04:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cbdd2584d0 | |||
| f3161c2712 | |||
| 148542dd5a | |||
| d2b2c339f2 | |||
| d2578c48f0 | |||
| fecfd54728 | |||
| 17e4162d3e | |||
| 742209d773 | |||
| 1fa4b7cf27 | |||
| 5dc5507039 | |||
| 3035a20b9f | |||
| 04b3bb034f | |||
| bf84be0136 | |||
| 890cf48749 | |||
| 2b3436c6ce | |||
| 4df8c3b2e5 | |||
| 25a2696c32 | |||
| 52f879f842 | |||
| 4f70fa156d | |||
| 38e77f04be | |||
| 060206ddd4 | |||
| 0bc0909386 | |||
| 667d528a8c | |||
| fa7fbe26e7 | |||
| ba422a29d0 | |||
| d220ec5bf7 | |||
| 7baf5492bc | |||
| d5a8409bb5 |
@@ -5179,6 +5179,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@standardnotes/responses", "npm:1.13.24"],\
|
["@standardnotes/responses", "npm:1.13.24"],\
|
||||||
["@standardnotes/security", "workspace:packages/security"],\
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
["@standardnotes/settings", "workspace:packages/settings"],\
|
||||||
|
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
|
||||||
["@standardnotes/time", "workspace:packages/time"],\
|
["@standardnotes/time", "workspace:packages/time"],\
|
||||||
["@types/cors", "npm:2.8.13"],\
|
["@types/cors", "npm:2.8.13"],\
|
||||||
["@types/dotenv", "npm:8.2.0"],\
|
["@types/dotenv", "npm:8.2.0"],\
|
||||||
@@ -5188,6 +5189,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@types/newrelic", "npm:9.14.0"],\
|
["@types/newrelic", "npm:9.14.0"],\
|
||||||
["@types/node", "npm:20.2.5"],\
|
["@types/node", "npm:20.2.5"],\
|
||||||
["@types/prettyjson", "npm:0.0.30"],\
|
["@types/prettyjson", "npm:0.0.30"],\
|
||||||
|
["@types/semver", "npm:7.5.0"],\
|
||||||
["@types/ua-parser-js", "npm:0.7.36"],\
|
["@types/ua-parser-js", "npm:0.7.36"],\
|
||||||
["@types/uuid", "npm:8.3.4"],\
|
["@types/uuid", "npm:8.3.4"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.59.8"],\
|
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.59.8"],\
|
||||||
@@ -5210,6 +5212,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["prettier", "npm:2.8.8"],\
|
["prettier", "npm:2.8.8"],\
|
||||||
["prettyjson", "npm:1.2.5"],\
|
["prettyjson", "npm:1.2.5"],\
|
||||||
["reflect-metadata", "npm:0.1.13"],\
|
["reflect-metadata", "npm:0.1.13"],\
|
||||||
|
["semver", "npm:7.5.1"],\
|
||||||
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
|
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
|
||||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
|
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
|
||||||
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.16"],\
|
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.16"],\
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.24.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.5...@standardnotes/analytics@2.24.6) (2023-07-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.24.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.4...@standardnotes/analytics@2.24.5) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.24.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.3...@standardnotes/analytics@2.24.4) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.24.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.2...@standardnotes/analytics@2.24.3) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
## [2.24.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.1...@standardnotes/analytics@2.24.2) (2023-06-28)
|
## [2.24.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.1...@standardnotes/analytics@2.24.2) (2023-06-28)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/analytics
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/analytics",
|
"name": "@standardnotes/analytics",
|
||||||
"version": "2.24.2",
|
"version": "2.24.6",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,30 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.65.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.2...@standardnotes/api-gateway@1.65.3) (2023-07-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.65.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.1...@standardnotes/api-gateway@1.65.2) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.65.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.0...@standardnotes/api-gateway@1.65.1) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
# [1.65.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.64.3...@standardnotes/api-gateway@1.65.0) (2023-06-30)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* shared vaults functionality in api-gateway,auth,files,common,security,domain-events. ([#629](https://github.com/standardnotes/api-gateway/issues/629)) ([fa7fbe2](https://github.com/standardnotes/api-gateway/commit/fa7fbe26e7b0707fc21d71e04af76870f5248baf))
|
||||||
|
|
||||||
|
## [1.64.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.64.2...@standardnotes/api-gateway@1.64.3) (2023-06-28)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add debug logs for invalid-auth responses ([d5a8409](https://github.com/standardnotes/api-gateway/commit/d5a8409bb5d35b9caf410a36ea0d5cb747129e8d))
|
||||||
|
|
||||||
## [1.64.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.64.1...@standardnotes/api-gateway@1.64.2) (2023-06-22)
|
## [1.64.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.64.1...@standardnotes/api-gateway@1.64.2) (2023-06-22)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import '../src/Controller/v1/OfflineController'
|
|||||||
import '../src/Controller/v1/FilesController'
|
import '../src/Controller/v1/FilesController'
|
||||||
import '../src/Controller/v1/SubscriptionInvitesController'
|
import '../src/Controller/v1/SubscriptionInvitesController'
|
||||||
import '../src/Controller/v1/AuthenticatorsController'
|
import '../src/Controller/v1/AuthenticatorsController'
|
||||||
|
import '../src/Controller/v1/AsymmetricMessagesController'
|
||||||
|
import '../src/Controller/v1/SharedVaultsController'
|
||||||
|
|
||||||
import '../src/Controller/v2/PaymentsControllerV2'
|
import '../src/Controller/v2/PaymentsControllerV2'
|
||||||
import '../src/Controller/v2/ActionsControllerV2'
|
import '../src/Controller/v2/ActionsControllerV2'
|
||||||
@@ -45,28 +47,29 @@ void container.load().then((container) => {
|
|||||||
response.setHeader('X-API-Gateway-Version', container.get(TYPES.VERSION))
|
response.setHeader('X-API-Gateway-Version', container.get(TYPES.VERSION))
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
/* eslint-disable */
|
app.use(
|
||||||
app.use(helmet({
|
helmet({
|
||||||
contentSecurityPolicy: {
|
contentSecurityPolicy: {
|
||||||
directives: {
|
directives: {
|
||||||
defaultSrc: ["https: 'self'"],
|
defaultSrc: ["https: 'self'"],
|
||||||
baseUri: ["'self'"],
|
baseUri: ["'self'"],
|
||||||
childSrc: ["*", "blob:"],
|
childSrc: ['*', 'blob:'],
|
||||||
connectSrc: ["*"],
|
connectSrc: ['*'],
|
||||||
fontSrc: ["*", "'self'"],
|
fontSrc: ['*', "'self'"],
|
||||||
formAction: ["'self'"],
|
formAction: ["'self'"],
|
||||||
frameAncestors: ["*", "*.standardnotes.org", "*.standardnotes.com"],
|
frameAncestors: ['*', '*.standardnotes.org', '*.standardnotes.com'],
|
||||||
frameSrc: ["*", "blob:"],
|
frameSrc: ['*', 'blob:'],
|
||||||
imgSrc: ["'self'", "*", "data:"],
|
imgSrc: ["'self'", '*', 'data:'],
|
||||||
manifestSrc: ["'self'"],
|
manifestSrc: ["'self'"],
|
||||||
mediaSrc: ["'self'"],
|
mediaSrc: ["'self'"],
|
||||||
objectSrc: ["'self'"],
|
objectSrc: ["'self'"],
|
||||||
scriptSrc: ["'self'"],
|
scriptSrc: ["'self'"],
|
||||||
styleSrc: ["'self'"]
|
styleSrc: ["'self'"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}))
|
}),
|
||||||
/* eslint-enable */
|
)
|
||||||
|
|
||||||
app.use(json({ limit: '50mb' }))
|
app.use(json({ limit: '50mb' }))
|
||||||
app.use(
|
app.use(
|
||||||
text({
|
text({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/api-gateway",
|
"name": "@standardnotes/api-gateway",
|
||||||
"version": "1.64.2",
|
"version": "1.65.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export abstract class AuthMiddleware extends BaseMiddleware {
|
|||||||
private crossServiceTokenCacheTTL: number,
|
private crossServiceTokenCacheTTL: number,
|
||||||
private crossServiceTokenCache: CrossServiceTokenCacheInterface,
|
private crossServiceTokenCache: CrossServiceTokenCacheInterface,
|
||||||
private timer: TimerInterface,
|
private timer: TimerInterface,
|
||||||
private logger: Logger,
|
protected logger: Logger,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ export class RequiredCrossServiceTokenMiddleware extends AuthMiddleware {
|
|||||||
_next: NextFunction,
|
_next: NextFunction,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!authHeaderValue) {
|
if (!authHeaderValue) {
|
||||||
|
this.logger.debug('Missing auth header')
|
||||||
|
|
||||||
response.status(401).send({
|
response.status(401).send({
|
||||||
error: {
|
error: {
|
||||||
tag: 'invalid-auth',
|
tag: 'invalid-auth',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export * from './SubscriptionTokenAuthMiddleware'
|
|||||||
export * from './TokenAuthenticationMethod'
|
export * from './TokenAuthenticationMethod'
|
||||||
export * from './WebSocketAuthMiddleware'
|
export * from './WebSocketAuthMiddleware'
|
||||||
export * from './v1/ActionsController'
|
export * from './v1/ActionsController'
|
||||||
|
export * from './v1/AsymmetricMessagesController'
|
||||||
export * from './v1/AuthenticatorsController'
|
export * from './v1/AuthenticatorsController'
|
||||||
export * from './v1/FilesController'
|
export * from './v1/FilesController'
|
||||||
export * from './v1/InvoicesController'
|
export * from './v1/InvoicesController'
|
||||||
@@ -12,6 +13,7 @@ export * from './v1/OfflineController'
|
|||||||
export * from './v1/PaymentsController'
|
export * from './v1/PaymentsController'
|
||||||
export * from './v1/RevisionsController'
|
export * from './v1/RevisionsController'
|
||||||
export * from './v1/SessionsController'
|
export * from './v1/SessionsController'
|
||||||
|
export * from './v1/SharedVaultsController'
|
||||||
export * from './v1/SubscriptionInvitesController'
|
export * from './v1/SubscriptionInvitesController'
|
||||||
export * from './v1/TokensController'
|
export * from './v1/TokensController'
|
||||||
export * from './v1/UsersController'
|
export * from './v1/UsersController'
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Request, Response } from 'express'
|
||||||
|
import { inject } from 'inversify'
|
||||||
|
import { BaseHttpController, controller, all } from 'inversify-express-utils'
|
||||||
|
import { TYPES } from '../../Bootstrap/Types'
|
||||||
|
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
|
||||||
|
|
||||||
|
@controller('/v1/asymmetric-messages')
|
||||||
|
export class AsymmetricMessagesController extends BaseHttpController {
|
||||||
|
constructor(@inject(TYPES.ServiceProxy) private serviceProxy: ServiceProxyInterface) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
@all('*', TYPES.RequiredCrossServiceTokenMiddleware)
|
||||||
|
async subscriptions(request: Request, response: Response): Promise<void> {
|
||||||
|
await this.serviceProxy.callSyncingServer(request, response, request.path.replace('/v1/', ''), request.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Request, Response } from 'express'
|
||||||
|
import { inject } from 'inversify'
|
||||||
|
import { BaseHttpController, controller, all } from 'inversify-express-utils'
|
||||||
|
import { TYPES } from '../../Bootstrap/Types'
|
||||||
|
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
|
||||||
|
|
||||||
|
@controller('/v1/shared-vaults')
|
||||||
|
export class SharedVaultsController extends BaseHttpController {
|
||||||
|
constructor(@inject(TYPES.ServiceProxy) private serviceProxy: ServiceProxyInterface) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
@all('*', TYPES.RequiredCrossServiceTokenMiddleware)
|
||||||
|
async subscriptions(request: Request, response: Response): Promise<void> {
|
||||||
|
await this.serviceProxy.callSyncingServer(request, response, request.path.replace('/v1/', ''), request.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,44 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.122.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.121.0...@standardnotes/auth-server@1.122.0) (2023-07-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* deleting shared vaults. ([#640](https://github.com/standardnotes/server/issues/640)) ([f3161c2](https://github.com/standardnotes/server/commit/f3161c271296159331639814b2dbb2e566cc54c9))
|
||||||
|
|
||||||
|
# [1.121.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.120.2...@standardnotes/auth-server@1.121.0) (2023-07-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add notifications model ([#638](https://github.com/standardnotes/server/issues/638)) ([fecfd54](https://github.com/standardnotes/server/commit/fecfd5472824b5adae708db95d351e4ad65ee87b))
|
||||||
|
|
||||||
|
## [1.120.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.120.1...@standardnotes/auth-server@1.120.2) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.120.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.120.0...@standardnotes/auth-server@1.120.1) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
# [1.120.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.119.6...@standardnotes/auth-server@1.120.0) (2023-06-30)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* shared vaults functionality in api-gateway,auth,files,common,security,domain-events. ([#629](https://github.com/standardnotes/server/issues/629)) ([fa7fbe2](https://github.com/standardnotes/server/commit/fa7fbe26e7b0707fc21d71e04af76870f5248baf))
|
||||||
|
|
||||||
|
## [1.119.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.119.5...@standardnotes/auth-server@1.119.6) (2023-06-28)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** add debug logs for authentication method resolver ([d220ec5](https://github.com/standardnotes/server/commit/d220ec5bf7509f9eb19dcda71c3667aaf388a35b))
|
||||||
|
|
||||||
|
## [1.119.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.119.4...@standardnotes/auth-server@1.119.5) (2023-06-28)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add debug logs for invalid-auth responses ([d5a8409](https://github.com/standardnotes/server/commit/d5a8409bb5d35b9caf410a36ea0d5cb747129e8d))
|
||||||
|
|
||||||
## [1.119.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.119.3...@standardnotes/auth-server@1.119.4) (2023-06-28)
|
## [1.119.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.119.3...@standardnotes/auth-server@1.119.4) (2023-06-28)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/auth-server
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,16 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddNotifications1688540448427 implements MigrationInterface {
|
||||||
|
name = 'AddNotifications1688540448427'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
|
||||||
|
await queryRunner.query('DROP TABLE `notifications`')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddNotifications1688540623272 implements MigrationInterface {
|
||||||
|
name = 'AddNotifications1688540623272'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
|
||||||
|
)
|
||||||
|
await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('DROP INDEX "index_notifications_on_user_uuid"')
|
||||||
|
await queryRunner.query('DROP TABLE "notifications"')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/auth-server",
|
"name": "@standardnotes/auth-server",
|
||||||
"version": "1.119.4",
|
"version": "1.122.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
@@ -32,7 +32,8 @@
|
|||||||
"weekly-backup:email": "yarn node dist/bin/backup.js email weekly",
|
"weekly-backup:email": "yarn node dist/bin/backup.js email weekly",
|
||||||
"content-recalculation": "yarn node dist/bin/content.js",
|
"content-recalculation": "yarn node dist/bin/content.js",
|
||||||
"typeorm": "typeorm-ts-node-commonjs",
|
"typeorm": "typeorm-ts-node-commonjs",
|
||||||
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
|
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'",
|
||||||
|
"migrate": "yarn build && yarn typeorm migration:run -d dist/src/Bootstrap/DataSource.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sns": "^3.332.0",
|
"@aws-sdk/client-sns": "^3.332.0",
|
||||||
|
|||||||
@@ -18,21 +18,26 @@ import { TypeORMEmergencyAccessInvitation } from '../Infra/TypeORM/TypeORMEmerge
|
|||||||
import { TypeORMSessionTrace } from '../Infra/TypeORM/TypeORMSessionTrace'
|
import { TypeORMSessionTrace } from '../Infra/TypeORM/TypeORMSessionTrace'
|
||||||
import { Env } from './Env'
|
import { Env } from './Env'
|
||||||
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
|
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
|
||||||
|
import { TypeORMNotification } from '../Infra/TypeORM/TypeORMNotification'
|
||||||
|
|
||||||
export class AppDataSource {
|
export class AppDataSource {
|
||||||
private dataSource: DataSource | undefined
|
private _dataSource: DataSource | undefined
|
||||||
|
|
||||||
constructor(private env: Env) {}
|
constructor(private env: Env) {}
|
||||||
|
|
||||||
getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
|
getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
|
||||||
if (!this.dataSource) {
|
if (!this._dataSource) {
|
||||||
throw new Error('DataSource not initialized')
|
throw new Error('DataSource not initialized')
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.dataSource.getRepository(target)
|
return this._dataSource.getRepository(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
|
await this.dataSource.initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
get dataSource(): DataSource {
|
||||||
this.env.load()
|
this.env.load()
|
||||||
|
|
||||||
const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
|
const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
|
||||||
@@ -60,6 +65,7 @@ export class AppDataSource {
|
|||||||
TypeORMAuthenticatorChallenge,
|
TypeORMAuthenticatorChallenge,
|
||||||
TypeORMEmergencyAccessInvitation,
|
TypeORMEmergencyAccessInvitation,
|
||||||
TypeORMCacheEntry,
|
TypeORMCacheEntry,
|
||||||
|
TypeORMNotification,
|
||||||
],
|
],
|
||||||
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
|
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
|
||||||
migrationsRun: true,
|
migrationsRun: true,
|
||||||
@@ -104,7 +110,7 @@ export class AppDataSource {
|
|||||||
database: inReplicaMode ? undefined : this.env.get('DB_DATABASE'),
|
database: inReplicaMode ? undefined : this.env.get('DB_DATABASE'),
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dataSource = new DataSource(mySQLDataSourceOptions)
|
this._dataSource = new DataSource(mySQLDataSourceOptions)
|
||||||
} else {
|
} else {
|
||||||
const sqliteDataSourceOptions: SqliteConnectionOptions = {
|
const sqliteDataSourceOptions: SqliteConnectionOptions = {
|
||||||
...commonDataSourceOptions,
|
...commonDataSourceOptions,
|
||||||
@@ -112,9 +118,9 @@ export class AppDataSource {
|
|||||||
database: this.env.get('DB_SQLITE_DATABASE_PATH'),
|
database: this.env.get('DB_SQLITE_DATABASE_PATH'),
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dataSource = new DataSource(sqliteDataSourceOptions)
|
this._dataSource = new DataSource(sqliteDataSourceOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.dataSource.initialize()
|
return this._dataSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { AppDataSource } from './DataSource'
|
||||||
|
import { Env } from './Env'
|
||||||
|
|
||||||
|
const env: Env = new Env()
|
||||||
|
env.load()
|
||||||
|
|
||||||
|
export const MigrationsDataSource = new AppDataSource(env).dataSource
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
import { ProtocolVersion } from '@standardnotes/common'
|
import { SimpleUserProjection } from '../../Projection/SimpleUserProjection'
|
||||||
|
|
||||||
export interface AuthResponse {
|
export interface AuthResponse {
|
||||||
user: {
|
user: SimpleUserProjection
|
||||||
uuid: string
|
|
||||||
email: string
|
|
||||||
protocolVersion: ProtocolVersion
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import {
|
|||||||
TokenEncoderInterface,
|
TokenEncoderInterface,
|
||||||
} from '@standardnotes/security'
|
} from '@standardnotes/security'
|
||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import { ProtocolVersion } from '@standardnotes/common'
|
|
||||||
import { SessionBody } from '@standardnotes/responses'
|
import { SessionBody } from '@standardnotes/responses'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
|
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
|
||||||
|
import { SimpleUserProjection } from '../../Projection/SimpleUserProjection'
|
||||||
import { SessionServiceInterface } from '../Session/SessionServiceInterface'
|
import { SessionServiceInterface } from '../Session/SessionServiceInterface'
|
||||||
import { KeyParamsFactoryInterface } from '../User/KeyParamsFactoryInterface'
|
import { KeyParamsFactoryInterface } from '../User/KeyParamsFactoryInterface'
|
||||||
import { User } from '../User/User'
|
import { User } from '../User/User'
|
||||||
@@ -54,11 +54,7 @@ export class AuthResponseFactory20200115 extends AuthResponseFactory20190520 {
|
|||||||
return {
|
return {
|
||||||
session: sessionPayload,
|
session: sessionPayload,
|
||||||
key_params: this.keyParamsFactory.create(dto.user, true),
|
key_params: this.keyParamsFactory.create(dto.user, true),
|
||||||
user: this.userProjector.projectSimple(dto.user) as {
|
user: this.userProjector.projectSimple(dto.user) as SimpleUserProjection,
|
||||||
uuid: string
|
|
||||||
email: string
|
|
||||||
protocolVersion: ProtocolVersion
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { User } from '../User/User'
|
|||||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
|
|
||||||
import { AuthenticationMethodResolver } from './AuthenticationMethodResolver'
|
import { AuthenticationMethodResolver } from './AuthenticationMethodResolver'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
describe('AuthenticationMethodResolver', () => {
|
describe('AuthenticationMethodResolver', () => {
|
||||||
let userRepository: UserRepositoryInterface
|
let userRepository: UserRepositoryInterface
|
||||||
@@ -18,11 +19,15 @@ describe('AuthenticationMethodResolver', () => {
|
|||||||
let user: User
|
let user: User
|
||||||
let session: Session
|
let session: Session
|
||||||
let revokedSession: RevokedSession
|
let revokedSession: RevokedSession
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
const createResolver = () =>
|
const createResolver = () =>
|
||||||
new AuthenticationMethodResolver(userRepository, sessionService, sessionTokenDecoder, fallbackTokenDecoder)
|
new AuthenticationMethodResolver(userRepository, sessionService, sessionTokenDecoder, fallbackTokenDecoder, logger)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.debug = jest.fn()
|
||||||
|
|
||||||
user = {} as jest.Mocked<User>
|
user = {} as jest.Mocked<User>
|
||||||
|
|
||||||
session = {} as jest.Mocked<Session>
|
session = {} as jest.Mocked<Session>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { SessionServiceInterface } from '../Session/SessionServiceInterface'
|
|||||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
import { AuthenticationMethod } from './AuthenticationMethod'
|
import { AuthenticationMethod } from './AuthenticationMethod'
|
||||||
import { AuthenticationMethodResolverInterface } from './AuthenticationMethodResolverInterface'
|
import { AuthenticationMethodResolverInterface } from './AuthenticationMethodResolverInterface'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class AuthenticationMethodResolver implements AuthenticationMethodResolverInterface {
|
export class AuthenticationMethodResolver implements AuthenticationMethodResolverInterface {
|
||||||
@@ -14,15 +15,20 @@ export class AuthenticationMethodResolver implements AuthenticationMethodResolve
|
|||||||
@inject(TYPES.Auth_SessionTokenDecoder) private sessionTokenDecoder: TokenDecoderInterface<SessionTokenData>,
|
@inject(TYPES.Auth_SessionTokenDecoder) private sessionTokenDecoder: TokenDecoderInterface<SessionTokenData>,
|
||||||
@inject(TYPES.Auth_FallbackSessionTokenDecoder)
|
@inject(TYPES.Auth_FallbackSessionTokenDecoder)
|
||||||
private fallbackSessionTokenDecoder: TokenDecoderInterface<SessionTokenData>,
|
private fallbackSessionTokenDecoder: TokenDecoderInterface<SessionTokenData>,
|
||||||
|
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async resolve(token: string): Promise<AuthenticationMethod | undefined> {
|
async resolve(token: string): Promise<AuthenticationMethod | undefined> {
|
||||||
let decodedToken: SessionTokenData | undefined = this.sessionTokenDecoder.decodeToken(token)
|
let decodedToken: SessionTokenData | undefined = this.sessionTokenDecoder.decodeToken(token)
|
||||||
if (decodedToken === undefined) {
|
if (decodedToken === undefined) {
|
||||||
|
this.logger.debug('Could not decode token with primary decoder, trying fallback decoder.')
|
||||||
|
|
||||||
decodedToken = this.fallbackSessionTokenDecoder.decodeToken(token)
|
decodedToken = this.fallbackSessionTokenDecoder.decodeToken(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decodedToken) {
|
if (decodedToken) {
|
||||||
|
this.logger.debug('Token decoded successfully. User found.')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'jwt',
|
type: 'jwt',
|
||||||
user: await this.userRepository.findOneByUuid(<string>decodedToken.user_uuid),
|
user: await this.userRepository.findOneByUuid(<string>decodedToken.user_uuid),
|
||||||
@@ -32,6 +38,8 @@ export class AuthenticationMethodResolver implements AuthenticationMethodResolve
|
|||||||
|
|
||||||
const session = await this.sessionService.getSessionFromToken(token)
|
const session = await this.sessionService.getSessionFromToken(token)
|
||||||
if (session) {
|
if (session) {
|
||||||
|
this.logger.debug('Token decoded successfully. Session found.')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'session_token',
|
type: 'session_token',
|
||||||
user: await this.userRepository.findOneByUuid(session.userUuid),
|
user: await this.userRepository.findOneByUuid(session.userUuid),
|
||||||
@@ -41,6 +49,8 @@ export class AuthenticationMethodResolver implements AuthenticationMethodResolve
|
|||||||
|
|
||||||
const revokedSession = await this.sessionService.getRevokedSessionFromToken(token)
|
const revokedSession = await this.sessionService.getRevokedSessionFromToken(token)
|
||||||
if (revokedSession) {
|
if (revokedSession) {
|
||||||
|
this.logger.debug('Token decoded successfully. Revoked session found.')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'revoked',
|
type: 'revoked',
|
||||||
revokedSession: await this.sessionService.markRevokedSessionAsReceived(revokedSession),
|
revokedSession: await this.sessionService.markRevokedSessionAsReceived(revokedSession),
|
||||||
@@ -48,6 +58,8 @@ export class AuthenticationMethodResolver implements AuthenticationMethodResolve
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.debug('Could not decode token.')
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { NotificationType, Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { Notification } from './Notification'
|
||||||
|
|
||||||
|
describe('Notification', () => {
|
||||||
|
it('should create an entity', () => {
|
||||||
|
const entityOrError = Notification.create({
|
||||||
|
timestamps: Timestamps.create(123456789, 123456789).getValue(),
|
||||||
|
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
|
payload: 'payload',
|
||||||
|
type: NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue(),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(entityOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(entityOrError.getValue().id).not.toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { NotificationProps } from './NotificationProps'
|
||||||
|
|
||||||
|
export class Notification extends Entity<NotificationProps> {
|
||||||
|
get id(): UniqueEntityId {
|
||||||
|
return this._id
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: NotificationProps, id?: UniqueEntityId) {
|
||||||
|
super(props, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(props: NotificationProps, id?: UniqueEntityId): Result<Notification> {
|
||||||
|
return Result.ok<Notification>(new Notification(props, id))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { NotificationType, Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
export interface NotificationProps {
|
||||||
|
userUuid: Uuid
|
||||||
|
type: NotificationType
|
||||||
|
payload: string
|
||||||
|
timestamps: Timestamps
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ describe('SettingService', () => {
|
|||||||
user = {
|
user = {
|
||||||
uuid: '4-5-6',
|
uuid: '4-5-6',
|
||||||
} as jest.Mocked<User>
|
} as jest.Mocked<User>
|
||||||
user.isPotentiallyAVaultAccount = jest.fn().mockReturnValue(false)
|
user.isPotentiallyAPrivateUsernameAccount = jest.fn().mockReturnValue(false)
|
||||||
|
|
||||||
setting = {
|
setting = {
|
||||||
name: SettingName.NAMES.DropboxBackupToken,
|
name: SettingName.NAMES.DropboxBackupToken,
|
||||||
@@ -66,7 +66,7 @@ describe('SettingService', () => {
|
|||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
|
||||||
settingsAssociationService.getDefaultSettingsAndValuesForNewVaultAccount = jest.fn().mockReturnValue(
|
settingsAssociationService.getDefaultSettingsAndValuesForNewPrivateUsernameAccount = jest.fn().mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SettingName.NAMES.LogSessionUserAgent,
|
SettingName.NAMES.LogSessionUserAgent,
|
||||||
@@ -98,7 +98,7 @@ describe('SettingService', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should create default settings for a newly registered vault account', async () => {
|
it('should create default settings for a newly registered vault account', async () => {
|
||||||
user.isPotentiallyAVaultAccount = jest.fn().mockReturnValue(true)
|
user.isPotentiallyAPrivateUsernameAccount = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
await createService().applyDefaultSettingsUponRegistration(user)
|
await createService().applyDefaultSettingsUponRegistration(user)
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ export class SettingService implements SettingServiceInterface {
|
|||||||
|
|
||||||
async applyDefaultSettingsUponRegistration(user: User): Promise<void> {
|
async applyDefaultSettingsUponRegistration(user: User): Promise<void> {
|
||||||
let defaultSettingsWithValues = this.settingsAssociationService.getDefaultSettingsAndValuesForNewUser()
|
let defaultSettingsWithValues = this.settingsAssociationService.getDefaultSettingsAndValuesForNewUser()
|
||||||
if (user.isPotentiallyAVaultAccount()) {
|
if (user.isPotentiallyAPrivateUsernameAccount()) {
|
||||||
defaultSettingsWithValues = this.settingsAssociationService.getDefaultSettingsAndValuesForNewVaultAccount()
|
defaultSettingsWithValues =
|
||||||
|
this.settingsAssociationService.getDefaultSettingsAndValuesForNewPrivateUsernameAccount()
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const settingName of defaultSettingsWithValues.keys()) {
|
for (const settingName of defaultSettingsWithValues.keys()) {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ describe('SettingsAssociationService', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should return the default set of settings for a newly registered vault account', () => {
|
it('should return the default set of settings for a newly registered vault account', () => {
|
||||||
const settings = createService().getDefaultSettingsAndValuesForNewVaultAccount()
|
const settings = createService().getDefaultSettingsAndValuesForNewPrivateUsernameAccount()
|
||||||
const flatSettings = [...(settings as Map<string, SettingDescription>).keys()]
|
const flatSettings = [...(settings as Map<string, SettingDescription>).keys()]
|
||||||
expect(flatSettings).toEqual(['MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
expect(flatSettings).toEqual(['MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
|||||||
],
|
],
|
||||||
])
|
])
|
||||||
|
|
||||||
private readonly vaultAccountDefaultSettingsOverwrites = new Map<string, SettingDescription>([
|
private readonly privateUsernameAccountDefaultSettingsOverwrites = new Map<string, SettingDescription>([
|
||||||
[
|
[
|
||||||
SettingName.NAMES.LogSessionUserAgent,
|
SettingName.NAMES.LogSessionUserAgent,
|
||||||
{
|
{
|
||||||
@@ -114,16 +114,18 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
|||||||
return this.defaultSettings
|
return this.defaultSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<string, SettingDescription> {
|
getDefaultSettingsAndValuesForNewPrivateUsernameAccount(): Map<string, SettingDescription> {
|
||||||
const defaultVaultSettings = new Map(this.defaultSettings)
|
const defaultPrivateUsernameSettings = new Map(this.defaultSettings)
|
||||||
|
|
||||||
for (const vaultAccountDefaultSettingOverwriteKey of this.vaultAccountDefaultSettingsOverwrites.keys()) {
|
for (const privateUsernameAccountDefaultSettingOverwriteKey of this.privateUsernameAccountDefaultSettingsOverwrites.keys()) {
|
||||||
defaultVaultSettings.set(
|
defaultPrivateUsernameSettings.set(
|
||||||
vaultAccountDefaultSettingOverwriteKey,
|
privateUsernameAccountDefaultSettingOverwriteKey,
|
||||||
this.vaultAccountDefaultSettingsOverwrites.get(vaultAccountDefaultSettingOverwriteKey) as SettingDescription,
|
this.privateUsernameAccountDefaultSettingsOverwrites.get(
|
||||||
|
privateUsernameAccountDefaultSettingOverwriteKey,
|
||||||
|
) as SettingDescription,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultVaultSettings
|
return defaultPrivateUsernameSettings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { SettingDescription } from './SettingDescription'
|
|||||||
|
|
||||||
export interface SettingsAssociationServiceInterface {
|
export interface SettingsAssociationServiceInterface {
|
||||||
getDefaultSettingsAndValuesForNewUser(): Map<string, SettingDescription>
|
getDefaultSettingsAndValuesForNewUser(): Map<string, SettingDescription>
|
||||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<string, SettingDescription>
|
getDefaultSettingsAndValuesForNewPrivateUsernameAccount(): Map<string, SettingDescription>
|
||||||
getPermissionAssociatedWithSetting(settingName: SettingName): PermissionName | undefined
|
getPermissionAssociatedWithSetting(settingName: SettingName): PermissionName | undefined
|
||||||
getEncryptionVersionForSetting(settingName: SettingName): EncryptionVersion
|
getEncryptionVersionForSetting(settingName: SettingName): EncryptionVersion
|
||||||
getSensitivityForSetting(settingName: SettingName): boolean
|
getSensitivityForSetting(settingName: SettingName): boolean
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export class AuthenticateRequest implements UseCaseInterface {
|
|||||||
|
|
||||||
async execute(dto: AuthenticateRequestDTO): Promise<AuthenticateRequestResponse> {
|
async execute(dto: AuthenticateRequestDTO): Promise<AuthenticateRequestResponse> {
|
||||||
if (!dto.authorizationHeader) {
|
if (!dto.authorizationHeader) {
|
||||||
|
this.logger.debug('Authorization header not provided.')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
responseCode: 401,
|
responseCode: 401,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { AuthenticateUser } from './AuthenticateUser'
|
|||||||
import { RevokedSession } from '../Session/RevokedSession'
|
import { RevokedSession } from '../Session/RevokedSession'
|
||||||
import { AuthenticationMethodResolverInterface } from '../Auth/AuthenticationMethodResolverInterface'
|
import { AuthenticationMethodResolverInterface } from '../Auth/AuthenticationMethodResolverInterface'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
describe('AuthenticateUser', () => {
|
describe('AuthenticateUser', () => {
|
||||||
let user: User
|
let user: User
|
||||||
@@ -14,11 +15,15 @@ describe('AuthenticateUser', () => {
|
|||||||
let revokedSession: RevokedSession
|
let revokedSession: RevokedSession
|
||||||
let authenticationMethodResolver: AuthenticationMethodResolverInterface
|
let authenticationMethodResolver: AuthenticationMethodResolverInterface
|
||||||
let timer: TimerInterface
|
let timer: TimerInterface
|
||||||
|
let logger: Logger
|
||||||
const accessTokenAge = 3600
|
const accessTokenAge = 3600
|
||||||
|
|
||||||
const createUseCase = () => new AuthenticateUser(authenticationMethodResolver, timer, accessTokenAge)
|
const createUseCase = () => new AuthenticateUser(authenticationMethodResolver, timer, accessTokenAge, logger)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.debug = jest.fn()
|
||||||
|
|
||||||
user = {} as jest.Mocked<User>
|
user = {} as jest.Mocked<User>
|
||||||
user.supportsSessions = jest.fn().mockReturnValue(false)
|
user.supportsSessions = jest.fn().mockReturnValue(false)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Session } from '../Session/Session'
|
|||||||
import { AuthenticateUserDTO } from './AuthenticateUserDTO'
|
import { AuthenticateUserDTO } from './AuthenticateUserDTO'
|
||||||
import { AuthenticateUserResponse } from './AuthenticateUserResponse'
|
import { AuthenticateUserResponse } from './AuthenticateUserResponse'
|
||||||
import { UseCaseInterface } from './UseCaseInterface'
|
import { UseCaseInterface } from './UseCaseInterface'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class AuthenticateUser implements UseCaseInterface {
|
export class AuthenticateUser implements UseCaseInterface {
|
||||||
@@ -17,11 +18,14 @@ export class AuthenticateUser implements UseCaseInterface {
|
|||||||
private authenticationMethodResolver: AuthenticationMethodResolverInterface,
|
private authenticationMethodResolver: AuthenticationMethodResolverInterface,
|
||||||
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
|
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
|
||||||
@inject(TYPES.Auth_ACCESS_TOKEN_AGE) private accessTokenAge: number,
|
@inject(TYPES.Auth_ACCESS_TOKEN_AGE) private accessTokenAge: number,
|
||||||
|
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: AuthenticateUserDTO): Promise<AuthenticateUserResponse> {
|
async execute(dto: AuthenticateUserDTO): Promise<AuthenticateUserResponse> {
|
||||||
const authenticationMethod = await this.authenticationMethodResolver.resolve(dto.token)
|
const authenticationMethod = await this.authenticationMethodResolver.resolve(dto.token)
|
||||||
if (!authenticationMethod) {
|
if (!authenticationMethod) {
|
||||||
|
this.logger.debug('No authentication method found for token.')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
failureType: 'INVALID_AUTH',
|
failureType: 'INVALID_AUTH',
|
||||||
@@ -37,6 +41,8 @@ export class AuthenticateUser implements UseCaseInterface {
|
|||||||
|
|
||||||
const user = authenticationMethod.user
|
const user = authenticationMethod.user
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
this.logger.debug('No user found for authentication method.')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
failureType: 'INVALID_AUTH',
|
failureType: 'INVALID_AUTH',
|
||||||
@@ -44,6 +50,8 @@ export class AuthenticateUser implements UseCaseInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authenticationMethod.type == 'jwt' && user.supportsSessions()) {
|
if (authenticationMethod.type == 'jwt' && user.supportsSessions()) {
|
||||||
|
this.logger.debug('User supports sessions but is trying to authenticate with a JWT.')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
failureType: 'INVALID_AUTH',
|
failureType: 'INVALID_AUTH',
|
||||||
@@ -56,6 +64,8 @@ export class AuthenticateUser implements UseCaseInterface {
|
|||||||
const encryptedPasswordDigest = crypto.createHash('sha256').update(user.encryptedPassword).digest('hex')
|
const encryptedPasswordDigest = crypto.createHash('sha256').update(user.encryptedPassword).digest('hex')
|
||||||
|
|
||||||
if (!pwHash || !crypto.timingSafeEqual(Buffer.from(pwHash), Buffer.from(encryptedPasswordDigest))) {
|
if (!pwHash || !crypto.timingSafeEqual(Buffer.from(pwHash), Buffer.from(encryptedPasswordDigest))) {
|
||||||
|
this.logger.debug('Password hash does not match.')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
failureType: 'INVALID_AUTH',
|
failureType: 'INVALID_AUTH',
|
||||||
@@ -66,6 +76,8 @@ export class AuthenticateUser implements UseCaseInterface {
|
|||||||
case 'session_token': {
|
case 'session_token': {
|
||||||
const session = authenticationMethod.session
|
const session = authenticationMethod.session
|
||||||
if (!session) {
|
if (!session) {
|
||||||
|
this.logger.debug('No session found for authentication method.')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
failureType: 'INVALID_AUTH',
|
failureType: 'INVALID_AUTH',
|
||||||
@@ -73,6 +85,8 @@ export class AuthenticateUser implements UseCaseInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (session.refreshExpiration < this.timer.getUTCDate()) {
|
if (session.refreshExpiration < this.timer.getUTCDate()) {
|
||||||
|
this.logger.debug('Session refresh token has expired.')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
failureType: 'INVALID_AUTH',
|
failureType: 'INVALID_AUTH',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { inject, injectable } from 'inversify'
|
|||||||
import { SubscriptionName } from '@standardnotes/common'
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { TokenEncoderInterface, ValetTokenData } from '@standardnotes/security'
|
import { TokenEncoderInterface, ValetTokenData } from '@standardnotes/security'
|
||||||
import { CreateValetTokenPayload, CreateValetTokenResponseData } from '@standardnotes/responses'
|
import { CreateValetTokenResponseData } from '@standardnotes/responses'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/settings'
|
||||||
|
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
@@ -12,6 +12,7 @@ import { SubscriptionSettingServiceInterface } from '../../Setting/SubscriptionS
|
|||||||
import { CreateValetTokenDTO } from './CreateValetTokenDTO'
|
import { CreateValetTokenDTO } from './CreateValetTokenDTO'
|
||||||
import { SubscriptionSettingsAssociationServiceInterface } from '../../Setting/SubscriptionSettingsAssociationServiceInterface'
|
import { SubscriptionSettingsAssociationServiceInterface } from '../../Setting/SubscriptionSettingsAssociationServiceInterface'
|
||||||
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
|
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
|
||||||
|
import { CreateValetTokenPayload } from '../../ValetToken/CreateValetTokenPayload'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class CreateValetToken implements UseCaseInterface {
|
export class CreateValetToken implements UseCaseInterface {
|
||||||
|
|||||||
+2
-2
@@ -69,7 +69,7 @@ export class InviteToSharedSubscription implements UseCaseInterface {
|
|||||||
sharedSubscriptionInvition.inviterIdentifier = dto.inviterEmail
|
sharedSubscriptionInvition.inviterIdentifier = dto.inviterEmail
|
||||||
sharedSubscriptionInvition.inviterIdentifierType = InviterIdentifierType.Email
|
sharedSubscriptionInvition.inviterIdentifierType = InviterIdentifierType.Email
|
||||||
sharedSubscriptionInvition.inviteeIdentifier = dto.inviteeIdentifier
|
sharedSubscriptionInvition.inviteeIdentifier = dto.inviteeIdentifier
|
||||||
sharedSubscriptionInvition.inviteeIdentifierType = this.isInviteeIdentifierPotentiallyAVaultAccount(
|
sharedSubscriptionInvition.inviteeIdentifierType = this.isInviteeIdentifierPotentiallyAPrivateUsernameAccount(
|
||||||
dto.inviteeIdentifier,
|
dto.inviteeIdentifier,
|
||||||
)
|
)
|
||||||
? InviteeIdentifierType.Hash
|
? InviteeIdentifierType.Hash
|
||||||
@@ -107,7 +107,7 @@ export class InviteToSharedSubscription implements UseCaseInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private isInviteeIdentifierPotentiallyAVaultAccount(identifier: string): boolean {
|
private isInviteeIdentifierPotentiallyAPrivateUsernameAccount(identifier: string): boolean {
|
||||||
return identifier.length === 64 && !identifier.includes('@')
|
return identifier.length === 64 && !identifier.includes('@')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,21 +44,13 @@ describe('UpdateUser', () => {
|
|||||||
user,
|
user,
|
||||||
updatedWithUserAgent: 'Mozilla',
|
updatedWithUserAgent: 'Mozilla',
|
||||||
apiVersion: '20190520',
|
apiVersion: '20190520',
|
||||||
version: '004',
|
|
||||||
pwCost: 11,
|
|
||||||
pwSalt: 'qweqwe',
|
|
||||||
pwNonce: undefined,
|
|
||||||
}),
|
}),
|
||||||
).toEqual({ success: true, authResponse: { foo: 'bar' } })
|
).toEqual({ success: true, authResponse: { foo: 'bar' } })
|
||||||
|
|
||||||
expect(userRepository.save).toHaveBeenCalledWith({
|
expect(userRepository.save).toHaveBeenCalledWith({
|
||||||
createdAt: new Date(1),
|
createdAt: new Date(1),
|
||||||
pwCost: 11,
|
|
||||||
email: 'test@test.te',
|
email: 'test@test.te',
|
||||||
pwSalt: 'qweqwe',
|
|
||||||
updatedWithUserAgent: 'Mozilla',
|
|
||||||
uuid: '123',
|
uuid: '123',
|
||||||
version: '004',
|
|
||||||
updatedAt: new Date(1),
|
updatedAt: new Date(1),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,25 +17,17 @@ export class UpdateUser implements UseCaseInterface {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: UpdateUserDTO): Promise<UpdateUserResponse> {
|
async execute(dto: UpdateUserDTO): Promise<UpdateUserResponse> {
|
||||||
const { user, apiVersion, ...updateFields } = dto
|
dto.user.updatedAt = this.timer.getUTCDate()
|
||||||
|
|
||||||
Object.keys(updateFields).forEach(
|
const updatedUser = await this.userRepository.save(dto.user)
|
||||||
(key) => (updateFields[key] === undefined || updateFields[key] === null) && delete updateFields[key],
|
|
||||||
)
|
|
||||||
|
|
||||||
Object.assign(user, updateFields)
|
const authResponseFactory = this.authResponseFactoryResolver.resolveAuthResponseFactoryVersion(dto.apiVersion)
|
||||||
|
|
||||||
user.updatedAt = this.timer.getUTCDate()
|
|
||||||
|
|
||||||
await this.userRepository.save(user)
|
|
||||||
|
|
||||||
const authResponseFactory = this.authResponseFactoryResolver.resolveAuthResponseFactoryVersion(apiVersion)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
authResponse: await authResponseFactory.createResponse({
|
authResponse: await authResponseFactory.createResponse({
|
||||||
user,
|
user: updatedUser,
|
||||||
apiVersion,
|
apiVersion: dto.apiVersion,
|
||||||
userAgent: dto.updatedWithUserAgent,
|
userAgent: dto.updatedWithUserAgent,
|
||||||
ephemeralSession: false,
|
ephemeralSession: false,
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
|
|||||||
@@ -1,18 +1,7 @@
|
|||||||
import { User } from '../User/User'
|
import { User } from '../User/User'
|
||||||
|
|
||||||
export type UpdateUserDTO = {
|
export type UpdateUserDTO = {
|
||||||
[key: string]: string | User | Date | undefined | number
|
|
||||||
user: User
|
user: User
|
||||||
updatedWithUserAgent: string
|
|
||||||
apiVersion: string
|
apiVersion: string
|
||||||
email?: string
|
updatedWithUserAgent: string
|
||||||
pwFunc?: string
|
|
||||||
pwAlg?: string
|
|
||||||
pwCost?: number
|
|
||||||
pwKeySize?: number
|
|
||||||
pwNonce?: string
|
|
||||||
pwSalt?: string
|
|
||||||
kpOrigination?: string
|
|
||||||
kpCreated?: Date
|
|
||||||
version?: string
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ describe('User', () => {
|
|||||||
const user = createUser()
|
const user = createUser()
|
||||||
user.email = 'a75a31ce95365904ef0e0a8e6cefc1f5e99adfef81bbdb6d4499eeb10ae0ff67'
|
user.email = 'a75a31ce95365904ef0e0a8e6cefc1f5e99adfef81bbdb6d4499eeb10ae0ff67'
|
||||||
|
|
||||||
expect(user.isPotentiallyAVaultAccount()).toBeTruthy()
|
expect(user.isPotentiallyAPrivateUsernameAccount()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should indicate if the user is not a vault account', () => {
|
it('should indicate if the user is not a vault account', () => {
|
||||||
const user = createUser()
|
const user = createUser()
|
||||||
user.email = 'test@test.te'
|
user.email = 'test@test.te'
|
||||||
|
|
||||||
expect(user.isPotentiallyAVaultAccount()).toBeFalsy()
|
expect(user.isPotentiallyAPrivateUsernameAccount()).toBeFalsy()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ export class User {
|
|||||||
return parseInt(this.version) >= parseInt(ProtocolVersion.V004)
|
return parseInt(this.version) >= parseInt(ProtocolVersion.V004)
|
||||||
}
|
}
|
||||||
|
|
||||||
isPotentiallyAVaultAccount(): boolean {
|
isPotentiallyAPrivateUsernameAccount(): boolean {
|
||||||
return this.email.length === 64 && !this.email.includes('@')
|
return this.email.length === 64 && !this.email.includes('@')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export type CreateValetTokenPayload = {
|
||||||
|
operation: 'read' | 'write' | 'delete' | 'move'
|
||||||
|
resources: Array<{
|
||||||
|
remoteIdentifier: string
|
||||||
|
unencryptedFileSize?: number
|
||||||
|
}>
|
||||||
|
}
|
||||||
@@ -60,15 +60,6 @@ export class HomeServerUsersController extends BaseHttpController {
|
|||||||
user: response.locals.user,
|
user: response.locals.user,
|
||||||
updatedWithUserAgent: <string>request.headers['user-agent'],
|
updatedWithUserAgent: <string>request.headers['user-agent'],
|
||||||
apiVersion: request.body.api,
|
apiVersion: request.body.api,
|
||||||
pwFunc: request.body.pw_func,
|
|
||||||
pwAlg: request.body.pw_alg,
|
|
||||||
pwCost: request.body.pw_cost,
|
|
||||||
pwKeySize: request.body.pw_key_size,
|
|
||||||
pwNonce: request.body.pw_nonce,
|
|
||||||
pwSalt: request.body.pw_salt,
|
|
||||||
kpOrigination: request.body.origination,
|
|
||||||
kpCreated: request.body.created,
|
|
||||||
version: request.body.version,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (updateResult.success) {
|
if (updateResult.success) {
|
||||||
|
|||||||
+3
-2
@@ -1,10 +1,11 @@
|
|||||||
import { ControllerContainerInterface, Uuid } from '@standardnotes/domain-core'
|
import { ControllerContainerInterface, Uuid } from '@standardnotes/domain-core'
|
||||||
import { Request, Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
import { BaseHttpController, results } from 'inversify-express-utils'
|
import { BaseHttpController, results } from 'inversify-express-utils'
|
||||||
|
import { ErrorTag } from '@standardnotes/responses'
|
||||||
|
import { ValetTokenOperation } from '@standardnotes/security'
|
||||||
|
|
||||||
import { CreateValetToken } from '../../../Domain/UseCase/CreateValetToken/CreateValetToken'
|
import { CreateValetToken } from '../../../Domain/UseCase/CreateValetToken/CreateValetToken'
|
||||||
import { CreateValetTokenPayload, ErrorTag } from '@standardnotes/responses'
|
import { CreateValetTokenPayload } from '../../../Domain/ValetToken/CreateValetTokenPayload'
|
||||||
import { ValetTokenOperation } from '@standardnotes/security'
|
|
||||||
|
|
||||||
export class HomeServerValetTokenController extends BaseHttpController {
|
export class HomeServerValetTokenController extends BaseHttpController {
|
||||||
constructor(protected createValetKey: CreateValetToken, private controllerContainer?: ControllerContainerInterface) {
|
constructor(protected createValetKey: CreateValetToken, private controllerContainer?: ControllerContainerInterface) {
|
||||||
|
|||||||
@@ -99,9 +99,7 @@ describe('InversifyExpressUsersController', () => {
|
|||||||
|
|
||||||
expect(updateUser.execute).toHaveBeenCalledWith({
|
expect(updateUser.execute).toHaveBeenCalledWith({
|
||||||
apiVersion: '20190520',
|
apiVersion: '20190520',
|
||||||
kpOrigination: 'test',
|
|
||||||
updatedWithUserAgent: 'Google Chrome',
|
updatedWithUserAgent: 'Google Chrome',
|
||||||
version: '002',
|
|
||||||
user: {
|
user: {
|
||||||
uuid: '123',
|
uuid: '123',
|
||||||
email: 'test@test.te',
|
email: 'test@test.te',
|
||||||
@@ -143,9 +141,7 @@ describe('InversifyExpressUsersController', () => {
|
|||||||
|
|
||||||
expect(updateUser.execute).toHaveBeenCalledWith({
|
expect(updateUser.execute).toHaveBeenCalledWith({
|
||||||
apiVersion: '20190520',
|
apiVersion: '20190520',
|
||||||
kpOrigination: 'test',
|
|
||||||
updatedWithUserAgent: 'Google Chrome',
|
updatedWithUserAgent: 'Google Chrome',
|
||||||
version: '002',
|
|
||||||
user: {
|
user: {
|
||||||
uuid: '123',
|
uuid: '123',
|
||||||
email: 'test@test.te',
|
email: 'test@test.te',
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
|
||||||
|
|
||||||
|
@Entity({ name: 'notifications' })
|
||||||
|
export class TypeORMNotification {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
declare uuid: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'user_uuid',
|
||||||
|
length: 36,
|
||||||
|
})
|
||||||
|
@Index('index_notifications_on_user_uuid')
|
||||||
|
declare userUuid: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'type',
|
||||||
|
length: 36,
|
||||||
|
})
|
||||||
|
declare type: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'payload',
|
||||||
|
type: 'text',
|
||||||
|
})
|
||||||
|
declare payload: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'created_at_timestamp',
|
||||||
|
type: 'bigint',
|
||||||
|
})
|
||||||
|
declare createdAtTimestamp: number
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'updated_at_timestamp',
|
||||||
|
type: 'bigint',
|
||||||
|
})
|
||||||
|
declare updatedAtTimestamp: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export type SimpleUserProjection = {
|
||||||
|
uuid: string
|
||||||
|
email: string
|
||||||
|
protocolVersion: string
|
||||||
|
}
|
||||||
@@ -2,10 +2,11 @@ import { injectable } from 'inversify'
|
|||||||
|
|
||||||
import { User } from '../Domain/User/User'
|
import { User } from '../Domain/User/User'
|
||||||
import { ProjectorInterface } from './ProjectorInterface'
|
import { ProjectorInterface } from './ProjectorInterface'
|
||||||
|
import { SimpleUserProjection } from './SimpleUserProjection'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class UserProjector implements ProjectorInterface<User> {
|
export class UserProjector implements ProjectorInterface<User> {
|
||||||
projectSimple(user: User): Record<string, unknown> {
|
projectSimple(user: User): SimpleUserProjection {
|
||||||
return {
|
return {
|
||||||
uuid: user.uuid,
|
uuid: user.uuid,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.49.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.48.3...@standardnotes/common@1.49.0) (2023-06-30)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* shared vaults functionality in api-gateway,auth,files,common,security,domain-events. ([#629](https://github.com/standardnotes/server/issues/629)) ([fa7fbe2](https://github.com/standardnotes/server/commit/fa7fbe26e7b0707fc21d71e04af76870f5248baf))
|
||||||
|
|
||||||
## [1.48.3](https://github.com/standardnotes/server/compare/@standardnotes/common@1.48.2...@standardnotes/common@1.48.3) (2023-06-28)
|
## [1.48.3](https://github.com/standardnotes/server/compare/@standardnotes/common@1.48.2...@standardnotes/common@1.48.3) (2023-06-28)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/common
|
**Note:** Version bump only for package @standardnotes/common
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/common",
|
"name": "@standardnotes/common",
|
||||||
"version": "1.48.3",
|
"version": "1.49.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ export enum ProtocolVersion {
|
|||||||
V002 = '002',
|
V002 = '002',
|
||||||
V003 = '003',
|
V003 = '003',
|
||||||
V004 = '004',
|
V004 = '004',
|
||||||
V005 = '005',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProtocolVersionLatest = ProtocolVersion.V004
|
export const ProtocolVersionLatest = ProtocolVersion.V004
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.20.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.19.0...@standardnotes/domain-core@1.20.0) (2023-07-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* deleting shared vaults. ([#640](https://github.com/standardnotes/server/issues/640)) ([f3161c2](https://github.com/standardnotes/server/commit/f3161c271296159331639814b2dbb2e566cc54c9))
|
||||||
|
|
||||||
|
# [1.19.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.18.0...@standardnotes/domain-core@1.19.0) (2023-06-30)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add shared vaults model. ([#631](https://github.com/standardnotes/server/issues/631)) ([38e77f0](https://github.com/standardnotes/server/commit/38e77f04be441b7506c3390fb0d9894b34119c3e))
|
||||||
|
|
||||||
# [1.18.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.17.0...@standardnotes/domain-core@1.18.0) (2023-06-02)
|
# [1.18.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.17.0...@standardnotes/domain-core@1.18.0) (2023-06-02)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-core",
|
"name": "@standardnotes/domain-core",
|
||||||
"version": "1.18.0",
|
"version": "1.20.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class Dates extends ValueObject<DatesProps> {
|
|||||||
return Result.fail<Dates>(`Could not create Dates. Creation date should be a date object, given: ${createdAt}`)
|
return Result.fail<Dates>(`Could not create Dates. Creation date should be a date object, given: ${createdAt}`)
|
||||||
}
|
}
|
||||||
if (!(updatedAt instanceof Date)) {
|
if (!(updatedAt instanceof Date)) {
|
||||||
return Result.fail<Dates>(`Could not create Dates. Update date should be a date object, given: ${createdAt}`)
|
return Result.fail<Dates>(`Could not create Dates. Update date should be a date object, given: ${updatedAt}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok<Dates>(new Dates({ createdAt, updatedAt }))
|
return Result.ok<Dates>(new Dates({ createdAt, updatedAt }))
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { ValueObject } from '../Core/ValueObject'
|
||||||
|
import { TimestampsProps } from './TimestampsProps'
|
||||||
|
|
||||||
|
export class Timestamps extends ValueObject<TimestampsProps> {
|
||||||
|
get createdAt(): number {
|
||||||
|
return this.props.createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
get updatedAt(): number {
|
||||||
|
return this.props.updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: TimestampsProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(createdAt: number, updatedAt: number): Result<Timestamps> {
|
||||||
|
if (isNaN(createdAt)) {
|
||||||
|
return Result.fail<Timestamps>(
|
||||||
|
`Could not create Timestamps. Creation date should be a number, given: ${createdAt}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isNaN(updatedAt)) {
|
||||||
|
return Result.fail<Timestamps>(`Could not create Timestamps. Update date should be a number, given: ${updatedAt}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok<Timestamps>(new Timestamps({ createdAt, updatedAt }))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface TimestampsProps {
|
||||||
|
createdAt: number
|
||||||
|
updatedAt: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { NotificationType } from './NotificationType'
|
||||||
|
|
||||||
|
describe('NotificationType', () => {
|
||||||
|
it('should create a value object', () => {
|
||||||
|
const valueOrError = NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().value).toEqual('shared_vault_item_removed')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create an invalid value object', () => {
|
||||||
|
const valueOrError = NotificationType.create('TEST')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { ValueObject } from '../Core/ValueObject'
|
||||||
|
|
||||||
|
import { NotificationTypeProps } from './NotificationTypeProps'
|
||||||
|
|
||||||
|
export class NotificationType extends ValueObject<NotificationTypeProps> {
|
||||||
|
static readonly TYPES = {
|
||||||
|
SharedVaultItemRemoved: 'shared_vault_item_removed',
|
||||||
|
RemovedFromSharedVault: 'removed_from_shared_vault',
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): string {
|
||||||
|
return this.props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: NotificationTypeProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(notificationType: string): Result<NotificationType> {
|
||||||
|
const isValidPermission = Object.values(this.TYPES).includes(notificationType)
|
||||||
|
if (!isValidPermission) {
|
||||||
|
return Result.fail<NotificationType>(`Invalid shared vault user permission ${notificationType}`)
|
||||||
|
} else {
|
||||||
|
return Result.ok<NotificationType>(new NotificationType({ value: notificationType }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface NotificationTypeProps {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
@@ -17,6 +17,8 @@ export * from './Common/RoleName'
|
|||||||
export * from './Common/RoleNameProps'
|
export * from './Common/RoleNameProps'
|
||||||
export * from './Common/RoleNameCollection'
|
export * from './Common/RoleNameCollection'
|
||||||
export * from './Common/RoleNameCollectionProps'
|
export * from './Common/RoleNameCollectionProps'
|
||||||
|
export * from './Common/Timestamps'
|
||||||
|
export * from './Common/TimestampsProps'
|
||||||
export * from './Common/Username'
|
export * from './Common/Username'
|
||||||
export * from './Common/UsernameProps'
|
export * from './Common/UsernameProps'
|
||||||
export * from './Common/Uuid'
|
export * from './Common/Uuid'
|
||||||
@@ -41,6 +43,9 @@ export * from './Env/AbstractEnv'
|
|||||||
|
|
||||||
export * from './Mapping/MapperInterface'
|
export * from './Mapping/MapperInterface'
|
||||||
|
|
||||||
|
export * from './Notification/NotificationType'
|
||||||
|
export * from './Notification/NotificationTypeProps'
|
||||||
|
|
||||||
export * from './Service/ServiceConfiguration'
|
export * from './Service/ServiceConfiguration'
|
||||||
export * from './Service/ServiceContainer'
|
export * from './Service/ServiceContainer'
|
||||||
export * from './Service/ServiceContainerInterface'
|
export * from './Service/ServiceContainerInterface'
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.12.8](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.7...@standardnotes/domain-events-infra@1.12.8) (2023-07-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.12.7](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.6...@standardnotes/domain-events-infra@1.12.7) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.12.6](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.5...@standardnotes/domain-events-infra@1.12.6) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
## [1.12.5](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.4...@standardnotes/domain-events-infra@1.12.5) (2023-06-01)
|
## [1.12.5](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.4...@standardnotes/domain-events-infra@1.12.5) (2023-06-01)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-events-infra",
|
"name": "@standardnotes/domain-events-infra",
|
||||||
"version": "1.12.5",
|
"version": "1.12.8",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [2.113.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.112.1...@standardnotes/domain-events@2.113.0) (2023-07-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* deleting shared vaults. ([#640](https://github.com/standardnotes/server/issues/640)) ([f3161c2](https://github.com/standardnotes/server/commit/f3161c271296159331639814b2dbb2e566cc54c9))
|
||||||
|
|
||||||
|
## [2.112.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.112.0...@standardnotes/domain-events@2.112.1) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events
|
||||||
|
|
||||||
|
# [2.112.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.111.4...@standardnotes/domain-events@2.112.0) (2023-06-30)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* shared vaults functionality in api-gateway,auth,files,common,security,domain-events. ([#629](https://github.com/standardnotes/server/issues/629)) ([fa7fbe2](https://github.com/standardnotes/server/commit/fa7fbe26e7b0707fc21d71e04af76870f5248baf))
|
||||||
|
|
||||||
## [2.111.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.111.3...@standardnotes/domain-events@2.111.4) (2023-06-01)
|
## [2.111.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.111.3...@standardnotes/domain-events@2.111.4) (2023-06-01)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/domain-events
|
**Note:** Version bump only for package @standardnotes/domain-events
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-events",
|
"name": "@standardnotes/domain-events",
|
||||||
"version": "2.111.4",
|
"version": "2.113.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export interface DomainEventInterface {
|
|||||||
meta: {
|
meta: {
|
||||||
correlation: {
|
correlation: {
|
||||||
userIdentifier: string
|
userIdentifier: string
|
||||||
userIdentifierType: 'uuid' | 'email'
|
userIdentifierType: 'uuid' | 'email' | 'shared-vault-uuid'
|
||||||
}
|
}
|
||||||
origin: DomainEventService
|
origin: DomainEventService
|
||||||
target?: DomainEventService
|
target?: DomainEventService
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { DomainEventInterface } from './DomainEventInterface'
|
||||||
|
import { NotificationRequestedEventPayload } from './NotificationRequestedEventPayload'
|
||||||
|
|
||||||
|
export interface NotificationRequestedEvent extends DomainEventInterface {
|
||||||
|
type: 'NOTIFICATION_REQUESTED'
|
||||||
|
payload: NotificationRequestedEventPayload
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface NotificationRequestedEventPayload {
|
||||||
|
userUuid: string
|
||||||
|
type: string
|
||||||
|
payload: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { DomainEventInterface } from './DomainEventInterface'
|
||||||
|
import { SharedVaultFileRemovedEventPayload } from './SharedVaultFileRemovedEventPayload'
|
||||||
|
|
||||||
|
export interface SharedVaultFileRemovedEvent extends DomainEventInterface {
|
||||||
|
type: 'SHARED_VAULT_FILE_REMOVED'
|
||||||
|
payload: SharedVaultFileRemovedEventPayload
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface SharedVaultFileRemovedEventPayload {
|
||||||
|
sharedVaultUuid: string
|
||||||
|
fileByteSize: number
|
||||||
|
filePath: string
|
||||||
|
fileName: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { DomainEventInterface } from './DomainEventInterface'
|
||||||
|
import { SharedVaultFileUploadedEventPayload } from './SharedVaultFileUploadedEventPayload'
|
||||||
|
|
||||||
|
export interface SharedVaultFileUploadedEvent extends DomainEventInterface {
|
||||||
|
type: 'SHARED_VAULT_FILE_UPLOADED'
|
||||||
|
payload: SharedVaultFileUploadedEventPayload
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface SharedVaultFileUploadedEventPayload {
|
||||||
|
sharedVaultUuid: string
|
||||||
|
fileByteSize: number
|
||||||
|
filePath: string
|
||||||
|
fileName: string
|
||||||
|
}
|
||||||
@@ -42,6 +42,8 @@ export * from './Event/ListedAccountRequestedEvent'
|
|||||||
export * from './Event/ListedAccountRequestedEventPayload'
|
export * from './Event/ListedAccountRequestedEventPayload'
|
||||||
export * from './Event/MuteEmailsSettingChangedEvent'
|
export * from './Event/MuteEmailsSettingChangedEvent'
|
||||||
export * from './Event/MuteEmailsSettingChangedEventPayload'
|
export * from './Event/MuteEmailsSettingChangedEventPayload'
|
||||||
|
export * from './Event/NotificationRequestedEvent'
|
||||||
|
export * from './Event/NotificationRequestedEventPayload'
|
||||||
export * from './Event/PaymentFailedEvent'
|
export * from './Event/PaymentFailedEvent'
|
||||||
export * from './Event/PaymentFailedEventPayload'
|
export * from './Event/PaymentFailedEventPayload'
|
||||||
export * from './Event/PaymentSuccessEvent'
|
export * from './Event/PaymentSuccessEvent'
|
||||||
@@ -62,6 +64,10 @@ export * from './Event/SharedSubscriptionInvitationCanceledEvent'
|
|||||||
export * from './Event/SharedSubscriptionInvitationCanceledEventPayload'
|
export * from './Event/SharedSubscriptionInvitationCanceledEventPayload'
|
||||||
export * from './Event/SharedSubscriptionInvitationCreatedEvent'
|
export * from './Event/SharedSubscriptionInvitationCreatedEvent'
|
||||||
export * from './Event/SharedSubscriptionInvitationCreatedEventPayload'
|
export * from './Event/SharedSubscriptionInvitationCreatedEventPayload'
|
||||||
|
export * from './Event/SharedVaultFileRemovedEvent'
|
||||||
|
export * from './Event/SharedVaultFileRemovedEventPayload'
|
||||||
|
export * from './Event/SharedVaultFileUploadedEvent'
|
||||||
|
export * from './Event/SharedVaultFileUploadedEventPayload'
|
||||||
export * from './Event/StatisticPersistenceRequestedEvent'
|
export * from './Event/StatisticPersistenceRequestedEvent'
|
||||||
export * from './Event/StatisticPersistenceRequestedEventPayload'
|
export * from './Event/StatisticPersistenceRequestedEventPayload'
|
||||||
export * from './Event/SubscriptionCancelledEvent'
|
export * from './Event/SubscriptionCancelledEvent'
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.11.3](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.2...@standardnotes/event-store@1.11.3) (2023-07-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.11.2](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.1...@standardnotes/event-store@1.11.2) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.11.1](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.0...@standardnotes/event-store@1.11.1) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
# [1.11.0](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.10.1...@standardnotes/event-store@1.11.0) (2023-06-30)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* shared vaults functionality in api-gateway,auth,files,common,security,domain-events. ([#629](https://github.com/standardnotes/server/issues/629)) ([fa7fbe2](https://github.com/standardnotes/server/commit/fa7fbe26e7b0707fc21d71e04af76870f5248baf))
|
||||||
|
|
||||||
## [1.10.1](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.10.0...@standardnotes/event-store@1.10.1) (2023-06-02)
|
## [1.10.1](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.10.0...@standardnotes/event-store@1.10.1) (2023-06-02)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/event-store",
|
"name": "@standardnotes/event-store",
|
||||||
"version": "1.10.1",
|
"version": "1.11.3",
|
||||||
"description": "Event Store Service",
|
"description": "Event Store Service",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.19.3](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.2...@standardnotes/files-server@1.19.3) (2023-07-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.19.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.1...@standardnotes/files-server@1.19.2) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.19.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.0...@standardnotes/files-server@1.19.1) (2023-06-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
# [1.19.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.18.3...@standardnotes/files-server@1.19.0) (2023-06-30)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* shared vaults functionality in api-gateway,auth,files,common,security,domain-events. ([#629](https://github.com/standardnotes/files/issues/629)) ([fa7fbe2](https://github.com/standardnotes/files/commit/fa7fbe26e7b0707fc21d71e04af76870f5248baf))
|
||||||
|
|
||||||
## [1.18.3](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.18.2...@standardnotes/files-server@1.18.3) (2023-06-22)
|
## [1.18.3](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.18.2...@standardnotes/files-server@1.18.3) (2023-06-22)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import * as busboy from 'connect-busboy'
|
|||||||
|
|
||||||
import '../src/Infra/InversifyExpress/InversifyExpressHealthCheckController'
|
import '../src/Infra/InversifyExpress/InversifyExpressHealthCheckController'
|
||||||
import '../src/Infra/InversifyExpress/InversifyExpressFilesController'
|
import '../src/Infra/InversifyExpress/InversifyExpressFilesController'
|
||||||
|
import '../src/Infra/InversifyExpress/InversifyExpressSharedVaultFilesController'
|
||||||
|
|
||||||
import helmet from 'helmet'
|
import helmet from 'helmet'
|
||||||
import * as cors from 'cors'
|
import * as cors from 'cors'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/files-server",
|
"name": "@standardnotes/files-server",
|
||||||
"version": "1.18.3",
|
"version": "1.19.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountD
|
|||||||
import { SharedSubscriptionInvitationCanceledEventHandler } from '../Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler'
|
import { SharedSubscriptionInvitationCanceledEventHandler } from '../Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler'
|
||||||
import { InMemoryUploadRepository } from '../Infra/InMemory/InMemoryUploadRepository'
|
import { InMemoryUploadRepository } from '../Infra/InMemory/InMemoryUploadRepository'
|
||||||
import { Transform } from 'stream'
|
import { Transform } from 'stream'
|
||||||
|
import { FileMoverInterface } from '../Domain/Services/FileMoverInterface'
|
||||||
|
import { S3FileMover } from '../Infra/S3/S3FileMover'
|
||||||
|
import { FSFileMover } from '../Infra/FS/FSFileMover'
|
||||||
|
import { MoveFile } from '../Domain/UseCase/MoveFile/MoveFile'
|
||||||
|
import { SharedVaultValetTokenAuthMiddleware } from '../Infra/InversifyExpress/Middleware/SharedVaultValetTokenAuthMiddleware'
|
||||||
|
|
||||||
export class ContainerConfigLoader {
|
export class ContainerConfigLoader {
|
||||||
async load(configuration?: {
|
async load(configuration?: {
|
||||||
@@ -177,6 +182,7 @@ export class ContainerConfigLoader {
|
|||||||
container.bind<FileDownloaderInterface>(TYPES.Files_FileDownloader).to(S3FileDownloader)
|
container.bind<FileDownloaderInterface>(TYPES.Files_FileDownloader).to(S3FileDownloader)
|
||||||
container.bind<FileUploaderInterface>(TYPES.Files_FileUploader).to(S3FileUploader)
|
container.bind<FileUploaderInterface>(TYPES.Files_FileUploader).to(S3FileUploader)
|
||||||
container.bind<FileRemoverInterface>(TYPES.Files_FileRemover).to(S3FileRemover)
|
container.bind<FileRemoverInterface>(TYPES.Files_FileRemover).to(S3FileRemover)
|
||||||
|
container.bind<FileMoverInterface>(TYPES.Files_FileMover).to(S3FileMover)
|
||||||
} else {
|
} else {
|
||||||
container.bind<FileDownloaderInterface>(TYPES.Files_FileDownloader).to(FSFileDownloader)
|
container.bind<FileDownloaderInterface>(TYPES.Files_FileDownloader).to(FSFileDownloader)
|
||||||
container
|
container
|
||||||
@@ -185,6 +191,7 @@ export class ContainerConfigLoader {
|
|||||||
new FSFileUploader(container.get(TYPES.Files_FILE_UPLOAD_PATH), container.get(TYPES.Files_Logger)),
|
new FSFileUploader(container.get(TYPES.Files_FILE_UPLOAD_PATH), container.get(TYPES.Files_Logger)),
|
||||||
)
|
)
|
||||||
container.bind<FileRemoverInterface>(TYPES.Files_FileRemover).to(FSFileRemover)
|
container.bind<FileRemoverInterface>(TYPES.Files_FileRemover).to(FSFileRemover)
|
||||||
|
container.bind<FileMoverInterface>(TYPES.Files_FileMover).to(FSFileMover)
|
||||||
}
|
}
|
||||||
|
|
||||||
// use cases
|
// use cases
|
||||||
@@ -194,10 +201,14 @@ export class ContainerConfigLoader {
|
|||||||
container.bind<FinishUploadSession>(TYPES.Files_FinishUploadSession).to(FinishUploadSession)
|
container.bind<FinishUploadSession>(TYPES.Files_FinishUploadSession).to(FinishUploadSession)
|
||||||
container.bind<GetFileMetadata>(TYPES.Files_GetFileMetadata).to(GetFileMetadata)
|
container.bind<GetFileMetadata>(TYPES.Files_GetFileMetadata).to(GetFileMetadata)
|
||||||
container.bind<RemoveFile>(TYPES.Files_RemoveFile).to(RemoveFile)
|
container.bind<RemoveFile>(TYPES.Files_RemoveFile).to(RemoveFile)
|
||||||
|
container.bind<MoveFile>(TYPES.Files_MoveFile).to(MoveFile)
|
||||||
container.bind<MarkFilesToBeRemoved>(TYPES.Files_MarkFilesToBeRemoved).to(MarkFilesToBeRemoved)
|
container.bind<MarkFilesToBeRemoved>(TYPES.Files_MarkFilesToBeRemoved).to(MarkFilesToBeRemoved)
|
||||||
|
|
||||||
// middleware
|
// middleware
|
||||||
container.bind<ValetTokenAuthMiddleware>(TYPES.Files_ValetTokenAuthMiddleware).to(ValetTokenAuthMiddleware)
|
container.bind<ValetTokenAuthMiddleware>(TYPES.Files_ValetTokenAuthMiddleware).to(ValetTokenAuthMiddleware)
|
||||||
|
container
|
||||||
|
.bind<SharedVaultValetTokenAuthMiddleware>(TYPES.Files_SharedVaultValetTokenAuthMiddleware)
|
||||||
|
.to(SharedVaultValetTokenAuthMiddleware)
|
||||||
|
|
||||||
// services
|
// services
|
||||||
container
|
container
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const TYPES = {
|
|||||||
Files_FinishUploadSession: Symbol.for('Files_FinishUploadSession'),
|
Files_FinishUploadSession: Symbol.for('Files_FinishUploadSession'),
|
||||||
Files_GetFileMetadata: Symbol.for('Files_GetFileMetadata'),
|
Files_GetFileMetadata: Symbol.for('Files_GetFileMetadata'),
|
||||||
Files_RemoveFile: Symbol.for('Files_RemoveFile'),
|
Files_RemoveFile: Symbol.for('Files_RemoveFile'),
|
||||||
|
Files_MoveFile: Symbol.for('Files_MoveFile'),
|
||||||
Files_MarkFilesToBeRemoved: Symbol.for('Files_MarkFilesToBeRemoved'),
|
Files_MarkFilesToBeRemoved: Symbol.for('Files_MarkFilesToBeRemoved'),
|
||||||
|
|
||||||
// services
|
// services
|
||||||
@@ -23,12 +24,14 @@ const TYPES = {
|
|||||||
Files_FileUploader: Symbol.for('Files_FileUploader'),
|
Files_FileUploader: Symbol.for('Files_FileUploader'),
|
||||||
Files_FileDownloader: Symbol.for('Files_FileDownloader'),
|
Files_FileDownloader: Symbol.for('Files_FileDownloader'),
|
||||||
Files_FileRemover: Symbol.for('Files_FileRemover'),
|
Files_FileRemover: Symbol.for('Files_FileRemover'),
|
||||||
|
Files_FileMover: Symbol.for('Files_FileMover'),
|
||||||
|
|
||||||
// repositories
|
// repositories
|
||||||
Files_UploadRepository: Symbol.for('Files_UploadRepository'),
|
Files_UploadRepository: Symbol.for('Files_UploadRepository'),
|
||||||
|
|
||||||
// middleware
|
// middleware
|
||||||
Files_ValetTokenAuthMiddleware: Symbol.for('Files_ValetTokenAuthMiddleware'),
|
Files_ValetTokenAuthMiddleware: Symbol.for('Files_ValetTokenAuthMiddleware'),
|
||||||
|
Files_SharedVaultValetTokenAuthMiddleware: Symbol.for('Files_SharedVaultValetTokenAuthMiddleware'),
|
||||||
|
|
||||||
// env vars
|
// env vars
|
||||||
Files_S3_ENDPOINT: Symbol.for('Files_S3_ENDPOINT'),
|
Files_S3_ENDPOINT: Symbol.for('Files_S3_ENDPOINT'),
|
||||||
|
|||||||
@@ -14,6 +14,60 @@ describe('DomainEventFactory', () => {
|
|||||||
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1))
|
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should create a SHARED_VAULT_FILE_UPLOADED event', () => {
|
||||||
|
expect(
|
||||||
|
createFactory().createSharedVaultFileUploadedEvent({
|
||||||
|
sharedVaultUuid: '1-2-3',
|
||||||
|
filePath: 'foo/bar',
|
||||||
|
fileName: 'baz',
|
||||||
|
fileByteSize: 123,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
createdAt: new Date(1),
|
||||||
|
meta: {
|
||||||
|
correlation: {
|
||||||
|
userIdentifier: '1-2-3',
|
||||||
|
userIdentifierType: 'shared-vault-uuid',
|
||||||
|
},
|
||||||
|
origin: 'files',
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
sharedVaultUuid: '1-2-3',
|
||||||
|
filePath: 'foo/bar',
|
||||||
|
fileName: 'baz',
|
||||||
|
fileByteSize: 123,
|
||||||
|
},
|
||||||
|
type: 'SHARED_VAULT_FILE_UPLOADED',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create a SHARED_VAULT_FILE_REMOVED event', () => {
|
||||||
|
expect(
|
||||||
|
createFactory().createSharedVaultFileRemovedEvent({
|
||||||
|
sharedVaultUuid: '1-2-3',
|
||||||
|
filePath: 'foo/bar',
|
||||||
|
fileName: 'baz',
|
||||||
|
fileByteSize: 123,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
createdAt: new Date(1),
|
||||||
|
meta: {
|
||||||
|
correlation: {
|
||||||
|
userIdentifier: '1-2-3',
|
||||||
|
userIdentifierType: 'shared-vault-uuid',
|
||||||
|
},
|
||||||
|
origin: 'files',
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
sharedVaultUuid: '1-2-3',
|
||||||
|
filePath: 'foo/bar',
|
||||||
|
fileName: 'baz',
|
||||||
|
fileByteSize: 123,
|
||||||
|
},
|
||||||
|
type: 'SHARED_VAULT_FILE_REMOVED',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should create a FILE_UPLOADED event', () => {
|
it('should create a FILE_UPLOADED event', () => {
|
||||||
expect(
|
expect(
|
||||||
createFactory().createFileUploadedEvent({
|
createFactory().createFileUploadedEvent({
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { FileUploadedEvent, FileRemovedEvent, DomainEventService } from '@standardnotes/domain-events'
|
import {
|
||||||
|
FileUploadedEvent,
|
||||||
|
FileRemovedEvent,
|
||||||
|
DomainEventService,
|
||||||
|
SharedVaultFileUploadedEvent,
|
||||||
|
SharedVaultFileRemovedEvent,
|
||||||
|
} from '@standardnotes/domain-events'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
|
||||||
@@ -49,4 +55,44 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
|||||||
payload,
|
payload,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createSharedVaultFileUploadedEvent(payload: {
|
||||||
|
sharedVaultUuid: string
|
||||||
|
filePath: string
|
||||||
|
fileName: string
|
||||||
|
fileByteSize: number
|
||||||
|
}): SharedVaultFileUploadedEvent {
|
||||||
|
return {
|
||||||
|
type: 'SHARED_VAULT_FILE_UPLOADED',
|
||||||
|
createdAt: this.timer.getUTCDate(),
|
||||||
|
meta: {
|
||||||
|
correlation: {
|
||||||
|
userIdentifier: payload.sharedVaultUuid,
|
||||||
|
userIdentifierType: 'shared-vault-uuid',
|
||||||
|
},
|
||||||
|
origin: DomainEventService.Files,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createSharedVaultFileRemovedEvent(payload: {
|
||||||
|
sharedVaultUuid: string
|
||||||
|
filePath: string
|
||||||
|
fileName: string
|
||||||
|
fileByteSize: number
|
||||||
|
}): SharedVaultFileRemovedEvent {
|
||||||
|
return {
|
||||||
|
type: 'SHARED_VAULT_FILE_REMOVED',
|
||||||
|
createdAt: this.timer.getUTCDate(),
|
||||||
|
meta: {
|
||||||
|
correlation: {
|
||||||
|
userIdentifier: payload.sharedVaultUuid,
|
||||||
|
userIdentifierType: 'shared-vault-uuid',
|
||||||
|
},
|
||||||
|
origin: DomainEventService.Files,
|
||||||
|
},
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { FileUploadedEvent, FileRemovedEvent } from '@standardnotes/domain-events'
|
import {
|
||||||
|
FileUploadedEvent,
|
||||||
|
FileRemovedEvent,
|
||||||
|
SharedVaultFileRemovedEvent,
|
||||||
|
SharedVaultFileUploadedEvent,
|
||||||
|
} from '@standardnotes/domain-events'
|
||||||
|
|
||||||
export interface DomainEventFactoryInterface {
|
export interface DomainEventFactoryInterface {
|
||||||
createFileUploadedEvent(payload: {
|
createFileUploadedEvent(payload: {
|
||||||
@@ -14,4 +19,16 @@ export interface DomainEventFactoryInterface {
|
|||||||
fileByteSize: number
|
fileByteSize: number
|
||||||
regularSubscriptionUuid: string
|
regularSubscriptionUuid: string
|
||||||
}): FileRemovedEvent
|
}): FileRemovedEvent
|
||||||
|
createSharedVaultFileUploadedEvent(payload: {
|
||||||
|
sharedVaultUuid: string
|
||||||
|
filePath: string
|
||||||
|
fileName: string
|
||||||
|
fileByteSize: number
|
||||||
|
}): SharedVaultFileUploadedEvent
|
||||||
|
createSharedVaultFileRemovedEvent(payload: {
|
||||||
|
sharedVaultUuid: string
|
||||||
|
filePath: string
|
||||||
|
fileName: string
|
||||||
|
fileByteSize: number
|
||||||
|
}): SharedVaultFileRemovedEvent
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ describe('AccountDeletionRequestedEventHandler', () => {
|
|||||||
it('should mark files to be remove for user', async () => {
|
it('should mark files to be remove for user', async () => {
|
||||||
await createHandler().handle(event)
|
await createHandler().handle(event)
|
||||||
|
|
||||||
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ userUuid: '1-2-3' })
|
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@@ -66,7 +66,7 @@ describe('AccountDeletionRequestedEventHandler', () => {
|
|||||||
|
|
||||||
await createHandler().handle(event)
|
await createHandler().handle(event)
|
||||||
|
|
||||||
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ userUuid: '1-2-3' })
|
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
|||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.markFilesToBeRemoved.execute({
|
const response = await this.markFilesToBeRemoved.execute({
|
||||||
userUuid: event.payload.userUuid,
|
ownerUuid: event.payload.userUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.success) {
|
if (!response.success) {
|
||||||
|
|||||||
+2
-2
@@ -44,7 +44,7 @@ describe('SharedSubscriptionInvitationCanceledEventHandler', () => {
|
|||||||
it('should mark files to be remove for user', async () => {
|
it('should mark files to be remove for user', async () => {
|
||||||
await createHandler().handle(event)
|
await createHandler().handle(event)
|
||||||
|
|
||||||
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ userUuid: '1-2-3' })
|
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@@ -66,7 +66,7 @@ describe('SharedSubscriptionInvitationCanceledEventHandler', () => {
|
|||||||
|
|
||||||
await createHandler().handle(event)
|
await createHandler().handle(event)
|
||||||
|
|
||||||
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ userUuid: '1-2-3' })
|
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ export class SharedSubscriptionInvitationCanceledEventHandler implements DomainE
|
|||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.markFilesToBeRemoved.execute({
|
const response = await this.markFilesToBeRemoved.execute({
|
||||||
userUuid: event.payload.inviteeIdentifier,
|
ownerUuid: event.payload.inviteeIdentifier,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.success) {
|
if (!response.success) {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface FileMoverInterface {
|
||||||
|
moveFile(sourcePath: string, destinationPath: string): Promise<void>
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ describe('CreateUploadSession', () => {
|
|||||||
expect(
|
expect(
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
resourceRemoteIdentifier: '2-3-4',
|
resourceRemoteIdentifier: '2-3-4',
|
||||||
userUuid: '1-2-3',
|
ownerUuid: '1-2-3',
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
success: false,
|
success: false,
|
||||||
@@ -44,7 +44,7 @@ describe('CreateUploadSession', () => {
|
|||||||
it('should create an upload session', async () => {
|
it('should create an upload session', async () => {
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
resourceRemoteIdentifier: '2-3-4',
|
resourceRemoteIdentifier: '2-3-4',
|
||||||
userUuid: '1-2-3',
|
ownerUuid: '1-2-3',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(fileUploader.createUploadSession).toHaveBeenCalledWith('1-2-3/2-3-4')
|
expect(fileUploader.createUploadSession).toHaveBeenCalledWith('1-2-3/2-3-4')
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class CreateUploadSession implements UseCaseInterface {
|
|||||||
try {
|
try {
|
||||||
this.logger.debug(`Creating upload session for resource: ${dto.resourceRemoteIdentifier}`)
|
this.logger.debug(`Creating upload session for resource: ${dto.resourceRemoteIdentifier}`)
|
||||||
|
|
||||||
const filePath = `${dto.userUuid}/${dto.resourceRemoteIdentifier}`
|
const filePath = `${dto.ownerUuid}/${dto.resourceRemoteIdentifier}`
|
||||||
|
|
||||||
const uploadId = await this.fileUploader.createUploadSession(filePath)
|
const uploadId = await this.fileUploader.createUploadSession(filePath)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type CreateUploadSessionDTO = {
|
export type CreateUploadSessionDTO = {
|
||||||
userUuid: string
|
ownerUuid: string
|
||||||
resourceRemoteIdentifier: string
|
resourceRemoteIdentifier: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { DomainEventPublisherInterface, FileUploadedEvent } from '@standardnotes/domain-events'
|
import {
|
||||||
|
DomainEventPublisherInterface,
|
||||||
|
FileUploadedEvent,
|
||||||
|
SharedVaultFileUploadedEvent,
|
||||||
|
} from '@standardnotes/domain-events'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||||
import { FileUploaderInterface } from '../../Services/FileUploaderInterface'
|
import { FileUploaderInterface } from '../../Services/FileUploaderInterface'
|
||||||
@@ -31,6 +35,9 @@ describe('FinishUploadSession', () => {
|
|||||||
|
|
||||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||||
domainEventFactory.createFileUploadedEvent = jest.fn().mockReturnValue({} as jest.Mocked<FileUploadedEvent>)
|
domainEventFactory.createFileUploadedEvent = jest.fn().mockReturnValue({} as jest.Mocked<FileUploadedEvent>)
|
||||||
|
domainEventFactory.createSharedVaultFileUploadedEvent = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue({} as jest.Mocked<SharedVaultFileUploadedEvent>)
|
||||||
|
|
||||||
logger = {} as jest.Mocked<Logger>
|
logger = {} as jest.Mocked<Logger>
|
||||||
logger.debug = jest.fn()
|
logger.debug = jest.fn()
|
||||||
@@ -43,7 +50,8 @@ describe('FinishUploadSession', () => {
|
|||||||
|
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
resourceRemoteIdentifier: '2-3-4',
|
resourceRemoteIdentifier: '2-3-4',
|
||||||
userUuid: '1-2-3',
|
ownerUuid: '1-2-3',
|
||||||
|
ownerType: 'user',
|
||||||
uploadBytesLimit: 100,
|
uploadBytesLimit: 100,
|
||||||
uploadBytesUsed: 0,
|
uploadBytesUsed: 0,
|
||||||
})
|
})
|
||||||
@@ -60,7 +68,8 @@ describe('FinishUploadSession', () => {
|
|||||||
expect(
|
expect(
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
resourceRemoteIdentifier: '2-3-4',
|
resourceRemoteIdentifier: '2-3-4',
|
||||||
userUuid: '1-2-3',
|
ownerUuid: '1-2-3',
|
||||||
|
ownerType: 'user',
|
||||||
uploadBytesLimit: 100,
|
uploadBytesLimit: 100,
|
||||||
uploadBytesUsed: 0,
|
uploadBytesUsed: 0,
|
||||||
}),
|
}),
|
||||||
@@ -76,7 +85,23 @@ describe('FinishUploadSession', () => {
|
|||||||
it('should finish an upload session', async () => {
|
it('should finish an upload session', async () => {
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
resourceRemoteIdentifier: '2-3-4',
|
resourceRemoteIdentifier: '2-3-4',
|
||||||
userUuid: '1-2-3',
|
ownerUuid: '1-2-3',
|
||||||
|
ownerType: 'user',
|
||||||
|
uploadBytesLimit: 100,
|
||||||
|
uploadBytesUsed: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(fileUploader.finishUploadSession).toHaveBeenCalledWith('123', '1-2-3/2-3-4', [
|
||||||
|
{ tag: '123', chunkId: 1, chunkSize: 1 },
|
||||||
|
])
|
||||||
|
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should finish an upload session for a vault shared file', async () => {
|
||||||
|
await createUseCase().execute({
|
||||||
|
resourceRemoteIdentifier: '2-3-4',
|
||||||
|
ownerUuid: '1-2-3',
|
||||||
|
ownerType: 'shared-vault',
|
||||||
uploadBytesLimit: 100,
|
uploadBytesLimit: 100,
|
||||||
uploadBytesUsed: 0,
|
uploadBytesUsed: 0,
|
||||||
})
|
})
|
||||||
@@ -97,7 +122,8 @@ describe('FinishUploadSession', () => {
|
|||||||
expect(
|
expect(
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
resourceRemoteIdentifier: '2-3-4',
|
resourceRemoteIdentifier: '2-3-4',
|
||||||
userUuid: '1-2-3',
|
ownerUuid: '1-2-3',
|
||||||
|
ownerType: 'user',
|
||||||
uploadBytesLimit: 100,
|
uploadBytesLimit: 100,
|
||||||
uploadBytesUsed: 20,
|
uploadBytesUsed: 20,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class FinishUploadSession implements UseCaseInterface {
|
|||||||
try {
|
try {
|
||||||
this.logger.debug(`Finishing upload session for resource: ${dto.resourceRemoteIdentifier}`)
|
this.logger.debug(`Finishing upload session for resource: ${dto.resourceRemoteIdentifier}`)
|
||||||
|
|
||||||
const filePath = `${dto.userUuid}/${dto.resourceRemoteIdentifier}`
|
const filePath = `${dto.ownerUuid}/${dto.resourceRemoteIdentifier}`
|
||||||
|
|
||||||
const uploadId = await this.uploadRepository.retrieveUploadSessionId(filePath)
|
const uploadId = await this.uploadRepository.retrieveUploadSessionId(filePath)
|
||||||
if (uploadId === undefined) {
|
if (uploadId === undefined) {
|
||||||
@@ -53,14 +53,25 @@ export class FinishUploadSession implements UseCaseInterface {
|
|||||||
|
|
||||||
await this.fileUploader.finishUploadSession(uploadId, filePath, uploadChunkResults)
|
await this.fileUploader.finishUploadSession(uploadId, filePath, uploadChunkResults)
|
||||||
|
|
||||||
await this.domainEventPublisher.publish(
|
if (dto.ownerType === 'user') {
|
||||||
this.domainEventFactory.createFileUploadedEvent({
|
await this.domainEventPublisher.publish(
|
||||||
userUuid: dto.userUuid,
|
this.domainEventFactory.createFileUploadedEvent({
|
||||||
filePath: `${dto.userUuid}/${dto.resourceRemoteIdentifier}`,
|
userUuid: dto.ownerUuid,
|
||||||
fileName: dto.resourceRemoteIdentifier,
|
filePath: `${dto.ownerUuid}/${dto.resourceRemoteIdentifier}`,
|
||||||
fileByteSize: totalFileSize,
|
fileName: dto.resourceRemoteIdentifier,
|
||||||
}),
|
fileByteSize: totalFileSize,
|
||||||
)
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
await this.domainEventPublisher.publish(
|
||||||
|
this.domainEventFactory.createSharedVaultFileUploadedEvent({
|
||||||
|
sharedVaultUuid: dto.ownerUuid,
|
||||||
|
filePath: `${dto.ownerUuid}/${dto.resourceRemoteIdentifier}`,
|
||||||
|
fileName: dto.resourceRemoteIdentifier,
|
||||||
|
fileByteSize: totalFileSize,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export type FinishUploadSessionDTO = {
|
export type FinishUploadSessionDTO = {
|
||||||
userUuid: string
|
ownerUuid: string
|
||||||
|
ownerType: 'user' | 'shared-vault'
|
||||||
resourceRemoteIdentifier: string
|
resourceRemoteIdentifier: string
|
||||||
uploadBytesUsed: number
|
uploadBytesUsed: number
|
||||||
uploadBytesLimit: number
|
uploadBytesLimit: number
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ describe('GetFileMetadata', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should return the file metadata', async () => {
|
it('should return the file metadata', async () => {
|
||||||
expect(await createUseCase().execute({ resourceRemoteIdentifier: '1-2-3', userUuid: '2-3-4' })).toEqual({
|
expect(await createUseCase().execute({ resourceRemoteIdentifier: '1-2-3', ownerUuid: '2-3-4' })).toEqual({
|
||||||
success: true,
|
success: true,
|
||||||
size: 123,
|
size: 123,
|
||||||
})
|
})
|
||||||
@@ -30,7 +30,7 @@ describe('GetFileMetadata', () => {
|
|||||||
throw new Error('ooops')
|
throw new Error('ooops')
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(await createUseCase().execute({ resourceRemoteIdentifier: '1-2-3', userUuid: '2-3-4' })).toEqual({
|
expect(await createUseCase().execute({ resourceRemoteIdentifier: '1-2-3', ownerUuid: '2-3-4' })).toEqual({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Could not get file metadata.',
|
message: 'Could not get file metadata.',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ export class GetFileMetadata implements UseCaseInterface {
|
|||||||
|
|
||||||
async execute(dto: GetFileMetadataDTO): Promise<GetFileMetadataResponse> {
|
async execute(dto: GetFileMetadataDTO): Promise<GetFileMetadataResponse> {
|
||||||
try {
|
try {
|
||||||
const size = await this.fileDownloader.getFileSize(`${dto.userUuid}/${dto.resourceRemoteIdentifier}`)
|
const size = await this.fileDownloader.getFileSize(`${dto.ownerUuid}/${dto.resourceRemoteIdentifier}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
size,
|
size,
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Could not get file metadata for resource: ${dto.userUuid}/${dto.resourceRemoteIdentifier}`)
|
this.logger.error(`Could not get file metadata for resource: ${dto.ownerUuid}/${dto.resourceRemoteIdentifier}`)
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Could not get file metadata.',
|
message: 'Could not get file metadata.',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type GetFileMetadataDTO = {
|
export type GetFileMetadataDTO = {
|
||||||
userUuid: string
|
ownerUuid: string
|
||||||
resourceRemoteIdentifier: string
|
resourceRemoteIdentifier: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ describe('MarkFilesToBeRemoved', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should mark files for being removed', async () => {
|
it('should mark files for being removed', async () => {
|
||||||
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({ success: true })
|
expect(await createUseCase().execute({ ownerUuid: '1-2-3' })).toEqual({ success: true })
|
||||||
|
|
||||||
expect(fileRemover.markFilesToBeRemoved).toHaveBeenCalledWith('1-2-3')
|
expect(fileRemover.markFilesToBeRemoved).toHaveBeenCalledWith('1-2-3')
|
||||||
})
|
})
|
||||||
@@ -31,7 +31,7 @@ describe('MarkFilesToBeRemoved', () => {
|
|||||||
throw new Error('Oops')
|
throw new Error('Oops')
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
|
expect(await createUseCase().execute({ ownerUuid: '1-2-3' })).toEqual({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Could not mark resources for removal',
|
message: 'Could not mark resources for removal',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,16 +16,16 @@ export class MarkFilesToBeRemoved implements UseCaseInterface {
|
|||||||
|
|
||||||
async execute(dto: MarkFilesToBeRemovedDTO): Promise<MarkFilesToBeRemovedResponse> {
|
async execute(dto: MarkFilesToBeRemovedDTO): Promise<MarkFilesToBeRemovedResponse> {
|
||||||
try {
|
try {
|
||||||
this.logger.debug(`Marking files for later removal for user: ${dto.userUuid}`)
|
this.logger.debug(`Marking files for later removal for user: ${dto.ownerUuid}`)
|
||||||
|
|
||||||
const filesRemoved = await this.fileRemover.markFilesToBeRemoved(dto.userUuid)
|
const filesRemoved = await this.fileRemover.markFilesToBeRemoved(dto.ownerUuid)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
filesRemoved,
|
filesRemoved,
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Could not mark resources for removal: ${dto.userUuid} - ${(error as Error).message}`)
|
this.logger.error(`Could not mark resources for removal: ${dto.ownerUuid} - ${(error as Error).message}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export type MarkFilesToBeRemovedDTO = {
|
export type MarkFilesToBeRemovedDTO = {
|
||||||
userUuid: string
|
ownerUuid: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import 'reflect-metadata'
|
||||||
|
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
import { MoveFile } from './MoveFile'
|
||||||
|
import { FileMoverInterface } from '../../Services/FileMoverInterface'
|
||||||
|
|
||||||
|
describe('MoveFile', () => {
|
||||||
|
let fileMover: FileMoverInterface
|
||||||
|
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
|
const createUseCase = () => new MoveFile(fileMover, logger)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fileMover = {} as jest.Mocked<FileMoverInterface>
|
||||||
|
fileMover.moveFile = jest.fn().mockReturnValue(413)
|
||||||
|
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.debug = jest.fn()
|
||||||
|
logger.error = jest.fn()
|
||||||
|
logger.warn = jest.fn()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should move a file', async () => {
|
||||||
|
await createUseCase().execute({
|
||||||
|
resourceRemoteIdentifier: '2-3-4',
|
||||||
|
fromUuid: '1-2-3',
|
||||||
|
toUuid: '4-5-6',
|
||||||
|
moveType: 'shared-vault-to-user',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(fileMover.moveFile).toHaveBeenCalledWith('1-2-3/2-3-4', '4-5-6/2-3-4')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should indicate an error if moving fails', async () => {
|
||||||
|
fileMover.moveFile = jest.fn().mockImplementation(() => {
|
||||||
|
throw new Error('oops')
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await createUseCase().execute({
|
||||||
|
resourceRemoteIdentifier: '2-3-4',
|
||||||
|
fromUuid: '1-2-3',
|
||||||
|
toUuid: '4-5-6',
|
||||||
|
moveType: 'shared-vault-to-user',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
|
import { FileMoverInterface } from '../../Services/FileMoverInterface'
|
||||||
|
import { MoveFileDTO } from './MoveFileDTO'
|
||||||
|
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class MoveFile implements UseCaseInterface<boolean> {
|
||||||
|
constructor(
|
||||||
|
@inject(TYPES.Files_FileMover) private fileMover: FileMoverInterface,
|
||||||
|
@inject(TYPES.Files_Logger) private logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(dto: MoveFileDTO): Promise<Result<boolean>> {
|
||||||
|
try {
|
||||||
|
const srcPath = `${dto.fromUuid}/${dto.resourceRemoteIdentifier}`
|
||||||
|
const destPath = `${dto.toUuid}/${dto.resourceRemoteIdentifier}`
|
||||||
|
|
||||||
|
this.logger.debug(`Moving file from ${srcPath} to ${destPath}`)
|
||||||
|
|
||||||
|
await this.fileMover.moveFile(srcPath, destPath)
|
||||||
|
|
||||||
|
return Result.ok()
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Could not move resource: ${dto.resourceRemoteIdentifier} - ${(error as Error).message}`)
|
||||||
|
|
||||||
|
return Result.fail('Could not move resource')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user