Compare commits

...

55 Commits

Author SHA1 Message Date
standardci
6c95ab8a1c chore(release): publish new version
- @standardnotes/api-gateway@1.89.12
 - @standardnotes/auth-server@1.177.10
 - @standardnotes/home-server@1.22.35
 - @standardnotes/syncing-server@1.129.9
2024-01-02 10:14:36 +00:00
Karol Sójko
5987b0899c fix: adjust grpc keepalive parameters 2024-01-02 10:54:07 +01:00
standardci
664a92132b chore(release): publish new version
- @standardnotes/auth-server@1.177.9
 - @standardnotes/home-server@1.22.34
2023-12-29 10:29:32 +00:00
Karol Sójko
26b13ed6d4 fix(auth): adding user roles upon renewal of shared subscription (#1012)
* fix(auth): adding user roles upon renewal of shared subscription

* feat(auth): add procedure to fix roles on shared subscriptions
2023-12-29 11:07:51 +01:00
standardci
be7c66b145 chore(release): publish new version
- @standardnotes/analytics@2.34.11
 - @standardnotes/api-gateway@1.89.11
 - @standardnotes/auth-server@1.177.8
 - @standardnotes/domain-events-infra@1.22.10
 - @standardnotes/domain-events@2.139.3
 - @standardnotes/files-server@1.37.5
 - @standardnotes/home-server@1.22.33
 - @standardnotes/revisions-server@1.51.11
 - @standardnotes/scheduler-server@1.27.16
 - @standardnotes/syncing-server@1.129.8
 - @standardnotes/websockets-server@1.22.7
2023-12-29 07:16:40 +00:00
Karol Sójko
7e8d7f6874 fix: cleanup revision requests 2023-12-29 07:55:16 +01:00
standardci
c1805ee2ac chore(release): publish new version
- @standardnotes/auth-server@1.177.7
 - @standardnotes/home-server@1.22.32
2023-12-28 15:59:49 +00:00
Karol Sójko
eff1d642cf fix(auth): missing return statement in lock middleware 2023-12-28 16:39:38 +01:00
standardci
a05d7afe00 chore(release): publish new version
- @standardnotes/api-gateway@1.89.10
 - @standardnotes/home-server@1.22.31
2023-12-28 15:13:45 +00:00
Karol Sójko
c2650646d1 fix(api-gateway): log severity 2023-12-28 15:53:26 +01:00
standardci
f73e4adf5a chore(release): publish new version
- @standardnotes/api-gateway@1.89.9
 - @standardnotes/auth-server@1.177.6
 - @standardnotes/grpc@1.4.1
 - @standardnotes/home-server@1.22.30
 - @standardnotes/syncing-server@1.129.7
2023-12-28 14:42:08 +00:00
Karol Sójko
7c67a5a0f9 fix: retry grpc calls upon service unavailable response (#1011)
* fix: retry grpc calls upon service unavailable response

* fix: retry grpc calls for session verification
2023-12-28 15:21:40 +01:00
standardci
6dde9209af chore(release): publish new version
- @standardnotes/analytics@2.34.10
 - @standardnotes/api-gateway@1.89.8
 - @standardnotes/auth-server@1.177.5
 - @standardnotes/domain-events-infra@1.22.9
 - @standardnotes/files-server@1.37.4
 - @standardnotes/home-server@1.22.29
 - @standardnotes/revisions-server@1.51.10
 - @standardnotes/scheduler-server@1.27.15
 - @standardnotes/syncing-server@1.129.6
 - @standardnotes/websockets-server@1.22.6
2023-12-28 13:33:52 +00:00
Karol Sójko
264fd4c36e chore: upgrade node version to 20.10.0 (#1010)
* chore: upgrade node version to 20.10.0

* fix specs

* fix pip system managed packages
2023-12-28 14:13:07 +01:00
standardci
4c0bc82090 chore(release): publish new version
- @standardnotes/analytics@2.34.9
 - @standardnotes/api-gateway@1.89.7
 - @standardnotes/auth-server@1.177.4
 - @standardnotes/common@1.52.2
 - @standardnotes/domain-events-infra@1.22.8
 - @standardnotes/domain-events@2.139.2
 - @standardnotes/files-server@1.37.3
 - @standardnotes/home-server@1.22.28
 - @standardnotes/revisions-server@1.51.9
 - @standardnotes/scheduler-server@1.27.14
 - @standardnotes/security@1.17.3
 - @standardnotes/settings@1.23.2
 - @standardnotes/sncrypto-node@1.16.2
 - @standardnotes/syncing-server@1.129.5
 - @standardnotes/time@1.18.2
 - @standardnotes/websockets-server@1.22.5
2023-12-28 10:26:09 +00:00
Karol Sójko
f3598fa5a1 chore: upgrade reflect-metadata package (#1009) 2023-12-28 11:05:06 +01:00
standardci
8097d7239d chore(release): publish new version
- @standardnotes/analytics@2.34.8
 - @standardnotes/api-gateway@1.89.6
 - @standardnotes/auth-server@1.177.3
 - @standardnotes/domain-events-infra@1.22.7
 - @standardnotes/domain-events@2.139.1
 - @standardnotes/files-server@1.37.2
 - @standardnotes/home-server@1.22.27
 - @standardnotes/revisions-server@1.51.8
 - @standardnotes/scheduler-server@1.27.13
 - @standardnotes/syncing-server@1.129.4
 - @standardnotes/websockets-server@1.22.4
2023-12-26 11:09:32 +00:00
Karol Sójko
7962b245b5 fix: stop revisions propagation (#1008) 2023-12-26 11:40:09 +01:00
standardci
ce0450becf chore(release): publish new version
- @standardnotes/api-gateway@1.89.5
 - @standardnotes/auth-server@1.177.2
 - @standardnotes/home-server@1.22.26
 - @standardnotes/syncing-server@1.129.3
2023-12-22 14:03:05 +00:00
Karol Sójko
0762ed1127 fix: logs severity 2023-12-22 14:42:41 +01:00
standardci
3563a0b64a chore(release): publish new version
- @standardnotes/api-gateway@1.89.4
 - @standardnotes/auth-server@1.177.1
 - @standardnotes/home-server@1.22.25
2023-12-22 11:50:04 +00:00
Karol Sójko
0342cdcfc0 fix(auth): specs issue 2023-12-22 12:19:44 +01:00
Karol Sójko
b1cb6a1d21 fix: case sensitive typo 2023-12-22 12:14:58 +01:00
standardci
615e4184f6 chore(release): publish new version
- @standardnotes/api-gateway@1.89.3
 - @standardnotes/home-server@1.22.24
 - @standardnotes/syncing-server@1.129.2
2023-12-22 10:35:31 +00:00
Karol Sójko
54091f23da fix: add more readonly access debug logs 2023-12-22 11:14:54 +01:00
Karol Sójko
e4a8324db2 fix(api-gateway): add debug log for operating on readonly access 2023-12-22 11:08:46 +01:00
standardci
01bb55be39 chore(release): publish new version
- @standardnotes/api-gateway@1.89.2
 - @standardnotes/home-server@1.22.23
2023-12-21 15:35:54 +00:00
Karol Sójko
e551a364f6 fix(api-gateway): missing readonly access for demo when utilizing grpc workflow (#1005) 2023-12-21 16:14:55 +01:00
standardci
7e65f85377 chore(release): publish new version
- @standardnotes/files-server@1.37.1
 - @standardnotes/home-server@1.22.22
2023-12-14 12:29:00 +00:00
Karol Sójko
e6a21606a3 fix(files): list files command 2023-12-14 13:08:18 +01:00
standardci
f275b48770 chore(release): publish new version
- @standardnotes/analytics@2.34.7
 - @standardnotes/api-gateway@1.89.1
 - @standardnotes/auth-server@1.177.0
 - @standardnotes/domain-events-infra@1.22.6
 - @standardnotes/domain-events@2.139.0
 - @standardnotes/files-server@1.37.0
 - @standardnotes/home-server@1.22.21
 - @standardnotes/revisions-server@1.51.7
 - @standardnotes/scheduler-server@1.27.12
 - @standardnotes/syncing-server@1.129.1
 - @standardnotes/websockets-server@1.22.3
2023-12-14 11:51:47 +00:00
Karol Sójko
de4fcf9a4c feat: add procedure for recalculating file quota for user (#980)
* fix(auth): safe guard file upload bytes used to be positive intiger

* feat: add procedure for recalculating file quota for user

* add more meta to logs
2023-12-14 12:31:19 +01:00
standardci
a1455d281f chore(release): publish new version
- @standardnotes/api-gateway@1.89.0
 - @standardnotes/auth-server@1.176.5
 - @standardnotes/home-server@1.22.20
 - @standardnotes/syncing-server@1.129.0
2023-12-12 12:18:49 +00:00
Karol Sójko
cfbe2bbac6 fix(auth): add user uuid context to sign in emails log context 2023-12-12 12:58:11 +01:00
Karol Sójko
398c10ce4b feat(syncing-server): add extended revisions frequency for free users (#965) 2023-12-12 12:42:31 +01:00
standardci
c7d21b092d chore(release): publish new version
- @standardnotes/auth-server@1.176.4
 - @standardnotes/home-server@1.22.19
2023-12-11 13:20:06 +00:00
Karol Sójko
031fa71e7d fix(auth): generate new recovery codes when enabling mfa (#964) 2023-12-11 13:59:57 +01:00
standardci
948e843ad6 chore(release): publish new version
- @standardnotes/analytics@2.34.6
 - @standardnotes/api-gateway@1.88.4
 - @standardnotes/auth-server@1.176.3
 - @standardnotes/domain-events-infra@1.22.5
 - @standardnotes/domain-events@2.138.2
 - @standardnotes/files-server@1.36.6
 - @standardnotes/home-server@1.22.18
 - @standardnotes/revisions-server@1.51.6
 - @standardnotes/scheduler-server@1.27.11
 - @standardnotes/syncing-server@1.128.2
 - @standardnotes/websockets-server@1.22.2
2023-12-11 11:25:27 +00:00
Karol Sójko
7b0ea0a069 fix(syncing-server): add user uuid for the emails requesting backup 2023-12-11 12:05:16 +01:00
standardci
8887b6e642 chore(release): publish new version
- @standardnotes/auth-server@1.176.2
 - @standardnotes/home-server@1.22.17
 - @standardnotes/syncing-server@1.128.1
2023-12-11 10:15:36 +00:00
Karol Sójko
597ff13393 fix(syncing-server): logs meta for email with backup requested 2023-12-11 10:55:16 +01:00
Karol Sójko
4ab61b94a4 fix(auth): error log meta on triggering email backups 2023-12-11 10:52:17 +01:00
standardci
e19652d62a chore(release): publish new version
- @standardnotes/api-gateway@1.88.3
 - @standardnotes/home-server@1.22.16
2023-12-08 16:15:29 +00:00
Karol Sójko
a341e78909 fix(api-gateway): add extra meta to logs 2023-12-08 16:55:18 +01:00
standardci
48e52ac48c chore(release): publish new version
- @standardnotes/home-server@1.22.15
 - @standardnotes/syncing-server@1.128.0
2023-12-08 12:30:03 +00:00
Karol Sójko
6dbb87708f feat(syncing-server): send websocket event to shared vault members upon items change in shared vault (#961) 2023-12-08 13:09:35 +01:00
standardci
d15d51eae6 chore(release): publish new version
- @standardnotes/api-gateway@1.88.2
 - @standardnotes/home-server@1.22.14
2023-12-07 13:48:28 +00:00
Karol Sójko
0058368681 fix(api-gateway): add userId to logs in error handler if possible 2023-12-07 14:28:12 +01:00
standardci
746c821698 chore(release): publish new version
- @standardnotes/analytics@2.34.5
 - @standardnotes/api-gateway@1.88.1
 - @standardnotes/auth-server@1.176.1
 - @standardnotes/files-server@1.36.5
 - @standardnotes/home-server@1.22.13
 - @standardnotes/revisions-server@1.51.5
 - @standardnotes/scheduler-server@1.27.10
 - @standardnotes/syncing-server@1.127.9
 - @standardnotes/websockets-server@1.22.1
2023-12-07 13:03:23 +00:00
Karol Sójko
3f2d8c902c fix(auth): logger meta on disabling settings 2023-12-07 13:43:21 +01:00
Karol Sójko
3637db2563 fix(api-gateway): response header on grpc websocket connection validation 2023-12-07 13:41:28 +01:00
Karol Sójko
8ac84c59af fix: logs meta 2023-12-07 13:39:30 +01:00
Karol Sójko
a2b1323568 fix: logger meta information 2023-12-07 13:29:36 +01:00
standardci
d35391288d chore(release): publish new version
- @standardnotes/api-gateway@1.88.0
 - @standardnotes/auth-server@1.176.0
 - @standardnotes/grpc@1.4.0
 - @standardnotes/home-server@1.22.12
 - @standardnotes/syncing-server@1.127.8
 - @standardnotes/websockets-server@1.22.0
2023-12-07 12:18:19 +00:00
Karol Sójko
d5c1b76de0 feat: replace websocket connection validation with grpc (#954)
* feat: replace websocket connection validation with grpc

* fix(api-gateway): error logs metadata details

* fix binding

* fix logs severity on websockets

* add user uuid to grpc call error logs
2023-12-07 12:58:07 +01:00
155 changed files with 3025 additions and 226 deletions

2
.nvmrc
View File

@@ -1 +1 @@
20.6.1
20.10.0

51
.pnp.cjs generated
View File

@@ -2190,10 +2190,10 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["@grpc/grpc-js", [\
["npm:1.9.12", {\
"packageLocation": "./.yarn/cache/@grpc-grpc-js-npm-1.9.12-cb97be6754-fe13b04844.zip/node_modules/@grpc/grpc-js/",\
["npm:1.9.13", {\
"packageLocation": "./.yarn/cache/@grpc-grpc-js-npm-1.9.13-33f9b49e10-c52150053c.zip/node_modules/@grpc/grpc-js/",\
"packageDependencies": [\
["@grpc/grpc-js", "npm:1.9.12"],\
["@grpc/grpc-js", "npm:1.9.13"],\
["@grpc/proto-loader", "npm:0.7.10"],\
["@types/node", "npm:20.2.5"]\
],\
@@ -5502,7 +5502,7 @@ const RAW_RUNTIME_STATE =
["mixpanel", "npm:0.17.0"],\
["mysql2", "npm:3.3.3"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.17"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
@@ -5532,7 +5532,7 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./packages/api-gateway/",\
"packageDependencies": [\
["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\
["@grpc/grpc-js", "npm:1.9.12"],\
["@grpc/grpc-js", "npm:1.9.13"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
@@ -5564,7 +5564,7 @@ const RAW_RUNTIME_STATE =
["jsonwebtoken", "npm:9.0.0"],\
["prettier", "npm:3.0.3"],\
["prettyjson", "npm:1.2.5"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
["winston", "npm:3.9.0"]\
@@ -5582,7 +5582,7 @@ const RAW_RUNTIME_STATE =
["@aws-sdk/client-sqs", "npm:3.462.0"],\
["@cbor-extract/cbor-extract-linux-arm64", "npm:2.1.1"],\
["@cbor-extract/cbor-extract-linux-x64", "npm:2.1.1"],\
["@grpc/grpc-js", "npm:1.9.12"],\
["@grpc/grpc-js", "npm:1.9.13"],\
["@simplewebauthn/server", "npm:8.1.1"],\
["@simplewebauthn/typescript-types", "npm:8.0.0"],\
["@standardnotes/api", "npm:1.26.26"],\
@@ -5626,7 +5626,7 @@ const RAW_RUNTIME_STATE =
["otplib", "npm:12.0.1"],\
["prettier", "npm:3.0.3"],\
["prettyjson", "npm:1.2.5"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.17"],\
@@ -5651,7 +5651,7 @@ const RAW_RUNTIME_STATE =
["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.0.0"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"]\
],\
@@ -5730,7 +5730,7 @@ const RAW_RUNTIME_STATE =
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["opentelemetry-instrumentation-typeorm", "virtual:685a6222c3349423674bb7f0684ba34e2ab20912010f352e04dcf707a156e13183fc382e2417cb37a60f3e7b52fd0178c53181674890e1773eb83e190dc13378#npm:0.39.1"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["sqs-consumer", "virtual:685a6222c3349423674bb7f0684ba34e2ab20912010f352e04dcf707a156e13183fc382e2417cb37a60f3e7b52fd0178c53181674890e1773eb83e190dc13378#npm:8.1.0"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
@@ -5795,7 +5795,7 @@ const RAW_RUNTIME_STATE =
["jsonwebtoken", "npm:9.0.0"],\
["prettier", "npm:3.0.3"],\
["prettyjson", "npm:1.2.5"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
["uuid", "npm:9.0.0"],\
@@ -5809,7 +5809,7 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./packages/grpc/",\
"packageDependencies": [\
["@standardnotes/grpc", "workspace:packages/grpc"],\
["@grpc/grpc-js", "npm:1.9.12"],\
["@grpc/grpc-js", "npm:1.9.13"],\
["@types/google-protobuf", "npm:3.15.10"],\
["google-protobuf", "npm:3.21.2"],\
["grpc-tools", "npm:1.12.4"],\
@@ -5845,7 +5845,7 @@ const RAW_RUNTIME_STATE =
["inversify", "npm:6.0.1"],\
["inversify-express-utils", "npm:6.4.3"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
["winston", "npm:3.9.0"]\
],\
@@ -5934,7 +5934,7 @@ const RAW_RUNTIME_STATE =
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.17"],\
@@ -5970,7 +5970,7 @@ const RAW_RUNTIME_STATE =
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.17"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
@@ -5993,7 +5993,7 @@ const RAW_RUNTIME_STATE =
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["jsonwebtoken", "npm:9.0.0"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"]\
],\
@@ -6034,7 +6034,7 @@ const RAW_RUNTIME_STATE =
["eslint", "npm:8.41.0"],\
["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.0.0"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"]\
],\
"linkType": "SOFT"\
@@ -6064,7 +6064,7 @@ const RAW_RUNTIME_STATE =
["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.0.0"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["regenerator-runtime", "npm:0.13.11"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["ts-loader", "virtual:251b55e6186f136d0456117ba65ba163d1a38b49e5d09875aa42c66c71e5a9085f9a3cc24c7aae5da7499c53d95e6948b9284db4d7d1f035f288826df740c6bf#npm:9.4.3"],\
@@ -6082,7 +6082,7 @@ const RAW_RUNTIME_STATE =
["@aws-sdk/client-s3", "npm:3.462.0"],\
["@aws-sdk/client-sns", "npm:3.462.0"],\
["@aws-sdk/client-sqs", "npm:3.462.0"],\
["@grpc/grpc-js", "npm:1.9.12"],\
["@grpc/grpc-js", "npm:1.9.13"],\
["@standardnotes/api", "npm:1.26.26"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
@@ -6121,7 +6121,7 @@ const RAW_RUNTIME_STATE =
["mysql2", "npm:3.3.3"],\
["prettier", "npm:3.0.3"],\
["prettyjson", "npm:1.2.5"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["semver", "npm:7.5.4"],\
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
@@ -6149,7 +6149,7 @@ const RAW_RUNTIME_STATE =
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["microtime", "npm:3.1.1"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"]\
],\
@@ -6200,7 +6200,7 @@ const RAW_RUNTIME_STATE =
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.17"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
@@ -14478,6 +14478,13 @@ const RAW_RUNTIME_STATE =
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}],\
["npm:0.2.1", {\
"packageLocation": "./.yarn/cache/reflect-metadata-npm-0.2.1-7f12b5924d-394b293bd4.zip/node_modules/reflect-metadata/",\
"packageDependencies": [\
["reflect-metadata", "npm:0.2.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["regenerator-runtime", [\

Binary file not shown.

View File

@@ -1,4 +1,4 @@
FROM node:20.6.1-alpine
FROM node:20.10.0-alpine
ENV NODE_ENV production
@@ -10,7 +10,7 @@ RUN apk add --update --no-cache \
bash \
py3-pip
RUN pip install --no-cache-dir --upgrade supervisor
RUN pip install --no-cache-dir --upgrade --break-system-packages supervisor
RUN mkdir -p /var/lib/server/logs

View File

@@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.34.11](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.10...@standardnotes/analytics@2.34.11) (2023-12-29)
**Note:** Version bump only for package @standardnotes/analytics
## [2.34.10](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.9...@standardnotes/analytics@2.34.10) (2023-12-28)
**Note:** Version bump only for package @standardnotes/analytics
## [2.34.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.8...@standardnotes/analytics@2.34.9) (2023-12-28)
**Note:** Version bump only for package @standardnotes/analytics
## [2.34.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.7...@standardnotes/analytics@2.34.8) (2023-12-26)
**Note:** Version bump only for package @standardnotes/analytics
## [2.34.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.6...@standardnotes/analytics@2.34.7) (2023-12-14)
**Note:** Version bump only for package @standardnotes/analytics
## [2.34.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.5...@standardnotes/analytics@2.34.6) (2023-12-11)
**Note:** Version bump only for package @standardnotes/analytics
## [2.34.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.4...@standardnotes/analytics@2.34.5) (2023-12-07)
### Bug Fixes
* logger meta information ([a2b1323](https://github.com/standardnotes/server/commit/a2b1323568f5ced74b41aa4634340a6ca0668683))
## [2.34.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.3...@standardnotes/analytics@2.34.4) (2023-12-01)
### Bug Fixes

View File

@@ -1,4 +1,4 @@
FROM node:20.6.1-alpine
FROM node:20.10.0-alpine
RUN apk add --update \
curl \

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.34.4",
"version": "2.34.11",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -58,7 +58,7 @@
"ioredis": "^5.2.4",
"mixpanel": "^0.17.0",
"mysql2": "^3.0.1",
"reflect-metadata": "^0.1.13",
"reflect-metadata": "^0.2.1",
"typeorm": "^0.3.17",
"winston": "^3.8.1"
}

View File

@@ -84,6 +84,7 @@ export class ContainerConfigLoader {
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: 'analytics' },
})
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)

View File

@@ -3,6 +3,104 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.89.12](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.11...@standardnotes/api-gateway@1.89.12) (2024-01-02)
### Bug Fixes
* adjust grpc keepalive parameters ([5987b08](https://github.com/standardnotes/server/commit/5987b0899c21d40cc2c90c29fcf3c25b27660d0c))
## [1.89.11](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.10...@standardnotes/api-gateway@1.89.11) (2023-12-29)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.89.10](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.9...@standardnotes/api-gateway@1.89.10) (2023-12-28)
### Bug Fixes
* **api-gateway:** log severity ([c265064](https://github.com/standardnotes/server/commit/c2650646d1f89ca509b4d256e4305a592d12f850))
## [1.89.9](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.8...@standardnotes/api-gateway@1.89.9) (2023-12-28)
### Bug Fixes
* retry grpc calls upon service unavailable response ([#1011](https://github.com/standardnotes/server/issues/1011)) ([7c67a5a](https://github.com/standardnotes/server/commit/7c67a5a0f93f9d506ee4010e2837288457fc2c1d))
## [1.89.8](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.7...@standardnotes/api-gateway@1.89.8) (2023-12-28)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.89.7](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.6...@standardnotes/api-gateway@1.89.7) (2023-12-28)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.89.6](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.5...@standardnotes/api-gateway@1.89.6) (2023-12-26)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.89.5](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.4...@standardnotes/api-gateway@1.89.5) (2023-12-22)
### Bug Fixes
* logs severity ([0762ed1](https://github.com/standardnotes/server/commit/0762ed1127a5de295bd50c14afad31c7fb88a853))
## [1.89.4](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.3...@standardnotes/api-gateway@1.89.4) (2023-12-22)
### Bug Fixes
* case sensitive typo ([b1cb6a1](https://github.com/standardnotes/server/commit/b1cb6a1d21479b67d9826b55e1221a803e2f41a7))
## [1.89.3](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.2...@standardnotes/api-gateway@1.89.3) (2023-12-22)
### Bug Fixes
* add more readonly access debug logs ([54091f2](https://github.com/standardnotes/server/commit/54091f23da33a5d1df2ecffdfa559e3fa4c562f6))
* **api-gateway:** add debug log for operating on readonly access ([e4a8324](https://github.com/standardnotes/server/commit/e4a8324db26454a7587f37096653ad9565541295))
## [1.89.2](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.1...@standardnotes/api-gateway@1.89.2) (2023-12-21)
### Bug Fixes
* **api-gateway:** missing readonly access for demo when utilizing grpc workflow ([#1005](https://github.com/standardnotes/server/issues/1005)) ([e551a36](https://github.com/standardnotes/server/commit/e551a364f63e28c9329dbce492488b9f112e3473))
## [1.89.1](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.0...@standardnotes/api-gateway@1.89.1) (2023-12-14)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.89.0](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.88.4...@standardnotes/api-gateway@1.89.0) (2023-12-12)
### Features
* **syncing-server:** add extended revisions frequency for free users ([#965](https://github.com/standardnotes/server/issues/965)) ([398c10c](https://github.com/standardnotes/server/commit/398c10ce4b8e357728a8b4f354b3bf6ccc8e438d))
## [1.88.4](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.88.3...@standardnotes/api-gateway@1.88.4) (2023-12-11)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.88.3](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.88.2...@standardnotes/api-gateway@1.88.3) (2023-12-08)
### Bug Fixes
* **api-gateway:** add extra meta to logs ([a341e78](https://github.com/standardnotes/server/commit/a341e789093556f09c2a337e39a8053abdcf587b))
## [1.88.2](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.88.1...@standardnotes/api-gateway@1.88.2) (2023-12-07)
### Bug Fixes
* **api-gateway:** add userId to logs in error handler if possible ([0058368](https://github.com/standardnotes/server/commit/005836868126ae5fa4c4468644704938aea0f4ec))
## [1.88.1](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.88.0...@standardnotes/api-gateway@1.88.1) (2023-12-07)
### Bug Fixes
* **api-gateway:** response header on grpc websocket connection validation ([3637db2](https://github.com/standardnotes/server/commit/3637db2563255aaddd44700c039495c6b9a9e4aa))
* logger meta information ([a2b1323](https://github.com/standardnotes/server/commit/a2b1323568f5ced74b41aa4634340a6ca0668683))
# [1.88.0](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.7...@standardnotes/api-gateway@1.88.0) (2023-12-07)
### Features
* replace websocket connection validation with grpc ([#954](https://github.com/standardnotes/server/issues/954)) ([d5c1b76](https://github.com/standardnotes/server/commit/d5c1b76de068a64b334c4347cbefa973447a0f60))
## [1.87.7](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.6...@standardnotes/api-gateway@1.87.7) (2023-12-07)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

@@ -1,4 +1,4 @@
FROM node:20.6.1-alpine
FROM node:20.10.0-alpine
RUN apk add --update \
curl \

View File

@@ -91,9 +91,13 @@ void container.load().then((container) => {
server.setErrorConfig((app) => {
app.use((error: Record<string, unknown>, request: Request, response: Response, _next: NextFunction) => {
logger.error(
`[URL: |${request.method}| ${request.url}][SNJS: ${request.headers['x-snjs-version']}][Application: ${request.headers['x-application-version']}] Error thrown: ${error.stack}`,
)
logger.error(`${error.stack}`, {
method: request.method,
url: request.url,
snjs: request.headers['x-snjs-version'],
application: request.headers['x-application-version'],
userId: response.locals.user ? response.locals.user.uuid : undefined,
})
logger.debug(
`[URL: |${request.method}| ${request.url}][SNJS: ${request.headers['x-snjs-version']}][Application: ${
request.headers['x-application-version']

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.87.7",
"version": "1.89.12",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -31,7 +31,7 @@
"start": "yarn node dist/bin/server.js"
},
"dependencies": {
"@grpc/grpc-js": "^1.9.12",
"@grpc/grpc-js": "^1.9.13",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
@@ -50,7 +50,7 @@
"ioredis": "^5.2.4",
"jsonwebtoken": "^9.0.0",
"prettyjson": "^1.2.5",
"reflect-metadata": "0.1.13",
"reflect-metadata": "^0.2.1",
"winston": "^3.8.1"
},
"devDependencies": {

View File

@@ -22,19 +22,13 @@ import { EndpointResolver } from '../Service/Resolver/EndpointResolver'
import { RequiredCrossServiceTokenMiddleware } from '../Controller/RequiredCrossServiceTokenMiddleware'
import { OptionalCrossServiceTokenMiddleware } from '../Controller/OptionalCrossServiceTokenMiddleware'
import { Transform } from 'stream'
import {
ISessionsClient,
ISyncingClient,
SessionsClient,
SyncRequest,
SyncResponse,
SyncingClient,
} from '@standardnotes/grpc'
import { AuthClient, IAuthClient, ISyncingClient, SyncRequest, SyncResponse, SyncingClient } from '@standardnotes/grpc'
import { GRPCServiceProxy } from '../Service/gRPC/GRPCServiceProxy'
import { GRPCSyncingServerServiceProxy } from '../Service/gRPC/GRPCSyncingServerServiceProxy'
import { SyncResponseHttpRepresentation } from '../Mapping/Sync/Http/SyncResponseHttpRepresentation'
import { SyncRequestGRPCMapper } from '../Mapping/Sync/GRPC/SyncRequestGRPCMapper'
import { SyncResponseGRPCMapper } from '../Mapping/Sync/GRPC/SyncResponseGRPCMapper'
import { GRPCWebSocketAuthMiddleware } from '../Controller/GRPCWebSocketAuthMiddleware'
export class ContainerConfigLoader {
async load(configuration?: {
@@ -51,6 +45,7 @@ export class ContainerConfigLoader {
const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted'
const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
const isConfiguredForGRPCProxy = env.get('SERVICE_PROXY_TYPE', true) === 'grpc'
container
.bind<boolean>(TYPES.ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING)
@@ -122,7 +117,6 @@ export class ContainerConfigLoader {
container
.bind<OptionalCrossServiceTokenMiddleware>(TYPES.ApiGateway_OptionalCrossServiceTokenMiddleware)
.to(OptionalCrossServiceTokenMiddleware)
container.bind<WebSocketAuthMiddleware>(TYPES.ApiGateway_WebSocketAuthMiddleware).to(WebSocketAuthMiddleware)
container
.bind<SubscriptionTokenAuthMiddleware>(TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
.to(SubscriptionTokenAuthMiddleware)
@@ -153,24 +147,22 @@ export class ContainerConfigLoader {
new DirectCallServiceProxy(configuration.serviceContainer, container.get(TYPES.ApiGateway_FILES_SERVER_URL)),
)
} else {
const isConfiguredForGRPCProxy = env.get('SERVICE_PROXY_TYPE', true) === 'grpc'
if (isConfiguredForGRPCProxy) {
container.bind(TYPES.ApiGateway_AUTH_SERVER_GRPC_URL).toConstantValue(env.get('AUTH_SERVER_GRPC_URL'))
container.bind(TYPES.ApiGateway_SYNCING_SERVER_GRPC_URL).toConstantValue(env.get('SYNCING_SERVER_GRPC_URL'))
const grpcAgentKeepAliveTimeout = env.get('GRPC_AGENT_KEEP_ALIVE_TIMEOUT', true)
? +env.get('GRPC_AGENT_KEEP_ALIVE_TIMEOUT', true)
: 8_000
: 20_000
const grpcMaxMessageSize = env.get('GRPC_MAX_MESSAGE_SIZE', true)
? +env.get('GRPC_MAX_MESSAGE_SIZE', true)
: 1024 * 1024 * 50
container.bind<ISessionsClient>(TYPES.ApiGateway_GRPCSessionsClient).toConstantValue(
new SessionsClient(
container.bind<IAuthClient>(TYPES.ApiGateway_GRPCAuthClient).toConstantValue(
new AuthClient(
container.get<string>(TYPES.ApiGateway_AUTH_SERVER_GRPC_URL),
grpc.credentials.createInsecure(),
{
'grpc.keepalive_time_ms': grpcAgentKeepAliveTimeout * 2,
'grpc.keepalive_timeout_ms': grpcAgentKeepAliveTimeout,
'grpc.default_compression_algorithm': grpc.compressionAlgorithms.gzip,
'grpc.default_compression_level': 2,
@@ -184,7 +176,6 @@ export class ContainerConfigLoader {
container.get<string>(TYPES.ApiGateway_SYNCING_SERVER_GRPC_URL),
grpc.credentials.createInsecure(),
{
'grpc.keepalive_time_ms': grpcAgentKeepAliveTimeout * 2,
'grpc.keepalive_timeout_ms': grpcAgentKeepAliveTimeout,
'grpc.default_compression_algorithm': grpc.compressionAlgorithms.gzip,
'grpc.default_compression_level': 2,
@@ -229,7 +220,7 @@ export class ContainerConfigLoader {
container.get<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache),
container.get<winston.Logger>(TYPES.ApiGateway_Logger),
container.get<TimerInterface>(TYPES.ApiGateway_Timer),
container.get<ISessionsClient>(TYPES.ApiGateway_GRPCSessionsClient),
container.get<IAuthClient>(TYPES.ApiGateway_GRPCAuthClient),
container.get<GRPCSyncingServerServiceProxy>(TYPES.ApiGateway_GRPCSyncingServerServiceProxy),
),
)
@@ -238,6 +229,20 @@ export class ContainerConfigLoader {
}
}
if (isConfiguredForGRPCProxy) {
container
.bind<GRPCWebSocketAuthMiddleware>(TYPES.ApiGateway_WebSocketAuthMiddleware)
.toConstantValue(
new GRPCWebSocketAuthMiddleware(
container.get<IAuthClient>(TYPES.ApiGateway_GRPCAuthClient),
container.get<string>(TYPES.ApiGateway_AUTH_JWT_SECRET),
container.get<winston.Logger>(TYPES.ApiGateway_Logger),
),
)
} else {
container.bind<WebSocketAuthMiddleware>(TYPES.ApiGateway_WebSocketAuthMiddleware).to(WebSocketAuthMiddleware)
}
logger.debug('Configuration complete')
return container

View File

@@ -34,6 +34,6 @@ export const TYPES = {
ApiGateway_CrossServiceTokenCache: Symbol.for('ApiGateway_CrossServiceTokenCache'),
ApiGateway_Timer: Symbol.for('ApiGateway_Timer'),
ApiGateway_EndpointResolver: Symbol.for('ApiGateway_EndpointResolver'),
ApiGateway_GRPCSessionsClient: Symbol.for('ApiGateway_GRPCSessionsClient'),
ApiGateway_GRPCAuthClient: Symbol.for('ApiGateway_GRPCAuthClient'),
ApiGateway_GRPCSyncingClient: Symbol.for('ApiGateway_GRPCSyncingClient'),
}

View File

@@ -74,6 +74,13 @@ export abstract class AuthMiddleware extends BaseMiddleware {
response.locals.session = decodedToken.session
response.locals.roles = decodedToken.roles
response.locals.sharedVaultOwnerContext = decodedToken.shared_vault_owner_context
response.locals.readOnlyAccess = decodedToken.session?.readonly_access ?? false
if (response.locals.readOnlyAccess) {
this.logger.debug('User operates on read-only access', {
codeTag: 'AuthMiddleware',
userId: response.locals.user.uuid,
})
}
response.locals.belongsToSharedVaults = decodedToken.belongs_to_shared_vaults ?? []
} catch (error) {
let detailedErrorMessage = (error as Error).message

View File

@@ -0,0 +1,120 @@
import { CrossServiceTokenData } from '@standardnotes/security'
import * as grpc from '@grpc/grpc-js'
import { NextFunction, Request, Response } from 'express'
import { BaseMiddleware } from 'inversify-express-utils'
import { verify } from 'jsonwebtoken'
import { Logger } from 'winston'
import { ConnectionValidationResponse, IAuthClient, WebsocketConnectionAuthorizationHeader } from '@standardnotes/grpc'
import { RoleName } from '@standardnotes/domain-core'
export class GRPCWebSocketAuthMiddleware extends BaseMiddleware {
constructor(
private authClient: IAuthClient,
private jwtSecret: string,
private logger: Logger,
) {
super()
}
async handler(request: Request, response: Response, next: NextFunction): Promise<void> {
const authHeaderValue = request.headers.authorization as string
if (!authHeaderValue) {
response.status(401).send({
error: {
tag: 'invalid-auth',
message: 'Invalid login credentials.',
},
})
return
}
const promise = new Promise((resolve, reject) => {
try {
const request = new WebsocketConnectionAuthorizationHeader()
request.setToken(authHeaderValue)
this.authClient.validateWebsocket(
request,
(error: grpc.ServiceError | null, response: ConnectionValidationResponse) => {
if (error) {
const responseCode = error.metadata.get('x-auth-error-response-code').pop()
if (responseCode) {
return resolve({
status: +responseCode,
data: {
error: {
message: error.metadata.get('x-auth-error-message').pop(),
tag: error.metadata.get('x-auth-error-tag').pop(),
},
},
headers: {
contentType: 'application/json',
},
})
}
return reject(error)
}
return resolve({
status: 200,
data: {
authToken: response.getCrossServiceToken(),
},
headers: {
contentType: 'application/json',
},
})
},
)
} catch (error) {
return reject(error)
}
})
try {
const authResponse = (await promise) as {
status: number
headers: Record<string, unknown>
data: Record<string, unknown>
}
if (authResponse.status > 200) {
response.setHeader('content-type', 'application/json')
response.status(authResponse.status).send(authResponse.data)
return
}
const crossServiceToken = authResponse.data.authToken as string
response.locals.authToken = crossServiceToken
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
response.locals.user = decodedToken.user
response.locals.session = decodedToken.session
response.locals.roles = decodedToken.roles
response.locals.isFreeUser =
decodedToken.roles.length === 1 && decodedToken.roles[0].name === RoleName.NAMES.CoreUser
} catch (error) {
this.logger.error(
`Could not pass the request to websocket connection validation on underlying service: ${
(error as Error).message
}`,
)
response
.status(500)
.send(
"Unfortunately, we couldn't handle your request. Please try again or contact our support if the error persists.",
)
return
}
return next()
}
}

View File

@@ -240,6 +240,9 @@ export class HttpServiceProxy implements ServiceProxyInterface {
tooManyRetryAttempts
? `Request to ${serverUrl}/${endpoint} timed out after ${retryAttempt} retries`
: `Could not pass the request to ${serverUrl}/${endpoint} on underlying service: ${detailedErrorMessage}`,
{
userId: response.locals.user ? response.locals.user.uuid : undefined,
},
)
this.logger.debug(`Response error: ${JSON.stringify(error)}`)

View File

@@ -2,12 +2,13 @@ import { AxiosInstance, AxiosError, AxiosResponse, Method } from 'axios'
import { Request, Response } from 'express'
import { Logger } from 'winston'
import { TimerInterface } from '@standardnotes/time'
import { ISessionsClient, AuthorizationHeader, SessionValidationResponse } from '@standardnotes/grpc'
import { IAuthClient, AuthorizationHeader, SessionValidationResponse } from '@standardnotes/grpc'
import * as grpc from '@grpc/grpc-js'
import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
import { GRPCSyncingServerServiceProxy } from './GRPCSyncingServerServiceProxy'
import { Status } from '@grpc/grpc-js/build/src/constants'
export class GRPCServiceProxy implements ServiceProxyInterface {
constructor(
@@ -23,15 +24,18 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
private crossServiceTokenCache: CrossServiceTokenCacheInterface,
private logger: Logger,
private timer: TimerInterface,
private sessionsClient: ISessionsClient,
private authClient: IAuthClient,
private gRPCSyncingServerServiceProxy: GRPCSyncingServerServiceProxy,
) {}
async validateSession(headers: {
authorization: string
sharedVaultOwnerContext?: string
}): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
return new Promise((resolve, reject) => {
async validateSession(
headers: {
authorization: string
sharedVaultOwnerContext?: string
},
retryAttempt?: number,
): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
const promise = new Promise((resolve, reject) => {
try {
const request = new AuthorizationHeader()
request.setBearerToken(headers.authorization)
@@ -41,7 +45,7 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
this.logger.debug('[GRPCServiceProxy] Validating session via gRPC')
this.sessionsClient.validate(
this.authClient.validate(
request,
metadata,
(error: grpc.ServiceError | null, response: SessionValidationResponse) => {
@@ -80,6 +84,32 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
return reject(error)
}
})
try {
const result = await promise
if (retryAttempt) {
this.logger.debug(`Request to Auth Server succeeded after ${retryAttempt} retries`)
}
return result as { status: number; data: unknown; headers: { contentType: string } }
} catch (error) {
const requestDidNotMakeIt =
'code' in (error as Record<string, unknown>) && (error as Record<string, unknown>).code === Status.UNAVAILABLE
const tooManyRetryAttempts = retryAttempt && retryAttempt > 2
if (!tooManyRetryAttempts && requestDidNotMakeIt) {
await this.timer.sleep(50)
const nextRetryAttempt = retryAttempt ? retryAttempt + 1 : 1
this.logger.debug(`Retrying request to Auth Server for the ${nextRetryAttempt} time`)
return this.validateSession(headers, nextRetryAttempt)
}
throw error
}
}
async callSyncingServer(
@@ -92,6 +122,21 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
payload !== undefined && typeof payload !== 'string' && 'api' in payload && payload.api === '20200115'
if (requestIsUsingLatestApiVersions && endpoint === 'items/sync') {
await this.callSyncingServerGRPC(request, response, payload)
return
}
await this.callServer(this.syncingServerJsUrl, request, response, endpoint, payload)
}
private async callSyncingServerGRPC(
request: Request,
response: Response,
payload?: Record<string, unknown> | string,
retryAttempt?: number,
): Promise<void> {
try {
const result = await this.gRPCSyncingServerServiceProxy.sync(request, response, payload)
response.status(result.status).send({
@@ -107,10 +152,30 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
data: result.data,
})
return
}
if (retryAttempt) {
this.logger.debug(`Request to Syncing Server succeeded after ${retryAttempt} retries`, {
userId: response.locals.user ? response.locals.user.uuid : undefined,
})
}
} catch (error) {
const requestDidNotMakeIt =
'code' in (error as Record<string, unknown>) && (error as Record<string, unknown>).code === Status.UNAVAILABLE
await this.callServer(this.syncingServerJsUrl, request, response, endpoint, payload)
const tooManyRetryAttempts = retryAttempt && retryAttempt > 2
if (!tooManyRetryAttempts && requestDidNotMakeIt) {
await this.timer.sleep(50)
const nextRetryAttempt = retryAttempt ? retryAttempt + 1 : 1
this.logger.debug(`Retrying request to Syncing Server for the ${nextRetryAttempt} time`, {
userId: response.locals.user ? response.locals.user.uuid : undefined,
})
return this.callSyncingServerGRPC(request, response, payload, nextRetryAttempt)
}
throw error
}
}
async callRevisionsServer(
@@ -275,6 +340,9 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
tooManyRetryAttempts
? `Request to ${serverUrl}/${endpoint} timed out after ${retryAttempt} retries`
: `Could not pass the request to ${serverUrl}/${endpoint} on underlying service: ${detailedErrorMessage}`,
{
userId: response.locals.user ? response.locals.user.uuid : undefined,
},
)
this.logger.debug(`Response error: ${JSON.stringify(error)}`)

View File

@@ -27,10 +27,17 @@ export class GRPCSyncingServerServiceProxy {
const metadata = new Metadata()
metadata.set('x-user-uuid', response.locals.user.uuid)
metadata.set('x-snjs-version', request.headers['x-snjs-version'] as string)
metadata.set('x-read-only-access', response.locals.readonlyAccess ? 'true' : 'false')
metadata.set('x-read-only-access', response.locals.readOnlyAccess ? 'true' : 'false')
if (response.locals.readOnlyAccess) {
this.logger.debug('Syncing with read-only access', {
codeTag: 'GRPCSyncingServerServiceProxy',
userId: response.locals.user.uuid,
})
}
if (response.locals.session) {
metadata.set('x-session-uuid', response.locals.session.uuid)
}
metadata.set('x-is-free-user', response.locals.isFreeUser ? 'true' : 'false')
this.syncingClient.syncItems(syncRequest, metadata, (error, syncResponse) => {
if (error) {
@@ -43,11 +50,10 @@ export class GRPCSyncingServerServiceProxy {
}
if (error.code === Status.INTERNAL) {
this.logger.error(
`[GRPCSyncingServerServiceProxy] Internal gRPC error: ${error.message}. Payload: ${JSON.stringify(
payload,
)}`,
)
this.logger.error(`Internal gRPC error: ${error.message}. Payload: ${JSON.stringify(payload)}`, {
codeTag: 'GRPCSyncingServerServiceProxy',
userId: response.locals.user.uuid,
})
}
return reject(error)
@@ -60,11 +66,10 @@ export class GRPCSyncingServerServiceProxy {
'code' in (error as Record<string, unknown>) &&
(error as Record<string, unknown>).code === Status.INTERNAL
) {
this.logger.error(
`[GRPCSyncingServerServiceProxy] Internal gRPC error: ${JSON.stringify(error)}. Payload: ${JSON.stringify(
payload,
)}`,
)
this.logger.error(`Internal gRPC error: ${JSON.stringify(error)}. Payload: ${JSON.stringify(payload)}`, {
codeTag: 'GRPCSyncingServerServiceProxy.catch',
userId: response.locals.user.uuid,
})
}
reject(error)

View File

@@ -3,6 +3,105 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.177.10](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.9...@standardnotes/auth-server@1.177.10) (2024-01-02)
### Bug Fixes
* adjust grpc keepalive parameters ([5987b08](https://github.com/standardnotes/server/commit/5987b0899c21d40cc2c90c29fcf3c25b27660d0c))
## [1.177.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.8...@standardnotes/auth-server@1.177.9) (2023-12-29)
### Bug Fixes
* **auth:** adding user roles upon renewal of shared subscription ([#1012](https://github.com/standardnotes/server/issues/1012)) ([26b13ed](https://github.com/standardnotes/server/commit/26b13ed6d488fb5038a3928482b3079ab2b5f8d8))
## [1.177.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.7...@standardnotes/auth-server@1.177.8) (2023-12-29)
### Bug Fixes
* cleanup revision requests ([7e8d7f6](https://github.com/standardnotes/server/commit/7e8d7f6874bb1db55ee6feb9e128c684a6900189))
## [1.177.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.6...@standardnotes/auth-server@1.177.7) (2023-12-28)
### Bug Fixes
* **auth:** missing return statement in lock middleware ([eff1d64](https://github.com/standardnotes/server/commit/eff1d642cfd3502f479761a3bb76a543ae0166af))
## [1.177.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.5...@standardnotes/auth-server@1.177.6) (2023-12-28)
### Bug Fixes
* retry grpc calls upon service unavailable response ([#1011](https://github.com/standardnotes/server/issues/1011)) ([7c67a5a](https://github.com/standardnotes/server/commit/7c67a5a0f93f9d506ee4010e2837288457fc2c1d))
## [1.177.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.4...@standardnotes/auth-server@1.177.5) (2023-12-28)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.177.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.3...@standardnotes/auth-server@1.177.4) (2023-12-28)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.177.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.2...@standardnotes/auth-server@1.177.3) (2023-12-26)
### Bug Fixes
* stop revisions propagation ([#1008](https://github.com/standardnotes/server/issues/1008)) ([7962b24](https://github.com/standardnotes/server/commit/7962b245b51703ff7d33728dee117b2074f2692a))
## [1.177.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.1...@standardnotes/auth-server@1.177.2) (2023-12-22)
### Bug Fixes
* logs severity ([0762ed1](https://github.com/standardnotes/server/commit/0762ed1127a5de295bd50c14afad31c7fb88a853))
## [1.177.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.0...@standardnotes/auth-server@1.177.1) (2023-12-22)
### Bug Fixes
* **auth:** specs issue ([0342cdc](https://github.com/standardnotes/server/commit/0342cdcfc06bd3bb0192a428c30c5af15e5eb0d7))
* case sensitive typo ([b1cb6a1](https://github.com/standardnotes/server/commit/b1cb6a1d21479b67d9826b55e1221a803e2f41a7))
# [1.177.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.176.5...@standardnotes/auth-server@1.177.0) (2023-12-14)
### Features
* add procedure for recalculating file quota for user ([#980](https://github.com/standardnotes/server/issues/980)) ([de4fcf9](https://github.com/standardnotes/server/commit/de4fcf9a4c308ad7d71c42fe5c27af18b8614e1a))
## [1.176.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.176.4...@standardnotes/auth-server@1.176.5) (2023-12-12)
### Bug Fixes
* **auth:** add user uuid context to sign in emails log context ([cfbe2bb](https://github.com/standardnotes/server/commit/cfbe2bbac60e9014d7ba0967e4b996fba7dc8629))
## [1.176.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.176.3...@standardnotes/auth-server@1.176.4) (2023-12-11)
### Bug Fixes
* **auth:** generate new recovery codes when enabling mfa ([#964](https://github.com/standardnotes/server/issues/964)) ([031fa71](https://github.com/standardnotes/server/commit/031fa71e7d86221ec7fb0f4b21c62454646564e2))
## [1.176.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.176.2...@standardnotes/auth-server@1.176.3) (2023-12-11)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.176.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.176.1...@standardnotes/auth-server@1.176.2) (2023-12-11)
### Bug Fixes
* **auth:** error log meta on triggering email backups ([4ab61b9](https://github.com/standardnotes/server/commit/4ab61b94a4aee361399a76c9f2b6b977c4832b06))
## [1.176.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.176.0...@standardnotes/auth-server@1.176.1) (2023-12-07)
### Bug Fixes
* **auth:** logger meta on disabling settings ([3f2d8c9](https://github.com/standardnotes/server/commit/3f2d8c902c5331e07959f0b7b42684ef47346220))
* logger meta information ([a2b1323](https://github.com/standardnotes/server/commit/a2b1323568f5ced74b41aa4634340a6ca0668683))
* logs meta ([8ac84c5](https://github.com/standardnotes/server/commit/8ac84c59af886bb9c42de012fb1e7864e116ab55))
# [1.176.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.175.1...@standardnotes/auth-server@1.176.0) (2023-12-07)
### Features
* replace websocket connection validation with grpc ([#954](https://github.com/standardnotes/server/issues/954)) ([d5c1b76](https://github.com/standardnotes/server/commit/d5c1b76de068a64b334c4347cbefa973447a0f60))
## [1.175.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.175.0...@standardnotes/auth-server@1.175.1) (2023-12-07)
**Note:** Version bump only for package @standardnotes/auth-server

View File

@@ -1,4 +1,4 @@
FROM node:20.6.1-alpine
FROM node:20.10.0-alpine
RUN apk add --update \
curl \

View File

@@ -0,0 +1,45 @@
import 'reflect-metadata'
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { FixStorageQuotaForUser } from '../src/Domain/UseCase/FixStorageQuotaForUser/FixStorageQuotaForUser'
const inputArgs = process.argv.slice(2)
const userEmail = inputArgs[0]
const container = new ContainerConfigLoader('worker')
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Auth_Logger)
logger.info('Starting storage quota fix...', {
userId: userEmail,
})
const fixStorageQuota = container.get<FixStorageQuotaForUser>(TYPES.Auth_FixStorageQuotaForUser)
Promise.resolve(
fixStorageQuota.execute({
userEmail,
}),
)
.then(() => {
logger.info('Storage quota fixed', {
userId: userEmail,
})
process.exit(0)
})
.catch((error) => {
logger.error(`Could not fix storage quota: ${error.message}`, {
userId: userEmail,
})
process.exit(1)
})
})

View File

@@ -0,0 +1,67 @@
import 'reflect-metadata'
import { Logger } from 'winston'
import * as dayjs from 'dayjs'
import * as utc from 'dayjs/plugin/utc'
import { Uuid } from '@standardnotes/domain-core'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { UserSubscriptionRepositoryInterface } from '../src/Domain/Subscription/UserSubscriptionRepositoryInterface'
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
import { UserSubscriptionType } from '../src/Domain/Subscription/UserSubscriptionType'
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
const fixRoles = async (
userRepository: UserRepositoryInterface,
userSubscriptionRepository: UserSubscriptionRepositoryInterface,
roleService: RoleServiceInterface,
): Promise<void> => {
const subscriptions = await userSubscriptionRepository.findActiveByType(UserSubscriptionType.Shared)
for (const subscription of subscriptions) {
const userUuidOrError = Uuid.create(subscription.userUuid)
if (userUuidOrError.isFailed()) {
continue
}
const userUuid = userUuidOrError.getValue()
const user = await userRepository.findOneByUuid(userUuid)
if (!user) {
continue
}
await roleService.addUserRoleBasedOnSubscription(user, subscription.planName)
}
}
const container = new ContainerConfigLoader('worker')
void container.load().then((container) => {
dayjs.extend(utc)
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Auth_Logger)
logger.info('Starting roles fix for shared subscriptions...')
const userRepository = container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository)
const userSubscriptionRepository = container.get<UserSubscriptionRepositoryInterface>(
TYPES.Auth_UserSubscriptionRepository,
)
const roleService = container.get<RoleServiceInterface>(TYPES.Auth_RoleService)
Promise.resolve(fixRoles(userRepository, userSubscriptionRepository, roleService))
.then(() => {
logger.info('Finished fixing roles for shared subscriptions')
process.exit(0)
})
.catch((error) => {
logger.error(`Error while fixing roles for shared subscriptions: ${(error as Error).message}`)
process.exit(1)
})
})

View File

@@ -30,10 +30,11 @@ import { InversifyExpressServer } from 'inversify-express-utils'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { SessionsServer } from '../src/Infra/gRPC/SessionsServer'
import { SessionsService } from '@standardnotes/grpc'
import { AuthServer } from '../src/Infra/gRPC/AuthServer'
import { AuthService } from '@standardnotes/grpc'
import { AuthenticateRequest } from '../src/Domain/UseCase/AuthenticateRequest'
import { CreateCrossServiceToken } from '../src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
import { TokenDecoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security'
const container = new ContainerConfigLoader()
void container.load().then((container) => {
@@ -57,8 +58,14 @@ void container.load().then((container) => {
const logger: winston.Logger = container.get(TYPES.Auth_Logger)
server.setErrorConfig((app) => {
app.use((error: Record<string, unknown>, _request: Request, response: Response, _next: NextFunction) => {
logger.error(error.stack)
app.use((error: Record<string, unknown>, request: Request, response: Response, _next: NextFunction) => {
logger.error(`${error.stack}`, {
method: request.method,
url: request.url,
snjs: request.headers['x-snjs-version'],
application: request.headers['x-application-version'],
userId: response.locals.user ? response.locals.user.uuid : undefined,
})
response.status(500).send({
error: {
@@ -77,16 +84,18 @@ void container.load().then((container) => {
serverInstance.keepAliveTimeout = httpKeepAliveTimeout
const grpcKeepAliveTime = env.get('GRPC_KEEP_ALIVE_TIME', true) ? +env.get('GRPC_KEEP_ALIVE_TIME', true) : 7_200_000
const grpcKeepAliveTimeout = env.get('GRPC_KEEP_ALIVE_TIMEOUT', true)
? +env.get('GRPC_KEEP_ALIVE_TIMEOUT', true)
: 10_000
: 20_000
const grpcMaxMessageSize = env.get('GRPC_MAX_MESSAGE_SIZE', true)
? +env.get('GRPC_MAX_MESSAGE_SIZE', true)
: 1024 * 1024 * 50
const grpcServer = new grpc.Server({
'grpc.keepalive_time_ms': grpcKeepAliveTimeout * 2,
'grpc.keepalive_time_ms': grpcKeepAliveTime,
'grpc.keepalive_timeout_ms': grpcKeepAliveTimeout,
'grpc.default_compression_algorithm': grpc.compressionAlgorithms.gzip,
'grpc.max_receive_message_length': grpcMaxMessageSize,
@@ -95,14 +104,16 @@ void container.load().then((container) => {
const gRPCPort = env.get('GRPC_PORT', true) ? +env.get('GRPC_PORT', true) : 50051
const sessionsServer = new SessionsServer(
const authServer = new AuthServer(
container.get<AuthenticateRequest>(TYPES.Auth_AuthenticateRequest),
container.get<CreateCrossServiceToken>(TYPES.Auth_CreateCrossServiceToken),
container.get<TokenDecoderInterface<WebSocketConnectionTokenData>>(TYPES.Auth_WebSocketConnectionTokenDecoder),
container.get<winston.Logger>(TYPES.Auth_Logger),
)
grpcServer.addService(SessionsService, {
validate: sessionsServer.validate.bind(sessionsServer),
grpcServer.addService(AuthService, {
validate: authServer.validate.bind(authServer),
validateWebsocket: authServer.validateWebsocket.bind(authServer),
})
grpcServer.bindAsync(`0.0.0.0:${gRPCPort}`, grpc.ServerCredentials.createInsecure(), (error, port) => {
if (error) {

View File

@@ -0,0 +1,11 @@
'use strict'
const path = require('path')
const pnp = require(path.normalize(path.resolve(__dirname, '../../..', '.pnp.cjs'))).setup()
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/fix_quota.js')))
Object.defineProperty(exports, '__esModule', { value: true })
exports.default = index

View File

@@ -0,0 +1,11 @@
'use strict'
const path = require('path')
const pnp = require(path.normalize(path.resolve(__dirname, '../../..', '.pnp.cjs'))).setup()
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/fix_roles.js')))
Object.defineProperty(exports, '__esModule', { value: true })
exports.default = index

View File

@@ -5,43 +5,44 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-web' )
echo "[Docker] Starting Web..."
exec node docker/entrypoint-server.js
;;
'start-worker' )
echo "[Docker] Starting Worker..."
exec node docker/entrypoint-worker.js
;;
'cleanup' )
echo "[Docker] Starting Cleanup..."
exec node docker/entrypoint-cleanup.js
;;
'stats' )
echo "[Docker] Starting Persisting Stats..."
exec node docker/entrypoint-stats.js
;;
'email-daily-backup' )
echo "[Docker] Starting Email Daily Backup..."
exec node docker/entrypoint-backup.js daily
;;
'email-weekly-backup' )
echo "[Docker] Starting Email Weekly Backup..."
exec node docker/entrypoint-backup.js weekly
;;
'email-backup' )
echo "[Docker] Starting Email Backup For Single User..."
EMAIL=$1 && shift 1
exec node docker/entrypoint-user-email-backup.js $EMAIL
;;
'fix-quota' )
EMAIL=$1 && shift 1
exec node docker/entrypoint-fix-quota.js $EMAIL
;;
'fix-roles' )
exec node docker/entrypoint-fix-roles.js
;;
'delete-accounts' )
echo "[Docker] Starting Accounts Deleting from CSV..."
FILE_NAME=$1 && shift 1
MODE=$1 && shift 1
exec node docker/entrypoint-delete-accounts.js $FILE_NAME $MODE

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.175.1",
"version": "1.177.10",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -43,7 +43,7 @@
"@aws-sdk/client-sqs": "^3.462.0",
"@cbor-extract/cbor-extract-linux-arm64": "^2.1.1",
"@cbor-extract/cbor-extract-linux-x64": "^2.1.1",
"@grpc/grpc-js": "^1.9.12",
"@grpc/grpc-js": "^1.9.13",
"@simplewebauthn/server": "^8.1.1",
"@simplewebauthn/typescript-types": "^8.0.0",
"@standardnotes/api": "^1.26.26",
@@ -71,7 +71,7 @@
"mysql2": "^3.0.1",
"otplib": "12.0.1",
"prettyjson": "^1.2.5",
"reflect-metadata": "0.1.13",
"reflect-metadata": "^0.2.1",
"sqlite3": "^5.1.6",
"typeorm": "^0.3.17",
"ua-parser-js": "^1.0.35",

View File

@@ -282,6 +282,8 @@ import { S3CsvFileReader } from '../Infra/S3/S3CsvFileReader'
import { DeleteAccountsFromCSVFile } from '../Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFile'
import { AccountDeletionVerificationPassedEventHandler } from '../Domain/Handler/AccountDeletionVerificationPassedEventHandler'
import { RenewSharedSubscriptions } from '../Domain/UseCase/RenewSharedSubscriptions/RenewSharedSubscriptions'
import { FixStorageQuotaForUser } from '../Domain/UseCase/FixStorageQuotaForUser/FixStorageQuotaForUser'
import { FileQuotaRecalculatedEventHandler } from '../Domain/Handler/FileQuotaRecalculatedEventHandler'
export class ContainerConfigLoader {
constructor(private mode: 'server' | 'worker' = 'server') {}
@@ -310,7 +312,7 @@ export class ContainerConfigLoader {
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' },
defaultMeta: { service: `auth:${this.mode}` },
})
}
container.bind<winston.Logger>(TYPES.Auth_Logger).toConstantValue(logger)
@@ -1269,6 +1271,7 @@ export class ContainerConfigLoader {
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
container.get<TriggerEmailBackupForUser>(TYPES.Auth_TriggerEmailBackupForUser),
container.get<GenerateRecoveryCodes>(TYPES.Auth_GenerateRecoveryCodes),
),
)
container
@@ -1281,6 +1284,21 @@ export class ContainerConfigLoader {
),
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container
.bind<FixStorageQuotaForUser>(TYPES.Auth_FixStorageQuotaForUser)
.toConstantValue(
new FixStorageQuotaForUser(
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
container.get<GetRegularSubscriptionForUser>(TYPES.Auth_GetRegularSubscriptionForUser),
container.get<GetSharedSubscriptionForUser>(TYPES.Auth_GetSharedSubscriptionForUser),
container.get<SetSubscriptionSettingValue>(TYPES.Auth_SetSubscriptionSettingValue),
container.get<ListSharedSubscriptionInvitations>(TYPES.Auth_ListSharedSubscriptionInvitations),
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
@@ -1540,6 +1558,14 @@ export class ContainerConfigLoader {
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
),
)
container
.bind<FileQuotaRecalculatedEventHandler>(TYPES.Auth_FileQuotaRecalculatedEventHandler)
.toConstantValue(
new FileQuotaRecalculatedEventHandler(
container.get<UpdateStorageQuotaUsedForUser>(TYPES.Auth_UpdateStorageQuotaUsedForUser),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Auth_AccountDeletionRequestedEventHandler)],
@@ -1577,6 +1603,10 @@ export class ContainerConfigLoader {
container.get(TYPES.Auth_UserDesignatedAsSurvivorInSharedVaultEventHandler),
],
['USER_INVITED_TO_SHARED_VAULT', container.get(TYPES.Auth_UserInvitedToSharedVaultEventHandler)],
[
'FILE_QUOTA_RECALCULATED',
container.get<FileQuotaRecalculatedEventHandler>(TYPES.Auth_FileQuotaRecalculatedEventHandler),
],
])
if (isConfiguredForHomeServer) {

View File

@@ -170,6 +170,7 @@ const TYPES = {
Auth_TriggerEmailBackupForAllUsers: Symbol.for('Auth_TriggerEmailBackupForAllUsers'),
Auth_DeleteAccountsFromCSVFile: Symbol.for('Auth_DeleteAccountsFromCSVFile'),
Auth_RenewSharedSubscriptions: Symbol.for('Auth_RenewSharedSubscriptions'),
Auth_FixStorageQuotaForUser: Symbol.for('Auth_FixStorageQuotaForUser'),
// Handlers
Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'),
Auth_AccountDeletionVerificationPassedEventHandler: Symbol.for('Auth_AccountDeletionVerificationPassedEventHandler'),
@@ -203,6 +204,7 @@ const TYPES = {
'Auth_UserDesignatedAsSurvivorInSharedVaultEventHandler',
),
Auth_UserInvitedToSharedVaultEventHandler: Symbol.for('Auth_UserInvitedToSharedVaultEventHandler'),
Auth_FileQuotaRecalculatedEventHandler: Symbol.for('Auth_FileQuotaRecalculatedEventHandler'),
// Services
Auth_DeviceDetector: Symbol.for('Auth_DeviceDetector'),
Auth_SessionService: Symbol.for('Auth_SessionService'),

View File

@@ -27,6 +27,7 @@ describe('AuthenticationMethodResolver', () => {
beforeEach(() => {
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
logger.info = jest.fn()
user = {} as jest.Mocked<User>

View File

@@ -36,6 +36,10 @@ export class AuthenticationMethodResolver implements AuthenticationMethodResolve
}
const userUuid = userUuidOrError.getValue()
this.logger.debug('User utilizing JWT authentication method.', {
userId: userUuid.value,
})
return {
type: 'jwt',
user: await this.userRepository.findOneByUuid(userUuid),

View File

@@ -21,6 +21,7 @@ import {
SessionCreatedEvent,
SessionRefreshedEvent,
AccountDeletionVerificationRequestedEvent,
FileQuotaRecalculationRequestedEvent,
} from '@standardnotes/domain-events'
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
import { TimerInterface } from '@standardnotes/time'
@@ -33,6 +34,20 @@ import { KeyParamsData } from '@standardnotes/responses'
@injectable()
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Auth_Timer) private timer: TimerInterface) {}
createFileQuotaRecalculationRequestedEvent(dto: { userUuid: string }): FileQuotaRecalculationRequestedEvent {
return {
type: 'FILE_QUOTA_RECALCULATION_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Auth,
},
payload: dto,
}
}
createAccountDeletionVerificationRequestedEvent(dto: {
userUuid: string
@@ -159,6 +174,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
level: string
body: string
subject: string
userUuid?: string
}): EmailRequestedEvent {
return {
type: 'EMAIL_REQUESTED',

View File

@@ -19,11 +19,13 @@ import {
SessionCreatedEvent,
SessionRefreshedEvent,
AccountDeletionVerificationRequestedEvent,
FileQuotaRecalculationRequestedEvent,
} from '@standardnotes/domain-events'
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
import { KeyParamsData } from '@standardnotes/responses'
export interface DomainEventFactoryInterface {
createFileQuotaRecalculationRequestedEvent(dto: { userUuid: string }): FileQuotaRecalculationRequestedEvent
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: JSONString }): WebSocketMessageRequestedEvent
createEmailRequestedEvent(dto: {
userEmail: string
@@ -31,6 +33,7 @@ export interface DomainEventFactoryInterface {
level: string
body: string
subject: string
userUuid?: string
}): EmailRequestedEvent
createListedAccountRequestedEvent(userUuid: string, userEmail: string): ListedAccountRequestedEvent
createUserRegisteredEvent(dto: {

View File

@@ -19,7 +19,7 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
async handle(event: AccountDeletionRequestedEvent): Promise<void> {
const userUuidOrError = Uuid.create(event.payload.userUuid)
if (userUuidOrError.isFailed()) {
this.logger.warn(`Could not find user with uuid: ${event.payload.userUuid}`)
this.logger.warn('Could not find user.', { userId: event.payload.userUuid })
return
}
@@ -28,7 +28,7 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
this.logger.warn(`Could not find user with uuid: ${userUuid.value}`)
this.logger.warn('Could not find user.', { userId: userUuid.value })
return
}
@@ -37,7 +37,9 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
await this.userRepository.remove(user)
this.logger.info(`Finished account cleanup for user: ${userUuid.value}`)
this.logger.info('Finished account cleanup.', {
userId: userUuid.value,
})
}
private async removeSessions(userUuid: string): Promise<void> {

View File

@@ -15,7 +15,9 @@ export class EmailSubscriptionUnsubscribedEventHandler implements DomainEventHan
})
if (result.isFailed()) {
this.logger.error(`Failed to disable email setting for user ${event.payload.userEmail}: ${result.getError()}`)
this.logger.error(`Failed to disable email setting for user: ${result.getError()}`, {
userId: event.payload.userEmail,
})
}
}
}

View File

@@ -0,0 +1,38 @@
import { DomainEventHandlerInterface, FileQuotaRecalculatedEvent } from '@standardnotes/domain-events'
import { UpdateStorageQuotaUsedForUser } from '../UseCase/UpdateStorageQuotaUsedForUser/UpdateStorageQuotaUsedForUser'
import { Logger } from 'winston'
export class FileQuotaRecalculatedEventHandler implements DomainEventHandlerInterface {
constructor(
private updateStorageQuota: UpdateStorageQuotaUsedForUser,
private logger: Logger,
) {}
async handle(event: FileQuotaRecalculatedEvent): Promise<void> {
this.logger.info('Updating storage quota for user...', {
userId: event.payload.userUuid,
totalFileByteSize: event.payload.totalFileByteSize,
codeTag: 'FileQuotaRecalculatedEventHandler',
})
const result = await this.updateStorageQuota.execute({
userUuid: event.payload.userUuid,
bytesUsed: event.payload.totalFileByteSize,
})
if (result.isFailed()) {
this.logger.error('Could not update storage quota', {
userId: event.payload.userUuid,
codeTag: 'FileQuotaRecalculatedEventHandler',
})
return
}
this.logger.info('Storage quota updated', {
userId: event.payload.userUuid,
totalFileByteSize: event.payload.totalFileByteSize,
codeTag: 'FileQuotaRecalculatedEventHandler',
})
}
}

View File

@@ -35,6 +35,7 @@ export class UserInvitedToSharedVaultEventHandler implements DomainEventHandlerI
subject: getSubject(),
messageIdentifier: 'USER_INVITED_TO_SHARED_VAULT',
userEmail: user.email,
userUuid: user.uuid,
}),
)
}

View File

@@ -1,3 +1,4 @@
import { SubscriptionPlanName } from '@standardnotes/domain-core'
import { UserSubscription } from './UserSubscription'
import { UserSubscriptionType } from './UserSubscriptionType'
@@ -7,6 +8,9 @@ export interface UserSubscriptionRepositoryInterface {
findOneByUserUuid(userUuid: string): Promise<UserSubscription | null>
findOneByUserUuidAndType(userUuid: string, type: UserSubscriptionType): Promise<UserSubscription | null>
findByUserUuid(userUuid: string): Promise<UserSubscription[]>
countByPlanName(planNames: SubscriptionPlanName[]): Promise<number>
findByPlanName(planNames: SubscriptionPlanName[], offset: number, limit: number): Promise<UserSubscription[]>
findActiveByType(type: UserSubscriptionType): Promise<UserSubscription[]>
findOneByUserUuidAndSubscriptionId(userUuid: string, subscriptionId: number): Promise<UserSubscription | null>
findBySubscriptionIdAndType(subscriptionId: number, type: UserSubscriptionType): Promise<UserSubscription[]>
findBySubscriptionId(subscriptionId: number): Promise<UserSubscription[]>

View File

@@ -0,0 +1,204 @@
import { DomainEventPublisherInterface, FileQuotaRecalculationRequestedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { GetRegularSubscriptionForUser } from '../GetRegularSubscriptionForUser/GetRegularSubscriptionForUser'
import { GetSharedSubscriptionForUser } from '../GetSharedSubscriptionForUser/GetSharedSubscriptionForUser'
import { ListSharedSubscriptionInvitations } from '../ListSharedSubscriptionInvitations/ListSharedSubscriptionInvitations'
import { SetSubscriptionSettingValue } from '../SetSubscriptionSettingValue/SetSubscriptionSettingValue'
import { FixStorageQuotaForUser } from './FixStorageQuotaForUser'
import { User } from '../../User/User'
import { Result } from '@standardnotes/domain-core'
import { UserSubscription } from '../../Subscription/UserSubscription'
import { InvitationStatus } from '../../SharedSubscription/InvitationStatus'
import { SharedSubscriptionInvitation } from '../../SharedSubscription/SharedSubscriptionInvitation'
describe('FixStorageQuotaForUser', () => {
let userRepository: UserRepositoryInterface
let getRegularSubscription: GetRegularSubscriptionForUser
let getSharedSubscriptionForUser: GetSharedSubscriptionForUser
let setSubscriptonSettingValue: SetSubscriptionSettingValue
let listSharedSubscriptionInvitations: ListSharedSubscriptionInvitations
let domainEventFactory: DomainEventFactoryInterface
let domainEventPublisher: DomainEventPublisherInterface
let logger: Logger
const createUseCase = () =>
new FixStorageQuotaForUser(
userRepository,
getRegularSubscription,
getSharedSubscriptionForUser,
setSubscriptonSettingValue,
listSharedSubscriptionInvitations,
domainEventFactory,
domainEventPublisher,
logger,
)
beforeEach(() => {
userRepository = {} as jest.Mocked<UserRepositoryInterface>
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue({
uuid: '00000000-0000-0000-0000-000000000000',
} as jest.Mocked<User>)
getRegularSubscription = {} as jest.Mocked<GetRegularSubscriptionForUser>
getRegularSubscription.execute = jest.fn().mockReturnValue(
Result.ok({
uuid: '00000000-0000-0000-0000-000000000000',
} as jest.Mocked<UserSubscription>),
)
getSharedSubscriptionForUser = {} as jest.Mocked<GetSharedSubscriptionForUser>
getSharedSubscriptionForUser.execute = jest.fn().mockReturnValue(
Result.ok({
uuid: '00000000-0000-0000-0000-000000000000',
} as jest.Mocked<UserSubscription>),
)
setSubscriptonSettingValue = {} as jest.Mocked<SetSubscriptionSettingValue>
setSubscriptonSettingValue.execute = jest.fn().mockReturnValue(Result.ok(Result.ok()))
listSharedSubscriptionInvitations = {} as jest.Mocked<ListSharedSubscriptionInvitations>
listSharedSubscriptionInvitations.execute = jest.fn().mockReturnValue({
invitations: [
{
uuid: '00000000-0000-0000-0000-000000000000',
status: InvitationStatus.Accepted,
inviteeIdentifier: 'test2@test.te',
} as jest.Mocked<SharedSubscriptionInvitation>,
],
})
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createFileQuotaRecalculationRequestedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<FileQuotaRecalculationRequestedEvent>)
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
})
it('should return error result if user cannot be found', async () => {
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(null)
const useCase = createUseCase()
const result = await useCase.execute({
userEmail: 'test@test.te',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error result if regular subscription cannot be found', async () => {
getRegularSubscription.execute = jest.fn().mockReturnValue(Result.fail('test'))
const useCase = createUseCase()
const result = await useCase.execute({
userEmail: 'test@test.te',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error result if shared subscription cannot be found', async () => {
getSharedSubscriptionForUser.execute = jest.fn().mockReturnValue(Result.fail('test'))
const useCase = createUseCase()
const result = await useCase.execute({
userEmail: 'test@test.te',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error result if setting value cannot be set', async () => {
setSubscriptonSettingValue.execute = jest.fn().mockReturnValue(Result.fail('test'))
const useCase = createUseCase()
const result = await useCase.execute({
userEmail: 'test@test.te',
})
expect(result.isFailed()).toBeTruthy()
})
it('should reset storage quota and ask for recalculation for user and all its shared subscriptions', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userEmail: 'test@test.te',
})
expect(result.isFailed()).toBeFalsy()
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(2)
})
it('should return error if the username is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userEmail: '',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error if the invitee username is invalid', async () => {
listSharedSubscriptionInvitations.execute = jest.fn().mockReturnValue({
invitations: [
{
uuid: '00000000-0000-0000-0000-000000000000',
status: InvitationStatus.Accepted,
inviteeIdentifier: '',
} as jest.Mocked<SharedSubscriptionInvitation>,
],
})
const useCase = createUseCase()
const result = await useCase.execute({
userEmail: 'test@test.te',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error if the invitee cannot be found', async () => {
userRepository.findOneByUsernameOrEmail = jest
.fn()
.mockReturnValueOnce({
uuid: '00000000-0000-0000-0000-000000000000',
} as jest.Mocked<User>)
.mockReturnValueOnce(null)
const useCase = createUseCase()
const result = await useCase.execute({
userEmail: 'test@test.te',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error if fails to reset storage quota for the invitee', async () => {
setSubscriptonSettingValue.execute = jest
.fn()
.mockReturnValueOnce(Result.ok())
.mockReturnValueOnce(Result.fail('test'))
const useCase = createUseCase()
const result = await useCase.execute({
userEmail: 'test@test.te',
})
expect(result.isFailed()).toBeTruthy()
})
})

View File

@@ -0,0 +1,121 @@
import { Result, SettingName, UseCaseInterface, Username } from '@standardnotes/domain-core'
import { FixStorageQuotaForUserDTO } from './FixStorageQuotaForUserDTO'
import { GetRegularSubscriptionForUser } from '../GetRegularSubscriptionForUser/GetRegularSubscriptionForUser'
import { SetSubscriptionSettingValue } from '../SetSubscriptionSettingValue/SetSubscriptionSettingValue'
import { ListSharedSubscriptionInvitations } from '../ListSharedSubscriptionInvitations/ListSharedSubscriptionInvitations'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { InvitationStatus } from '../../SharedSubscription/InvitationStatus'
import { GetSharedSubscriptionForUser } from '../GetSharedSubscriptionForUser/GetSharedSubscriptionForUser'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { Logger } from 'winston'
export class FixStorageQuotaForUser implements UseCaseInterface<void> {
constructor(
private userRepository: UserRepositoryInterface,
private getRegularSubscription: GetRegularSubscriptionForUser,
private getSharedSubscriptionForUser: GetSharedSubscriptionForUser,
private setSubscriptonSettingValue: SetSubscriptionSettingValue,
private listSharedSubscriptionInvitations: ListSharedSubscriptionInvitations,
private domainEventFactory: DomainEventFactoryInterface,
private domainEventPublisher: DomainEventPublisherInterface,
private logger: Logger,
) {}
async execute(dto: FixStorageQuotaForUserDTO): Promise<Result<void>> {
const usernameOrError = Username.create(dto.userEmail)
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(`Could not find user with email: ${username.value}`)
}
const regularSubscriptionOrError = await this.getRegularSubscription.execute({
userUuid: user.uuid,
})
if (regularSubscriptionOrError.isFailed()) {
return Result.fail(`Could not find regular user subscription for user with uuid: ${user.uuid}`)
}
const regularSubscription = regularSubscriptionOrError.getValue()
const result = await this.setSubscriptonSettingValue.execute({
userSubscriptionUuid: regularSubscription.uuid,
settingName: SettingName.NAMES.FileUploadBytesUsed,
value: '0',
})
if (result.isFailed()) {
return Result.fail(result.getError())
}
this.logger.info('Resetted storage quota for user', {
userId: user.uuid,
})
await this.domainEventPublisher.publish(
this.domainEventFactory.createFileQuotaRecalculationRequestedEvent({
userUuid: user.uuid,
}),
)
this.logger.info('Requested storage quota recalculation for user', {
userId: user.uuid,
})
const invitationsResult = await this.listSharedSubscriptionInvitations.execute({
inviterEmail: user.email,
})
const acceptedInvitations = invitationsResult.invitations.filter(
(invitation) => invitation.status === InvitationStatus.Accepted,
)
for (const invitation of acceptedInvitations) {
const inviteeUsernameOrError = Username.create(invitation.inviteeIdentifier)
if (inviteeUsernameOrError.isFailed()) {
return Result.fail(inviteeUsernameOrError.getError())
}
const inviteeUsername = inviteeUsernameOrError.getValue()
const invitee = await this.userRepository.findOneByUsernameOrEmail(inviteeUsername)
if (invitee === null) {
return Result.fail(`Could not find user with email: ${inviteeUsername.value}`)
}
const invitationSubscriptionOrError = await this.getSharedSubscriptionForUser.execute({
userUuid: invitee.uuid,
})
if (invitationSubscriptionOrError.isFailed()) {
return Result.fail(`Could not find shared subscription for user with email: ${invitation.inviteeIdentifier}`)
}
const invitationSubscription = invitationSubscriptionOrError.getValue()
const result = await this.setSubscriptonSettingValue.execute({
userSubscriptionUuid: invitationSubscription.uuid,
settingName: SettingName.NAMES.FileUploadBytesUsed,
value: '0',
})
if (result.isFailed()) {
return Result.fail(result.getError())
}
this.logger.info('Resetted storage quota for user', {
userId: invitee.uuid,
})
await this.domainEventPublisher.publish(
this.domainEventFactory.createFileQuotaRecalculationRequestedEvent({
userUuid: invitee.uuid,
}),
)
this.logger.info('Requested storage quota recalculation for user', {
userId: invitee.uuid,
})
}
return Result.ok()
}
}

View File

@@ -0,0 +1,3 @@
export interface FixStorageQuotaForUserDTO {
userEmail: string
}

View File

@@ -8,6 +8,7 @@ import { SharedSubscriptionInvitation } from '../../SharedSubscription/SharedSub
import { InviteeIdentifierType } from '../../SharedSubscription/InviteeIdentifierType'
import { User } from '../../User/User'
import { InvitationStatus } from '../../SharedSubscription/InvitationStatus'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
describe('RenewSharedSubscriptions', () => {
let listSharedSubscriptionInvitations: ListSharedSubscriptionInvitations
@@ -17,6 +18,7 @@ describe('RenewSharedSubscriptions', () => {
let logger: Logger
let sharedSubscriptionInvitation: SharedSubscriptionInvitation
let user: User
let roleService: RoleServiceInterface
const createUseCase = () =>
new RenewSharedSubscriptions(
@@ -24,6 +26,7 @@ describe('RenewSharedSubscriptions', () => {
sharedSubscriptionInvitationRepository,
userSubscriptionRepository,
userRepository,
roleService,
logger,
)
@@ -48,8 +51,12 @@ describe('RenewSharedSubscriptions', () => {
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
userSubscriptionRepository.save = jest.fn()
roleService = {} as jest.Mocked<RoleServiceInterface>
roleService.addUserRoleBasedOnSubscription = jest.fn()
userRepository = {} as jest.Mocked<UserRepositoryInterface>
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(user)
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
logger = {} as jest.Mocked<Logger>
logger.error = jest.fn()
@@ -71,7 +78,7 @@ describe('RenewSharedSubscriptions', () => {
expect(userSubscriptionRepository.save).toBeCalledTimes(1)
})
it('should log error if user not found', async () => {
it('should log error if user not found by email', async () => {
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(null)
const useCase = createUseCase()
@@ -88,6 +95,42 @@ describe('RenewSharedSubscriptions', () => {
expect(logger.error).toBeCalledTimes(1)
})
it('should log error if user not found by uuid', async () => {
sharedSubscriptionInvitation.inviteeIdentifierType = InviteeIdentifierType.Uuid
sharedSubscriptionInvitation.inviteeIdentifier = '00000000-0000-0000-0000-000000000000'
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
const useCase = createUseCase()
const result = await useCase.execute({
inviterEmail: 'inviter@test.te',
newSubscriptionId: 123,
newSubscriptionName: 'test',
newSubscriptionExpiresAt: 123,
timestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(logger.error).toBeCalledTimes(1)
})
it('should log error if user not found by unknown identifier type', async () => {
sharedSubscriptionInvitation.inviteeIdentifierType = 'unknown' as InviteeIdentifierType
const useCase = createUseCase()
const result = await useCase.execute({
inviterEmail: 'inviter@test.te',
newSubscriptionId: 123,
newSubscriptionName: 'test',
newSubscriptionExpiresAt: 123,
timestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(logger.error).toBeCalledTimes(1)
})
it('should log error if error occurs', async () => {
userRepository.findOneByUsernameOrEmail = jest.fn().mockImplementation(() => {
throw new Error('test')
@@ -125,6 +168,24 @@ describe('RenewSharedSubscriptions', () => {
expect(logger.error).toBeCalledTimes(1)
})
it('should log error if uuid is invalid', async () => {
sharedSubscriptionInvitation.inviteeIdentifierType = InviteeIdentifierType.Uuid
sharedSubscriptionInvitation.inviteeIdentifier = 'invalid'
const useCase = createUseCase()
const result = await useCase.execute({
inviterEmail: 'inviter@test.te',
newSubscriptionId: 123,
newSubscriptionName: 'test',
newSubscriptionExpiresAt: 123,
timestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(logger.error).toBeCalledTimes(1)
})
it('should renew shared subscription for invitations by user uuid', async () => {
sharedSubscriptionInvitation.inviteeIdentifierType = InviteeIdentifierType.Uuid
sharedSubscriptionInvitation.inviteeIdentifier = '00000000-0000-0000-0000-000000000000'

View File

@@ -1,4 +1,4 @@
import { Result, UseCaseInterface, Username } from '@standardnotes/domain-core'
import { Result, UseCaseInterface, Username, Uuid } from '@standardnotes/domain-core'
import { Logger } from 'winston'
import { RenewSharedSubscriptionsDTO } from './RenewSharedSubscriptionsDTO'
@@ -10,6 +10,8 @@ import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { InviteeIdentifierType } from '../../SharedSubscription/InviteeIdentifierType'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
import { User } from '../../User/User'
export class RenewSharedSubscriptions implements UseCaseInterface<void> {
constructor(
@@ -17,6 +19,7 @@ export class RenewSharedSubscriptions implements UseCaseInterface<void> {
private sharedSubscriptionInvitationRepository: SharedSubscriptionInvitationRepositoryInterface,
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
private userRepository: UserRepositoryInterface,
private roleService: RoleServiceInterface,
private logger: Logger,
) {}
@@ -31,8 +34,8 @@ export class RenewSharedSubscriptions implements UseCaseInterface<void> {
for (const invitation of acceptedInvitations) {
try {
const userUuid = await this.getInviteeUserUuid(invitation.inviteeIdentifier, invitation.inviteeIdentifierType)
if (userUuid === null) {
const user = await this.getInviteeUserUuid(invitation.inviteeIdentifier, invitation.inviteeIdentifierType)
if (user === null) {
this.logger.error(
`[SUBSCRIPTION: ${dto.newSubscriptionId}] Could not renew shared subscription for invitation: ${invitation.uuid}: Could not find user with identifier: ${invitation.inviteeIdentifier}`,
)
@@ -42,11 +45,13 @@ export class RenewSharedSubscriptions implements UseCaseInterface<void> {
await this.createSharedSubscription({
subscriptionId: dto.newSubscriptionId,
subscriptionName: dto.newSubscriptionName,
userUuid,
userUuid: user.uuid,
timestamp: dto.timestamp,
subscriptionExpiresAt: dto.newSubscriptionExpiresAt,
})
await this.roleService.addUserRoleBasedOnSubscription(user, dto.newSubscriptionName)
invitation.subscriptionId = dto.newSubscriptionId
invitation.updatedAt = dto.timestamp
@@ -83,7 +88,7 @@ export class RenewSharedSubscriptions implements UseCaseInterface<void> {
return this.userSubscriptionRepository.save(subscription)
}
private async getInviteeUserUuid(inviteeIdentifier: string, inviteeIdentifierType: string): Promise<string | null> {
private async getInviteeUserUuid(inviteeIdentifier: string, inviteeIdentifierType: string): Promise<User | null> {
if (inviteeIdentifierType === InviteeIdentifierType.Email) {
const usernameOrError = Username.create(inviteeIdentifier)
if (usernameOrError.isFailed()) {
@@ -91,14 +96,16 @@ export class RenewSharedSubscriptions implements UseCaseInterface<void> {
}
const username = usernameOrError.getValue()
const user = await this.userRepository.findOneByUsernameOrEmail(username)
if (user === null) {
return this.userRepository.findOneByUsernameOrEmail(username)
} else if (inviteeIdentifierType === InviteeIdentifierType.Uuid) {
const uuidOrError = Uuid.create(inviteeIdentifier)
if (uuidOrError.isFailed()) {
return null
}
return user.uuid
const uuid = uuidOrError.getValue()
return this.userRepository.findOneByUuid(uuid)
}
return inviteeIdentifier
return null
}
}

View File

@@ -131,6 +131,7 @@ export class SignIn implements UseCaseInterface {
),
messageIdentifier: 'SIGN_IN',
subject: getSubject(user.email),
userUuid: user.uuid,
}),
)
} catch (error) {

View File

@@ -39,7 +39,9 @@ export class TriggerEmailBackupForAllUsers implements UseCaseInterface<void> {
})
/* istanbul ignore next */
if (result.isFailed()) {
this.logger.error(`Failed to trigger email backup for user ${setting.props.userUuid.value}`)
this.logger.error(`Failed to trigger email backup for user: ${result.getError()}`, {
userId: setting.props.userUuid.value,
})
failedUsers++
}
}

View File

@@ -7,6 +7,7 @@ import {
import { EmailBackupFrequency, LogSessionUserAgentOption, MuteMarketingEmailsOption } from '@standardnotes/settings'
import { SettingName, Result } from '@standardnotes/domain-core'
import { GenerateRecoveryCodes } from '../GenerateRecoveryCodes/GenerateRecoveryCodes'
import { TriggerPostSettingUpdateActions } from './TriggerPostSettingUpdateActions'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { TriggerEmailBackupForUser } from '../TriggerEmailBackupForUser/TriggerEmailBackupForUser'
@@ -15,11 +16,20 @@ describe('TriggerPostSettingUpdateActions', () => {
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
let triggerEmailBackupForUser: TriggerEmailBackupForUser
let generateRecoveryCodes: GenerateRecoveryCodes
const createUseCase = () =>
new TriggerPostSettingUpdateActions(domainEventPublisher, domainEventFactory, triggerEmailBackupForUser)
new TriggerPostSettingUpdateActions(
domainEventPublisher,
domainEventFactory,
triggerEmailBackupForUser,
generateRecoveryCodes,
)
beforeEach(() => {
generateRecoveryCodes = {} as jest.Mocked<GenerateRecoveryCodes>
generateRecoveryCodes.execute = jest.fn().mockReturnValue(Result.ok())
triggerEmailBackupForUser = {} as jest.Mocked<TriggerEmailBackupForUser>
triggerEmailBackupForUser.execute = jest.fn().mockReturnValue(Result.ok())
@@ -101,4 +111,15 @@ describe('TriggerPostSettingUpdateActions', () => {
username: 'test@test.te',
})
})
it('should generate new recovery codes upon enabling mfa setting', async () => {
await createUseCase().execute({
updatedSettingName: SettingName.NAMES.MfaSecret,
userUuid: '4-5-6',
userEmail: 'test@test.te',
unencryptedValue: '123',
})
expect(generateRecoveryCodes.execute).toHaveBeenCalled()
})
})

View File

@@ -5,6 +5,7 @@ import { EmailBackupFrequency, LogSessionUserAgentOption } from '@standardnotes/
import { TriggerPostSettingUpdateActionsDTO } from './TriggerPostSettingUpdateActionsDTO'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { TriggerEmailBackupForUser } from '../TriggerEmailBackupForUser/TriggerEmailBackupForUser'
import { GenerateRecoveryCodes } from '../GenerateRecoveryCodes/GenerateRecoveryCodes'
export class TriggerPostSettingUpdateActions implements UseCaseInterface<void> {
private readonly emailSettingToSubscriptionRejectionLevelMap: Map<string, string> = new Map([
@@ -18,6 +19,7 @@ export class TriggerPostSettingUpdateActions implements UseCaseInterface<void> {
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private triggerEmailBackupForUser: TriggerEmailBackupForUser,
private generateRecoveryCodes: GenerateRecoveryCodes,
) {}
async execute(dto: TriggerPostSettingUpdateActionsDTO): Promise<Result<void>> {
@@ -35,6 +37,12 @@ export class TriggerPostSettingUpdateActions implements UseCaseInterface<void> {
await this.triggerSessionUserAgentCleanup(dto.userEmail, dto.userUuid)
}
if (this.isEnablingMFASetting(dto.updatedSettingName, dto.unencryptedValue)) {
await this.generateRecoveryCodes.execute({
userUuid: dto.userUuid,
})
}
return Result.ok()
}
@@ -54,6 +62,10 @@ export class TriggerPostSettingUpdateActions implements UseCaseInterface<void> {
)
}
private isEnablingMFASetting(settingName: string, newValue: string | null): boolean {
return settingName === SettingName.NAMES.MfaSecret && newValue !== null
}
private isDisablingSessionUserAgentLogging(settingName: string, newValue: string | null): boolean {
return SettingName.NAMES.LogSessionUserAgent === settingName && LogSessionUserAgentOption.Disabled === newValue
}

View File

@@ -163,6 +163,20 @@ describe('UpdateStorageQuotaUsedForUser', () => {
})
})
it('should not subtract below 0', async () => {
const result = await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
bytesUsed: -1234,
})
expect(result.isFailed()).toBeFalsy()
expect(setSubscriptonSettingValue.execute).toHaveBeenCalledWith({
settingName: 'FILE_UPLOAD_BYTES_USED',
value: '0',
userSubscriptionUuid: '00000000-0000-0000-0000-000000000000',
})
})
it('should update a bytes used setting on both regular and shared subscription', async () => {
const result = await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',

View File

@@ -68,10 +68,13 @@ export class UpdateStorageQuotaUsedForUser implements UseCaseInterface<void> {
bytesAlreadyUsed = bytesUsedSetting.setting.props.value as string
}
const bytesUsedNewTotal = +bytesAlreadyUsed + bytesUsed
const bytesUsedValue = bytesUsedNewTotal < 0 ? 0 : bytesUsedNewTotal
const result = await this.setSubscriptonSettingValue.execute({
userSubscriptionUuid: subscription.uuid,
settingName: SettingName.NAMES.FileUploadBytesUsed,
value: (+bytesAlreadyUsed + bytesUsed).toString(),
value: bytesUsedValue.toString(),
})
/* istanbul ignore next */

View File

@@ -26,6 +26,8 @@ export class LockMiddleware extends BaseMiddleware {
message: usernameOrError.getError(),
},
})
return
}
const username = usernameOrError.getValue()

View File

@@ -1,3 +1,4 @@
import { SubscriptionPlanName } from '@standardnotes/domain-core'
import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
import { Repository } from 'typeorm'
@@ -15,6 +16,36 @@ export class TypeORMUserSubscriptionRepository implements UserSubscriptionReposi
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
) {}
async findActiveByType(type: UserSubscriptionType): Promise<UserSubscription[]> {
return await this.ormRepository
.createQueryBuilder()
.where('ends_at > :timestamp', { timestamp: this.timer.getTimestampInMicroseconds() })
.andWhere('subscription_type = :type', { type })
.orderBy('created_at', 'ASC')
.getMany()
}
async countByPlanName(planNames: SubscriptionPlanName[]): Promise<number> {
return await this.ormRepository
.createQueryBuilder()
.where('plan_name IN (:...planNames)', {
planNames: planNames.map((planName) => planName.value),
})
.getCount()
}
async findByPlanName(planNames: SubscriptionPlanName[], offset: number, limit: number): Promise<UserSubscription[]> {
return await this.ormRepository
.createQueryBuilder()
.where('plan_name IN (:...planNames)', {
planNames: planNames.map((planName) => planName.value),
})
.orderBy('created_at', 'ASC')
.skip(offset)
.take(limit)
.getMany()
}
async countActiveSubscriptions(): Promise<number> {
return await this.ormRepository
.createQueryBuilder()

View File

@@ -22,7 +22,7 @@ export class WebSocketsClientService implements ClientServiceInterface {
(await user.roles).map((role) => role.name),
)
this.logger.info(`[WebSockets] Requesting message ${event.type} to user ${user.uuid}`)
this.logger.debug(`[WebSockets] Requesting message ${event.type} to user ${user.uuid}`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createWebSocketMessageRequestedEvent({

View File

@@ -1,20 +1,92 @@
import * as grpc from '@grpc/grpc-js'
import { Status } from '@grpc/grpc-js/build/src/constants'
import { AuthorizationHeader, ISessionsServer, SessionValidationResponse } from '@standardnotes/grpc'
import {
AuthorizationHeader,
ConnectionValidationResponse,
IAuthServer,
SessionValidationResponse,
WebsocketConnectionAuthorizationHeader,
} from '@standardnotes/grpc'
import { AuthenticateRequest } from '../../Domain/UseCase/AuthenticateRequest'
import { User } from '../../Domain/User/User'
import { CreateCrossServiceToken } from '../../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
import { Logger } from 'winston'
import { ErrorTag } from '@standardnotes/responses'
import { TokenDecoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security'
export class SessionsServer implements ISessionsServer {
export class AuthServer implements IAuthServer {
constructor(
private authenticateRequest: AuthenticateRequest,
private createCrossServiceToken: CreateCrossServiceToken,
protected tokenDecoder: TokenDecoderInterface<WebSocketConnectionTokenData>,
private logger: Logger,
) {}
async validateWebsocket(
call: grpc.ServerUnaryCall<WebsocketConnectionAuthorizationHeader, ConnectionValidationResponse>,
callback: grpc.sendUnaryData<ConnectionValidationResponse>,
): Promise<void> {
try {
const token: WebSocketConnectionTokenData | undefined = this.tokenDecoder.decodeToken(call.request.getToken())
if (token === undefined) {
const metadata = new grpc.Metadata()
metadata.set('x-auth-error-message', 'Invalid authorization token.')
metadata.set('x-auth-error-tag', ErrorTag.AuthInvalid)
metadata.set('x-auth-error-response-code', '401')
return callback(
{
code: Status.PERMISSION_DENIED,
message: 'Invalid authorization token.',
name: ErrorTag.AuthInvalid,
metadata,
},
null,
)
}
const resultOrError = await this.createCrossServiceToken.execute({
userUuid: token.userUuid,
sessionUuid: token.sessionUuid,
})
if (resultOrError.isFailed()) {
const metadata = new grpc.Metadata()
metadata.set('x-auth-error-message', resultOrError.getError())
metadata.set('x-auth-error-response-code', '400')
return callback(
{
code: Status.INVALID_ARGUMENT,
message: resultOrError.getError(),
name: 'INVALID_ARGUMENT',
metadata,
},
null,
)
}
const response = new ConnectionValidationResponse()
response.setCrossServiceToken(resultOrError.getValue())
this.logger.debug('[SessionsServer] Websocket connection validated via gRPC')
callback(null, response)
} catch (error) {
this.logger.error(`[SessionsServer] Error validating websocket connection via gRPC: ${(error as Error).message}`)
callback(
{
code: Status.UNKNOWN,
message: 'An error occurred while validating websocket connection',
name: 'UNKNOWN',
},
null,
)
}
}
async validate(
call: grpc.ServerUnaryCall<AuthorizationHeader, SessionValidationResponse>,
callback: grpc.sendUnaryData<SessionValidationResponse>,

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.52.2](https://github.com/standardnotes/server/compare/@standardnotes/common@1.52.1...@standardnotes/common@1.52.2) (2023-12-28)
**Note:** Version bump only for package @standardnotes/common
## [1.52.1](https://github.com/standardnotes/server/compare/@standardnotes/common@1.52.0...@standardnotes/common@1.52.1) (2023-11-27)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/common",
"version": "1.52.1",
"version": "1.52.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -41,6 +41,6 @@
"typescript": "^5.0.4"
},
"dependencies": {
"reflect-metadata": "^0.1.13"
"reflect-metadata": "^0.2.1"
}
}

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.10](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.22.9...@standardnotes/domain-events-infra@1.22.10) (2023-12-29)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.22.9](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.22.8...@standardnotes/domain-events-infra@1.22.9) (2023-12-28)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.22.8](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.22.7...@standardnotes/domain-events-infra@1.22.8) (2023-12-28)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.22.7](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.22.6...@standardnotes/domain-events-infra@1.22.7) (2023-12-26)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.22.6](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.22.5...@standardnotes/domain-events-infra@1.22.6) (2023-12-14)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.22.5](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.22.4...@standardnotes/domain-events-infra@1.22.5) (2023-12-11)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.22.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.22.3...@standardnotes/domain-events-infra@1.22.4) (2023-12-01)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.22.4",
"version": "1.22.10",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -48,7 +48,7 @@
"@standardnotes/domain-events": "workspace:*",
"ioredis": "^5.2.4",
"opentelemetry-instrumentation-typeorm": "^0.39.1",
"reflect-metadata": "^0.1.13",
"reflect-metadata": "^0.2.1",
"sqs-consumer": "^8.1.0",
"winston": "^3.8.1"
},

View File

@@ -26,7 +26,7 @@ describe('RedisDomainEventPublisher', () => {
expect(redisClient.publish).toHaveBeenCalledWith(
'events',
'eJyrViqpLEhVslIKcQ0OUdJRKkiszMlPTFGyqlZKy88HiiclFinV1gIA9tQMhA==',
'eJyrViqpLEhVslIKcQ0OUdJRKkiszMlPTFGyqlZKy89XslJKSixSqq0FAPbUDIQ=',
)
})
})

View File

@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.139.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.139.2...@standardnotes/domain-events@2.139.3) (2023-12-29)
### Bug Fixes
* cleanup revision requests ([7e8d7f6](https://github.com/standardnotes/server/commit/7e8d7f6874bb1db55ee6feb9e128c684a6900189))
## [2.139.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.139.1...@standardnotes/domain-events@2.139.2) (2023-12-28)
**Note:** Version bump only for package @standardnotes/domain-events
## [2.139.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.139.0...@standardnotes/domain-events@2.139.1) (2023-12-26)
### Bug Fixes
* stop revisions propagation ([#1008](https://github.com/standardnotes/server/issues/1008)) ([7962b24](https://github.com/standardnotes/server/commit/7962b245b51703ff7d33728dee117b2074f2692a))
# [2.139.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.138.2...@standardnotes/domain-events@2.139.0) (2023-12-14)
### Features
* add procedure for recalculating file quota for user ([#980](https://github.com/standardnotes/server/issues/980)) ([de4fcf9](https://github.com/standardnotes/server/commit/de4fcf9a4c308ad7d71c42fe5c27af18b8614e1a))
## [2.138.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.138.1...@standardnotes/domain-events@2.138.2) (2023-12-11)
### Bug Fixes
* **syncing-server:** add user uuid for the emails requesting backup ([7b0ea0a](https://github.com/standardnotes/server/commit/7b0ea0a06975902e01951b13c84e941827dedd84))
## [2.138.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.138.0...@standardnotes/domain-events@2.138.1) (2023-11-28)
**Note:** Version bump only for package @standardnotes/domain-events

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.138.1",
"version": "2.139.3",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -12,4 +12,5 @@ export interface EmailRequestedEventPayload {
attachmentFileName: string
attachmentContentType: string
}>
userUuid?: string
}

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { FileQuotaRecalculatedEventPayload } from './FileQuotaRecalculatedEventPayload'
export interface FileQuotaRecalculatedEvent extends DomainEventInterface {
type: 'FILE_QUOTA_RECALCULATED'
payload: FileQuotaRecalculatedEventPayload
}

View File

@@ -0,0 +1,4 @@
export interface FileQuotaRecalculatedEventPayload {
userUuid: string
totalFileByteSize: number
}

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { FileQuotaRecalculationRequestedEventPayload } from './FileQuotaRecalculationRequestedEventPayload'
export interface FileQuotaRecalculationRequestedEvent extends DomainEventInterface {
type: 'FILE_QUOTA_RECALCULATION_REQUESTED'
payload: FileQuotaRecalculationRequestedEventPayload
}

View File

@@ -0,0 +1,3 @@
export interface FileQuotaRecalculationRequestedEventPayload {
userUuid: string
}

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { ItemDeletedEventPayload } from './ItemDeletedEventPayload'
export interface ItemDeletedEvent extends DomainEventInterface {
type: 'ITEM_DELETED'
payload: ItemDeletedEventPayload
}

View File

@@ -0,0 +1,4 @@
export interface ItemDeletedEventPayload {
userUuid: string
itemUuid: string
}

View File

@@ -30,12 +30,18 @@ export * from './Event/ExitDiscountWithdrawRequestedEvent'
export * from './Event/ExitDiscountWithdrawRequestedEventPayload'
export * from './Event/ExtensionKeyGrantedEvent'
export * from './Event/ExtensionKeyGrantedEventPayload'
export * from './Event/FileQuotaRecalculatedEvent'
export * from './Event/FileQuotaRecalculatedEventPayload'
export * from './Event/FileQuotaRecalculationRequestedEvent'
export * from './Event/FileQuotaRecalculationRequestedEventPayload'
export * from './Event/FileRemovedEvent'
export * from './Event/FileRemovedEventPayload'
export * from './Event/FileUploadedEvent'
export * from './Event/FileUploadedEventPayload'
export * from './Event/ItemDumpedEvent'
export * from './Event/ItemDumpedEventPayload'
export * from './Event/ItemDeletedEvent'
export * from './Event/ItemDeletedEventPayload'
export * from './Event/ItemRemovedFromSharedVaultEvent'
export * from './Event/ItemRemovedFromSharedVaultEventPayload'
export * from './Event/ItemRevisionCreationRequestedEvent'

View File

@@ -3,6 +3,44 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.37.5](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.37.4...@standardnotes/files-server@1.37.5) (2023-12-29)
**Note:** Version bump only for package @standardnotes/files-server
## [1.37.4](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.37.3...@standardnotes/files-server@1.37.4) (2023-12-28)
**Note:** Version bump only for package @standardnotes/files-server
## [1.37.3](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.37.2...@standardnotes/files-server@1.37.3) (2023-12-28)
**Note:** Version bump only for package @standardnotes/files-server
## [1.37.2](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.37.1...@standardnotes/files-server@1.37.2) (2023-12-26)
**Note:** Version bump only for package @standardnotes/files-server
## [1.37.1](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.37.0...@standardnotes/files-server@1.37.1) (2023-12-14)
### Bug Fixes
* **files:** list files command ([e6a2160](https://github.com/standardnotes/server/commit/e6a21606a3070bbfbf2de9e4b10daa50c43c3786))
# [1.37.0](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.36.6...@standardnotes/files-server@1.37.0) (2023-12-14)
### Features
* add procedure for recalculating file quota for user ([#980](https://github.com/standardnotes/server/issues/980)) ([de4fcf9](https://github.com/standardnotes/server/commit/de4fcf9a4c308ad7d71c42fe5c27af18b8614e1a))
## [1.36.6](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.36.5...@standardnotes/files-server@1.36.6) (2023-12-11)
**Note:** Version bump only for package @standardnotes/files-server
## [1.36.5](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.36.4...@standardnotes/files-server@1.36.5) (2023-12-07)
### Bug Fixes
* logger meta information ([a2b1323](https://github.com/standardnotes/server/commit/a2b1323568f5ced74b41aa4634340a6ca0668683))
## [1.36.4](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.36.3...@standardnotes/files-server@1.36.4) (2023-12-01)
### Bug Fixes

View File

@@ -1,4 +1,4 @@
FROM node:20.6.1-alpine
FROM node:20.10.0-alpine
RUN apk add --update \
curl \

View File

@@ -19,7 +19,7 @@ import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
const container = new ContainerConfigLoader()
const container = new ContainerConfigLoader('server')
void container.load().then((container) => {
const env: Env = new Env()
env.load()

View File

@@ -9,7 +9,7 @@ import { DomainEventSubscriberInterface } from '@standardnotes/domain-events'
import * as dayjs from 'dayjs'
import * as utc from 'dayjs/plugin/utc'
const container = new ContainerConfigLoader()
const container = new ContainerConfigLoader('worker')
void container.load().then((container) => {
dayjs.extend(utc)

View File

@@ -5,12 +5,10 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-web' )
echo "Starting Web..."
exec node docker/entrypoint-server.js
;;
'start-worker' )
echo "Starting Worker..."
exec node docker/entrypoint-worker.js
;;

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.36.4",
"version": "1.37.5",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -55,7 +55,7 @@
"ioredis": "^5.2.4",
"jsonwebtoken": "^9.0.0",
"prettyjson": "^1.2.5",
"reflect-metadata": "^0.1.13",
"reflect-metadata": "^0.2.1",
"winston": "^3.8.1"
},
"devDependencies": {

View File

@@ -52,8 +52,12 @@ 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'
import { RecalculateQuota } from '../Domain/UseCase/RecalculateQuota/RecalculateQuota'
import { FileQuotaRecalculationRequestedEventHandler } from '../Domain/Handler/FileQuotaRecalculationRequestedEventHandler'
export class ContainerConfigLoader {
constructor(private mode: 'server' | 'worker' = 'server') {}
async load(configuration?: {
directCallDomainEventPublisher?: DirectCallDomainEventPublisher
logger?: Transform
@@ -242,6 +246,15 @@ export class ContainerConfigLoader {
),
)
container.bind<MarkFilesToBeRemoved>(TYPES.Files_MarkFilesToBeRemoved).to(MarkFilesToBeRemoved)
container
.bind<RecalculateQuota>(TYPES.Files_RecalculateQuota)
.toConstantValue(
new RecalculateQuota(
container.get<FileDownloaderInterface>(TYPES.Files_FileDownloader),
container.get<DomainEventPublisherInterface>(TYPES.Files_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Files_DomainEventFactory),
),
)
// middleware
container.bind<ValetTokenAuthMiddleware>(TYPES.Files_ValetTokenAuthMiddleware).to(ValetTokenAuthMiddleware)
@@ -272,6 +285,14 @@ export class ContainerConfigLoader {
container.get<winston.Logger>(TYPES.Files_Logger),
),
)
container
.bind<FileQuotaRecalculationRequestedEventHandler>(TYPES.Files_FileQuotaRecalculationRequestedEventHandler)
.toConstantValue(
new FileQuotaRecalculationRequestedEventHandler(
container.get<RecalculateQuota>(TYPES.Files_RecalculateQuota),
container.get<winston.Logger>(TYPES.Files_Logger),
),
)
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Files_AccountDeletionRequestedEventHandler)],
@@ -279,6 +300,12 @@ export class ContainerConfigLoader {
'SHARED_SUBSCRIPTION_INVITATION_CANCELED',
container.get(TYPES.Files_SharedSubscriptionInvitationCanceledEventHandler),
],
[
'FILE_QUOTA_RECALCULATION_REQUESTED',
container.get<FileQuotaRecalculationRequestedEventHandler>(
TYPES.Files_FileQuotaRecalculationRequestedEventHandler,
),
],
])
if (isConfiguredForHomeServer) {
@@ -316,7 +343,7 @@ export class ContainerConfigLoader {
level: env.get('LOG_LEVEL', true) || 'info',
format: winston.format.combine(winston.format.splat(), winston.format.json()),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL', true) || 'info' })],
defaultMeta: { service: 'files' },
defaultMeta: { service: `files:${this.mode}` },
})
}
}

View File

@@ -15,6 +15,7 @@ const TYPES = {
Files_RemoveFile: Symbol.for('Files_RemoveFile'),
Files_MoveFile: Symbol.for('Files_MoveFile'),
Files_MarkFilesToBeRemoved: Symbol.for('Files_MarkFilesToBeRemoved'),
Files_RecalculateQuota: Symbol.for('Files_RecalculateQuota'),
// services
Files_ValetTokenDecoder: Symbol.for('Files_ValetTokenDecoder'),
@@ -57,6 +58,7 @@ const TYPES = {
Files_SharedSubscriptionInvitationCanceledEventHandler: Symbol.for(
'Files_SharedSubscriptionInvitationCanceledEventHandler',
),
Files_FileQuotaRecalculationRequestedEventHandler: Symbol.for('Files_FileQuotaRecalculationRequestedEventHandler'),
}
export default TYPES

View File

@@ -5,6 +5,7 @@ import {
SharedVaultFileUploadedEvent,
SharedVaultFileRemovedEvent,
SharedVaultFileMovedEvent,
FileQuotaRecalculatedEvent,
} from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
@@ -13,6 +14,24 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(private timer: TimerInterface) {}
createFileQuotaRecalculatedEvent(payload: {
userUuid: string
totalFileByteSize: number
}): FileQuotaRecalculatedEvent {
return {
type: 'FILE_QUOTA_RECALCULATED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: payload.userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Files,
},
payload,
}
}
createFileRemovedEvent(payload: {
userUuid: string
filePath: string

View File

@@ -4,9 +4,11 @@ import {
SharedVaultFileRemovedEvent,
SharedVaultFileUploadedEvent,
SharedVaultFileMovedEvent,
FileQuotaRecalculatedEvent,
} from '@standardnotes/domain-events'
export interface DomainEventFactoryInterface {
createFileQuotaRecalculatedEvent(payload: { userUuid: string; totalFileByteSize: number }): FileQuotaRecalculatedEvent
createFileUploadedEvent(payload: {
userUuid: string
filePath: string

View File

@@ -0,0 +1,33 @@
import { DomainEventHandlerInterface, FileQuotaRecalculationRequestedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { RecalculateQuota } from '../UseCase/RecalculateQuota/RecalculateQuota'
export class FileQuotaRecalculationRequestedEventHandler implements DomainEventHandlerInterface {
constructor(
private recalculateQuota: RecalculateQuota,
private logger: Logger,
) {}
async handle(event: FileQuotaRecalculationRequestedEvent): Promise<void> {
this.logger.info('Recalculating quota for user...', {
userId: event.payload.userUuid,
})
const result = await this.recalculateQuota.execute({
userUuid: event.payload.userUuid,
})
if (result.isFailed()) {
this.logger.error('Could not recalculate quota', {
userId: event.payload.userUuid,
})
return
}
this.logger.info('Quota recalculated', {
userId: event.payload.userUuid,
})
}
}

View File

@@ -3,4 +3,5 @@ import { Readable } from 'stream'
export interface FileDownloaderInterface {
createDownloadStream(filePath: string, startRange: number, endRange: number): Promise<Readable>
getFileSize(filePath: string): Promise<number>
listFiles(userUuid: string): Promise<{ name: string; size: number }[]>
}

View File

@@ -0,0 +1,55 @@
import { DomainEventPublisherInterface, FileQuotaRecalculatedEvent } from '@standardnotes/domain-events'
import { FileDownloaderInterface } from '../../Services/FileDownloaderInterface'
import { RecalculateQuota } from './RecalculateQuota'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
describe('RecalculateQuota', () => {
let fileDownloader: FileDownloaderInterface
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
const createUseCase = () => new RecalculateQuota(fileDownloader, domainEventPublisher, domainEventFactory)
beforeEach(() => {
fileDownloader = {} as jest.Mocked<FileDownloaderInterface>
fileDownloader.listFiles = jest.fn().mockResolvedValue([
{
name: 'test-file',
size: 123,
},
])
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createFileQuotaRecalculatedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<FileQuotaRecalculatedEvent>)
})
it('publishes a file quota recalculated event', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
expect(domainEventFactory.createFileQuotaRecalculatedEvent).toHaveBeenCalledWith({
userUuid: '00000000-0000-0000-0000-000000000000',
totalFileByteSize: 123,
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
})
it('returns a failure result if user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: 'invalid-user-uuid',
})
expect(result.isFailed()).toBeTruthy()
})
})

View File

@@ -0,0 +1,37 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { RecalculateQuotaDTO } from './RecalculateQuotaDTO'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { FileDownloaderInterface } from '../../Services/FileDownloaderInterface'
export class RecalculateQuota implements UseCaseInterface<void> {
constructor(
private fileDownloader: FileDownloaderInterface,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
) {}
async execute(dto: RecalculateQuotaDTO): Promise<Result<void>> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
const filesList = await this.fileDownloader.listFiles(userUuid.value)
let totalFileByteSize = 0
for (const file of filesList) {
totalFileByteSize += file.size
}
const event = this.domainEventFactory.createFileQuotaRecalculatedEvent({
userUuid: dto.userUuid,
totalFileByteSize,
})
await this.domainEventPublisher.publish(event)
return Result.ok()
}
}

View File

@@ -0,0 +1,3 @@
export interface RecalculateQuotaDTO {
userUuid: string
}

View File

@@ -9,6 +9,21 @@ import TYPES from '../../Bootstrap/Types'
export class FSFileDownloader implements FileDownloaderInterface {
constructor(@inject(TYPES.Files_FILE_UPLOAD_PATH) private fileUploadPath: string) {}
async listFiles(userUuid: string): Promise<{ name: string; size: number }[]> {
const filesList = []
const files = await promises.readdir(`${this.fileUploadPath}/${userUuid}`)
for (const file of files) {
const fileStat = await promises.stat(`${this.fileUploadPath}/${userUuid}/${file}`)
filesList.push({
name: file,
size: fileStat.size,
})
}
return filesList
}
async getFileSize(filePath: string): Promise<number> {
return (await promises.stat(`${this.fileUploadPath}/${filePath}`)).size
}

View File

@@ -1,4 +1,4 @@
import { GetObjectCommand, HeadObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, S3Client } from '@aws-sdk/client-s3'
import { inject, injectable } from 'inversify'
import { Readable } from 'stream'
@@ -34,4 +34,26 @@ export class S3FileDownloader implements FileDownloaderInterface {
return head.ContentLength as number
}
async listFiles(userUuid: string): Promise<{ name: string; size: number }[]> {
const objectsList = await this.s3Client.send(
new ListObjectsV2Command({
Bucket: this.s3BuckeName,
Prefix: userUuid,
}),
)
const filesList = []
for (const object of objectsList.Contents ?? []) {
if (!object.Key) {
continue
}
filesList.push({
name: object.Key,
size: object.Size ?? 0,
})
}
return filesList
}
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.1](https://github.com/standardnotes/server/compare/@standardnotes/grpc@1.4.0...@standardnotes/grpc@1.4.1) (2023-12-28)
### Bug Fixes
* retry grpc calls upon service unavailable response ([#1011](https://github.com/standardnotes/server/issues/1011)) ([7c67a5a](https://github.com/standardnotes/server/commit/7c67a5a0f93f9d506ee4010e2837288457fc2c1d))
# [1.4.0](https://github.com/standardnotes/server/compare/@standardnotes/grpc@1.3.2...@standardnotes/grpc@1.4.0) (2023-12-07)
### Features
* replace websocket connection validation with grpc ([#954](https://github.com/standardnotes/server/issues/954)) ([d5c1b76](https://github.com/standardnotes/server/commit/d5c1b76de068a64b334c4347cbefa973447a0f60))
## [1.3.2](https://github.com/standardnotes/server/compare/@standardnotes/grpc@1.3.1...@standardnotes/grpc@1.3.2) (2023-12-07)
**Note:** Version bump only for package @standardnotes/grpc

View File

@@ -7,12 +7,13 @@
import * as grpc from "@grpc/grpc-js";
import * as auth_pb from "./auth_pb";
interface ISessionsService extends grpc.ServiceDefinition<grpc.UntypedServiceImplementation> {
validate: ISessionsService_Ivalidate;
interface IAuthService extends grpc.ServiceDefinition<grpc.UntypedServiceImplementation> {
validate: IAuthService_Ivalidate;
validateWebsocket: IAuthService_IvalidateWebsocket;
}
interface ISessionsService_Ivalidate extends grpc.MethodDefinition<auth_pb.AuthorizationHeader, auth_pb.SessionValidationResponse> {
path: "/auth.Sessions/validate";
interface IAuthService_Ivalidate extends grpc.MethodDefinition<auth_pb.AuthorizationHeader, auth_pb.SessionValidationResponse> {
path: "/auth.Auth/validate";
requestStream: false;
responseStream: false;
requestSerialize: grpc.serialize<auth_pb.AuthorizationHeader>;
@@ -20,22 +21,38 @@ interface ISessionsService_Ivalidate extends grpc.MethodDefinition<auth_pb.Autho
responseSerialize: grpc.serialize<auth_pb.SessionValidationResponse>;
responseDeserialize: grpc.deserialize<auth_pb.SessionValidationResponse>;
}
export const SessionsService: ISessionsService;
export interface ISessionsServer {
validate: grpc.handleUnaryCall<auth_pb.AuthorizationHeader, auth_pb.SessionValidationResponse>;
interface IAuthService_IvalidateWebsocket extends grpc.MethodDefinition<auth_pb.WebsocketConnectionAuthorizationHeader, auth_pb.ConnectionValidationResponse> {
path: "/auth.Auth/validateWebsocket";
requestStream: false;
responseStream: false;
requestSerialize: grpc.serialize<auth_pb.WebsocketConnectionAuthorizationHeader>;
requestDeserialize: grpc.deserialize<auth_pb.WebsocketConnectionAuthorizationHeader>;
responseSerialize: grpc.serialize<auth_pb.ConnectionValidationResponse>;
responseDeserialize: grpc.deserialize<auth_pb.ConnectionValidationResponse>;
}
export interface ISessionsClient {
export const AuthService: IAuthService;
export interface IAuthServer {
validate: grpc.handleUnaryCall<auth_pb.AuthorizationHeader, auth_pb.SessionValidationResponse>;
validateWebsocket: grpc.handleUnaryCall<auth_pb.WebsocketConnectionAuthorizationHeader, auth_pb.ConnectionValidationResponse>;
}
export interface IAuthClient {
validate(request: auth_pb.AuthorizationHeader, callback: (error: grpc.ServiceError | null, response: auth_pb.SessionValidationResponse) => void): grpc.ClientUnaryCall;
validate(request: auth_pb.AuthorizationHeader, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: auth_pb.SessionValidationResponse) => void): grpc.ClientUnaryCall;
validate(request: auth_pb.AuthorizationHeader, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: auth_pb.SessionValidationResponse) => void): grpc.ClientUnaryCall;
validateWebsocket(request: auth_pb.WebsocketConnectionAuthorizationHeader, callback: (error: grpc.ServiceError | null, response: auth_pb.ConnectionValidationResponse) => void): grpc.ClientUnaryCall;
validateWebsocket(request: auth_pb.WebsocketConnectionAuthorizationHeader, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: auth_pb.ConnectionValidationResponse) => void): grpc.ClientUnaryCall;
validateWebsocket(request: auth_pb.WebsocketConnectionAuthorizationHeader, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: auth_pb.ConnectionValidationResponse) => void): grpc.ClientUnaryCall;
}
export class SessionsClient extends grpc.Client implements ISessionsClient {
export class AuthClient extends grpc.Client implements IAuthClient {
constructor(address: string, credentials: grpc.ChannelCredentials, options?: object);
public validate(request: auth_pb.AuthorizationHeader, callback: (error: grpc.ServiceError | null, response: auth_pb.SessionValidationResponse) => void): grpc.ClientUnaryCall;
public validate(request: auth_pb.AuthorizationHeader, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: auth_pb.SessionValidationResponse) => void): grpc.ClientUnaryCall;
public validate(request: auth_pb.AuthorizationHeader, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: auth_pb.SessionValidationResponse) => void): grpc.ClientUnaryCall;
public validateWebsocket(request: auth_pb.WebsocketConnectionAuthorizationHeader, callback: (error: grpc.ServiceError | null, response: auth_pb.ConnectionValidationResponse) => void): grpc.ClientUnaryCall;
public validateWebsocket(request: auth_pb.WebsocketConnectionAuthorizationHeader, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: auth_pb.ConnectionValidationResponse) => void): grpc.ClientUnaryCall;
public validateWebsocket(request: auth_pb.WebsocketConnectionAuthorizationHeader, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: auth_pb.ConnectionValidationResponse) => void): grpc.ClientUnaryCall;
}

View File

@@ -15,6 +15,17 @@ function deserialize_auth_AuthorizationHeader(buffer_arg) {
return auth_pb.AuthorizationHeader.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_auth_ConnectionValidationResponse(arg) {
if (!(arg instanceof auth_pb.ConnectionValidationResponse)) {
throw new Error('Expected argument of type auth.ConnectionValidationResponse');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_auth_ConnectionValidationResponse(buffer_arg) {
return auth_pb.ConnectionValidationResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_auth_SessionValidationResponse(arg) {
if (!(arg instanceof auth_pb.SessionValidationResponse)) {
throw new Error('Expected argument of type auth.SessionValidationResponse');
@@ -26,10 +37,21 @@ function deserialize_auth_SessionValidationResponse(buffer_arg) {
return auth_pb.SessionValidationResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_auth_WebsocketConnectionAuthorizationHeader(arg) {
if (!(arg instanceof auth_pb.WebsocketConnectionAuthorizationHeader)) {
throw new Error('Expected argument of type auth.WebsocketConnectionAuthorizationHeader');
}
return Buffer.from(arg.serializeBinary());
}
var SessionsService = exports.SessionsService = {
function deserialize_auth_WebsocketConnectionAuthorizationHeader(buffer_arg) {
return auth_pb.WebsocketConnectionAuthorizationHeader.deserializeBinary(new Uint8Array(buffer_arg));
}
var AuthService = exports.AuthService = {
validate: {
path: '/auth.Sessions/validate',
path: '/auth.Auth/validate',
requestStream: false,
responseStream: false,
requestType: auth_pb.AuthorizationHeader,
@@ -39,6 +61,17 @@ var SessionsService = exports.SessionsService = {
responseSerialize: serialize_auth_SessionValidationResponse,
responseDeserialize: deserialize_auth_SessionValidationResponse,
},
validateWebsocket: {
path: '/auth.Auth/validateWebsocket',
requestStream: false,
responseStream: false,
requestType: auth_pb.WebsocketConnectionAuthorizationHeader,
responseType: auth_pb.ConnectionValidationResponse,
requestSerialize: serialize_auth_WebsocketConnectionAuthorizationHeader,
requestDeserialize: deserialize_auth_WebsocketConnectionAuthorizationHeader,
responseSerialize: serialize_auth_ConnectionValidationResponse,
responseDeserialize: deserialize_auth_ConnectionValidationResponse,
},
};
exports.SessionsClient = grpc.makeGenericClientConstructor(SessionsService);
exports.AuthClient = grpc.makeGenericClientConstructor(AuthService);

View File

@@ -45,3 +45,43 @@ export namespace SessionValidationResponse {
crossServiceToken: string,
}
}
export class WebsocketConnectionAuthorizationHeader extends jspb.Message {
getToken(): string;
setToken(value: string): WebsocketConnectionAuthorizationHeader;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): WebsocketConnectionAuthorizationHeader.AsObject;
static toObject(includeInstance: boolean, msg: WebsocketConnectionAuthorizationHeader): WebsocketConnectionAuthorizationHeader.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: WebsocketConnectionAuthorizationHeader, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): WebsocketConnectionAuthorizationHeader;
static deserializeBinaryFromReader(message: WebsocketConnectionAuthorizationHeader, reader: jspb.BinaryReader): WebsocketConnectionAuthorizationHeader;
}
export namespace WebsocketConnectionAuthorizationHeader {
export type AsObject = {
token: string,
}
}
export class ConnectionValidationResponse extends jspb.Message {
getCrossServiceToken(): string;
setCrossServiceToken(value: string): ConnectionValidationResponse;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): ConnectionValidationResponse.AsObject;
static toObject(includeInstance: boolean, msg: ConnectionValidationResponse): ConnectionValidationResponse.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: ConnectionValidationResponse, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): ConnectionValidationResponse;
static deserializeBinaryFromReader(message: ConnectionValidationResponse, reader: jspb.BinaryReader): ConnectionValidationResponse;
}
export namespace ConnectionValidationResponse {
export type AsObject = {
crossServiceToken: string,
}
}

View File

@@ -22,7 +22,9 @@ var global = (function() {
}.call(null));
goog.exportSymbol('proto.auth.AuthorizationHeader', null, global);
goog.exportSymbol('proto.auth.ConnectionValidationResponse', null, global);
goog.exportSymbol('proto.auth.SessionValidationResponse', null, global);
goog.exportSymbol('proto.auth.WebsocketConnectionAuthorizationHeader', null, global);
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
@@ -65,6 +67,48 @@ if (goog.DEBUG && !COMPILED) {
*/
proto.auth.SessionValidationResponse.displayName = 'proto.auth.SessionValidationResponse';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.auth.WebsocketConnectionAuthorizationHeader = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.auth.WebsocketConnectionAuthorizationHeader, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.auth.WebsocketConnectionAuthorizationHeader.displayName = 'proto.auth.WebsocketConnectionAuthorizationHeader';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.auth.ConnectionValidationResponse = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.auth.ConnectionValidationResponse, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.auth.ConnectionValidationResponse.displayName = 'proto.auth.ConnectionValidationResponse';
}
@@ -325,4 +369,264 @@ proto.auth.SessionValidationResponse.prototype.setCrossServiceToken = function(v
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.auth.WebsocketConnectionAuthorizationHeader.prototype.toObject = function(opt_includeInstance) {
return proto.auth.WebsocketConnectionAuthorizationHeader.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.auth.WebsocketConnectionAuthorizationHeader} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.auth.WebsocketConnectionAuthorizationHeader.toObject = function(includeInstance, msg) {
var f, obj = {
token: jspb.Message.getFieldWithDefault(msg, 1, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.auth.WebsocketConnectionAuthorizationHeader}
*/
proto.auth.WebsocketConnectionAuthorizationHeader.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.auth.WebsocketConnectionAuthorizationHeader;
return proto.auth.WebsocketConnectionAuthorizationHeader.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.auth.WebsocketConnectionAuthorizationHeader} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.auth.WebsocketConnectionAuthorizationHeader}
*/
proto.auth.WebsocketConnectionAuthorizationHeader.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setToken(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.auth.WebsocketConnectionAuthorizationHeader.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.auth.WebsocketConnectionAuthorizationHeader.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.auth.WebsocketConnectionAuthorizationHeader} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.auth.WebsocketConnectionAuthorizationHeader.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getToken();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
};
/**
* optional string token = 1;
* @return {string}
*/
proto.auth.WebsocketConnectionAuthorizationHeader.prototype.getToken = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.auth.WebsocketConnectionAuthorizationHeader} returns this
*/
proto.auth.WebsocketConnectionAuthorizationHeader.prototype.setToken = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.auth.ConnectionValidationResponse.prototype.toObject = function(opt_includeInstance) {
return proto.auth.ConnectionValidationResponse.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.auth.ConnectionValidationResponse} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.auth.ConnectionValidationResponse.toObject = function(includeInstance, msg) {
var f, obj = {
crossServiceToken: jspb.Message.getFieldWithDefault(msg, 1, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.auth.ConnectionValidationResponse}
*/
proto.auth.ConnectionValidationResponse.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.auth.ConnectionValidationResponse;
return proto.auth.ConnectionValidationResponse.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.auth.ConnectionValidationResponse} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.auth.ConnectionValidationResponse}
*/
proto.auth.ConnectionValidationResponse.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setCrossServiceToken(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.auth.ConnectionValidationResponse.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.auth.ConnectionValidationResponse.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.auth.ConnectionValidationResponse} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.auth.ConnectionValidationResponse.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getCrossServiceToken();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
};
/**
* optional string cross_service_token = 1;
* @return {string}
*/
proto.auth.ConnectionValidationResponse.prototype.getCrossServiceToken = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.auth.ConnectionValidationResponse} returns this
*/
proto.auth.ConnectionValidationResponse.prototype.setCrossServiceToken = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
goog.object.extend(exports, proto.auth);

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/grpc",
"version": "1.3.2",
"version": "1.4.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -27,7 +27,7 @@
"build": "tsc --build"
},
"dependencies": {
"@grpc/grpc-js": "^1.9.12",
"@grpc/grpc-js": "^1.9.13",
"google-protobuf": "^3.21.2"
},
"devDependencies": {

View File

@@ -10,6 +10,15 @@ message SessionValidationResponse {
string cross_service_token = 1;
}
service Sessions {
rpc validate(AuthorizationHeader) returns (SessionValidationResponse) {}
message WebsocketConnectionAuthorizationHeader {
string token = 1;
}
message ConnectionValidationResponse {
string cross_service_token = 1;
}
service Auth {
rpc validate(AuthorizationHeader) returns (SessionValidationResponse) {}
rpc validateWebsocket(WebsocketConnectionAuthorizationHeader) returns (ConnectionValidationResponse) {}
}

View File

@@ -3,6 +3,102 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.35](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.34...@standardnotes/home-server@1.22.35) (2024-01-02)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.34](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.33...@standardnotes/home-server@1.22.34) (2023-12-29)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.33](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.32...@standardnotes/home-server@1.22.33) (2023-12-29)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.32](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.31...@standardnotes/home-server@1.22.32) (2023-12-28)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.31](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.30...@standardnotes/home-server@1.22.31) (2023-12-28)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.30](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.29...@standardnotes/home-server@1.22.30) (2023-12-28)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.29](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.28...@standardnotes/home-server@1.22.29) (2023-12-28)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.28](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.27...@standardnotes/home-server@1.22.28) (2023-12-28)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.27](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.26...@standardnotes/home-server@1.22.27) (2023-12-26)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.26](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.25...@standardnotes/home-server@1.22.26) (2023-12-22)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.25](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.24...@standardnotes/home-server@1.22.25) (2023-12-22)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.24](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.23...@standardnotes/home-server@1.22.24) (2023-12-22)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.23](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.22...@standardnotes/home-server@1.22.23) (2023-12-21)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.22](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.21...@standardnotes/home-server@1.22.22) (2023-12-14)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.21](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.20...@standardnotes/home-server@1.22.21) (2023-12-14)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.20](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.19...@standardnotes/home-server@1.22.20) (2023-12-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.19](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.18...@standardnotes/home-server@1.22.19) (2023-12-11)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.18](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.17...@standardnotes/home-server@1.22.18) (2023-12-11)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.17](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.16...@standardnotes/home-server@1.22.17) (2023-12-11)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.16](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.15...@standardnotes/home-server@1.22.16) (2023-12-08)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.15](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.14...@standardnotes/home-server@1.22.15) (2023-12-08)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.14](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.13...@standardnotes/home-server@1.22.14) (2023-12-07)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.13](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.12...@standardnotes/home-server@1.22.13) (2023-12-07)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.12](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.11...@standardnotes/home-server@1.22.12) (2023-12-07)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.11](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.10...@standardnotes/home-server@1.22.11) (2023-12-07)
**Note:** Version bump only for package @standardnotes/home-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/home-server",
"version": "1.22.11",
"version": "1.22.35",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -40,7 +40,7 @@
"helmet": "^7.0.0",
"inversify": "^6.0.1",
"inversify-express-utils": "^6.4.3",
"reflect-metadata": "0.1.13",
"reflect-metadata": "^0.2.1",
"winston": "^3.8.1"
},
"devDependencies": {

View File

@@ -3,6 +3,39 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.51.11](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.51.10...@standardnotes/revisions-server@1.51.11) (2023-12-29)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.51.10](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.51.9...@standardnotes/revisions-server@1.51.10) (2023-12-28)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.51.9](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.51.8...@standardnotes/revisions-server@1.51.9) (2023-12-28)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.51.8](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.51.7...@standardnotes/revisions-server@1.51.8) (2023-12-26)
### Bug Fixes
* stop revisions propagation ([#1008](https://github.com/standardnotes/server/issues/1008)) ([7962b24](https://github.com/standardnotes/server/commit/7962b245b51703ff7d33728dee117b2074f2692a))
## [1.51.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.51.6...@standardnotes/revisions-server@1.51.7) (2023-12-14)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.51.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.51.5...@standardnotes/revisions-server@1.51.6) (2023-12-11)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.51.5](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.51.4...@standardnotes/revisions-server@1.51.5) (2023-12-07)
### Bug Fixes
* logger meta information ([a2b1323](https://github.com/standardnotes/server/commit/a2b1323568f5ced74b41aa4634340a6ca0668683))
* logs meta ([8ac84c5](https://github.com/standardnotes/server/commit/8ac84c59af886bb9c42de012fb1e7864e116ab55))
## [1.51.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.51.3...@standardnotes/revisions-server@1.51.4) (2023-12-01)
### Bug Fixes

View File

@@ -1,4 +1,4 @@
FROM node:20.6.1-alpine
FROM node:20.10.0-alpine
RUN apk add --update \
curl \

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.51.4",
"version": "1.51.11",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -48,7 +48,7 @@
"inversify-express-utils": "^6.4.3",
"ioredis": "^5.3.2",
"mysql2": "^3.0.1",
"reflect-metadata": "0.1.13",
"reflect-metadata": "^0.2.1",
"sqlite3": "^5.1.6",
"typeorm": "^0.3.17",
"winston": "^3.8.1"

View File

@@ -52,6 +52,8 @@ import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevision
import { ItemRemovedFromSharedVaultEventHandler } from '../Domain/Handler/ItemRemovedFromSharedVaultEventHandler'
import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler'
import { CreateRevisionFromDump } from '../Domain/UseCase/CreateRevisionFromDump/CreateRevisionFromDump'
import { DeleteRevisions } from '../Domain/UseCase/DeleteRevisions/DeleteRevisions'
import { ItemDeletedEventHandler } from '../Domain/Handler/ItemDeletedEventHandler'
export class ContainerConfigLoader {
constructor(private mode: 'server' | 'worker' = 'server') {}
@@ -90,7 +92,7 @@ export class ContainerConfigLoader {
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: 'revisions' },
defaultMeta: { service: `revisions:${this.mode}` },
})
}
container.bind<winston.Logger>(TYPES.Revisions_Logger).toConstantValue(logger)
@@ -226,6 +228,11 @@ export class ContainerConfigLoader {
.toConstantValue(
new DeleteRevision(container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository)),
)
container
.bind<DeleteRevisions>(TYPES.Revisions_DeleteRevisions)
.toConstantValue(
new DeleteRevisions(container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository)),
)
container
.bind<CopyRevisions>(TYPES.Revisions_CopyRevisions)
.toConstantValue(
@@ -311,6 +318,14 @@ export class ContainerConfigLoader {
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)
container
.bind<ItemDeletedEventHandler>(TYPES.Revisions_ItemDeletedEventHandler)
.toConstantValue(
new ItemDeletedEventHandler(
container.get<DeleteRevisions>(TYPES.Revisions_DeleteRevisions),
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['ITEM_DUMPED', container.get(TYPES.Revisions_ItemDumpedEventHandler)],
@@ -318,6 +333,7 @@ export class ContainerConfigLoader {
['REVISIONS_COPY_REQUESTED', container.get(TYPES.Revisions_RevisionsCopyRequestedEventHandler)],
['ITEM_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler)],
['SHARED_VAULT_REMOVED', container.get(TYPES.Revisions_SharedVaultRemovedEventHandler)],
['ITEM_DELETED', container.get(TYPES.Revisions_ItemDeletedEventHandler)],
])
if (isConfiguredForHomeServer) {

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