Compare commits

..

55 Commits

Author SHA1 Message Date
standardci 4f70fa156d chore(release): publish new version
- @standardnotes/analytics@2.24.5
 - @standardnotes/api-gateway@1.65.2
 - @standardnotes/auth-server@1.120.2
 - @standardnotes/domain-core@1.19.0
 - @standardnotes/event-store@1.11.2
 - @standardnotes/files-server@1.19.2
 - @standardnotes/home-server@1.11.17
 - @standardnotes/revisions-server@1.23.6
 - @standardnotes/scheduler-server@1.20.4
 - @standardnotes/settings@1.21.9
 - @standardnotes/syncing-server@1.45.0
 - @standardnotes/websockets-server@1.9.5
2023-06-30 11:00:32 +00:00
Karol Sójko 38e77f04be feat: add shared vaults model. (#631)
Co-authored-by: Mo <mo@standardnotes.com>
2023-06-30 12:44:27 +02:00
standardci 060206ddd4 chore(release): publish new version
- @standardnotes/analytics@2.24.4
 - @standardnotes/api-gateway@1.65.1
 - @standardnotes/auth-server@1.120.1
 - @standardnotes/domain-events-infra@1.12.7
 - @standardnotes/domain-events@2.112.1
 - @standardnotes/event-store@1.11.1
 - @standardnotes/files-server@1.19.1
 - @standardnotes/home-server@1.11.16
 - @standardnotes/revisions-server@1.23.5
 - @standardnotes/scheduler-server@1.20.3
 - @standardnotes/security@1.8.1
 - @standardnotes/syncing-server@1.44.6
 - @standardnotes/websockets-server@1.9.4
2023-06-30 10:24:00 +00:00
Mo 0bc0909386 chore: types lint (#630) 2023-06-30 05:07:47 -05:00
standardci 667d528a8c chore(release): publish new version
- @standardnotes/analytics@2.24.3
 - @standardnotes/api-gateway@1.65.0
 - @standardnotes/auth-server@1.120.0
 - @standardnotes/common@1.49.0
 - @standardnotes/domain-events-infra@1.12.6
 - @standardnotes/domain-events@2.112.0
 - @standardnotes/event-store@1.11.0
 - @standardnotes/files-server@1.19.0
 - @standardnotes/home-server@1.11.15
 - @standardnotes/revisions-server@1.23.4
 - @standardnotes/scheduler-server@1.20.2
 - @standardnotes/security@1.8.0
 - @standardnotes/syncing-server@1.44.5
 - @standardnotes/websockets-server@1.9.3
2023-06-30 09:47:02 +00:00
Karol Sójko fa7fbe26e7 feat: shared vaults functionality in api-gateway,auth,files,common,security,domain-events. (#629)
Co-authored-by: Mo <mo@standardnotes.com>
2023-06-30 11:31:25 +02:00
standardci ba422a29d0 chore(release): publish new version
- @standardnotes/auth-server@1.119.6
 - @standardnotes/home-server@1.11.14
2023-06-28 16:11:23 +00:00
Karol Sójko d220ec5bf7 fix(auth): add debug logs for authentication method resolver 2023-06-28 17:56:59 +02:00
standardci 7baf5492bc chore(release): publish new version
- @standardnotes/api-gateway@1.64.3
 - @standardnotes/auth-server@1.119.5
 - @standardnotes/home-server@1.11.13
 - @standardnotes/syncing-server@1.44.4
2023-06-28 12:45:50 +00:00
Karol Sójko d5a8409bb5 fix: add debug logs for invalid-auth responses 2023-06-28 14:30:39 +02:00
standardci f58f90667c chore(release): publish new version
- @standardnotes/analytics@2.24.2
 - @standardnotes/auth-server@1.119.4
 - @standardnotes/common@1.48.3
 - @standardnotes/home-server@1.11.12
 - @standardnotes/revisions-server@1.23.3
 - @standardnotes/syncing-server@1.44.3
 - @standardnotes/websockets-server@1.9.2
2023-06-28 11:17:34 +00:00
Mo a388e1a802 chore: add new content types 2023-06-28 06:02:18 -05:00
standardci 8811d10a73 chore(release): publish new version
- @standardnotes/home-server@1.11.11
2023-06-22 12:10:36 +00:00
Karol Sójko c7a394cd1a fix(home-server): destroy winston loggers upon server shutdown 2023-06-22 13:56:05 +02:00
standardci b7615a7f2e chore(release): publish new version
- @standardnotes/home-server@1.11.10
2023-06-22 11:30:12 +00:00
Karol Sójko 1ca70c1e50 fix(home-server): pass the log stream callback before loggers are created 2023-06-22 13:15:33 +02:00
standardci 253cbb1d0c chore(release): publish new version
- @standardnotes/home-server@1.11.9
2023-06-22 10:52:11 +00:00
Karol Sójko e38a16404c fix(home-server): listening on log stream 2023-06-22 12:37:19 +02:00
Karol Sójko f17a1f875c fix(home-server): passthrough stream for loggers 2023-06-22 12:33:23 +02:00
standardci 2237e0f5df chore(release): publish new version
- @standardnotes/api-gateway@1.64.2
 - @standardnotes/auth-server@1.119.3
 - @standardnotes/files-server@1.18.3
 - @standardnotes/home-server@1.11.8
 - @standardnotes/revisions-server@1.23.2
 - @standardnotes/syncing-server@1.44.2
2023-06-22 10:05:28 +00:00
Karol Sójko 0df471585f fix(home-server): add debug logs about container initalizations 2023-06-22 11:50:59 +02:00
standardci 95aac1a7bf chore(release): publish new version
- @standardnotes/home-server@1.11.7
2023-06-22 08:23:45 +00:00
Karol Sójko c078bc958d fix(home-server): add default log level to overrides 2023-06-22 10:06:54 +02:00
Karol Sójko 49c27924ea fix(home-server): add log leve information to starting the home server information 2023-06-22 09:08:37 +02:00
standardci c9dd8e7338 chore(release): publish new version
- @standardnotes/home-server@1.11.6
2023-06-16 09:44:09 +00:00
Karol Sójko 5ef90cc75b fix(home-server): unref the server instance when stopping the home server 2023-06-16 11:30:04 +02:00
standardci 063c61d96c chore(release): publish new version
- @standardnotes/auth-server@1.119.2
 - @standardnotes/home-server@1.11.5
 - @standardnotes/revisions-server@1.23.1
 - @standardnotes/syncing-server@1.44.1
2023-06-14 06:19:56 +00:00
Karol Sójko 0cb5e36b20 fix(home-server): env var determining the sqlite database location (#626) 2023-06-14 08:05:48 +02:00
standardci 319bab5b34 chore(release): publish new version
- @standardnotes/home-server@1.11.4
2023-06-13 12:19:56 +00:00
Karol Sójko 90a4f2111f fix(home-server): encapsulate starting the server with a result return 2023-06-13 14:03:39 +02:00
standardci 3aba202970 chore(release): publish new version
- @standardnotes/files-server@1.18.2
 - @standardnotes/home-server@1.11.3
2023-06-12 10:09:28 +00:00
Karol Sójko c8974b7fa2 fix(home-server): accept application/octet-stream requests for files (#625)
* fix(home-server): accept application/octet-stream requests for files

* fix(files): check for empty chunks
2023-06-12 11:55:18 +02:00
standardci 3654a19586 chore(release): publish new version
- @standardnotes/files-server@1.18.1
 - @standardnotes/home-server@1.11.2
2023-06-09 11:51:44 +00:00
Karol Sójko 5f0929c1aa fix(files): add debug logs for checking chunks upon finishing upload session 2023-06-09 13:37:30 +02:00
standardci c0fa83bce6 chore(release): publish new version
- @standardnotes/api-gateway@1.64.1
 - @standardnotes/auth-server@1.119.1
 - @standardnotes/home-server@1.11.1
2023-06-09 11:14:28 +00:00
Karol Sójko c201ee42a0 fix(home-server): add default value for valet token ttl 2023-06-09 12:56:57 +02:00
Karol Sójko e6a4cc3098 fix(api-gateway): direct call service proxy to return 400 responses instead of throwing errors 2023-06-09 12:52:58 +02:00
standardci 39f2fe2ba1 chore(release): publish new version
- @standardnotes/auth-server@1.119.0
 - @standardnotes/home-server@1.11.0
2023-06-09 06:13:41 +00:00
Karol Sójko 72ce190996 feat(home-server): add activating premium features (#624) 2023-06-09 07:59:07 +02:00
standardci 527dd1b61b chore(release): publish new version
- @standardnotes/auth-server@1.118.0
 - @standardnotes/home-server@1.10.0
 - @standardnotes/revisions-server@1.23.0
 - @standardnotes/syncing-server@1.44.0
2023-06-07 08:53:34 +00:00
Karol Sójko af8feaadfe feat: configurable path for uploads and db (#623)
* fix(home-server): passing the database path

* fix(home-server): passing the file upload path
2023-06-07 10:39:33 +02:00
standardci 3164f76662 chore(release): publish new version
- @standardnotes/api-gateway@1.64.0
 - @standardnotes/auth-server@1.117.0
 - @standardnotes/files-server@1.18.0
 - @standardnotes/home-server@1.9.0
 - @standardnotes/revisions-server@1.22.0
 - @standardnotes/syncing-server@1.43.0
2023-06-05 10:01:28 +00:00
Karol Sójko d6e531d4b6 feat(home-server): allow running the home server with a mysql and redis configuration (#622)
* feat(home-server): allow running the home server with a mysql and redis configuration

* fix(files): config for file uploader

* fix(files): if condition on fs file uploader bootstrap
2023-06-05 11:47:10 +02:00
standardci af76878dad chore(release): publish new version
- @standardnotes/analytics@2.24.1
 - @standardnotes/api-gateway@1.63.2
 - @standardnotes/auth-server@1.116.2
 - @standardnotes/event-store@1.10.1
 - @standardnotes/files-server@1.17.1
 - @standardnotes/home-server@1.8.5
 - @standardnotes/revisions-server@1.21.3
 - @standardnotes/scheduler-server@1.20.1
 - @standardnotes/syncing-server@1.42.2
 - @standardnotes/websockets-server@1.9.1
2023-06-02 12:30:06 +00:00
Karol Sójko 28cce39fe7 fix(home-server): linter issues 2023-06-02 14:15:39 +02:00
Karol Sójko a8b806af08 fix(home-server): streaming logs 2023-06-02 14:10:20 +02:00
standardci fa0b0294b4 chore(release): publish new version
- @standardnotes/home-server@1.8.4
2023-06-02 11:41:59 +00:00
Karol Sójko 58ab410b0a fix(home-server): remove redundant restart method 2023-06-02 13:27:45 +02:00
standardci 51c8b20506 chore(release): publish new version
- @standardnotes/api-gateway@1.63.1
 - @standardnotes/home-server@1.8.3
 - @standardnotes/revisions-server@1.21.2
2023-06-02 11:26:26 +00:00
Karol Sójko 1e62a3760e fix(home-server): displaying the port on which it is running 2023-06-02 13:08:23 +02:00
Karol Sójko 2f569d4104 fix(home-server): add default for VERSION environment variable 2023-06-02 12:52:51 +02:00
standardci f23e444ed0 chore(release): publish new version
- @standardnotes/home-server@1.8.2
2023-06-02 10:43:32 +00:00
Karol Sójko e6e9a32f03 fix(home-server): default configuration variables 2023-06-02 12:29:24 +02:00
standardci 8237df33a7 chore(release): publish new version
- @standardnotes/auth-server@1.116.1
 - @standardnotes/home-server@1.8.1
 - @standardnotes/revisions-server@1.21.1
 - @standardnotes/syncing-server@1.42.1
2023-06-02 09:37:30 +00:00
Karol Sójko 624b574013 fix: initializing data source with already configured environment 2023-06-02 11:20:34 +02:00
162 changed files with 2611 additions and 708 deletions
+1
View File
@@ -4,6 +4,7 @@ DB_USERNAME=std_notes_user
DB_PASSWORD=changeme123 DB_PASSWORD=changeme123
DB_DATABASE=standard_notes_db DB_DATABASE=standard_notes_db
DB_PORT=3306 DB_PORT=3306
DB_SQLITE_DATABASE_PATH=standard_notes_db
REDIS_PORT=6379 REDIS_PORT=6379
REDIS_HOST=cache REDIS_HOST=cache
AUTH_SERVER_ACCESS_TOKEN_AGE=4 AUTH_SERVER_ACCESS_TOKEN_AGE=4
+25
View File
@@ -54,6 +54,11 @@ jobs:
e2e-home-server: e2e-home-server:
name: (Home Server) E2E Test Suite name: (Home Server) E2E Test Suite
strategy:
matrix:
db_type: [mysql, sqlite]
cache_type: [redis, memory]
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
@@ -61,6 +66,19 @@ jobs:
image: standardnotes/snjs:${{ inputs.snjs_image_tag }} image: standardnotes/snjs:${{ inputs.snjs_image_tag }}
ports: ports:
- 9001:9001 - 9001:9001
cache:
image: redis
ports:
- 6379:6379
db:
image: mysql
ports:
- 3306:3306
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: standardnotes
MYSQL_USER: standardnotes
MYSQL_PASSWORD: standardnotes
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -90,6 +108,13 @@ jobs:
echo "ACCESS_TOKEN_AGE=4" >> packages/home-server/.env echo "ACCESS_TOKEN_AGE=4" >> packages/home-server/.env
echo "REFRESH_TOKEN_AGE=7" >> packages/home-server/.env echo "REFRESH_TOKEN_AGE=7" >> packages/home-server/.env
echo "REVISIONS_FREQUENCY=5" >> packages/home-server/.env echo "REVISIONS_FREQUENCY=5" >> packages/home-server/.env
echo "DB_HOST=db" >> packages/home-server/.env
echo "DB_PORT=3306" >> packages/home-server/.env
echo "DB_USERNAME=standardnotes" >> packages/home-server/.env
echo "DB_PASSWORD=standardnotes" >> packages/home-server/.env
echo "DB_TYPE=${{ matrix.db_type }}" >> packages/home-server/.env
echo "REDIS_URL=redis://cache" >> packages/home-server/.env
echo "CACHE_TYPE=${{ matrix.cache_type }}" >> packages/home-server/.env
- name: Run Server - name: Run Server
run: nohup yarn workspace @standardnotes/home-server start & run: nohup yarn workspace @standardnotes/home-server start &
Generated
+3
View File
@@ -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"],\
+22
View File
@@ -3,6 +3,28 @@
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.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)
**Note:** Version bump only for package @standardnotes/analytics
## [2.24.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.0...@standardnotes/analytics@2.24.1) (2023-06-02)
### Bug Fixes
* **home-server:** streaming logs ([a8b806a](https://github.com/standardnotes/server/commit/a8b806af084b3e3fe8707ff0cb041a74042ee049))
# [2.24.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.23.7...@standardnotes/analytics@2.24.0) (2023-06-02) # [2.24.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.23.7...@standardnotes/analytics@2.24.0) (2023-06-02)
### Features ### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/analytics", "name": "@standardnotes/analytics",
"version": "2.24.0", "version": "2.24.5",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -88,9 +88,9 @@ export class ContainerConfigLoader {
} }
const logger = winston.createLogger({ const logger = winston.createLogger({
level: env.get('LOG_LEVEL') || 'info', level: env.get('LOG_LEVEL', true) || 'info',
format: winston.format.combine(...winstonFormatters), format: winston.format.combine(...winstonFormatters),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL') || 'info' })], transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL', true) || 'info' })],
}) })
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger) container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
+1
View File
@@ -1,3 +1,4 @@
MODE=microservice # microservice | home-server
LOG_LEVEL=debug LOG_LEVEL=debug
NODE_ENV=development NODE_ENV=development
VERSION=development VERSION=development
+50
View File
@@ -3,6 +3,56 @@
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.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)
### Bug Fixes
* **home-server:** add debug logs about container initalizations ([0df4715](https://github.com/standardnotes/api-gateway/commit/0df471585fd5b4626ec2972f3b9a3e33b2830e65))
## [1.64.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.64.0...@standardnotes/api-gateway@1.64.1) (2023-06-09)
### Bug Fixes
* **api-gateway:** direct call service proxy to return 400 responses instead of throwing errors ([e6a4cc3](https://github.com/standardnotes/api-gateway/commit/e6a4cc3098bdf84fc9d48ed0d9098ebb52afb0e7))
# [1.64.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.63.2...@standardnotes/api-gateway@1.64.0) (2023-06-05)
### Features
* **home-server:** allow running the home server with a mysql and redis configuration ([#622](https://github.com/standardnotes/api-gateway/issues/622)) ([d6e531d](https://github.com/standardnotes/api-gateway/commit/d6e531d4b6c1c80a894f6d7ec93632595268dd64))
## [1.63.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.63.1...@standardnotes/api-gateway@1.63.2) (2023-06-02)
### Bug Fixes
* **home-server:** streaming logs ([a8b806a](https://github.com/standardnotes/api-gateway/commit/a8b806af084b3e3fe8707ff0cb041a74042ee049))
## [1.63.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.63.0...@standardnotes/api-gateway@1.63.1) (2023-06-02)
### Bug Fixes
* **home-server:** add default for VERSION environment variable ([2f569d4](https://github.com/standardnotes/api-gateway/commit/2f569d41047a802eb72ef1a3618ffe4df28a709c))
# [1.63.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.62.4...@standardnotes/api-gateway@1.63.0) (2023-06-02) # [1.63.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.62.4...@standardnotes/api-gateway@1.63.0) (2023-06-02)
### Features ### Features
+25 -22
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/api-gateway", "name": "@standardnotes/api-gateway",
"version": "1.63.0", "version": "1.65.2",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -34,7 +34,8 @@ export class ContainerConfigLoader {
const container = new Container() const container = new Container()
const isConfiguredForHomeServer = env.get('CACHE_TYPE') === 'memory' const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
const winstonFormatters = [winston.format.splat(), winston.format.json()] const winstonFormatters = [winston.format.splat(), winston.format.json()]
if (env.get('NEW_RELIC_ENABLED', true) === 'true') { if (env.get('NEW_RELIC_ENABLED', true) === 'true') {
@@ -45,19 +46,20 @@ export class ContainerConfigLoader {
winstonFormatters.push(newrelicWinstonFormatter()) winstonFormatters.push(newrelicWinstonFormatter())
} }
let logger: winston.Logger
if (configuration?.logger) { if (configuration?.logger) {
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(configuration.logger as winston.Logger) logger = configuration.logger as winston.Logger
} else { } else {
const logger = winston.createLogger({ logger = winston.createLogger({
level: env.get('LOG_LEVEL') || 'info', level: env.get('LOG_LEVEL', true) || 'info',
format: winston.format.combine(...winstonFormatters), format: winston.format.combine(...winstonFormatters),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL') || 'info' })], transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL', true) || 'info' })],
defaultMeta: { service: 'api-gateway' }, defaultMeta: { service: 'api-gateway' },
}) })
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
} }
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
if (!isConfiguredForHomeServer) { if (!isConfiguredForInMemoryCache) {
const redisUrl = env.get('REDIS_URL') const redisUrl = env.get('REDIS_URL')
const isRedisInClusterMode = redisUrl.indexOf(',') > 0 const isRedisInClusterMode = redisUrl.indexOf(',') > 0
let redis let redis
@@ -83,7 +85,7 @@ export class ContainerConfigLoader {
container container
.bind(TYPES.HTTP_CALL_TIMEOUT) .bind(TYPES.HTTP_CALL_TIMEOUT)
.toConstantValue(env.get('HTTP_CALL_TIMEOUT', true) ? +env.get('HTTP_CALL_TIMEOUT', true) : 60_000) .toConstantValue(env.get('HTTP_CALL_TIMEOUT', true) ? +env.get('HTTP_CALL_TIMEOUT', true) : 60_000)
container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION')) container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION', true) ?? 'development')
container.bind(TYPES.CROSS_SERVICE_TOKEN_CACHE_TTL).toConstantValue(+env.get('CROSS_SERVICE_TOKEN_CACHE_TTL', true)) container.bind(TYPES.CROSS_SERVICE_TOKEN_CACHE_TTL).toConstantValue(+env.get('CROSS_SERVICE_TOKEN_CACHE_TTL', true))
// Middleware // Middleware
@@ -124,6 +126,8 @@ export class ContainerConfigLoader {
.bind<EndpointResolverInterface>(TYPES.EndpointResolver) .bind<EndpointResolverInterface>(TYPES.EndpointResolver)
.toConstantValue(new EndpointResolver(isConfiguredForHomeServer)) .toConstantValue(new EndpointResolver(isConfiguredForHomeServer))
logger.debug('Configuration complete')
return container return container
} }
} }
@@ -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)
}
}
@@ -1,7 +1,7 @@
import { Request, Response } from 'express' import { Request, Response } from 'express'
import { ServiceContainerInterface, ServiceIdentifier } from '@standardnotes/domain-core'
import { ServiceProxyInterface } from '../Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../Http/ServiceProxyInterface'
import { ServiceContainerInterface, ServiceIdentifier } from '@standardnotes/domain-core'
export class DirectCallServiceProxy implements ServiceProxyInterface { export class DirectCallServiceProxy implements ServiceProxyInterface {
constructor(private serviceContainer: ServiceContainerInterface, private filesServerUrl: string) {} constructor(private serviceContainer: ServiceContainerInterface, private filesServerUrl: string) {}
@@ -34,8 +34,12 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
} }
} }
async callEmailServer(_request: Request, _response: Response, _endpointOrMethodIdentifier: string): Promise<void> { async callEmailServer(_request: Request, response: Response, _endpointOrMethodIdentifier: string): Promise<void> {
throw new Error('Email server is not available.') response.status(400).send({
error: {
message: 'Email server is not available.',
},
})
} }
async callAuthServer(request: never, response: never, endpointOrMethodIdentifier: string): Promise<void> { async callAuthServer(request: never, response: never, endpointOrMethodIdentifier: string): Promise<void> {
@@ -54,10 +58,14 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
async callAuthServerWithLegacyFormat( async callAuthServerWithLegacyFormat(
_request: Request, _request: Request,
_response: Response, response: Response,
_endpointOrMethodIdentifier: string, _endpointOrMethodIdentifier: string,
): Promise<void> { ): Promise<void> {
throw new Error('Legacy auth endpoints are no longer available.') response.status(400).send({
error: {
message: 'Legacy auth endpoints are no longer available.',
},
})
} }
async callRevisionsServer(request: never, response: never, endpointOrMethodIdentifier: string): Promise<void> { async callRevisionsServer(request: never, response: never, endpointOrMethodIdentifier: string): Promise<void> {
@@ -92,22 +100,30 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
async callLegacySyncingServer( async callLegacySyncingServer(
_request: Request, _request: Request,
_response: Response, response: Response,
_endpointOrMethodIdentifier: string, _endpointOrMethodIdentifier: string,
): Promise<void> { ): Promise<void> {
throw new Error('Legacy syncing server endpoints are no longer available.') response.status(400).send({
error: {
message: 'Legacy syncing server endpoints are no longer available.',
},
})
} }
async callPaymentsServer(_request: Request, _response: Response, _endpointOrMethodIdentifier: string): Promise<void> { async callPaymentsServer(_request: Request, response: Response, _endpointOrMethodIdentifier: string): Promise<void> {
throw new Error('Payments server is not available.') response.status(400).send({
error: {
message: 'Payments server is not available.',
},
})
} }
async callWebSocketServer( async callWebSocketServer(_request: Request, response: Response, _endpointOrMethodIdentifier: string): Promise<void> {
_request: Request, response.status(400).send({
_response: Response, error: {
_endpointOrMethodIdentifier: string, message: 'Websockets server is not available.',
): Promise<void> { },
throw new Error('Websockets server is not available.') })
} }
private sendDecoratedResponse( private sendDecoratedResponse(
+3
View File
@@ -1,3 +1,4 @@
MODE=microservice # microservice | home-server
LOG_LEVEL=debug LOG_LEVEL=debug
NODE_ENV=development NODE_ENV=development
VERSION=development VERSION=development
@@ -20,8 +21,10 @@ DB_USERNAME=auth
DB_PASSWORD=changeme123 DB_PASSWORD=changeme123
DB_DATABASE=auth DB_DATABASE=auth
DB_DEBUG_LEVEL=all # "all" | "query" | "schema" | "error" | "warn" | "info" | "log" | "migration" DB_DEBUG_LEVEL=all # "all" | "query" | "schema" | "error" | "warn" | "info" | "log" | "migration"
DB_TYPE=mysql
REDIS_URL=redis://cache REDIS_URL=redis://cache
CACHE_TYPE=redis
DISABLE_USER_REGISTRATION=false DISABLE_USER_REGISTRATION=false
+78
View File
@@ -3,6 +3,84 @@
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.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)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.119.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.119.2...@standardnotes/auth-server@1.119.3) (2023-06-22)
### Bug Fixes
* **home-server:** add debug logs about container initalizations ([0df4715](https://github.com/standardnotes/server/commit/0df471585fd5b4626ec2972f3b9a3e33b2830e65))
## [1.119.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.119.1...@standardnotes/auth-server@1.119.2) (2023-06-14)
### Bug Fixes
* **home-server:** env var determining the sqlite database location ([#626](https://github.com/standardnotes/server/issues/626)) ([0cb5e36](https://github.com/standardnotes/server/commit/0cb5e36b20d9b095ea0edbcd877387e6c0069856))
## [1.119.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.119.0...@standardnotes/auth-server@1.119.1) (2023-06-09)
### Bug Fixes
* **home-server:** add default value for valet token ttl ([c201ee4](https://github.com/standardnotes/server/commit/c201ee42a00d9e5402afea2f2c5848a362c1529e))
# [1.119.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.118.0...@standardnotes/auth-server@1.119.0) (2023-06-09)
### Features
* **home-server:** add activating premium features ([#624](https://github.com/standardnotes/server/issues/624)) ([72ce190](https://github.com/standardnotes/server/commit/72ce1909960fbd2ec6a47b8dbdfbe53a4f10e776))
# [1.118.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.117.0...@standardnotes/auth-server@1.118.0) (2023-06-07)
### Features
* configurable path for uploads and db ([#623](https://github.com/standardnotes/server/issues/623)) ([af8feaa](https://github.com/standardnotes/server/commit/af8feaadfe2dd58baab4cca217d6307b4a221326))
# [1.117.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.116.2...@standardnotes/auth-server@1.117.0) (2023-06-05)
### Features
* **home-server:** allow running the home server with a mysql and redis configuration ([#622](https://github.com/standardnotes/server/issues/622)) ([d6e531d](https://github.com/standardnotes/server/commit/d6e531d4b6c1c80a894f6d7ec93632595268dd64))
## [1.116.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.116.1...@standardnotes/auth-server@1.116.2) (2023-06-02)
### Bug Fixes
* **home-server:** streaming logs ([a8b806a](https://github.com/standardnotes/server/commit/a8b806af084b3e3fe8707ff0cb041a74042ee049))
## [1.116.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.116.0...@standardnotes/auth-server@1.116.1) (2023-06-02)
### Bug Fixes
* initializing data source with already configured environment ([624b574](https://github.com/standardnotes/server/commit/624b574013157e9e044d4a8ed53cadb7fcc567ae))
# [1.116.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.115.5...@standardnotes/auth-server@1.116.0) (2023-06-02) # [1.116.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.115.5...@standardnotes/auth-server@1.116.0) (2023-06-02)
### Features ### Features
+3 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/auth-server", "name": "@standardnotes/auth-server",
"version": "1.116.0", "version": "1.120.2",
"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",
@@ -0,0 +1,5 @@
import { Result, ServiceInterface } from '@standardnotes/domain-core'
export interface AuthServiceInterface extends ServiceInterface {
activatePremiumFeatures(username: string): Promise<Result<string>>
}
+69 -49
View File
@@ -251,6 +251,7 @@ import { HomeServerValetTokenController } from '../Infra/InversifyExpressUtils/H
import { HomeServerWebSocketsController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerWebSocketsController' import { HomeServerWebSocketsController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerWebSocketsController'
import { HomeServerSessionsController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerSessionsController' import { HomeServerSessionsController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerSessionsController'
import { Transform } from 'stream' import { Transform } from 'stream'
import { ActivatePremiumFeatures } from '../Domain/UseCase/ActivatePremiumFeatures/ActivatePremiumFeatures'
export class ContainerConfigLoader { export class ContainerConfigLoader {
async load(configuration?: { async load(configuration?: {
@@ -267,11 +268,37 @@ export class ContainerConfigLoader {
const container = new Container() const container = new Container()
await AppDataSource.initialize() const winstonFormatters = [winston.format.splat(), winston.format.json()]
if (env.get('NEW_RELIC_ENABLED', true) === 'true') {
await import('newrelic')
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
const newrelicWinstonFormatter = newrelicFormatter(winston)
winstonFormatters.push(newrelicWinstonFormatter())
}
const isConfiguredForHomeServer = env.get('DB_TYPE') === 'sqlite' let logger: winston.Logger
if (configuration?.logger) {
logger = configuration.logger as winston.Logger
} else {
logger = winston.createLogger({
level: env.get('LOG_LEVEL', true) || 'info',
format: winston.format.combine(...winstonFormatters),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL', true) || 'info' })],
defaultMeta: { service: 'auth' },
})
}
container.bind<winston.Logger>(TYPES.Auth_Logger).toConstantValue(logger)
if (!isConfiguredForHomeServer) { const appDataSource = new AppDataSource(env)
await appDataSource.initialize()
logger.debug('Database initialized')
const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
if (!isConfiguredForInMemoryCache) {
const redisUrl = env.get('REDIS_URL') const redisUrl = env.get('REDIS_URL')
const isRedisInClusterMode = redisUrl.indexOf(',') > 0 const isRedisInClusterMode = redisUrl.indexOf(',') > 0
let redis let redis
@@ -284,27 +311,6 @@ export class ContainerConfigLoader {
container.bind(TYPES.Auth_Redis).toConstantValue(redis) container.bind(TYPES.Auth_Redis).toConstantValue(redis)
} }
const winstonFormatters = [winston.format.splat(), winston.format.json()]
if (env.get('NEW_RELIC_ENABLED', true) === 'true') {
await import('newrelic')
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
const newrelicWinstonFormatter = newrelicFormatter(winston)
winstonFormatters.push(newrelicWinstonFormatter())
}
if (configuration?.logger) {
container.bind<winston.Logger>(TYPES.Auth_Logger).toConstantValue(configuration.logger as winston.Logger)
} else {
const logger = winston.createLogger({
level: env.get('LOG_LEVEL') || 'info',
format: winston.format.combine(...winstonFormatters),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL') || 'info' })],
defaultMeta: { service: 'auth' },
})
container.bind<winston.Logger>(TYPES.Auth_Logger).toConstantValue(logger)
}
container.bind<TimerInterface>(TYPES.Auth_Timer).toConstantValue(new Timer()) container.bind<TimerInterface>(TYPES.Auth_Timer).toConstantValue(new Timer())
if (!isConfiguredForHomeServer) { if (!isConfiguredForHomeServer) {
@@ -359,42 +365,42 @@ export class ContainerConfigLoader {
// ORM // ORM
container container
.bind<Repository<OfflineSetting>>(TYPES.Auth_ORMOfflineSettingRepository) .bind<Repository<OfflineSetting>>(TYPES.Auth_ORMOfflineSettingRepository)
.toConstantValue(AppDataSource.getRepository(OfflineSetting)) .toConstantValue(appDataSource.getRepository(OfflineSetting))
container container
.bind<Repository<OfflineUserSubscription>>(TYPES.Auth_ORMOfflineUserSubscriptionRepository) .bind<Repository<OfflineUserSubscription>>(TYPES.Auth_ORMOfflineUserSubscriptionRepository)
.toConstantValue(AppDataSource.getRepository(OfflineUserSubscription)) .toConstantValue(appDataSource.getRepository(OfflineUserSubscription))
container container
.bind<Repository<RevokedSession>>(TYPES.Auth_ORMRevokedSessionRepository) .bind<Repository<RevokedSession>>(TYPES.Auth_ORMRevokedSessionRepository)
.toConstantValue(AppDataSource.getRepository(RevokedSession)) .toConstantValue(appDataSource.getRepository(RevokedSession))
container.bind<Repository<Role>>(TYPES.Auth_ORMRoleRepository).toConstantValue(AppDataSource.getRepository(Role)) container.bind<Repository<Role>>(TYPES.Auth_ORMRoleRepository).toConstantValue(appDataSource.getRepository(Role))
container container
.bind<Repository<Session>>(TYPES.Auth_ORMSessionRepository) .bind<Repository<Session>>(TYPES.Auth_ORMSessionRepository)
.toConstantValue(AppDataSource.getRepository(Session)) .toConstantValue(appDataSource.getRepository(Session))
container container
.bind<Repository<Setting>>(TYPES.Auth_ORMSettingRepository) .bind<Repository<Setting>>(TYPES.Auth_ORMSettingRepository)
.toConstantValue(AppDataSource.getRepository(Setting)) .toConstantValue(appDataSource.getRepository(Setting))
container container
.bind<Repository<SharedSubscriptionInvitation>>(TYPES.Auth_ORMSharedSubscriptionInvitationRepository) .bind<Repository<SharedSubscriptionInvitation>>(TYPES.Auth_ORMSharedSubscriptionInvitationRepository)
.toConstantValue(AppDataSource.getRepository(SharedSubscriptionInvitation)) .toConstantValue(appDataSource.getRepository(SharedSubscriptionInvitation))
container container
.bind<Repository<SubscriptionSetting>>(TYPES.Auth_ORMSubscriptionSettingRepository) .bind<Repository<SubscriptionSetting>>(TYPES.Auth_ORMSubscriptionSettingRepository)
.toConstantValue(AppDataSource.getRepository(SubscriptionSetting)) .toConstantValue(appDataSource.getRepository(SubscriptionSetting))
container.bind<Repository<User>>(TYPES.Auth_ORMUserRepository).toConstantValue(AppDataSource.getRepository(User)) container.bind<Repository<User>>(TYPES.Auth_ORMUserRepository).toConstantValue(appDataSource.getRepository(User))
container container
.bind<Repository<UserSubscription>>(TYPES.Auth_ORMUserSubscriptionRepository) .bind<Repository<UserSubscription>>(TYPES.Auth_ORMUserSubscriptionRepository)
.toConstantValue(AppDataSource.getRepository(UserSubscription)) .toConstantValue(appDataSource.getRepository(UserSubscription))
container container
.bind<Repository<TypeORMSessionTrace>>(TYPES.Auth_ORMSessionTraceRepository) .bind<Repository<TypeORMSessionTrace>>(TYPES.Auth_ORMSessionTraceRepository)
.toConstantValue(AppDataSource.getRepository(TypeORMSessionTrace)) .toConstantValue(appDataSource.getRepository(TypeORMSessionTrace))
container container
.bind<Repository<TypeORMAuthenticator>>(TYPES.Auth_ORMAuthenticatorRepository) .bind<Repository<TypeORMAuthenticator>>(TYPES.Auth_ORMAuthenticatorRepository)
.toConstantValue(AppDataSource.getRepository(TypeORMAuthenticator)) .toConstantValue(appDataSource.getRepository(TypeORMAuthenticator))
container container
.bind<Repository<TypeORMAuthenticatorChallenge>>(TYPES.Auth_ORMAuthenticatorChallengeRepository) .bind<Repository<TypeORMAuthenticatorChallenge>>(TYPES.Auth_ORMAuthenticatorChallengeRepository)
.toConstantValue(AppDataSource.getRepository(TypeORMAuthenticatorChallenge)) .toConstantValue(appDataSource.getRepository(TypeORMAuthenticatorChallenge))
container container
.bind<Repository<TypeORMCacheEntry>>(TYPES.Auth_ORMCacheEntryRepository) .bind<Repository<TypeORMCacheEntry>>(TYPES.Auth_ORMCacheEntryRepository)
.toConstantValue(AppDataSource.getRepository(TypeORMCacheEntry)) .toConstantValue(appDataSource.getRepository(TypeORMCacheEntry))
// Repositories // Repositories
container.bind<SessionRepositoryInterface>(TYPES.Auth_SessionRepository).to(TypeORMSessionRepository) container.bind<SessionRepositoryInterface>(TYPES.Auth_SessionRepository).to(TypeORMSessionRepository)
@@ -487,7 +493,9 @@ export class ContainerConfigLoader {
.bind(TYPES.Auth_AUTH_JWT_TTL) .bind(TYPES.Auth_AUTH_JWT_TTL)
.toConstantValue(env.get('AUTH_JWT_TTL', true) ? +env.get('AUTH_JWT_TTL') : 60_000) .toConstantValue(env.get('AUTH_JWT_TTL', true) ? +env.get('AUTH_JWT_TTL') : 60_000)
container.bind(TYPES.Auth_VALET_TOKEN_SECRET).toConstantValue(env.get('VALET_TOKEN_SECRET', true)) container.bind(TYPES.Auth_VALET_TOKEN_SECRET).toConstantValue(env.get('VALET_TOKEN_SECRET', true))
container.bind(TYPES.Auth_VALET_TOKEN_TTL).toConstantValue(+env.get('VALET_TOKEN_TTL', true)) container
.bind(TYPES.Auth_VALET_TOKEN_TTL)
.toConstantValue(env.get('VALET_TOKEN_TTL', true) ? +env.get('VALET_TOKEN_TTL', true) : 7200)
container container
.bind(TYPES.Auth_WEB_SOCKET_CONNECTION_TOKEN_SECRET) .bind(TYPES.Auth_WEB_SOCKET_CONNECTION_TOKEN_SECRET)
.toConstantValue(env.get('WEB_SOCKET_CONNECTION_TOKEN_SECRET', true)) .toConstantValue(env.get('WEB_SOCKET_CONNECTION_TOKEN_SECRET', true))
@@ -549,7 +557,16 @@ export class ContainerConfigLoader {
.bind(TYPES.Auth_READONLY_USERS) .bind(TYPES.Auth_READONLY_USERS)
.toConstantValue(env.get('READONLY_USERS', true) ? env.get('READONLY_USERS', true).split(',') : []) .toConstantValue(env.get('READONLY_USERS', true) ? env.get('READONLY_USERS', true).split(',') : [])
if (isConfiguredForHomeServer) { if (isConfiguredForInMemoryCache) {
container
.bind<PKCERepositoryInterface>(TYPES.Auth_PKCERepository)
.toConstantValue(
new TypeORMPKCERepository(
container.get(TYPES.Auth_CacheEntryRepository),
container.get(TYPES.Auth_Logger),
container.get(TYPES.Auth_Timer),
),
)
container container
.bind<LockRepositoryInterface>(TYPES.Auth_LockRepository) .bind<LockRepositoryInterface>(TYPES.Auth_LockRepository)
.toConstantValue( .toConstantValue(
@@ -577,15 +594,6 @@ export class ContainerConfigLoader {
container.get(TYPES.Auth_Timer), container.get(TYPES.Auth_Timer),
), ),
) )
container
.bind<PKCERepositoryInterface>(TYPES.Auth_PKCERepository)
.toConstantValue(
new TypeORMPKCERepository(
container.get(TYPES.Auth_CacheEntryRepository),
container.get(TYPES.Auth_Logger),
container.get(TYPES.Auth_Timer),
),
)
container container
.bind<SubscriptionTokenRepositoryInterface>(TYPES.Auth_SubscriptionTokenRepository) .bind<SubscriptionTokenRepositoryInterface>(TYPES.Auth_SubscriptionTokenRepository)
.toConstantValue( .toConstantValue(
@@ -779,6 +787,16 @@ export class ContainerConfigLoader {
container.get(TYPES.Auth_CryptoNode), container.get(TYPES.Auth_CryptoNode),
), ),
) )
container
.bind<ActivatePremiumFeatures>(TYPES.Auth_ActivatePremiumFeatures)
.toConstantValue(
new ActivatePremiumFeatures(
container.get(TYPES.Auth_UserRepository),
container.get(TYPES.Auth_UserSubscriptionRepository),
container.get(TYPES.Auth_RoleService),
container.get(TYPES.Auth_Timer),
),
)
container container
.bind<CleanupSessionTraces>(TYPES.Auth_CleanupSessionTraces) .bind<CleanupSessionTraces>(TYPES.Auth_CleanupSessionTraces)
@@ -1186,6 +1204,8 @@ export class ContainerConfigLoader {
) )
} }
logger.debug('Configuration complete')
return container return container
} }
} }
+96 -82
View File
@@ -1,4 +1,4 @@
import { DataSource, LoggerOptions } from 'typeorm' import { DataSource, EntityTarget, LoggerOptions, ObjectLiteral, Repository } from 'typeorm'
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions' import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
import { Permission } from '../Domain/Permission/Permission' import { Permission } from '../Domain/Permission/Permission'
import { Role } from '../Domain/Role/Role' import { Role } from '../Domain/Role/Role'
@@ -19,88 +19,102 @@ 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'
const env: Env = new Env() export class AppDataSource {
env.load() private dataSource: DataSource | undefined
const isConfiguredForMySQL = env.get('DB_TYPE') === 'mysql' constructor(private env: Env) {}
const maxQueryExecutionTime = env.get('DB_MAX_QUERY_EXECUTION_TIME', true) getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
? +env.get('DB_MAX_QUERY_EXECUTION_TIME', true) if (!this.dataSource) {
: 45_000 throw new Error('DataSource not initialized')
}
const commonDataSourceOptions = { return this.dataSource.getRepository(target)
maxQueryExecutionTime, }
entities: [
User, async initialize(): Promise<void> {
UserSubscription, this.env.load()
OfflineUserSubscription,
Session, const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
RevokedSession,
Role, const maxQueryExecutionTime = this.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
Permission, ? +this.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
Setting, : 45_000
OfflineSetting,
SharedSubscriptionInvitation, const commonDataSourceOptions = {
SubscriptionSetting, maxQueryExecutionTime,
TypeORMSessionTrace, entities: [
TypeORMAuthenticator, User,
TypeORMAuthenticatorChallenge, UserSubscription,
TypeORMEmergencyAccessInvitation, OfflineUserSubscription,
TypeORMCacheEntry, Session,
], RevokedSession,
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`], Role,
migrationsRun: true, Permission,
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL', true) ?? 'info', Setting,
OfflineSetting,
SharedSubscriptionInvitation,
SubscriptionSetting,
TypeORMSessionTrace,
TypeORMAuthenticator,
TypeORMAuthenticatorChallenge,
TypeORMEmergencyAccessInvitation,
TypeORMCacheEntry,
],
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
migrationsRun: true,
logging: <LoggerOptions>this.env.get('DB_DEBUG_LEVEL', true) ?? 'info',
}
if (isConfiguredForMySQL) {
const inReplicaMode = this.env.get('DB_REPLICA_HOST', true) ? true : false
const replicationConfig = {
master: {
host: this.env.get('DB_HOST'),
port: parseInt(this.env.get('DB_PORT')),
username: this.env.get('DB_USERNAME'),
password: this.env.get('DB_PASSWORD'),
database: this.env.get('DB_DATABASE'),
},
slaves: [
{
host: this.env.get('DB_REPLICA_HOST', true),
port: parseInt(this.env.get('DB_PORT')),
username: this.env.get('DB_USERNAME'),
password: this.env.get('DB_PASSWORD'),
database: this.env.get('DB_DATABASE'),
},
],
removeNodeErrorCount: 10,
restoreNodeTimeout: 5,
}
const mySQLDataSourceOptions: MysqlConnectionOptions = {
...commonDataSourceOptions,
type: 'mysql',
charset: 'utf8mb4',
supportBigNumbers: true,
bigNumberStrings: false,
replication: inReplicaMode ? replicationConfig : undefined,
host: inReplicaMode ? undefined : this.env.get('DB_HOST'),
port: inReplicaMode ? undefined : parseInt(this.env.get('DB_PORT')),
username: inReplicaMode ? undefined : this.env.get('DB_USERNAME'),
password: inReplicaMode ? undefined : this.env.get('DB_PASSWORD'),
database: inReplicaMode ? undefined : this.env.get('DB_DATABASE'),
}
this.dataSource = new DataSource(mySQLDataSourceOptions)
} else {
const sqliteDataSourceOptions: SqliteConnectionOptions = {
...commonDataSourceOptions,
type: 'sqlite',
database: this.env.get('DB_SQLITE_DATABASE_PATH'),
}
this.dataSource = new DataSource(sqliteDataSourceOptions)
}
await this.dataSource.initialize()
}
} }
let dataSource: DataSource
if (isConfiguredForMySQL) {
const inReplicaMode = env.get('DB_REPLICA_HOST', true) ? true : false
const replicationConfig = {
master: {
host: env.get('DB_HOST'),
port: parseInt(env.get('DB_PORT')),
username: env.get('DB_USERNAME'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
slaves: [
{
host: env.get('DB_REPLICA_HOST', true),
port: parseInt(env.get('DB_PORT')),
username: env.get('DB_USERNAME'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
],
removeNodeErrorCount: 10,
restoreNodeTimeout: 5,
}
const mySQLDataSourceOptions: MysqlConnectionOptions = {
...commonDataSourceOptions,
type: 'mysql',
charset: 'utf8mb4',
supportBigNumbers: true,
bigNumberStrings: false,
replication: inReplicaMode ? replicationConfig : undefined,
host: inReplicaMode ? undefined : env.get('DB_HOST'),
port: inReplicaMode ? undefined : parseInt(env.get('DB_PORT')),
username: inReplicaMode ? undefined : env.get('DB_USERNAME'),
password: inReplicaMode ? undefined : env.get('DB_PASSWORD'),
database: inReplicaMode ? undefined : env.get('DB_DATABASE'),
}
dataSource = new DataSource(mySQLDataSourceOptions)
} else {
const sqliteDataSourceOptions: SqliteConnectionOptions = {
...commonDataSourceOptions,
type: 'sqlite',
database: `data/${env.get('DB_DATABASE')}.sqlite`,
}
dataSource = new DataSource(sqliteDataSourceOptions)
}
export const AppDataSource = dataSource
+23 -3
View File
@@ -1,15 +1,21 @@
import { import {
ControllerContainerInterface, ControllerContainerInterface,
Result,
ServiceConfiguration, ServiceConfiguration,
ServiceContainerInterface, ServiceContainerInterface,
ServiceIdentifier, ServiceIdentifier,
ServiceInterface,
} from '@standardnotes/domain-core' } from '@standardnotes/domain-core'
import { ContainerConfigLoader } from './Container' import { ContainerConfigLoader } from './Container'
import { DirectCallDomainEventPublisher } from '@standardnotes/domain-events-infra' import { DirectCallDomainEventPublisher } from '@standardnotes/domain-events-infra'
import TYPES from './Types'
import { Container } from 'inversify'
import { ActivatePremiumFeatures } from '../Domain/UseCase/ActivatePremiumFeatures/ActivatePremiumFeatures'
import { AuthServiceInterface } from './AuthServiceInterface'
export class Service implements AuthServiceInterface {
private container: Container | undefined
export class Service implements ServiceInterface {
constructor( constructor(
private serviceContainer: ServiceContainerInterface, private serviceContainer: ServiceContainerInterface,
private controllerContainer: ControllerContainerInterface, private controllerContainer: ControllerContainerInterface,
@@ -18,6 +24,16 @@ export class Service implements ServiceInterface {
this.serviceContainer.register(this.getId(), this) this.serviceContainer.register(this.getId(), this)
} }
async activatePremiumFeatures(username: string): Promise<Result<string>> {
if (!this.container) {
return Result.fail('Container not initialized')
}
const activatePremiumFeatures = this.container.get(TYPES.Auth_ActivatePremiumFeatures) as ActivatePremiumFeatures
return activatePremiumFeatures.execute({ username })
}
async handleRequest(request: never, response: never, endpointOrMethodIdentifier: string): Promise<unknown> { async handleRequest(request: never, response: never, endpointOrMethodIdentifier: string): Promise<unknown> {
const method = this.controllerContainer.get(endpointOrMethodIdentifier) const method = this.controllerContainer.get(endpointOrMethodIdentifier)
@@ -31,12 +47,16 @@ export class Service implements ServiceInterface {
async getContainer(configuration?: ServiceConfiguration): Promise<unknown> { async getContainer(configuration?: ServiceConfiguration): Promise<unknown> {
const config = new ContainerConfigLoader() const config = new ContainerConfigLoader()
return config.load({ const container = await config.load({
controllerConatiner: this.controllerContainer, controllerConatiner: this.controllerContainer,
directCallDomainEventPublisher: this.directCallDomainEventPublisher, directCallDomainEventPublisher: this.directCallDomainEventPublisher,
logger: configuration?.logger, logger: configuration?.logger,
environmentOverrides: configuration?.environmentOverrides, environmentOverrides: configuration?.environmentOverrides,
}) })
this.container = container
return container
} }
getId(): ServiceIdentifier { getId(): ServiceIdentifier {
+1
View File
@@ -149,6 +149,7 @@ const TYPES = {
Auth_ListAuthenticators: Symbol.for('Auth_ListAuthenticators'), Auth_ListAuthenticators: Symbol.for('Auth_ListAuthenticators'),
Auth_DeleteAuthenticator: Symbol.for('Auth_DeleteAuthenticator'), Auth_DeleteAuthenticator: Symbol.for('Auth_DeleteAuthenticator'),
Auth_GenerateRecoveryCodes: Symbol.for('Auth_GenerateRecoveryCodes'), Auth_GenerateRecoveryCodes: Symbol.for('Auth_GenerateRecoveryCodes'),
Auth_ActivatePremiumFeatures: Symbol.for('Auth_ActivatePremiumFeatures'),
Auth_SignInWithRecoveryCodes: Symbol.for('Auth_SignInWithRecoveryCodes'), Auth_SignInWithRecoveryCodes: Symbol.for('Auth_SignInWithRecoveryCodes'),
Auth_GetUserKeyParamsRecovery: Symbol.for('Auth_GetUserKeyParamsRecovery'), Auth_GetUserKeyParamsRecovery: Symbol.for('Auth_GetUserKeyParamsRecovery'),
// Handlers // Handlers
+1
View File
@@ -1 +1,2 @@
export * from './AuthServiceInterface'
export * from './Service' export * from './Service'
@@ -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
} }
} }
@@ -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
@@ -0,0 +1,67 @@
import { TimerInterface } from '@standardnotes/time'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { ActivatePremiumFeatures } from './ActivatePremiumFeatures'
import { User } from '../../User/User'
describe('ActivatePremiumFeatures', () => {
let userRepository: UserRepositoryInterface
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
let roleService: RoleServiceInterface
let timer: TimerInterface
let user: User
const createUseCase = () =>
new ActivatePremiumFeatures(userRepository, userSubscriptionRepository, roleService, timer)
beforeEach(() => {
user = {} as jest.Mocked<User>
userRepository = {} as jest.Mocked<UserRepositoryInterface>
userRepository.findOneByUsernameOrEmail = jest.fn().mockResolvedValue(user)
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
userSubscriptionRepository.save = jest.fn()
roleService = {} as jest.Mocked<RoleServiceInterface>
roleService.addUserRole = jest.fn()
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123456789)
timer.convertDateToMicroseconds = jest.fn().mockReturnValue(123456789)
timer.getUTCDateNDaysAhead = jest.fn().mockReturnValue(new Date('2024-01-01T00:00:00.000Z'))
})
it('should return error when username is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({ username: '' })
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Username cannot be empty')
})
it('should return error when user is not found', async () => {
userRepository.findOneByUsernameOrEmail = jest.fn().mockResolvedValue(null)
const useCase = createUseCase()
const result = await useCase.execute({ username: 'test@test.te' })
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('User not found with username: test@test.te')
})
it('should save a subscription and add role to user', async () => {
const useCase = createUseCase()
const result = await useCase.execute({ username: 'test@test.te' })
expect(result.isFailed()).toBe(false)
expect(userSubscriptionRepository.save).toHaveBeenCalled()
expect(roleService.addUserRole).toHaveBeenCalled()
})
})
@@ -0,0 +1,49 @@
import { Result, SubscriptionPlanName, UseCaseInterface, Username } from '@standardnotes/domain-core'
import { TimerInterface } from '@standardnotes/time'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { UserSubscription } from '../../Subscription/UserSubscription'
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
import { ActivatePremiumFeaturesDTO } from './ActivatePremiumFeaturesDTO'
export class ActivatePremiumFeatures implements UseCaseInterface<string> {
constructor(
private userRepository: UserRepositoryInterface,
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
private roleService: RoleServiceInterface,
private timer: TimerInterface,
) {}
async execute(dto: ActivatePremiumFeaturesDTO): Promise<Result<string>> {
const usernameOrError = Username.create(dto.username)
if (usernameOrError.isFailed()) {
return Result.fail(usernameOrError.getError())
}
const username = usernameOrError.getValue()
const user = await this.userRepository.findOneByUsernameOrEmail(username)
if (user === null) {
return Result.fail(`User not found with username: ${username.value}`)
}
const timestamp = this.timer.getTimestampInMicroseconds()
const subscription = new UserSubscription()
subscription.planName = SubscriptionPlanName.NAMES.ProPlan
subscription.user = Promise.resolve(user)
subscription.createdAt = timestamp
subscription.updatedAt = timestamp
subscription.endsAt = this.timer.convertDateToMicroseconds(this.timer.getUTCDateNDaysAhead(365))
subscription.cancelled = false
subscription.subscriptionId = 1
subscription.subscriptionType = UserSubscriptionType.Regular
await this.userSubscriptionRepository.save(subscription)
await this.roleService.addUserRole(user, SubscriptionPlanName.NAMES.ProPlan)
return Result.ok('Premium features activated.')
}
}
@@ -0,0 +1,3 @@
export interface ActivatePremiumFeaturesDTO {
username: string
}
@@ -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 {
@@ -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),
}) })
}) })
+5 -13
View File
@@ -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
} }
+2 -2
View File
@@ -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()
}) })
}) })
+1 -1
View File
@@ -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) {
@@ -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,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,
+10
View File
@@ -3,6 +3,16 @@
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)
**Note:** Version bump only for package @standardnotes/common
## [1.48.2](https://github.com/standardnotes/server/compare/@standardnotes/common@1.48.0...@standardnotes/common@1.48.2) (2023-05-31) ## [1.48.2](https://github.com/standardnotes/server/compare/@standardnotes/common@1.48.0...@standardnotes/common@1.48.2) (2023-05-31)
### Bug Fixes ### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/common", "name": "@standardnotes/common",
"version": "1.48.2", "version": "1.49.0",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -2,6 +2,10 @@
export enum ContentType { export enum ContentType {
Any = '*', Any = '*',
Item = 'SF|Item', Item = 'SF|Item',
KeySystemItemsKey = 'SN|KeySystemItemsKey',
KeySystemRootKey = 'SN|KeySystemRootKey',
TrustedContact = 'SN|TrustedContact',
VaultListing = 'SN|VaultListing',
RootKey = 'SN|RootKey|NoSync', RootKey = 'SN|RootKey|NoSync',
ItemsKey = 'SN|ItemsKey', ItemsKey = 'SN|ItemsKey',
EncryptedStorage = 'SN|EncryptedStorage', EncryptedStorage = 'SN|EncryptedStorage',
@@ -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
+6
View File
@@ -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.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 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/domain-core", "name": "@standardnotes/domain-core",
"version": "1.18.0", "version": "1.19.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
}
+2
View File
@@ -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'
@@ -3,6 +3,14 @@
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.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 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/domain-events-infra", "name": "@standardnotes/domain-events-infra",
"version": "1.12.5", "version": "1.12.7",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
+10
View File
@@ -3,6 +3,16 @@
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.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 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/domain-events", "name": "@standardnotes/domain-events",
"version": "2.111.4", "version": "2.112.1",
"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 { 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
}
@@ -62,6 +62,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'
+20
View File
@@ -3,6 +3,26 @@
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.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)
### Bug Fixes
* **home-server:** streaming logs ([a8b806a](https://github.com/standardnotes/server/commit/a8b806af084b3e3fe8707ff0cb041a74042ee049))
# [1.10.0](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.9.6...@standardnotes/event-store@1.10.0) (2023-06-02) # [1.10.0](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.9.6...@standardnotes/event-store@1.10.0) (2023-06-02)
### Features ### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/event-store", "name": "@standardnotes/event-store",
"version": "1.10.0", "version": "1.11.2",
"description": "Event Store Service", "description": "Event Store Service",
"private": true, "private": true,
"main": "dist/src/index.js", "main": "dist/src/index.js",
@@ -49,9 +49,9 @@ export class ContainerConfigLoader {
} }
const logger = winston.createLogger({ const logger = winston.createLogger({
level: env.get('LOG_LEVEL') || 'info', level: env.get('LOG_LEVEL', true) || 'info',
format: winston.format.combine(winston.format.splat(), winston.format.json()), format: winston.format.combine(winston.format.splat(), winston.format.json()),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL') || 'info' })], transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL', true) || 'info' })],
}) })
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger) container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
+1
View File
@@ -1,3 +1,4 @@
MODE=microservice # microservice | home-server
LOG_LEVEL=debug LOG_LEVEL=debug
NODE_ENV=development NODE_ENV=development
VERSION=development VERSION=development
+44
View File
@@ -3,6 +3,50 @@
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.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)
### Bug Fixes
* **home-server:** add debug logs about container initalizations ([0df4715](https://github.com/standardnotes/files/commit/0df471585fd5b4626ec2972f3b9a3e33b2830e65))
## [1.18.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.18.1...@standardnotes/files-server@1.18.2) (2023-06-12)
### Bug Fixes
* **home-server:** accept application/octet-stream requests for files ([#625](https://github.com/standardnotes/files/issues/625)) ([c8974b7](https://github.com/standardnotes/files/commit/c8974b7fa229ff4f1e026e1ebff50d1081cc5f8b))
## [1.18.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.18.0...@standardnotes/files-server@1.18.1) (2023-06-09)
### Bug Fixes
* **files:** add debug logs for checking chunks upon finishing upload session ([5f0929c](https://github.com/standardnotes/files/commit/5f0929c1aa7b83661fb102bad34644db69ddf7eb))
# [1.18.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.17.1...@standardnotes/files-server@1.18.0) (2023-06-05)
### Features
* **home-server:** allow running the home server with a mysql and redis configuration ([#622](https://github.com/standardnotes/files/issues/622)) ([d6e531d](https://github.com/standardnotes/files/commit/d6e531d4b6c1c80a894f6d7ec93632595268dd64))
## [1.17.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.17.0...@standardnotes/files-server@1.17.1) (2023-06-02)
### Bug Fixes
* **home-server:** streaming logs ([a8b806a](https://github.com/standardnotes/files/commit/a8b806af084b3e3fe8707ff0cb041a74042ee049))
# [1.17.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.16.5...@standardnotes/files-server@1.17.0) (2023-06-02) # [1.17.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.16.5...@standardnotes/files-server@1.17.0) (2023-06-02)
### Features ### Features
+1
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/files-server", "name": "@standardnotes/files-server",
"version": "1.17.0", "version": "1.19.2",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
+37 -18
View File
@@ -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?: {
@@ -67,30 +72,24 @@ export class ContainerConfigLoader {
await import('newrelic') await import('newrelic')
} }
const isConfiguredForHomeServer = env.get('CACHE_TYPE') === 'memory' const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
let logger: winston.Logger
if (configuration?.logger) { if (configuration?.logger) {
container.bind<winston.Logger>(TYPES.Files_Logger).toConstantValue(configuration.logger as winston.Logger) logger = configuration.logger as winston.Logger
} else { } else {
container.bind<winston.Logger>(TYPES.Files_Logger).toConstantValue(this.createLogger({ env })) logger = this.createLogger({ env })
} }
container.bind<winston.Logger>(TYPES.Files_Logger).toConstantValue(logger)
container.bind<TimerInterface>(TYPES.Files_Timer).toConstantValue(new Timer()) container.bind<TimerInterface>(TYPES.Files_Timer).toConstantValue(new Timer())
if (isConfiguredForHomeServer) { if (isConfiguredForInMemoryCache) {
container container
.bind<UploadRepositoryInterface>(TYPES.Files_UploadRepository) .bind<UploadRepositoryInterface>(TYPES.Files_UploadRepository)
.toConstantValue(new InMemoryUploadRepository(container.get(TYPES.Files_Timer))) .toConstantValue(new InMemoryUploadRepository(container.get(TYPES.Files_Timer)))
container
.bind<DomainEventPublisherInterface>(TYPES.Files_DomainEventPublisher)
.toConstantValue(directCallDomainEventPublisher)
} else { } else {
container.bind(TYPES.Files_S3_BUCKET_NAME).toConstantValue(env.get('S3_BUCKET_NAME', true))
container.bind(TYPES.Files_S3_AWS_REGION).toConstantValue(env.get('S3_AWS_REGION', true))
container.bind(TYPES.Files_SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN'))
container.bind(TYPES.Files_SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true))
container.bind(TYPES.Files_SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL'))
container.bind(TYPES.Files_REDIS_URL).toConstantValue(env.get('REDIS_URL')) container.bind(TYPES.Files_REDIS_URL).toConstantValue(env.get('REDIS_URL'))
const redisUrl = container.get(TYPES.Files_REDIS_URL) as string const redisUrl = container.get(TYPES.Files_REDIS_URL) as string
@@ -104,6 +103,20 @@ export class ContainerConfigLoader {
container.bind(TYPES.Files_Redis).toConstantValue(redis) container.bind(TYPES.Files_Redis).toConstantValue(redis)
container.bind<UploadRepositoryInterface>(TYPES.Files_UploadRepository).to(RedisUploadRepository)
}
if (isConfiguredForHomeServer) {
container
.bind<DomainEventPublisherInterface>(TYPES.Files_DomainEventPublisher)
.toConstantValue(directCallDomainEventPublisher)
} else {
container.bind(TYPES.Files_S3_BUCKET_NAME).toConstantValue(env.get('S3_BUCKET_NAME', true))
container.bind(TYPES.Files_S3_AWS_REGION).toConstantValue(env.get('S3_AWS_REGION', true))
container.bind(TYPES.Files_SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN'))
container.bind(TYPES.Files_SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true))
container.bind(TYPES.Files_SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL'))
if (env.get('SNS_TOPIC_ARN', true)) { if (env.get('SNS_TOPIC_ARN', true)) {
const snsConfig: SNSClientConfig = { const snsConfig: SNSClientConfig = {
apiVersion: 'latest', apiVersion: 'latest',
@@ -137,8 +150,6 @@ export class ContainerConfigLoader {
container.bind<SQSClient>(TYPES.Files_SQS).toConstantValue(new SQSClient(sqsConfig)) container.bind<SQSClient>(TYPES.Files_SQS).toConstantValue(new SQSClient(sqsConfig))
} }
container.bind<UploadRepositoryInterface>(TYPES.Files_UploadRepository).to(RedisUploadRepository)
container container
.bind<DomainEventPublisherInterface>(TYPES.Files_DomainEventPublisher) .bind<DomainEventPublisherInterface>(TYPES.Files_DomainEventPublisher)
.toConstantValue( .toConstantValue(
@@ -156,7 +167,7 @@ export class ContainerConfigLoader {
.bind(TYPES.Files_FILE_UPLOAD_PATH) .bind(TYPES.Files_FILE_UPLOAD_PATH)
.toConstantValue(env.get('FILE_UPLOAD_PATH', true) ?? `${__dirname}/../../uploads`) .toConstantValue(env.get('FILE_UPLOAD_PATH', true) ?? `${__dirname}/../../uploads`)
if (env.get('S3_AWS_REGION', true) || env.get('S3_ENDPOINT', true)) { if (!isConfiguredForHomeServer && (env.get('S3_AWS_REGION', true) || env.get('S3_ENDPOINT', true))) {
const s3Opts: S3ClientConfig = { const s3Opts: S3ClientConfig = {
apiVersion: 'latest', apiVersion: 'latest',
} }
@@ -171,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
@@ -179,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
@@ -188,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
@@ -245,14 +262,16 @@ export class ContainerConfigLoader {
) )
} }
logger.debug('Configuration complete')
return container return container
} }
createLogger({ env }: { env: Env }): winston.Logger { createLogger({ env }: { env: Env }): winston.Logger {
return winston.createLogger({ return winston.createLogger({
level: env.get('LOG_LEVEL') || 'info', level: env.get('LOG_LEVEL', true) || 'info',
format: winston.format.combine(winston.format.splat(), winston.format.json()), format: winston.format.combine(winston.format.splat(), winston.format.json()),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL') || 'info' })], transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL', true) || 'info' })],
defaultMeta: { service: 'files' }, defaultMeta: { service: 'files' },
}) })
} }
+3
View File
@@ -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) {
@@ -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()
}) })
@@ -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
} }

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