mirror of
https://github.com/standardnotes/server
synced 2026-04-27 06:01:30 -04:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3da7a21cde | |||
| 351e18f638 | |||
| 4f2129c4e0 | |||
| d7a1c667dd | |||
| 4de0bfa36d | |||
| 0443de88ce | |||
| f830bac873 | |||
| 517ae5ded9 | |||
| 6062f85000 | |||
| e2205c3849 | |||
| 0b46eff16e | |||
| df67982bca | |||
| d44866b3c0 | |||
| 6ee18bffe6 | |||
| a881dd2d79 | |||
| b767e1f072 | |||
| e3cb1faba4 | |||
| 5c5f988055 | |||
| c7d2adf091 | |||
| a4ad37f309 | |||
| 73c2cc1222 | |||
| 9380900aaf | |||
| 02f4d5c717 | |||
| 1f4b26d269 | |||
| e253825da6 | |||
| 2bddd947ba | |||
| b7173346d2 | |||
| 01641975c0 | |||
| 7abd80cdba | |||
| aeb5ea1874 | |||
| d2a371b92c | |||
| 3ea3b24bb6 | |||
| 0c3bc0cae6 | |||
| d56410984a | |||
| 4dd2eb9349 | |||
| 709aec5eeb | |||
| f1aa431c22 | |||
| 86d0e565ed | |||
| 92bb447cac | |||
| 08966e7af7 | |||
| 2c732ff713 | |||
| 1493b7c478 | |||
| efd816a627 |
+1
-1
@@ -27,4 +27,4 @@ AUTH_JWT_SECRET=f95259c5e441f5a4646d76422cfb3df4c4488842901aa50b6c51b8be2e0040e9
|
||||
AUTH_SERVER_ENCRYPTION_SERVER_KEY=1087415dfde3093797f9a7ca93a49e7d7aa1861735eb0d32aae9c303b8c3d060
|
||||
VALET_TOKEN_SECRET=4b886819ebe1e908077c6cae96311b48a8416bd60cc91c03060e15bdf6b30d1f
|
||||
|
||||
SYNCING_SERVER_CONTENT_SIZE_TRANSFER_LIMIT=1000000
|
||||
SYNCING_SERVER_CONTENT_SIZE_TRANSFER_LIMIT=100000
|
||||
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
echo "ACCESS_TOKEN_AGE=4" >> packages/home-server/.env
|
||||
echo "REFRESH_TOKEN_AGE=10" >> packages/home-server/.env
|
||||
echo "REVISIONS_FREQUENCY=2" >> packages/home-server/.env
|
||||
echo "CONTENT_SIZE_TRANSFER_LIMIT=1000000" >> packages/home-server/.env
|
||||
echo "CONTENT_SIZE_TRANSFER_LIMIT=100000" >> packages/home-server/.env
|
||||
echo "DB_HOST=localhost" >> packages/home-server/.env
|
||||
echo "DB_PORT=3306" >> packages/home-server/.env
|
||||
echo "DB_DATABASE=standardnotes" >> packages/home-server/.env
|
||||
|
||||
@@ -300,6 +300,57 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/client-cloudwatch", [\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-client-cloudwatch-npm-3.485.0-afe4ac001f-0e02739ef1.zip/node_modules/@aws-sdk/client-cloudwatch/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/client-cloudwatch", "npm:3.485.0"],\
|
||||
["@aws-crypto/sha256-browser", "npm:3.0.0"],\
|
||||
["@aws-crypto/sha256-js", "npm:3.0.0"],\
|
||||
["@aws-sdk/client-sts", "npm:3.485.0"],\
|
||||
["@aws-sdk/core", "npm:3.485.0"],\
|
||||
["@aws-sdk/credential-provider-node", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-host-header", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-logger", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-recursion-detection", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-signing", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-user-agent", "npm:3.485.0"],\
|
||||
["@aws-sdk/region-config-resolver", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@aws-sdk/util-endpoints", "npm:3.485.0"],\
|
||||
["@aws-sdk/util-user-agent-browser", "npm:3.485.0"],\
|
||||
["@aws-sdk/util-user-agent-node", "virtual:5f6733bd23aee10dd05576af160f1b93e0bb4a20b288e9b818dc0b69bdb08ea1a09d5836816f02bdafc9c01487816ae339c6b680c2f7849dfe249436c5f2b499#npm:3.485.0"],\
|
||||
["@smithy/config-resolver", "npm:2.0.23"],\
|
||||
["@smithy/core", "npm:1.2.2"],\
|
||||
["@smithy/fetch-http-handler", "npm:2.3.2"],\
|
||||
["@smithy/hash-node", "npm:2.0.18"],\
|
||||
["@smithy/invalid-dependency", "npm:2.0.16"],\
|
||||
["@smithy/middleware-content-length", "npm:2.0.18"],\
|
||||
["@smithy/middleware-endpoint", "npm:2.3.0"],\
|
||||
["@smithy/middleware-retry", "npm:2.0.26"],\
|
||||
["@smithy/middleware-serde", "npm:2.0.16"],\
|
||||
["@smithy/middleware-stack", "npm:2.0.10"],\
|
||||
["@smithy/node-config-provider", "npm:2.1.9"],\
|
||||
["@smithy/node-http-handler", "npm:2.2.2"],\
|
||||
["@smithy/protocol-http", "npm:3.0.12"],\
|
||||
["@smithy/smithy-client", "npm:2.2.1"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["@smithy/url-parser", "npm:2.0.16"],\
|
||||
["@smithy/util-base64", "npm:2.0.1"],\
|
||||
["@smithy/util-body-length-browser", "npm:2.0.1"],\
|
||||
["@smithy/util-body-length-node", "npm:2.1.0"],\
|
||||
["@smithy/util-defaults-mode-browser", "npm:2.0.24"],\
|
||||
["@smithy/util-defaults-mode-node", "npm:2.0.32"],\
|
||||
["@smithy/util-endpoints", "npm:1.0.8"],\
|
||||
["@smithy/util-retry", "npm:2.0.9"],\
|
||||
["@smithy/util-utf8", "npm:2.0.2"],\
|
||||
["@smithy/util-waiter", "npm:2.0.16"],\
|
||||
["fast-xml-parser", "npm:4.2.5"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/client-s3", [\
|
||||
["npm:3.484.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-client-s3-npm-3.484.0-681638ab7a-701523f3b3.zip/node_modules/@aws-sdk/client-s3/",\
|
||||
@@ -512,6 +563,50 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-client-sso-npm-3.485.0-5f6733bd23-635de0e310.zip/node_modules/@aws-sdk/client-sso/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/client-sso", "npm:3.485.0"],\
|
||||
["@aws-crypto/sha256-browser", "npm:3.0.0"],\
|
||||
["@aws-crypto/sha256-js", "npm:3.0.0"],\
|
||||
["@aws-sdk/core", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-host-header", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-logger", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-recursion-detection", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-user-agent", "npm:3.485.0"],\
|
||||
["@aws-sdk/region-config-resolver", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@aws-sdk/util-endpoints", "npm:3.485.0"],\
|
||||
["@aws-sdk/util-user-agent-browser", "npm:3.485.0"],\
|
||||
["@aws-sdk/util-user-agent-node", "virtual:5f6733bd23aee10dd05576af160f1b93e0bb4a20b288e9b818dc0b69bdb08ea1a09d5836816f02bdafc9c01487816ae339c6b680c2f7849dfe249436c5f2b499#npm:3.485.0"],\
|
||||
["@smithy/config-resolver", "npm:2.0.23"],\
|
||||
["@smithy/core", "npm:1.2.2"],\
|
||||
["@smithy/fetch-http-handler", "npm:2.3.2"],\
|
||||
["@smithy/hash-node", "npm:2.0.18"],\
|
||||
["@smithy/invalid-dependency", "npm:2.0.16"],\
|
||||
["@smithy/middleware-content-length", "npm:2.0.18"],\
|
||||
["@smithy/middleware-endpoint", "npm:2.3.0"],\
|
||||
["@smithy/middleware-retry", "npm:2.0.26"],\
|
||||
["@smithy/middleware-serde", "npm:2.0.16"],\
|
||||
["@smithy/middleware-stack", "npm:2.0.10"],\
|
||||
["@smithy/node-config-provider", "npm:2.1.9"],\
|
||||
["@smithy/node-http-handler", "npm:2.2.2"],\
|
||||
["@smithy/protocol-http", "npm:3.0.12"],\
|
||||
["@smithy/smithy-client", "npm:2.2.1"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["@smithy/url-parser", "npm:2.0.16"],\
|
||||
["@smithy/util-base64", "npm:2.0.1"],\
|
||||
["@smithy/util-body-length-browser", "npm:2.0.1"],\
|
||||
["@smithy/util-body-length-node", "npm:2.1.0"],\
|
||||
["@smithy/util-defaults-mode-browser", "npm:2.0.24"],\
|
||||
["@smithy/util-defaults-mode-node", "npm:2.0.32"],\
|
||||
["@smithy/util-endpoints", "npm:1.0.8"],\
|
||||
["@smithy/util-retry", "npm:2.0.9"],\
|
||||
["@smithy/util-utf8", "npm:2.0.2"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/client-sts", [\
|
||||
@@ -561,6 +656,53 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-client-sts-npm-3.485.0-cc69ab3505-98c7f4d722.zip/node_modules/@aws-sdk/client-sts/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/client-sts", "npm:3.485.0"],\
|
||||
["@aws-crypto/sha256-browser", "npm:3.0.0"],\
|
||||
["@aws-crypto/sha256-js", "npm:3.0.0"],\
|
||||
["@aws-sdk/core", "npm:3.485.0"],\
|
||||
["@aws-sdk/credential-provider-node", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-host-header", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-logger", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-recursion-detection", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-user-agent", "npm:3.485.0"],\
|
||||
["@aws-sdk/region-config-resolver", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@aws-sdk/util-endpoints", "npm:3.485.0"],\
|
||||
["@aws-sdk/util-user-agent-browser", "npm:3.485.0"],\
|
||||
["@aws-sdk/util-user-agent-node", "virtual:5f6733bd23aee10dd05576af160f1b93e0bb4a20b288e9b818dc0b69bdb08ea1a09d5836816f02bdafc9c01487816ae339c6b680c2f7849dfe249436c5f2b499#npm:3.485.0"],\
|
||||
["@smithy/config-resolver", "npm:2.0.23"],\
|
||||
["@smithy/core", "npm:1.2.2"],\
|
||||
["@smithy/fetch-http-handler", "npm:2.3.2"],\
|
||||
["@smithy/hash-node", "npm:2.0.18"],\
|
||||
["@smithy/invalid-dependency", "npm:2.0.16"],\
|
||||
["@smithy/middleware-content-length", "npm:2.0.18"],\
|
||||
["@smithy/middleware-endpoint", "npm:2.3.0"],\
|
||||
["@smithy/middleware-retry", "npm:2.0.26"],\
|
||||
["@smithy/middleware-serde", "npm:2.0.16"],\
|
||||
["@smithy/middleware-stack", "npm:2.0.10"],\
|
||||
["@smithy/node-config-provider", "npm:2.1.9"],\
|
||||
["@smithy/node-http-handler", "npm:2.2.2"],\
|
||||
["@smithy/protocol-http", "npm:3.0.12"],\
|
||||
["@smithy/smithy-client", "npm:2.2.1"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["@smithy/url-parser", "npm:2.0.16"],\
|
||||
["@smithy/util-base64", "npm:2.0.1"],\
|
||||
["@smithy/util-body-length-browser", "npm:2.0.1"],\
|
||||
["@smithy/util-body-length-node", "npm:2.1.0"],\
|
||||
["@smithy/util-defaults-mode-browser", "npm:2.0.24"],\
|
||||
["@smithy/util-defaults-mode-node", "npm:2.0.32"],\
|
||||
["@smithy/util-endpoints", "npm:1.0.8"],\
|
||||
["@smithy/util-middleware", "npm:2.0.9"],\
|
||||
["@smithy/util-retry", "npm:2.0.9"],\
|
||||
["@smithy/util-utf8", "npm:2.0.2"],\
|
||||
["fast-xml-parser", "npm:4.2.5"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/core", [\
|
||||
@@ -576,6 +718,19 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-core-npm-3.485.0-77ed30ee18-b84dafb213.zip/node_modules/@aws-sdk/core/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/core", "npm:3.485.0"],\
|
||||
["@smithy/core", "npm:1.2.2"],\
|
||||
["@smithy/protocol-http", "npm:3.0.12"],\
|
||||
["@smithy/signature-v4", "npm:2.0.5"],\
|
||||
["@smithy/smithy-client", "npm:2.2.1"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/credential-provider-env", [\
|
||||
@@ -589,6 +744,17 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-env-npm-3.485.0-0fda7f74e0-b8346ea6f5.zip/node_modules/@aws-sdk/credential-provider-env/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/credential-provider-env", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/property-provider", "npm:2.0.5"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/credential-provider-ini", [\
|
||||
@@ -608,6 +774,23 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-ini-npm-3.485.0-bec3aaa989-3176b03ee1.zip/node_modules/@aws-sdk/credential-provider-ini/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/credential-provider-ini", "npm:3.485.0"],\
|
||||
["@aws-sdk/credential-provider-env", "npm:3.485.0"],\
|
||||
["@aws-sdk/credential-provider-process", "npm:3.485.0"],\
|
||||
["@aws-sdk/credential-provider-sso", "npm:3.485.0"],\
|
||||
["@aws-sdk/credential-provider-web-identity", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/credential-provider-imds", "npm:2.0.5"],\
|
||||
["@smithy/property-provider", "npm:2.0.5"],\
|
||||
["@smithy/shared-ini-file-loader", "npm:2.0.6"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/credential-provider-node", [\
|
||||
@@ -628,6 +811,24 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-node-npm-3.485.0-9f40e4a3cf-d31e5a95ea.zip/node_modules/@aws-sdk/credential-provider-node/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/credential-provider-node", "npm:3.485.0"],\
|
||||
["@aws-sdk/credential-provider-env", "npm:3.485.0"],\
|
||||
["@aws-sdk/credential-provider-ini", "npm:3.485.0"],\
|
||||
["@aws-sdk/credential-provider-process", "npm:3.485.0"],\
|
||||
["@aws-sdk/credential-provider-sso", "npm:3.485.0"],\
|
||||
["@aws-sdk/credential-provider-web-identity", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/credential-provider-imds", "npm:2.0.5"],\
|
||||
["@smithy/property-provider", "npm:2.0.5"],\
|
||||
["@smithy/shared-ini-file-loader", "npm:2.0.6"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/credential-provider-process", [\
|
||||
@@ -642,6 +843,18 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-process-npm-3.485.0-62d3460338-e740fb949e.zip/node_modules/@aws-sdk/credential-provider-process/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/credential-provider-process", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/property-provider", "npm:2.0.5"],\
|
||||
["@smithy/shared-ini-file-loader", "npm:2.0.6"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/credential-provider-sso", [\
|
||||
@@ -658,6 +871,20 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-sso-npm-3.485.0-42db25db09-7269315797.zip/node_modules/@aws-sdk/credential-provider-sso/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/credential-provider-sso", "npm:3.485.0"],\
|
||||
["@aws-sdk/client-sso", "npm:3.485.0"],\
|
||||
["@aws-sdk/token-providers", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/property-provider", "npm:2.0.5"],\
|
||||
["@smithy/shared-ini-file-loader", "npm:2.0.6"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/credential-provider-web-identity", [\
|
||||
@@ -671,6 +898,17 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-web-identity-npm-3.485.0-420b04bcce-33125ce0b7.zip/node_modules/@aws-sdk/credential-provider-web-identity/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/credential-provider-web-identity", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/property-provider", "npm:2.0.5"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/middleware-bucket-endpoint", [\
|
||||
@@ -730,6 +968,17 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-middleware-host-header-npm-3.485.0-2e625f9614-9ca3da2a26.zip/node_modules/@aws-sdk/middleware-host-header/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/middleware-host-header", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/protocol-http", "npm:3.0.12"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/middleware-location-constraint", [\
|
||||
@@ -754,6 +1003,16 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-middleware-logger-npm-3.485.0-3ff7eeabbb-2fcb731794.zip/node_modules/@aws-sdk/middleware-logger/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/middleware-logger", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/middleware-recursion-detection", [\
|
||||
@@ -767,6 +1026,17 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-middleware-recursion-detection-npm-3.485.0-af05ed4810-afdea18930.zip/node_modules/@aws-sdk/middleware-recursion-detection/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/middleware-recursion-detection", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/protocol-http", "npm:3.0.12"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/middleware-sdk-s3", [\
|
||||
@@ -815,6 +1085,20 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-middleware-signing-npm-3.485.0-3117db6053-f9dbb39d8d.zip/node_modules/@aws-sdk/middleware-signing/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/middleware-signing", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/property-provider", "npm:2.0.5"],\
|
||||
["@smithy/protocol-http", "npm:3.0.12"],\
|
||||
["@smithy/signature-v4", "npm:2.0.5"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["@smithy/util-middleware", "npm:2.0.9"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/middleware-ssec", [\
|
||||
@@ -841,6 +1125,18 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-middleware-user-agent-npm-3.485.0-983204fccf-a8fc812aff.zip/node_modules/@aws-sdk/middleware-user-agent/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/middleware-user-agent", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@aws-sdk/util-endpoints", "npm:3.485.0"],\
|
||||
["@smithy/protocol-http", "npm:3.0.12"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/region-config-resolver", [\
|
||||
@@ -855,6 +1151,18 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-region-config-resolver-npm-3.485.0-1a69e46754-55bc5128b8.zip/node_modules/@aws-sdk/region-config-resolver/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/region-config-resolver", "npm:3.485.0"],\
|
||||
["@smithy/node-config-provider", "npm:2.1.9"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["@smithy/util-config-provider", "npm:2.1.0"],\
|
||||
["@smithy/util-middleware", "npm:2.0.9"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/signature-v4-multi-region", [\
|
||||
@@ -916,6 +1224,50 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-token-providers-npm-3.485.0-1fb5ab9dfb-aed270b625.zip/node_modules/@aws-sdk/token-providers/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/token-providers", "npm:3.485.0"],\
|
||||
["@aws-crypto/sha256-browser", "npm:3.0.0"],\
|
||||
["@aws-crypto/sha256-js", "npm:3.0.0"],\
|
||||
["@aws-sdk/middleware-host-header", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-logger", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-recursion-detection", "npm:3.485.0"],\
|
||||
["@aws-sdk/middleware-user-agent", "npm:3.485.0"],\
|
||||
["@aws-sdk/region-config-resolver", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@aws-sdk/util-endpoints", "npm:3.485.0"],\
|
||||
["@aws-sdk/util-user-agent-browser", "npm:3.485.0"],\
|
||||
["@aws-sdk/util-user-agent-node", "virtual:5f6733bd23aee10dd05576af160f1b93e0bb4a20b288e9b818dc0b69bdb08ea1a09d5836816f02bdafc9c01487816ae339c6b680c2f7849dfe249436c5f2b499#npm:3.485.0"],\
|
||||
["@smithy/config-resolver", "npm:2.0.23"],\
|
||||
["@smithy/fetch-http-handler", "npm:2.3.2"],\
|
||||
["@smithy/hash-node", "npm:2.0.18"],\
|
||||
["@smithy/invalid-dependency", "npm:2.0.16"],\
|
||||
["@smithy/middleware-content-length", "npm:2.0.18"],\
|
||||
["@smithy/middleware-endpoint", "npm:2.3.0"],\
|
||||
["@smithy/middleware-retry", "npm:2.0.26"],\
|
||||
["@smithy/middleware-serde", "npm:2.0.16"],\
|
||||
["@smithy/middleware-stack", "npm:2.0.10"],\
|
||||
["@smithy/node-config-provider", "npm:2.1.9"],\
|
||||
["@smithy/node-http-handler", "npm:2.2.2"],\
|
||||
["@smithy/property-provider", "npm:2.0.5"],\
|
||||
["@smithy/protocol-http", "npm:3.0.12"],\
|
||||
["@smithy/shared-ini-file-loader", "npm:2.0.6"],\
|
||||
["@smithy/smithy-client", "npm:2.2.1"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["@smithy/url-parser", "npm:2.0.16"],\
|
||||
["@smithy/util-base64", "npm:2.0.1"],\
|
||||
["@smithy/util-body-length-browser", "npm:2.0.1"],\
|
||||
["@smithy/util-body-length-node", "npm:2.1.0"],\
|
||||
["@smithy/util-defaults-mode-browser", "npm:2.0.24"],\
|
||||
["@smithy/util-defaults-mode-node", "npm:2.0.32"],\
|
||||
["@smithy/util-endpoints", "npm:1.0.8"],\
|
||||
["@smithy/util-retry", "npm:2.0.9"],\
|
||||
["@smithy/util-utf8", "npm:2.0.2"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/types", [\
|
||||
@@ -935,6 +1287,15 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-types-npm-3.485.0-6aa8cab069-588aae4b49.zip/node_modules/@aws-sdk/types/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/util-arn-parser", [\
|
||||
@@ -957,6 +1318,16 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-util-endpoints-npm-3.485.0-5e0fad395e-c1844fed8b.zip/node_modules/@aws-sdk/util-endpoints/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/util-endpoints", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/util-endpoints", "npm:1.0.8"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/util-locate-window", [\
|
||||
@@ -980,6 +1351,17 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-util-user-agent-browser-npm-3.485.0-23925a5581-d1e4d4c635.zip/node_modules/@aws-sdk/util-user-agent-browser/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/util-user-agent-browser", "npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["bowser", "npm:2.11.0"],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@aws-sdk/util-user-agent-node", [\
|
||||
@@ -990,6 +1372,30 @@ const RAW_RUNTIME_STATE =
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}],\
|
||||
["npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/cache/@aws-sdk-util-user-agent-node-npm-3.485.0-7991a74cb3-e2805ef37b.zip/node_modules/@aws-sdk/util-user-agent-node/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/util-user-agent-node", "npm:3.485.0"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}],\
|
||||
["virtual:5f6733bd23aee10dd05576af160f1b93e0bb4a20b288e9b818dc0b69bdb08ea1a09d5836816f02bdafc9c01487816ae339c6b680c2f7849dfe249436c5f2b499#npm:3.485.0", {\
|
||||
"packageLocation": "./.yarn/__virtual__/@aws-sdk-util-user-agent-node-virtual-c26ab353dd/0/cache/@aws-sdk-util-user-agent-node-npm-3.485.0-7991a74cb3-e2805ef37b.zip/node_modules/@aws-sdk/util-user-agent-node/",\
|
||||
"packageDependencies": [\
|
||||
["@aws-sdk/util-user-agent-node", "virtual:5f6733bd23aee10dd05576af160f1b93e0bb4a20b288e9b818dc0b69bdb08ea1a09d5836816f02bdafc9c01487816ae339c6b680c2f7849dfe249436c5f2b499#npm:3.485.0"],\
|
||||
["@aws-sdk/types", "npm:3.485.0"],\
|
||||
["@smithy/node-config-provider", "npm:2.1.9"],\
|
||||
["@smithy/types", "npm:2.8.0"],\
|
||||
["@types/aws-crt", null],\
|
||||
["aws-crt", null],\
|
||||
["tslib", "npm:2.5.2"]\
|
||||
],\
|
||||
"packagePeers": [\
|
||||
"@types/aws-crt",\
|
||||
"aws-crt"\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["virtual:f5e5a564ba918754c8b3b080eb29a2688af35b9c1a773de63eb148390564369a40f304a7384da1d22d51cb840254882c972d18d82a96c4804ada9a0446f624d6#npm:3.470.0", {\
|
||||
"packageLocation": "./.yarn/__virtual__/@aws-sdk-util-user-agent-node-virtual-92b1b7bd6e/0/cache/@aws-sdk-util-user-agent-node-npm-3.470.0-99b784cecc-05571ba83d.zip/node_modules/@aws-sdk/util-user-agent-node/",\
|
||||
"packageDependencies": [\
|
||||
@@ -6125,6 +6531,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/syncing-server/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/syncing-server", "workspace:packages/syncing-server"],\
|
||||
["@aws-sdk/client-cloudwatch", "npm:3.485.0"],\
|
||||
["@aws-sdk/client-s3", "npm:3.484.0"],\
|
||||
["@aws-sdk/client-sns", "npm:3.484.0"],\
|
||||
["@aws-sdk/client-sqs", "npm:3.484.0"],\
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.34.14](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.13...@standardnotes/analytics@2.34.14) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.34.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.12...@standardnotes/analytics@2.34.13) (2024-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.34.13",
|
||||
"version": "2.34.14",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,32 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.89.19](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.18...@standardnotes/api-gateway@1.89.19) (2024-01-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add dedicated http code response upon a request with too large payload ([#1019](https://github.com/standardnotes/server/issues/1019)) ([6062f85](https://github.com/standardnotes/server/commit/6062f850000477983315d2d9b7c913956f755ebb))
|
||||
|
||||
## [1.89.18](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.17...@standardnotes/api-gateway@1.89.18) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.89.17](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.16...@standardnotes/api-gateway@1.89.17) (2024-01-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** disable http call retries ([7abd80c](https://github.com/standardnotes/server/commit/7abd80cdbaba53840f632d418bd557b35b722699))
|
||||
|
||||
## [1.89.16](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.15...@standardnotes/api-gateway@1.89.16) (2024-01-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** disable sync request retries ([d2a371b](https://github.com/standardnotes/server/commit/d2a371b92c8b2b7f8921fe57f162e74d4944715d))
|
||||
|
||||
## [1.89.15](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.14...@standardnotes/api-gateway@1.89.15) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.89.14](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.13...@standardnotes/api-gateway@1.89.14) (2024-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
@@ -36,12 +36,17 @@ 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 { ResponseLocals } from '../src/Controller/ResponseLocals'
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
const requestPayloadLimit = env.get('HTTP_REQUEST_PAYLOAD_LIMIT_MEGABYTES', true)
|
||||
? `${+env.get('HTTP_REQUEST_PAYLOAD_LIMIT_MEGABYTES', true)}mb`
|
||||
: '50mb'
|
||||
|
||||
const server = new InversifyExpressServer(container)
|
||||
|
||||
server.setConfig((app) => {
|
||||
@@ -72,7 +77,7 @@ void container.load().then((container) => {
|
||||
}),
|
||||
)
|
||||
|
||||
app.use(json({ limit: '50mb' }))
|
||||
app.use(json({ limit: requestPayloadLimit }))
|
||||
app.use(
|
||||
text({
|
||||
type: ['text/plain', 'application/x-www-form-urlencoded', 'application/x-www-form-urlencoded; charset=utf-8'],
|
||||
@@ -91,12 +96,14 @@ void container.load().then((container) => {
|
||||
|
||||
server.setErrorConfig((app) => {
|
||||
app.use((error: Record<string, unknown>, request: Request, response: Response, _next: NextFunction) => {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
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,
|
||||
userId: locals.user ? locals.user.uuid : undefined,
|
||||
})
|
||||
logger.debug(
|
||||
`[URL: |${request.method}| ${request.url}][SNJS: ${request.headers['x-snjs-version']}][Application: ${
|
||||
@@ -104,6 +111,16 @@ void container.load().then((container) => {
|
||||
}] Request body: ${JSON.stringify(request.body)}`,
|
||||
)
|
||||
|
||||
if ('type' in error && error.type === 'entity.too.large') {
|
||||
response.status(413).send({
|
||||
error: {
|
||||
message: 'The request payload is too large.',
|
||||
},
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
response.status(500).send({
|
||||
error: {
|
||||
message:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.89.14",
|
||||
"version": "1.89.19",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -8,6 +8,8 @@ import { Logger } from 'winston'
|
||||
|
||||
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
|
||||
import { ServiceProxyInterface } from '../Service/Proxy/ServiceProxyInterface'
|
||||
import { ResponseLocals } from './ResponseLocals'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
|
||||
export abstract class AuthMiddleware extends BaseMiddleware {
|
||||
constructor(
|
||||
@@ -55,33 +57,27 @@ export abstract class AuthMiddleware extends BaseMiddleware {
|
||||
crossServiceTokenFetchedFromCache = false
|
||||
}
|
||||
|
||||
response.locals.authToken = crossServiceToken
|
||||
|
||||
const decodedToken = <CrossServiceTokenData>(
|
||||
verify(response.locals.authToken, this.jwtSecret, { algorithms: ['HS256'] })
|
||||
)
|
||||
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
|
||||
|
||||
if (this.crossServiceTokenCacheTTL && !crossServiceTokenFetchedFromCache) {
|
||||
await this.crossServiceTokenCache.set({
|
||||
key: cacheKey,
|
||||
encodedCrossServiceToken: response.locals.authToken,
|
||||
encodedCrossServiceToken: crossServiceToken,
|
||||
expiresAtInSeconds: this.getCrossServiceTokenCacheExpireTimestamp(decodedToken),
|
||||
userUuid: decodedToken.user.uuid,
|
||||
})
|
||||
}
|
||||
|
||||
response.locals.user = decodedToken.user
|
||||
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 ?? []
|
||||
Object.assign(response.locals, {
|
||||
authToken: crossServiceToken,
|
||||
user: decodedToken.user,
|
||||
session: decodedToken.session,
|
||||
roles: decodedToken.roles,
|
||||
sharedVaultOwnerContext: decodedToken.shared_vault_owner_context,
|
||||
readOnlyAccess: decodedToken.session?.readonly_access ?? false,
|
||||
isFreeUser: decodedToken.roles.length === 1 && decodedToken.roles[0].name === RoleName.NAMES.CoreUser,
|
||||
belongsToSharedVaults: decodedToken.belongs_to_shared_vaults ?? [],
|
||||
} as ResponseLocals)
|
||||
} catch (error) {
|
||||
let detailedErrorMessage = (error as Error).message
|
||||
if (error instanceof AxiosError) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { verify } from 'jsonwebtoken'
|
||||
import { Logger } from 'winston'
|
||||
import { ConnectionValidationResponse, IAuthClient, WebsocketConnectionAuthorizationHeader } from '@standardnotes/grpc'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
import { ResponseLocals } from './ResponseLocals'
|
||||
|
||||
export class GRPCWebSocketAuthMiddleware extends BaseMiddleware {
|
||||
constructor(
|
||||
@@ -90,15 +91,16 @@ export class GRPCWebSocketAuthMiddleware extends BaseMiddleware {
|
||||
|
||||
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
|
||||
Object.assign(response.locals, {
|
||||
authToken: crossServiceToken,
|
||||
user: decodedToken.user,
|
||||
session: decodedToken.session,
|
||||
roles: decodedToken.roles,
|
||||
isFreeUser: decodedToken.roles.length === 1 && decodedToken.roles[0].name === RoleName.NAMES.CoreUser,
|
||||
readOnlyAccess: decodedToken.session?.readonly_access ?? false,
|
||||
} as ResponseLocals)
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Could not pass the request to websocket connection validation on underlying service: ${
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface OfflineResponseLocals {
|
||||
offlineAuthToken: string
|
||||
userEmail: string
|
||||
featuresToken: string
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Role } from '@standardnotes/security'
|
||||
|
||||
export interface ResponseLocals {
|
||||
authToken: string
|
||||
user: {
|
||||
uuid: string
|
||||
email: string
|
||||
}
|
||||
roles: Array<Role>
|
||||
session?: {
|
||||
uuid: string
|
||||
api_version: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
device_info: string
|
||||
readonly_access: boolean
|
||||
access_expiration: string
|
||||
refresh_expiration: string
|
||||
}
|
||||
readOnlyAccess: boolean
|
||||
isFreeUser: boolean
|
||||
belongsToSharedVaults?: Array<{
|
||||
shared_vault_uuid: string
|
||||
permission: string
|
||||
}>
|
||||
sharedVaultOwnerContext?: {
|
||||
upload_bytes_limit: number
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { TokenAuthenticationMethod } from './TokenAuthenticationMethod'
|
||||
|
||||
export interface SubscriptionResponseLocals {
|
||||
tokenAuthenticationMethod: TokenAuthenticationMethod
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import { AxiosError, AxiosInstance, AxiosResponse } from 'axios'
|
||||
import { Logger } from 'winston'
|
||||
import { TYPES } from '../Bootstrap/Types'
|
||||
import { TokenAuthenticationMethod } from './TokenAuthenticationMethod'
|
||||
import { ResponseLocals } from './ResponseLocals'
|
||||
import { OfflineResponseLocals } from './OfflineResponseLocals'
|
||||
import { SubscriptionResponseLocals } from './SubscriptionResponseLocals'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionTokenAuthMiddleware extends BaseMiddleware {
|
||||
@@ -34,13 +37,16 @@ export class SubscriptionTokenAuthMiddleware extends BaseMiddleware {
|
||||
return
|
||||
}
|
||||
|
||||
response.locals.tokenAuthenticationMethod = email
|
||||
? TokenAuthenticationMethod.OfflineSubscriptionToken
|
||||
: TokenAuthenticationMethod.SubscriptionToken
|
||||
const locals = {
|
||||
tokenAuthenticationMethod: email
|
||||
? TokenAuthenticationMethod.OfflineSubscriptionToken
|
||||
: TokenAuthenticationMethod.SubscriptionToken,
|
||||
} as SubscriptionResponseLocals
|
||||
Object.assign(response.locals, locals)
|
||||
|
||||
try {
|
||||
const url =
|
||||
response.locals.tokenAuthenticationMethod == TokenAuthenticationMethod.OfflineSubscriptionToken
|
||||
locals.tokenAuthenticationMethod == TokenAuthenticationMethod.OfflineSubscriptionToken
|
||||
? `${this.authServerUrl}/offline/subscription-tokens/${subscriptionToken}/validate`
|
||||
: `${this.authServerUrl}/subscription-tokens/${subscriptionToken}/validate`
|
||||
|
||||
@@ -65,7 +71,7 @@ export class SubscriptionTokenAuthMiddleware extends BaseMiddleware {
|
||||
return
|
||||
}
|
||||
|
||||
if (response.locals.tokenAuthenticationMethod == TokenAuthenticationMethod.OfflineSubscriptionToken) {
|
||||
if (locals.tokenAuthenticationMethod == TokenAuthenticationMethod.OfflineSubscriptionToken) {
|
||||
this.handleOfflineAuthTokenValidationResponse(response, authResponse)
|
||||
|
||||
return next()
|
||||
@@ -101,24 +107,26 @@ export class SubscriptionTokenAuthMiddleware extends BaseMiddleware {
|
||||
}
|
||||
|
||||
private handleOfflineAuthTokenValidationResponse(response: Response, authResponse: AxiosResponse) {
|
||||
response.locals.offlineAuthToken = authResponse.data.authToken
|
||||
|
||||
const decodedToken = <OfflineUserTokenData>(
|
||||
verify(authResponse.data.authToken, this.jwtSecret, { algorithms: ['HS256'] })
|
||||
)
|
||||
|
||||
response.locals.offlineUserEmail = decodedToken.userEmail
|
||||
response.locals.offlineFeaturesToken = decodedToken.featuresToken
|
||||
Object.assign(response.locals, {
|
||||
offlineAuthToken: authResponse.data.authToken,
|
||||
userEmail: decodedToken.userEmail,
|
||||
featuresToken: decodedToken.featuresToken,
|
||||
} as OfflineResponseLocals)
|
||||
}
|
||||
|
||||
private handleAuthTokenValidationResponse(response: Response, authResponse: AxiosResponse) {
|
||||
response.locals.authToken = authResponse.data.authToken
|
||||
|
||||
const decodedToken = <CrossServiceTokenData>(
|
||||
verify(authResponse.data.authToken, this.jwtSecret, { algorithms: ['HS256'] })
|
||||
)
|
||||
|
||||
response.locals.user = decodedToken.user
|
||||
response.locals.roles = decodedToken.roles
|
||||
Object.assign(response.locals, {
|
||||
authToken: authResponse.data.authToken,
|
||||
user: decodedToken.user,
|
||||
roles: decodedToken.roles,
|
||||
} as ResponseLocals)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { AxiosError, AxiosInstance } from 'axios'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { TYPES } from '../Bootstrap/Types'
|
||||
import { ResponseLocals } from './ResponseLocals'
|
||||
|
||||
@injectable()
|
||||
export class WebSocketAuthMiddleware extends BaseMiddleware {
|
||||
@@ -55,13 +56,14 @@ export class WebSocketAuthMiddleware extends BaseMiddleware {
|
||||
|
||||
const crossServiceToken = authResponse.data.authToken
|
||||
|
||||
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
|
||||
Object.assign(response.locals, {
|
||||
authToken: crossServiceToken,
|
||||
user: decodedToken.user,
|
||||
session: decodedToken.session,
|
||||
roles: decodedToken.roles,
|
||||
} as ResponseLocals)
|
||||
} catch (error) {
|
||||
const errorMessage = (error as AxiosError).isAxiosError
|
||||
? JSON.stringify((error as AxiosError).response?.data)
|
||||
|
||||
@@ -16,6 +16,8 @@ import { TYPES } from '../../Bootstrap/Types'
|
||||
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
|
||||
import { TokenAuthenticationMethod } from '../TokenAuthenticationMethod'
|
||||
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
import { SubscriptionResponseLocals } from '../SubscriptionResponseLocals'
|
||||
|
||||
@controller('/v1/users')
|
||||
export class UsersController extends BaseHttpController {
|
||||
@@ -214,7 +216,9 @@ export class UsersController extends BaseHttpController {
|
||||
|
||||
@httpGet('/subscription', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
|
||||
async getSubscriptionBySubscriptionToken(request: Request, response: Response): Promise<void> {
|
||||
if (response.locals.tokenAuthenticationMethod === TokenAuthenticationMethod.OfflineSubscriptionToken) {
|
||||
const locals = response.locals as SubscriptionResponseLocals & ResponseLocals
|
||||
|
||||
if (locals.tokenAuthenticationMethod === TokenAuthenticationMethod.OfflineSubscriptionToken) {
|
||||
await this.httpService.callAuthServer(
|
||||
request,
|
||||
response,
|
||||
@@ -227,11 +231,7 @@ export class UsersController extends BaseHttpController {
|
||||
await this.httpService.callAuthServer(
|
||||
request,
|
||||
response,
|
||||
this.endpointResolver.resolveEndpointOrMethodIdentifier(
|
||||
'GET',
|
||||
'users/:userUuid/subscription',
|
||||
response.locals.user.uuid,
|
||||
),
|
||||
this.endpointResolver.resolveEndpointOrMethodIdentifier('GET', 'users/:userUuid/subscription', locals.user.uuid),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Request, Response } from 'express'
|
||||
import { ServiceContainerInterface, ServiceIdentifier } from '@standardnotes/domain-core'
|
||||
|
||||
import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
|
||||
import { ResponseLocals } from '../../Controller/ResponseLocals'
|
||||
|
||||
export class DirectCallServiceProxy implements ServiceProxyInterface {
|
||||
constructor(
|
||||
@@ -134,11 +135,13 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
|
||||
response: Response,
|
||||
serviceResponse: { statusCode: number; json: Record<string, unknown> },
|
||||
): void {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
void response.status(serviceResponse.statusCode).send({
|
||||
meta: {
|
||||
auth: {
|
||||
userUuid: response.locals.user?.uuid,
|
||||
roles: response.locals.roles,
|
||||
userUuid: locals.user?.uuid,
|
||||
roles: locals.roles,
|
||||
},
|
||||
server: {
|
||||
filesServerUrl: this.filesServerUrl,
|
||||
|
||||
@@ -8,6 +8,8 @@ import { TYPES } from '../../Bootstrap/Types'
|
||||
import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface'
|
||||
import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { ResponseLocals } from '../../Controller/ResponseLocals'
|
||||
import { OfflineResponseLocals } from '../../Controller/OfflineResponseLocals'
|
||||
|
||||
@injectable()
|
||||
export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
@@ -175,8 +177,9 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
retryAttempt?: number,
|
||||
): Promise<AxiosResponse | undefined> {
|
||||
const locals = response.locals as ResponseLocals | OfflineResponseLocals
|
||||
|
||||
try {
|
||||
const headers: Record<string, string> = {}
|
||||
for (const headerName of Object.keys(request.headers)) {
|
||||
@@ -186,12 +189,12 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
delete headers.host
|
||||
delete headers['content-length']
|
||||
|
||||
if (response.locals.authToken) {
|
||||
headers['X-Auth-Token'] = response.locals.authToken
|
||||
if ('authToken' in locals && locals.authToken) {
|
||||
headers['X-Auth-Token'] = locals.authToken
|
||||
}
|
||||
|
||||
if (response.locals.offlineAuthToken) {
|
||||
headers['X-Auth-Offline-Token'] = response.locals.offlineAuthToken
|
||||
if ('offlineAuthToken' in locals && locals.offlineAuthToken) {
|
||||
headers['X-Auth-Offline-Token'] = locals.offlineAuthToken
|
||||
}
|
||||
|
||||
const serviceResponse = await this.httpClient.request({
|
||||
@@ -213,35 +216,17 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
await this.crossServiceTokenCache.invalidate(userUuid)
|
||||
}
|
||||
|
||||
if (retryAttempt) {
|
||||
this.logger.debug(`Request to ${serverUrl}/${endpoint} succeeded after ${retryAttempt} retries`)
|
||||
}
|
||||
|
||||
return serviceResponse
|
||||
} catch (error) {
|
||||
const requestDidNotMakeIt = this.requestTimedOutOrDidNotReachDestination(error as Record<string, unknown>)
|
||||
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 ${serverUrl}/${endpoint} for the ${nextRetryAttempt} time`)
|
||||
|
||||
return this.getServerResponse(serverUrl, request, response, endpoint, payload, nextRetryAttempt)
|
||||
}
|
||||
|
||||
let detailedErrorMessage = (error as Error).message
|
||||
if (error instanceof AxiosError) {
|
||||
detailedErrorMessage = `Status: ${error.status}, code: ${error.code}, message: ${error.message}`
|
||||
}
|
||||
|
||||
this.logger.error(
|
||||
tooManyRetryAttempts
|
||||
? `Request to ${serverUrl}/${endpoint} timed out after ${retryAttempt} retries`
|
||||
: `Could not pass the request to ${serverUrl}/${endpoint} on underlying service: ${detailedErrorMessage}`,
|
||||
`Could not pass the request to ${serverUrl}/${endpoint} on underlying service: ${detailedErrorMessage}`,
|
||||
{
|
||||
userId: response.locals.user ? response.locals.user.uuid : undefined,
|
||||
userId: (locals as ResponseLocals).user ? (locals as ResponseLocals).user.uuid : undefined,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -276,6 +261,7 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload)
|
||||
|
||||
if (!serviceResponse) {
|
||||
@@ -293,8 +279,8 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
response.status(serviceResponse.status).send({
|
||||
meta: {
|
||||
auth: {
|
||||
userUuid: response.locals.user?.uuid,
|
||||
roles: response.locals.roles,
|
||||
userUuid: locals.user?.uuid,
|
||||
roles: locals.roles,
|
||||
},
|
||||
server: {
|
||||
filesServerUrl: this.filesServerUrl,
|
||||
|
||||
@@ -40,7 +40,6 @@ export class EndpointResolver implements EndpointResolverInterface {
|
||||
// Tokens Controller
|
||||
['[POST]:subscription-tokens', 'auth.subscription-tokens.create'],
|
||||
// Users Controller
|
||||
['[PATCH]:users/:userId', 'auth.users.update'],
|
||||
['[PUT]:users/:userUuid/attributes/credentials', 'auth.users.updateCredentials'],
|
||||
['[DELETE]:users/:userUuid', 'auth.users.delete'],
|
||||
['[POST]:listed', 'auth.users.createListedAccount'],
|
||||
|
||||
@@ -9,6 +9,8 @@ import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCache
|
||||
import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
|
||||
import { GRPCSyncingServerServiceProxy } from './GRPCSyncingServerServiceProxy'
|
||||
import { Status } from '@grpc/grpc-js/build/src/constants'
|
||||
import { ResponseLocals } from '../../Controller/ResponseLocals'
|
||||
import { OfflineResponseLocals } from '../../Controller/OfflineResponseLocals'
|
||||
|
||||
export class GRPCServiceProxy implements ServiceProxyInterface {
|
||||
constructor(
|
||||
@@ -134,48 +136,23 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
|
||||
request: Request,
|
||||
response: Response,
|
||||
payload?: Record<string, unknown> | string,
|
||||
retryAttempt?: number,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const result = await this.gRPCSyncingServerServiceProxy.sync(request, response, payload)
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
response.status(result.status).send({
|
||||
meta: {
|
||||
auth: {
|
||||
userUuid: response.locals.user?.uuid,
|
||||
roles: response.locals.roles,
|
||||
},
|
||||
server: {
|
||||
filesServerUrl: this.filesServerUrl,
|
||||
},
|
||||
const result = await this.gRPCSyncingServerServiceProxy.sync(request, response, payload)
|
||||
|
||||
response.status(result.status).send({
|
||||
meta: {
|
||||
auth: {
|
||||
userUuid: locals.user?.uuid,
|
||||
roles: locals.roles,
|
||||
},
|
||||
data: result.data,
|
||||
})
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
server: {
|
||||
filesServerUrl: this.filesServerUrl,
|
||||
},
|
||||
},
|
||||
data: result.data,
|
||||
})
|
||||
}
|
||||
|
||||
async callRevisionsServer(
|
||||
@@ -277,6 +254,8 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
|
||||
payload?: Record<string, unknown> | string,
|
||||
retryAttempt?: number,
|
||||
): Promise<AxiosResponse | undefined> {
|
||||
const locals = response.locals as ResponseLocals | OfflineResponseLocals
|
||||
|
||||
try {
|
||||
const headers: Record<string, string> = {}
|
||||
for (const headerName of Object.keys(request.headers)) {
|
||||
@@ -286,12 +265,12 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
|
||||
delete headers.host
|
||||
delete headers['content-length']
|
||||
|
||||
if (response.locals.authToken) {
|
||||
headers['X-Auth-Token'] = response.locals.authToken
|
||||
if ('authToken' in locals && locals.authToken) {
|
||||
headers['X-Auth-Token'] = locals.authToken
|
||||
}
|
||||
|
||||
if (response.locals.offlineAuthToken) {
|
||||
headers['X-Auth-Offline-Token'] = response.locals.offlineAuthToken
|
||||
if ('offlineAuthToken' in locals && locals.offlineAuthToken) {
|
||||
headers['X-Auth-Offline-Token'] = locals.offlineAuthToken
|
||||
}
|
||||
|
||||
const serviceResponse = await this.httpClient.request({
|
||||
@@ -341,7 +320,7 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
|
||||
? `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,
|
||||
userId: (locals as ResponseLocals).user ? (locals as ResponseLocals).user.uuid : undefined,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -376,6 +355,8 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload)
|
||||
|
||||
if (!serviceResponse) {
|
||||
@@ -393,8 +374,8 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
|
||||
response.status(serviceResponse.status).send({
|
||||
meta: {
|
||||
auth: {
|
||||
userUuid: response.locals.user?.uuid,
|
||||
roles: response.locals.roles,
|
||||
userUuid: locals.user?.uuid,
|
||||
roles: locals.roles,
|
||||
},
|
||||
server: {
|
||||
filesServerUrl: this.filesServerUrl,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Metadata } from '@grpc/grpc-js'
|
||||
import { SyncResponseHttpRepresentation } from '../../Mapping/Sync/Http/SyncResponseHttpRepresentation'
|
||||
import { Status } from '@grpc/grpc-js/build/src/constants'
|
||||
import { Logger } from 'winston'
|
||||
import { ResponseLocals } from '../../Controller/ResponseLocals'
|
||||
|
||||
export class GRPCSyncingServerServiceProxy {
|
||||
constructor(
|
||||
@@ -20,24 +21,26 @@ export class GRPCSyncingServerServiceProxy {
|
||||
response: Response,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<{ status: number; data: unknown }> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const syncRequest = this.syncRequestGRPCMapper.toProjection(payload as Record<string, unknown>)
|
||||
|
||||
const metadata = new Metadata()
|
||||
metadata.set('x-user-uuid', response.locals.user.uuid)
|
||||
metadata.set('x-user-uuid', 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')
|
||||
if (response.locals.readOnlyAccess) {
|
||||
metadata.set('x-read-only-access', locals.readOnlyAccess ? 'true' : 'false')
|
||||
if (locals.readOnlyAccess) {
|
||||
this.logger.debug('Syncing with read-only access', {
|
||||
codeTag: 'GRPCSyncingServerServiceProxy',
|
||||
userId: response.locals.user.uuid,
|
||||
userId: locals.user.uuid,
|
||||
})
|
||||
}
|
||||
if (response.locals.session) {
|
||||
metadata.set('x-session-uuid', response.locals.session.uuid)
|
||||
if (locals.session) {
|
||||
metadata.set('x-session-uuid', locals.session.uuid)
|
||||
}
|
||||
metadata.set('x-is-free-user', response.locals.isFreeUser ? 'true' : 'false')
|
||||
metadata.set('x-is-free-user', locals.isFreeUser ? 'true' : 'false')
|
||||
|
||||
this.syncingClient.syncItems(syncRequest, metadata, (error, syncResponse) => {
|
||||
if (error) {
|
||||
@@ -52,7 +55,7 @@ export class GRPCSyncingServerServiceProxy {
|
||||
if (error.code === Status.INTERNAL) {
|
||||
this.logger.error(`Internal gRPC error: ${error.message}. Payload: ${JSON.stringify(payload)}`, {
|
||||
codeTag: 'GRPCSyncingServerServiceProxy',
|
||||
userId: response.locals.user.uuid,
|
||||
userId: locals.user.uuid,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -68,7 +71,7 @@ export class GRPCSyncingServerServiceProxy {
|
||||
) {
|
||||
this.logger.error(`Internal gRPC error: ${JSON.stringify(error)}. Payload: ${JSON.stringify(payload)}`, {
|
||||
codeTag: 'GRPCSyncingServerServiceProxy.catch',
|
||||
userId: response.locals.user.uuid,
|
||||
userId: locals.user.uuid,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,32 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.177.17](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.16...@standardnotes/auth-server@1.177.17) (2024-01-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** add debug logs for subscription sync requested event ([351e18f](https://github.com/standardnotes/server/commit/351e18f6389c2dbaa2107e6549be9928c2e8834f))
|
||||
|
||||
## [1.177.16](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.15...@standardnotes/auth-server@1.177.16) (2024-01-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** update shared subscriptions upon subscription sync ([#1022](https://github.com/standardnotes/server/issues/1022)) ([d7a1c66](https://github.com/standardnotes/server/commit/d7a1c667dd62dacc1ef15f2a4f408dc07045fcad))
|
||||
|
||||
## [1.177.15](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.14...@standardnotes/auth-server@1.177.15) (2024-01-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** check for user agent persisting on session during a session refresh ([#1016](https://github.com/standardnotes/server/issues/1016)) ([0b46eff](https://github.com/standardnotes/server/commit/0b46eff16ea0c32cac91ead04474303500359f4f))
|
||||
|
||||
## [1.177.14](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.13...@standardnotes/auth-server@1.177.14) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.177.13](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.12...@standardnotes/auth-server@1.177.13) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.177.12](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.11...@standardnotes/auth-server@1.177.12) (2024-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
@@ -35,6 +35,7 @@ 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'
|
||||
import { ResponseLocals } from '../src/Infra/InversifyExpressUtils/ResponseLocals'
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
void container.load().then((container) => {
|
||||
@@ -59,12 +60,13 @@ void container.load().then((container) => {
|
||||
|
||||
server.setErrorConfig((app) => {
|
||||
app.use((error: Record<string, unknown>, request: Request, response: Response, _next: NextFunction) => {
|
||||
const locals = response.locals as ResponseLocals
|
||||
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,
|
||||
userId: locals.user ? locals.user.uuid : undefined,
|
||||
})
|
||||
|
||||
response.status(500).send({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.177.12",
|
||||
"version": "1.177.17",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -284,6 +284,7 @@ import { AccountDeletionVerificationPassedEventHandler } from '../Domain/Handler
|
||||
import { RenewSharedSubscriptions } from '../Domain/UseCase/RenewSharedSubscriptions/RenewSharedSubscriptions'
|
||||
import { FixStorageQuotaForUser } from '../Domain/UseCase/FixStorageQuotaForUser/FixStorageQuotaForUser'
|
||||
import { FileQuotaRecalculatedEventHandler } from '../Domain/Handler/FileQuotaRecalculatedEventHandler'
|
||||
import { SessionServiceInterface } from '../Domain/Session/SessionServiceInterface'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
constructor(private mode: 'server' | 'worker' = 'server') {}
|
||||
@@ -986,7 +987,18 @@ export class ContainerConfigLoader {
|
||||
.toConstantValue(new CleanupExpiredSessions(container.get(TYPES.Auth_SessionRepository)))
|
||||
container.bind<AuthenticateUser>(TYPES.Auth_AuthenticateUser).to(AuthenticateUser)
|
||||
container.bind<AuthenticateRequest>(TYPES.Auth_AuthenticateRequest).to(AuthenticateRequest)
|
||||
container.bind<RefreshSessionToken>(TYPES.Auth_RefreshSessionToken).to(RefreshSessionToken)
|
||||
container
|
||||
.bind<RefreshSessionToken>(TYPES.Auth_RefreshSessionToken)
|
||||
.toConstantValue(
|
||||
new RefreshSessionToken(
|
||||
container.get<SessionServiceInterface>(TYPES.Auth_SessionService),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
|
||||
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||
container.get<GetSetting>(TYPES.Auth_GetSetting),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container.bind<SignIn>(TYPES.Auth_SignIn).to(SignIn)
|
||||
container
|
||||
.bind<VerifyMFA>(TYPES.Auth_VerifyMFA)
|
||||
@@ -1409,6 +1421,7 @@ export class ContainerConfigLoader {
|
||||
container.get<SetSettingValue>(TYPES.Auth_SetSettingValue),
|
||||
container.get<OfflineSettingServiceInterface>(TYPES.Auth_OfflineSettingService),
|
||||
container.get<ContentDecoderInterface>(TYPES.Auth_ContenDecoder),
|
||||
container.get<RenewSharedSubscriptions>(TYPES.Auth_RenewSharedSubscriptions),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
@@ -1708,7 +1721,6 @@ export class ContainerConfigLoader {
|
||||
.bind<BaseUsersController>(TYPES.Auth_BaseUsersController)
|
||||
.toConstantValue(
|
||||
new BaseUsersController(
|
||||
container.get<UpdateUser>(TYPES.Auth_UpdateUser),
|
||||
container.get<DeleteAccount>(TYPES.Auth_DeleteAccount),
|
||||
container.get<GetUserSubscription>(TYPES.Auth_GetUserSubscription),
|
||||
container.get<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts),
|
||||
|
||||
@@ -16,6 +16,7 @@ import { OfflineSettingName } from '../Setting/OfflineSettingName'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
import { ApplyDefaultSubscriptionSettings } from '../UseCase/ApplyDefaultSubscriptionSettings/ApplyDefaultSubscriptionSettings'
|
||||
import { SetSettingValue } from '../UseCase/SetSettingValue/SetSettingValue'
|
||||
import { RenewSharedSubscriptions } from '../UseCase/RenewSharedSubscriptions/RenewSharedSubscriptions'
|
||||
|
||||
export class SubscriptionSyncRequestedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@@ -27,10 +28,15 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
|
||||
private setSettingValue: SetSettingValue,
|
||||
private offlineSettingService: OfflineSettingServiceInterface,
|
||||
private contentDecoder: ContentDecoderInterface,
|
||||
private renewSharedSubscriptions: RenewSharedSubscriptions,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionSyncRequestedEvent): Promise<void> {
|
||||
this.logger.info('Subscription sync requested', {
|
||||
subscriptionId: event.payload.subscriptionId,
|
||||
})
|
||||
|
||||
if (event.payload.offline) {
|
||||
const offlineUserSubscription = await this.createOrUpdateOfflineSubscription(
|
||||
event.payload.subscriptionId,
|
||||
@@ -76,6 +82,11 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
|
||||
return
|
||||
}
|
||||
|
||||
this.logger.info('Syncing subscription', {
|
||||
userId: user.uuid,
|
||||
subscriptionId: event.payload.subscriptionId,
|
||||
})
|
||||
|
||||
const userSubscription = await this.createOrUpdateSubscription(
|
||||
event.payload.subscriptionId,
|
||||
event.payload.subscriptionName,
|
||||
@@ -85,6 +96,19 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
|
||||
event.payload.timestamp,
|
||||
)
|
||||
|
||||
const renewalResult = await this.renewSharedSubscriptions.execute({
|
||||
inviterEmail: user.email,
|
||||
newSubscriptionId: event.payload.subscriptionId,
|
||||
newSubscriptionName: event.payload.subscriptionName,
|
||||
newSubscriptionExpiresAt: event.payload.subscriptionExpiresAt,
|
||||
timestamp: event.payload.timestamp,
|
||||
})
|
||||
if (renewalResult.isFailed()) {
|
||||
this.logger.error(`Could not renew shared subscriptions for user: ${renewalResult.getError()}`, {
|
||||
userId: user.uuid,
|
||||
})
|
||||
}
|
||||
|
||||
await this.roleService.addUserRoleBasedOnSubscription(user, event.payload.subscriptionName)
|
||||
|
||||
const applyingSettingsResult = await this.applyDefaultSubscriptionSettings.execute({
|
||||
@@ -107,6 +131,11 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Could not set extension key for user ${user.uuid}`)
|
||||
}
|
||||
|
||||
this.logger.info('Subscription synced', {
|
||||
userId: user.uuid,
|
||||
subscriptionId: event.payload.subscriptionId,
|
||||
})
|
||||
}
|
||||
|
||||
private async createOrUpdateSubscription(
|
||||
|
||||
@@ -7,6 +7,10 @@ import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { Logger } from 'winston'
|
||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||
import { GetSetting } from './GetSetting/GetSetting'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { LogSessionUserAgentOption } from '@standardnotes/settings'
|
||||
import { Setting } from '../Setting/Setting'
|
||||
|
||||
describe('RefreshSessionToken', () => {
|
||||
let sessionService: SessionServiceInterface
|
||||
@@ -14,16 +18,20 @@ describe('RefreshSessionToken', () => {
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
let timer: TimerInterface
|
||||
let getSetting: GetSetting
|
||||
let logger: Logger
|
||||
|
||||
const createUseCase = () =>
|
||||
new RefreshSessionToken(sessionService, domainEventFactory, domainEventPublisher, timer, logger)
|
||||
new RefreshSessionToken(sessionService, domainEventFactory, domainEventPublisher, timer, getSetting, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
session = {} as jest.Mocked<Session>
|
||||
session.uuid = '1-2-3'
|
||||
session.refreshExpiration = new Date(123)
|
||||
|
||||
getSetting = {} as jest.Mocked<GetSetting>
|
||||
getSetting.execute = jest.fn().mockReturnValue(Result.fail('not found'))
|
||||
|
||||
sessionService = {} as jest.Mocked<SessionServiceInterface>
|
||||
sessionService.isRefreshTokenMatchingHashedSessionToken = jest.fn().mockReturnValue(true)
|
||||
sessionService.getSessionFromToken = jest.fn().mockReturnValue({ session, isEphemeral: false })
|
||||
@@ -69,6 +77,35 @@ describe('RefreshSessionToken', () => {
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should refresh session token and update user agent if enabled', async () => {
|
||||
getSetting.execute = jest.fn().mockReturnValue(
|
||||
Result.ok({
|
||||
setting: {} as jest.Mocked<Setting>,
|
||||
decryptedValue: LogSessionUserAgentOption.Enabled,
|
||||
}),
|
||||
)
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
accessToken: '123',
|
||||
refreshToken: '234',
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
|
||||
})
|
||||
|
||||
expect(sessionService.refreshTokens).toHaveBeenCalledWith({ session, isEphemeral: false })
|
||||
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
sessionPayload: {
|
||||
access_token: 'token1',
|
||||
refresh_token: 'token2',
|
||||
access_expiration: 123,
|
||||
refresh_expiration: 234,
|
||||
},
|
||||
})
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should refresh a session token even if publishing domain event fails', async () => {
|
||||
domainEventPublisher.publish = jest.fn().mockRejectedValue(new Error('test'))
|
||||
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { LogSessionUserAgentOption } from '@standardnotes/settings'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { SessionServiceInterface } from '../Session/SessionServiceInterface'
|
||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||
|
||||
import { RefreshSessionTokenResponse } from './RefreshSessionTokenResponse'
|
||||
import { RefreshSessionTokenDTO } from './RefreshSessionTokenDTO'
|
||||
import { GetSetting } from './GetSetting/GetSetting'
|
||||
|
||||
@injectable()
|
||||
export class RefreshSessionToken {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_SessionService) private sessionService: SessionServiceInterface,
|
||||
@inject(TYPES.Auth_DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
|
||||
@inject(TYPES.Auth_DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
|
||||
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
|
||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||
private sessionService: SessionServiceInterface,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
private timer: TimerInterface,
|
||||
private getSetting: GetSetting,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: RefreshSessionTokenDTO): Promise<RefreshSessionTokenResponse> {
|
||||
@@ -46,7 +47,9 @@ export class RefreshSessionToken {
|
||||
}
|
||||
}
|
||||
|
||||
session.userAgent = dto.userAgent
|
||||
if (await this.isLoggingUserAgentEnabledOnSessions(session.userUuid)) {
|
||||
session.userAgent = dto.userAgent
|
||||
}
|
||||
|
||||
const sessionPayload = await this.sessionService.refreshTokens({ session, isEphemeral })
|
||||
|
||||
@@ -64,4 +67,19 @@ export class RefreshSessionToken {
|
||||
userUuid: session.userUuid,
|
||||
}
|
||||
}
|
||||
|
||||
private async isLoggingUserAgentEnabledOnSessions(userUuid: string): Promise<boolean> {
|
||||
const loggingSettingOrError = await this.getSetting.execute({
|
||||
settingName: SettingName.NAMES.LogSessionUserAgent,
|
||||
decrypted: true,
|
||||
userUuid: userUuid,
|
||||
allowSensitiveRetrieval: true,
|
||||
})
|
||||
if (loggingSettingOrError.isFailed()) {
|
||||
return true
|
||||
}
|
||||
const loggingSetting = loggingSettingOrError.getValue()
|
||||
|
||||
return loggingSetting.decryptedValue === LogSessionUserAgentOption.Enabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,12 @@ import {
|
||||
controller,
|
||||
httpDelete,
|
||||
httpGet,
|
||||
httpPatch,
|
||||
httpPut,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
results,
|
||||
} from 'inversify-express-utils'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { DeleteAccount } from '../../Domain/UseCase/DeleteAccount/DeleteAccount'
|
||||
import { UpdateUser } from '../../Domain/UseCase/UpdateUser'
|
||||
import { GetUserSubscription } from '../../Domain/UseCase/GetUserSubscription/GetUserSubscription'
|
||||
import { ClearLoginAttempts } from '../../Domain/UseCase/ClearLoginAttempts'
|
||||
import { IncreaseLoginAttempts } from '../../Domain/UseCase/IncreaseLoginAttempts'
|
||||
@@ -21,26 +19,13 @@ import { BaseUsersController } from './Base/BaseUsersController'
|
||||
@controller('/users')
|
||||
export class AnnotatedUsersController extends BaseUsersController {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_UpdateUser) override updateUser: UpdateUser,
|
||||
@inject(TYPES.Auth_DeleteAccount) override doDeleteAccount: DeleteAccount,
|
||||
@inject(TYPES.Auth_GetUserSubscription) override doGetUserSubscription: GetUserSubscription,
|
||||
@inject(TYPES.Auth_ClearLoginAttempts) override clearLoginAttempts: ClearLoginAttempts,
|
||||
@inject(TYPES.Auth_IncreaseLoginAttempts) override increaseLoginAttempts: IncreaseLoginAttempts,
|
||||
@inject(TYPES.Auth_ChangeCredentials) override changeCredentialsUseCase: ChangeCredentials,
|
||||
) {
|
||||
super(
|
||||
updateUser,
|
||||
doDeleteAccount,
|
||||
doGetUserSubscription,
|
||||
clearLoginAttempts,
|
||||
increaseLoginAttempts,
|
||||
changeCredentialsUseCase,
|
||||
)
|
||||
}
|
||||
|
||||
@httpPatch('/:userId', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
|
||||
override async update(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
return super.update(request, response)
|
||||
super(doDeleteAccount, doGetUserSubscription, clearLoginAttempts, increaseLoginAttempts, changeCredentialsUseCase)
|
||||
}
|
||||
|
||||
@httpDelete('/:userUuid', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
|
||||
|
||||
@@ -8,6 +8,7 @@ import { IncreaseLoginAttempts } from '../../../Domain/UseCase/IncreaseLoginAtte
|
||||
import { SignIn } from '../../../Domain/UseCase/SignIn'
|
||||
import { VerifyMFA } from '../../../Domain/UseCase/VerifyMFA'
|
||||
import { AuthController } from '../../../Controller/AuthController'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
import { BaseHttpController, results } from 'inversify-express-utils'
|
||||
|
||||
export class BaseAuthController extends BaseHttpController {
|
||||
@@ -37,9 +38,11 @@ export class BaseAuthController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async params(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (response.locals.session) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (locals.session) {
|
||||
const result = await this.getUserKeyParams.execute({
|
||||
email: response.locals.user.email,
|
||||
email: locals.user.email,
|
||||
authenticated: true,
|
||||
})
|
||||
|
||||
@@ -145,6 +148,8 @@ export class BaseAuthController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async pkceParams(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (!request.body.code_challenge) {
|
||||
return this.json(
|
||||
{
|
||||
@@ -156,9 +161,9 @@ export class BaseAuthController extends BaseHttpController {
|
||||
)
|
||||
}
|
||||
|
||||
if (response.locals.session) {
|
||||
if (locals.session) {
|
||||
const result = await this.getUserKeyParams.execute({
|
||||
email: response.locals.user.email,
|
||||
email: locals.user.email,
|
||||
authenticated: true,
|
||||
codeChallenge: request.body.code_challenge as string,
|
||||
})
|
||||
@@ -248,8 +253,10 @@ export class BaseAuthController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async generateRecoveryCodes(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const result = await this.authController.generateRecoveryCodes({
|
||||
userUuid: response.locals.user.uuid,
|
||||
userUuid: locals.user.uuid,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
@@ -280,8 +287,10 @@ export class BaseAuthController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async signOut(request: Request, response: Response): Promise<results.JsonResult | void> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const result = await this.authController.signOut({
|
||||
readOnlyAccess: response.locals.readOnlyAccess,
|
||||
readOnlyAccess: locals.readOnlyAccess,
|
||||
authorizationHeader: <string>request.headers.authorization,
|
||||
})
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Request, Response } from 'express'
|
||||
|
||||
import { AuthenticatorsController } from '../../../Controller/AuthenticatorsController'
|
||||
import { BaseHttpController, results } from 'inversify-express-utils'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
|
||||
export class BaseAuthenticatorsController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -30,16 +31,20 @@ export class BaseAuthenticatorsController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async list(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const result = await this.authenticatorsController.list({
|
||||
userUuid: response.locals.user.uuid,
|
||||
userUuid: locals.user.uuid,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
async delete(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const result = await this.authenticatorsController.delete({
|
||||
userUuid: response.locals.user.uuid,
|
||||
userUuid: locals.user.uuid,
|
||||
authenticatorId: request.params.authenticatorId,
|
||||
})
|
||||
|
||||
@@ -47,17 +52,21 @@ export class BaseAuthenticatorsController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async generateRegistrationOptions(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const result = await this.authenticatorsController.generateRegistrationOptions({
|
||||
username: response.locals.user.email,
|
||||
userUuid: response.locals.user.uuid,
|
||||
username: locals.user.email,
|
||||
userUuid: locals.user.uuid,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
async verifyRegistration(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const result = await this.authenticatorsController.verifyRegistrationResponse({
|
||||
userUuid: response.locals.user.uuid,
|
||||
userUuid: locals.user.uuid,
|
||||
attestationResponse: request.body.attestationResponse,
|
||||
})
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Request, Response } from 'express'
|
||||
|
||||
import { GetUserFeatures } from '../../../Domain/UseCase/GetUserFeatures/GetUserFeatures'
|
||||
import { BaseHttpController, results } from 'inversify-express-utils'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
|
||||
export class BaseFeaturesController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -17,7 +18,9 @@ export class BaseFeaturesController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async getFeatures(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (request.params.userUuid !== response.locals.user.uuid) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (request.params.userUuid !== locals.user.uuid) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Request, Response } from 'express'
|
||||
|
||||
import { CreateListedAccount } from '../../../Domain/UseCase/CreateListedAccount/CreateListedAccount'
|
||||
import { BaseHttpController, results } from 'inversify-express-utils'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
|
||||
export class BaseListedController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -18,7 +19,9 @@ export class BaseListedController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async createListedAccount(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (response.locals.readOnlyAccess) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (locals.readOnlyAccess) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -31,8 +34,8 @@ export class BaseListedController extends BaseHttpController {
|
||||
}
|
||||
|
||||
await this.doCreateListedAccount.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
userEmail: response.locals.user.email,
|
||||
userUuid: locals.user.uuid,
|
||||
userEmail: locals.user.email,
|
||||
})
|
||||
|
||||
return this.json({
|
||||
|
||||
@@ -8,6 +8,7 @@ import { AuthenticateOfflineSubscriptionToken } from '../../../Domain/UseCase/Au
|
||||
import { CreateOfflineSubscriptionToken } from '../../../Domain/UseCase/CreateOfflineSubscriptionToken/CreateOfflineSubscriptionToken'
|
||||
import { GetUserFeatures } from '../../../Domain/UseCase/GetUserFeatures/GetUserFeatures'
|
||||
import { GetUserOfflineSubscription } from '../../../Domain/UseCase/GetUserOfflineSubscription/GetUserOfflineSubscription'
|
||||
import { OfflineResponseLocals } from '../OfflineResponseLocals'
|
||||
|
||||
export class BaseOfflineController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -30,8 +31,10 @@ export class BaseOfflineController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async getOfflineFeatures(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as OfflineResponseLocals
|
||||
|
||||
const result = await this.doGetUserFeatures.execute({
|
||||
email: response.locals.offlineUserEmail,
|
||||
email: locals.userEmail,
|
||||
offline: true,
|
||||
})
|
||||
|
||||
@@ -115,8 +118,10 @@ export class BaseOfflineController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async getSubscription(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as OfflineResponseLocals
|
||||
|
||||
const result = await this.getUserOfflineSubscription.execute({
|
||||
userEmail: response.locals.userEmail,
|
||||
userEmail: locals.userEmail,
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ErrorTag } from '@standardnotes/responses'
|
||||
import { DeleteOtherSessionsForUser } from '../../../Domain/UseCase/DeleteOtherSessionsForUser'
|
||||
import { DeleteSessionForUser } from '../../../Domain/UseCase/DeleteSessionForUser'
|
||||
import { RefreshSessionToken } from '../../../Domain/UseCase/RefreshSessionToken'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
|
||||
export class BaseSessionController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -24,7 +25,9 @@ export class BaseSessionController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async deleteSession(request: Request, response: Response): Promise<results.JsonResult | results.StatusCodeResult> {
|
||||
if (response.locals.readOnlyAccess) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (locals.readOnlyAccess) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -36,7 +39,7 @@ export class BaseSessionController extends BaseHttpController {
|
||||
)
|
||||
}
|
||||
|
||||
if (!request.body.uuid) {
|
||||
if (!request.body.uuid || !locals.session) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -47,7 +50,7 @@ export class BaseSessionController extends BaseHttpController {
|
||||
)
|
||||
}
|
||||
|
||||
if (request.body.uuid === response.locals.session.uuid) {
|
||||
if (request.body.uuid === locals.session.uuid) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -59,7 +62,7 @@ export class BaseSessionController extends BaseHttpController {
|
||||
}
|
||||
|
||||
const useCaseResponse = await this.deleteSessionForUser.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
userUuid: locals.user.uuid,
|
||||
sessionUuid: request.body.uuid,
|
||||
})
|
||||
|
||||
@@ -74,7 +77,7 @@ export class BaseSessionController extends BaseHttpController {
|
||||
)
|
||||
}
|
||||
|
||||
response.setHeader('x-invalidate-cache', response.locals.user.uuid)
|
||||
response.setHeader('x-invalidate-cache', locals.user.uuid)
|
||||
|
||||
return this.statusCode(204)
|
||||
}
|
||||
@@ -83,7 +86,9 @@ export class BaseSessionController extends BaseHttpController {
|
||||
_request: Request,
|
||||
response: Response,
|
||||
): Promise<results.JsonResult | results.StatusCodeResult> {
|
||||
if (response.locals.readOnlyAccess) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (locals.readOnlyAccess) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -95,7 +100,7 @@ export class BaseSessionController extends BaseHttpController {
|
||||
)
|
||||
}
|
||||
|
||||
if (!response.locals.user) {
|
||||
if (!locals.user || !locals.session) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -107,12 +112,12 @@ export class BaseSessionController extends BaseHttpController {
|
||||
}
|
||||
|
||||
await this.deleteOtherSessionsForUser.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
currentSessionUuid: response.locals.session.uuid,
|
||||
userUuid: locals.user.uuid,
|
||||
currentSessionUuid: locals.session.uuid,
|
||||
markAsRevoked: true,
|
||||
})
|
||||
|
||||
response.setHeader('x-invalidate-cache', response.locals.user.uuid)
|
||||
response.setHeader('x-invalidate-cache', locals.user.uuid)
|
||||
|
||||
return this.statusCode(204)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Session } from '../../../Domain/Session/Session'
|
||||
import { BaseHttpController, results } from 'inversify-express-utils'
|
||||
import { User } from '../../../Domain/User/User'
|
||||
import { SessionProjector } from '../../../Projection/SessionProjector'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
|
||||
export class BaseSessionsController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -67,12 +68,14 @@ export class BaseSessionsController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async getSessions(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (response.locals.readOnlyAccess) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (locals.readOnlyAccess) {
|
||||
return this.json([])
|
||||
}
|
||||
|
||||
const useCaseResponse = await this.getActiveSessionsForUser.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
userUuid: locals.user.uuid,
|
||||
})
|
||||
|
||||
return this.json(
|
||||
@@ -80,7 +83,7 @@ export class BaseSessionsController extends BaseHttpController {
|
||||
this.sessionProjector.projectCustom(
|
||||
SessionProjector.CURRENT_SESSION_PROJECTION.toString(),
|
||||
session,
|
||||
response.locals.session,
|
||||
locals.session,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ import { SubscriptionSetting } from '../../../Domain/Setting/SubscriptionSetting
|
||||
import { SubscriptionSettingHttpRepresentation } from '../../../Mapping/Http/SubscriptionSettingHttpRepresentation'
|
||||
import { SettingHttpRepresentation } from '../../../Mapping/Http/SettingHttpRepresentation'
|
||||
import { TriggerPostSettingUpdateActions } from '../../../Domain/UseCase/TriggerPostSettingUpdateActions/TriggerPostSettingUpdateActions'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
|
||||
export class BaseSettingsController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -40,7 +41,9 @@ export class BaseSettingsController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async getSettings(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (request.params.userUuid !== response.locals.user.uuid) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (request.params.userUuid !== locals.user.uuid) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -86,7 +89,9 @@ export class BaseSettingsController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async getSetting(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (request.params.userUuid !== response.locals.user.uuid) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (request.params.userUuid !== locals.user.uuid) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -135,7 +140,9 @@ export class BaseSettingsController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async updateSetting(request: Request, response: Response): Promise<results.JsonResult | results.StatusCodeResult> {
|
||||
if (response.locals.readOnlyAccess) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (locals.readOnlyAccess) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -147,7 +154,7 @@ export class BaseSettingsController extends BaseHttpController {
|
||||
)
|
||||
}
|
||||
|
||||
if (request.params.userUuid !== response.locals.user.uuid) {
|
||||
if (request.params.userUuid !== locals.user.uuid) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -163,7 +170,7 @@ export class BaseSettingsController extends BaseHttpController {
|
||||
const result = await this.setSettingValue.execute({
|
||||
settingName: name,
|
||||
value,
|
||||
userUuid: response.locals.user.uuid,
|
||||
userUuid: locals.user.uuid,
|
||||
checkUserPermissions: true,
|
||||
})
|
||||
|
||||
@@ -181,8 +188,8 @@ export class BaseSettingsController extends BaseHttpController {
|
||||
|
||||
const triggerResult = await this.triggerPostSettingUpdateActions.execute({
|
||||
updatedSettingName: setting.props.name,
|
||||
userUuid: response.locals.user.uuid,
|
||||
userEmail: response.locals.user.email,
|
||||
userUuid: locals.user.uuid,
|
||||
userEmail: locals.user.email,
|
||||
unencryptedValue: value,
|
||||
})
|
||||
if (triggerResult.isFailed()) {
|
||||
@@ -196,7 +203,9 @@ export class BaseSettingsController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async deleteSetting(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (response.locals.readOnlyAccess) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (locals.readOnlyAccess) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -208,7 +217,7 @@ export class BaseSettingsController extends BaseHttpController {
|
||||
)
|
||||
}
|
||||
|
||||
if (request.params.userUuid !== response.locals.user.uuid) {
|
||||
if (request.params.userUuid !== locals.user.uuid) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
|
||||
+16
-7
@@ -4,7 +4,8 @@ import { BaseHttpController, results } from 'inversify-express-utils'
|
||||
import { ApiVersion } from '@standardnotes/api'
|
||||
|
||||
import { SubscriptionInvitesController } from '../../../Controller/SubscriptionInvitesController'
|
||||
import { Role } from '../../../Domain/Role/Role'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
import { Role } from '@standardnotes/security'
|
||||
|
||||
export class BaseSubscriptionInvitesController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -23,12 +24,14 @@ export class BaseSubscriptionInvitesController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async acceptInvite(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const result = await this.subscriptionInvitesController.acceptInvite({
|
||||
api: request.query.api as ApiVersion,
|
||||
inviteUuid: request.params.inviteUuid,
|
||||
})
|
||||
|
||||
response.setHeader('x-invalidate-cache', response.locals.user.uuid)
|
||||
response.setHeader('x-invalidate-cache', locals.user.uuid)
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
@@ -43,30 +46,36 @@ export class BaseSubscriptionInvitesController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async inviteToSubscriptionSharing(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const result = await this.subscriptionInvitesController.invite({
|
||||
...request.body,
|
||||
inviterEmail: response.locals.user.email,
|
||||
inviterUuid: response.locals.user.uuid,
|
||||
inviterRoles: response.locals.roles.map((role: Role) => role.name),
|
||||
inviterEmail: locals.user.email,
|
||||
inviterUuid: locals.user.uuid,
|
||||
inviterRoles: locals.roles.map((role: Role) => role.name),
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
async cancelSubscriptionSharing(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const result = await this.subscriptionInvitesController.cancelInvite({
|
||||
...request.body,
|
||||
inviteUuid: request.params.inviteUuid,
|
||||
inviterEmail: response.locals.user.email,
|
||||
inviterEmail: locals.user.email,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
async listInvites(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const result = await this.subscriptionInvitesController.listInvites({
|
||||
...request.body,
|
||||
inviterEmail: response.locals.user.email,
|
||||
inviterEmail: locals.user.email,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
|
||||
+4
-1
@@ -6,6 +6,7 @@ import { GetSubscriptionSetting } from '../../../Domain/UseCase/GetSubscriptionS
|
||||
import { GetSharedOrRegularSubscriptionForUser } from '../../../Domain/UseCase/GetSharedOrRegularSubscriptionForUser/GetSharedOrRegularSubscriptionForUser'
|
||||
import { SubscriptionSetting } from '../../../Domain/Setting/SubscriptionSetting'
|
||||
import { SubscriptionSettingHttpRepresentation } from '../../../Mapping/Http/SubscriptionSettingHttpRepresentation'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
|
||||
export class BaseSubscriptionSettingsController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -22,8 +23,10 @@ export class BaseSubscriptionSettingsController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async getSubscriptionSetting(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const subscriptionOrError = await this.getSharedOrRegularSubscription.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
userUuid: locals.user.uuid,
|
||||
})
|
||||
if (subscriptionOrError.isFailed()) {
|
||||
return this.json(
|
||||
|
||||
+5
-2
@@ -9,6 +9,7 @@ import { CreateSubscriptionToken } from '../../../Domain/UseCase/CreateSubscript
|
||||
import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
|
||||
import { User } from '../../../Domain/User/User'
|
||||
import { GetSetting } from '../../../Domain/UseCase/GetSetting/GetSetting'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
|
||||
export class BaseSubscriptionTokensController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -29,7 +30,9 @@ export class BaseSubscriptionTokensController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async createToken(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (response.locals.readOnlyAccess) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (locals.readOnlyAccess) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -42,7 +45,7 @@ export class BaseSubscriptionTokensController extends BaseHttpController {
|
||||
}
|
||||
|
||||
const result = await this.createSubscriptionToken.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
userUuid: locals.user.uuid,
|
||||
})
|
||||
|
||||
return this.json({
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BaseHttpController, results } from 'inversify-express-utils'
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
import { UserRequestsController } from '../../../Controller/UserRequestsController'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
|
||||
export class BaseUserRequestsController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -17,10 +18,12 @@ export class BaseUserRequestsController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async submitRequest(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const result = await this.userRequestsController.submitUserRequest({
|
||||
requestType: request.body.requestType,
|
||||
userUuid: response.locals.user.uuid,
|
||||
userEmail: response.locals.user.email,
|
||||
userUuid: locals.user.uuid,
|
||||
userEmail: locals.user.email,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
|
||||
@@ -7,12 +7,11 @@ import { ClearLoginAttempts } from '../../../Domain/UseCase/ClearLoginAttempts'
|
||||
import { DeleteAccount } from '../../../Domain/UseCase/DeleteAccount/DeleteAccount'
|
||||
import { GetUserSubscription } from '../../../Domain/UseCase/GetUserSubscription/GetUserSubscription'
|
||||
import { IncreaseLoginAttempts } from '../../../Domain/UseCase/IncreaseLoginAttempts'
|
||||
import { UpdateUser } from '../../../Domain/UseCase/UpdateUser'
|
||||
import { ErrorTag } from '@standardnotes/responses'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
|
||||
export class BaseUsersController extends BaseHttpController {
|
||||
constructor(
|
||||
protected updateUser: UpdateUser,
|
||||
protected doDeleteAccount: DeleteAccount,
|
||||
protected doGetUserSubscription: GetUserSubscription,
|
||||
protected clearLoginAttempts: ClearLoginAttempts,
|
||||
@@ -23,61 +22,16 @@ export class BaseUsersController extends BaseHttpController {
|
||||
super()
|
||||
|
||||
if (this.controllerContainer !== undefined) {
|
||||
this.controllerContainer.register('auth.users.update', this.update.bind(this))
|
||||
this.controllerContainer.register('auth.users.getSubscription', this.getSubscription.bind(this))
|
||||
this.controllerContainer.register('auth.users.updateCredentials', this.changeCredentials.bind(this))
|
||||
this.controllerContainer.register('auth.users.delete', this.deleteAccount.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
async update(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (response.locals.readOnlyAccess) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
tag: ErrorTag.ReadOnlyAccess,
|
||||
message: 'Session has read-only access.',
|
||||
},
|
||||
},
|
||||
401,
|
||||
)
|
||||
}
|
||||
|
||||
if (request.params.userId !== response.locals.user.uuid) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
message: 'Operation not allowed.',
|
||||
},
|
||||
},
|
||||
401,
|
||||
)
|
||||
}
|
||||
|
||||
const updateResult = await this.updateUser.execute({
|
||||
user: response.locals.user,
|
||||
updatedWithUserAgent: <string>request.headers['user-agent'],
|
||||
apiVersion: request.body.api,
|
||||
})
|
||||
|
||||
if (updateResult.success) {
|
||||
response.setHeader('x-invalidate-cache', response.locals.user.uuid)
|
||||
|
||||
return this.json(updateResult.authResponse)
|
||||
}
|
||||
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
message: 'Could not update user.',
|
||||
},
|
||||
},
|
||||
400,
|
||||
)
|
||||
}
|
||||
|
||||
async deleteAccount(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (request.params.userUuid !== response.locals.user.uuid) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (request.params.userUuid !== locals.user.uuid) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -107,7 +61,9 @@ export class BaseUsersController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async getSubscription(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (request.params.userUuid !== response.locals.user.uuid) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (request.params.userUuid !== locals.user.uuid) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -130,7 +86,9 @@ export class BaseUsersController extends BaseHttpController {
|
||||
}
|
||||
|
||||
async changeCredentials(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (response.locals.readOnlyAccess) {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
if (locals.readOnlyAccess) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -175,7 +133,7 @@ export class BaseUsersController extends BaseHttpController {
|
||||
400,
|
||||
)
|
||||
}
|
||||
const usernameOrError = Username.create(response.locals.user.email)
|
||||
const usernameOrError = Username.create(locals.user.email)
|
||||
if (usernameOrError.isFailed()) {
|
||||
return this.json(
|
||||
{
|
||||
@@ -202,7 +160,7 @@ export class BaseUsersController extends BaseHttpController {
|
||||
})
|
||||
|
||||
if (changeCredentialsResult.isFailed()) {
|
||||
await this.increaseLoginAttempts.execute({ email: response.locals.user.email })
|
||||
await this.increaseLoginAttempts.execute({ email: locals.user.email })
|
||||
|
||||
return this.json(
|
||||
{
|
||||
@@ -214,9 +172,9 @@ export class BaseUsersController extends BaseHttpController {
|
||||
)
|
||||
}
|
||||
|
||||
await this.clearLoginAttempts.execute({ email: response.locals.user.email })
|
||||
await this.clearLoginAttempts.execute({ email: locals.user.email })
|
||||
|
||||
response.setHeader('x-invalidate-cache', response.locals.user.uuid)
|
||||
response.setHeader('x-invalidate-cache', locals.user.uuid)
|
||||
|
||||
return this.json(changeCredentialsResult.getValue())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ValetTokenOperation } from '@standardnotes/security'
|
||||
|
||||
import { CreateValetToken } from '../../../Domain/UseCase/CreateValetToken/CreateValetToken'
|
||||
import { CreateValetTokenPayload } from '../../../Domain/ValetToken/CreateValetTokenPayload'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
|
||||
export class BaseValetTokenController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -20,9 +21,11 @@ export class BaseValetTokenController extends BaseHttpController {
|
||||
}
|
||||
|
||||
public async create(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const locals = response.locals as ResponseLocals
|
||||
|
||||
const payload: CreateValetTokenPayload = request.body
|
||||
|
||||
if (response.locals.readOnlyAccess && payload.operation !== 'read') {
|
||||
if (locals.readOnlyAccess && payload.operation !== 'read') {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
@@ -50,7 +53,7 @@ export class BaseValetTokenController extends BaseHttpController {
|
||||
}
|
||||
|
||||
const createValetKeyResponse = await this.createValetKey.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
userUuid: locals.user.uuid,
|
||||
operation: payload.operation as ValetTokenOperation,
|
||||
resources: payload.resources,
|
||||
})
|
||||
|
||||
+7
-4
@@ -2,6 +2,7 @@ import { CrossServiceTokenData, TokenDecoderInterface } from '@standardnotes/sec
|
||||
import { NextFunction, Request, Response } from 'express'
|
||||
import { BaseMiddleware } from 'inversify-express-utils'
|
||||
import { Logger } from 'winston'
|
||||
import { ResponseLocals } from '../ResponseLocals'
|
||||
|
||||
export abstract class ApiGatewayAuthMiddleware extends BaseMiddleware {
|
||||
constructor(
|
||||
@@ -34,10 +35,12 @@ export abstract class ApiGatewayAuthMiddleware extends BaseMiddleware {
|
||||
return
|
||||
}
|
||||
|
||||
response.locals.user = token.user
|
||||
response.locals.roles = token.roles
|
||||
response.locals.session = token.session
|
||||
response.locals.readOnlyAccess = token.session?.readonly_access ?? false
|
||||
Object.assign(response.locals, {
|
||||
user: token.user,
|
||||
roles: token.roles,
|
||||
session: token.session,
|
||||
readOnlyAccess: token.session?.readonly_access ?? false,
|
||||
} as ResponseLocals)
|
||||
|
||||
return next()
|
||||
} catch (error) {
|
||||
|
||||
+5
-2
@@ -4,6 +4,7 @@ import { inject, injectable } from 'inversify'
|
||||
import { BaseMiddleware } from 'inversify-express-utils'
|
||||
import { Logger } from 'winston'
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { OfflineResponseLocals } from '../OfflineResponseLocals'
|
||||
|
||||
@injectable()
|
||||
export class ApiGatewayOfflineAuthMiddleware extends BaseMiddleware {
|
||||
@@ -48,8 +49,10 @@ export class ApiGatewayOfflineAuthMiddleware extends BaseMiddleware {
|
||||
return
|
||||
}
|
||||
|
||||
response.locals.featuresToken = token.featuresToken
|
||||
response.locals.userEmail = token.userEmail
|
||||
Object.assign(response.locals, {
|
||||
featuresToken: token.featuresToken,
|
||||
userEmail: token.userEmail,
|
||||
} as OfflineResponseLocals)
|
||||
|
||||
return next()
|
||||
} catch (error) {
|
||||
|
||||
+2
-2
@@ -44,8 +44,8 @@ describe('OfflineUserAuthMiddleware', () => {
|
||||
|
||||
await createMiddleware().handler(request, response, next)
|
||||
|
||||
expect(response.locals.offlineUserEmail).toEqual('test@test.com')
|
||||
expect(response.locals.offlineFeaturesToken).toEqual('offline-features-token')
|
||||
expect(response.locals.userEmail).toEqual('test@test.com')
|
||||
expect(response.locals.featuresToken).toEqual('offline-features-token')
|
||||
|
||||
expect(next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
+5
-2
@@ -5,6 +5,7 @@ import { Logger } from 'winston'
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { OfflineSettingName } from '../../../Domain/Setting/OfflineSettingName'
|
||||
import { OfflineSettingRepositoryInterface } from '../../../Domain/Setting/OfflineSettingRepositoryInterface'
|
||||
import { OfflineResponseLocals } from '../OfflineResponseLocals'
|
||||
|
||||
@injectable()
|
||||
export class OfflineUserAuthMiddleware extends BaseMiddleware {
|
||||
@@ -47,8 +48,10 @@ export class OfflineUserAuthMiddleware extends BaseMiddleware {
|
||||
return
|
||||
}
|
||||
|
||||
response.locals.offlineUserEmail = offlineFeaturesTokenSetting.email
|
||||
response.locals.offlineFeaturesToken = offlineFeaturesTokenSetting.value
|
||||
Object.assign(response.locals, {
|
||||
featuresToken: offlineFeaturesTokenSetting.value,
|
||||
userEmail: offlineFeaturesTokenSetting.email,
|
||||
} as OfflineResponseLocals)
|
||||
|
||||
return next()
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface OfflineResponseLocals {
|
||||
userEmail: string
|
||||
featuresToken: string
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Role } from '@standardnotes/security'
|
||||
|
||||
export interface ResponseLocals {
|
||||
user: {
|
||||
uuid: string
|
||||
email: string
|
||||
}
|
||||
roles: Array<Role>
|
||||
session?: {
|
||||
uuid: string
|
||||
api_version: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
device_info: string
|
||||
readonly_access: boolean
|
||||
access_expiration: string
|
||||
refresh_expiration: string
|
||||
}
|
||||
readOnlyAccess: boolean
|
||||
}
|
||||
@@ -84,6 +84,7 @@ export class TypeORMUserSubscriptionRepository implements UserSubscriptionReposi
|
||||
userUuid,
|
||||
subscriptionId,
|
||||
})
|
||||
.orderBy('ends_at', 'DESC')
|
||||
.getOne()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.37.9](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.37.8...@standardnotes/files-server@1.37.9) (2024-01-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add dedicated http code response upon a request with too large payload ([#1019](https://github.com/standardnotes/server/issues/1019)) ([6062f85](https://github.com/standardnotes/server/commit/6062f850000477983315d2d9b7c913956f755ebb))
|
||||
|
||||
## [1.37.8](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.37.7...@standardnotes/files-server@1.37.8) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.37.7](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.37.6...@standardnotes/files-server@1.37.7) (2024-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
@@ -24,6 +24,10 @@ void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
const requestPayloadLimit = env.get('HTTP_REQUEST_PAYLOAD_LIMIT_MEGABYTES', true)
|
||||
? `${+env.get('HTTP_REQUEST_PAYLOAD_LIMIT_MEGABYTES', true)}mb`
|
||||
: '50mb'
|
||||
|
||||
const server = new InversifyExpressServer(container)
|
||||
|
||||
server.setConfig((app) => {
|
||||
@@ -58,9 +62,9 @@ void container.load().then((container) => {
|
||||
}
|
||||
}))
|
||||
/* eslint-enable */
|
||||
app.use(json({ limit: '50mb' }))
|
||||
app.use(raw({ limit: '50mb', type: 'application/octet-stream' }))
|
||||
app.use(urlencoded({ extended: true, limit: '50mb' }))
|
||||
app.use(json({ limit: requestPayloadLimit }))
|
||||
app.use(raw({ limit: requestPayloadLimit, type: 'application/octet-stream' }))
|
||||
app.use(urlencoded({ extended: true, limit: requestPayloadLimit }))
|
||||
app.use(
|
||||
cors({
|
||||
exposedHeaders: ['Content-Range', 'Accept-Ranges'],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.37.7",
|
||||
"version": "1.37.9",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,84 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.22.56](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.55...@standardnotes/home-server@1.22.56) (2024-01-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.55](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.54...@standardnotes/home-server@1.22.55) (2024-01-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.54](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.53...@standardnotes/home-server@1.22.54) (2024-01-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.53](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.52...@standardnotes/home-server@1.22.53) (2024-01-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add dedicated http code response upon a request with too large payload ([#1019](https://github.com/standardnotes/server/issues/1019)) ([6062f85](https://github.com/standardnotes/server/commit/6062f850000477983315d2d9b7c913956f755ebb))
|
||||
|
||||
## [1.22.52](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.51...@standardnotes/home-server@1.22.52) (2024-01-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.51](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.50...@standardnotes/home-server@1.22.51) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.50](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.49...@standardnotes/home-server@1.22.50) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.49](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.48...@standardnotes/home-server@1.22.49) (2024-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.48](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.47...@standardnotes/home-server@1.22.48) (2024-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.47](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.46...@standardnotes/home-server@1.22.47) (2024-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.46](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.45...@standardnotes/home-server@1.22.46) (2024-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.45](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.44...@standardnotes/home-server@1.22.45) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.44](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.43...@standardnotes/home-server@1.22.44) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.43](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.42...@standardnotes/home-server@1.22.43) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.42](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.41...@standardnotes/home-server@1.22.42) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.41](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.40...@standardnotes/home-server@1.22.41) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.40](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.39...@standardnotes/home-server@1.22.40) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.39](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.38...@standardnotes/home-server@1.22.39) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.38](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.37...@standardnotes/home-server@1.22.38) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.37](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.36...@standardnotes/home-server@1.22.37) (2024-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/home-server",
|
||||
"version": "1.22.37",
|
||||
"version": "1.22.56",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -53,6 +53,10 @@ export class HomeServer implements HomeServerInterface {
|
||||
const env: Env = new Env(environmentOverrides)
|
||||
env.load()
|
||||
|
||||
const requestPayloadLimit = env.get('HTTP_REQUEST_PAYLOAD_LIMIT_MEGABYTES', true)
|
||||
? `${+env.get('HTTP_REQUEST_PAYLOAD_LIMIT_MEGABYTES', true)}mb`
|
||||
: '50mb'
|
||||
|
||||
this.configureLoggers(env, configuration)
|
||||
|
||||
const apiGatewayService = new ApiGatewayService(serviceContainer)
|
||||
@@ -114,8 +118,8 @@ export class HomeServer implements HomeServerInterface {
|
||||
}
|
||||
}))
|
||||
/* eslint-enable */
|
||||
app.use(json({ limit: '50mb' }))
|
||||
app.use(raw({ limit: '50mb', type: 'application/octet-stream' }))
|
||||
app.use(json({ limit: requestPayloadLimit }))
|
||||
app.use(raw({ limit: requestPayloadLimit, type: 'application/octet-stream' }))
|
||||
app.use(
|
||||
text({
|
||||
type: [
|
||||
@@ -160,8 +164,24 @@ export class HomeServer implements HomeServerInterface {
|
||||
const logger: winston.Logger = winston.loggers.get('home-server')
|
||||
|
||||
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,
|
||||
})
|
||||
|
||||
if ('type' in error && error.type === 'entity.too.large') {
|
||||
response.status(413).send({
|
||||
error: {
|
||||
message: 'The request payload is too large.',
|
||||
},
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
response.status(500).send({
|
||||
error: {
|
||||
|
||||
@@ -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.51.14](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.51.13...@standardnotes/revisions-server@1.51.14) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.51.13](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.51.12...@standardnotes/revisions-server@1.51.13) (2024-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.51.13",
|
||||
"version": "1.51.14",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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.27.19](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.27.18...@standardnotes/scheduler-server@1.27.19) (2024-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.27.18](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.27.17...@standardnotes/scheduler-server@1.27.18) (2024-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.27.18",
|
||||
"version": "1.27.19",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -41,3 +41,6 @@ FILE_UPLOAD_PATH=
|
||||
|
||||
VALET_TOKEN_SECRET=change-me-!
|
||||
VALET_TOKEN_TTL=7200
|
||||
|
||||
REDIS_URL=redis://cache
|
||||
CACHE_TYPE=redis
|
||||
|
||||
@@ -3,6 +3,92 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.134.0](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.133.6...@standardnotes/syncing-server@1.134.0) (2024-01-12)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** reduced abuse thresholds for free users ([#1021](https://github.com/standardnotes/server/issues/1021)) ([0443de8](https://github.com/standardnotes/server/commit/0443de88ceae3cb7c0793a3457753806b51db6e2))
|
||||
|
||||
## [1.133.6](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.133.5...@standardnotes/syncing-server@1.133.6) (2024-01-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add dedicated http code response upon a request with too large payload ([#1019](https://github.com/standardnotes/server/issues/1019)) ([6062f85](https://github.com/standardnotes/server/commit/6062f850000477983315d2d9b7c913956f755ebb))
|
||||
|
||||
## [1.133.5](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.133.4...@standardnotes/syncing-server@1.133.5) (2024-01-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
## [1.133.4](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.133.3...@standardnotes/syncing-server@1.133.4) (2024-01-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** disable ot tracing ([a881dd2](https://github.com/standardnotes/server/commit/a881dd2d79d0e394b0b4f5d07e63821c93d45219))
|
||||
|
||||
## [1.133.3](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.133.2...@standardnotes/syncing-server@1.133.3) (2024-01-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** add traffic abuse check in gRPC coms ([e3cb1fa](https://github.com/standardnotes/server/commit/e3cb1faba46bbfd8741f6c827daa9438934dd710))
|
||||
* **syncing-server:** remove excessive debug logs ([5c5f988](https://github.com/standardnotes/server/commit/5c5f9880556f14f5cbe4599ac0d639c970495056))
|
||||
|
||||
## [1.133.2](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.133.1...@standardnotes/syncing-server@1.133.2) (2024-01-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** add debug logs to redis metrics store ([a4ad37f](https://github.com/standardnotes/server/commit/a4ad37f30948ba4292f367240c3dbfca916282ac))
|
||||
* **syncing-server:** add metadata to transfer breach logs ([73c2cc1](https://github.com/standardnotes/server/commit/73c2cc1222b647e82de3755c7e28d283cd9f872f))
|
||||
|
||||
## [1.133.1](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.133.0...@standardnotes/syncing-server@1.133.1) (2024-01-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** add debug logs for checking traffic abuse ([1f4b26d](https://github.com/standardnotes/server/commit/1f4b26d269a92f5b43455ce3a3cf3d4f15f0d099))
|
||||
* **syncing-server:** error message ([e253825](https://github.com/standardnotes/server/commit/e253825da69d1be6d7bb4d0360f8c3add73516ef))
|
||||
* **syncing-server:** metadata in logs ([02f4d5c](https://github.com/standardnotes/server/commit/02f4d5c717cef1014930f11b2792868967812042))
|
||||
|
||||
# [1.133.0](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.132.0...@standardnotes/syncing-server@1.133.0) (2024-01-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** add traffic abuse checks ([#1014](https://github.com/standardnotes/server/issues/1014)) ([b717334](https://github.com/standardnotes/server/commit/b7173346d2949269b762b023da9ea67b7f327c35))
|
||||
|
||||
# [1.132.0](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.131.0...@standardnotes/syncing-server@1.132.0) (2024-01-04)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** send user based metrics to cloudwatch ([0c3bc0c](https://github.com/standardnotes/server/commit/0c3bc0cae654a6783f85e86995f978cc458d8b5c))
|
||||
|
||||
# [1.131.0](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.130.3...@standardnotes/syncing-server@1.131.0) (2024-01-04)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** store per user content size utilization and item operations metrics ([4dd2eb9](https://github.com/standardnotes/server/commit/4dd2eb9349eb16006d1ebba99c848b8f6c51baf9))
|
||||
|
||||
## [1.130.3](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.130.2...@standardnotes/syncing-server@1.130.3) (2024-01-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** decrease metric expiration time ([f1aa431](https://github.com/standardnotes/server/commit/f1aa431c223714e56942a32ab75e380baf4f84aa))
|
||||
|
||||
## [1.130.2](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.130.1...@standardnotes/syncing-server@1.130.2) (2024-01-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** amount of minutes to process for metrics ([92bb447](https://github.com/standardnotes/server/commit/92bb447cacd0a40f963c2bfa5e61cb0f451959e6))
|
||||
|
||||
## [1.130.1](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.130.0...@standardnotes/syncing-server@1.130.1) (2024-01-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** skip sending empty metrics ([2c732ff](https://github.com/standardnotes/server/commit/2c732ff713736b845691e5e195f6a99086b6f2d7))
|
||||
|
||||
# [1.130.0](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.129.11...@standardnotes/syncing-server@1.130.0) (2024-01-04)
|
||||
|
||||
### Features
|
||||
|
||||
* add storing item saving and modifying metrics ([#1013](https://github.com/standardnotes/server/issues/1013)) ([efd816a](https://github.com/standardnotes/server/commit/efd816a627d59f4baaa69aa081c2ad9c88af971c))
|
||||
|
||||
## [1.129.11](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.129.10...@standardnotes/syncing-server@1.129.11) (2024-01-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
|
||||
import { MapperInterface, ServiceIdentifier } from '@standardnotes/domain-core'
|
||||
|
||||
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.SyncingServer })
|
||||
sdk.start()
|
||||
import { MapperInterface } from '@standardnotes/domain-core'
|
||||
|
||||
import '../src/Infra/InversifyExpressUtils/AnnotatedFallbackController'
|
||||
import '../src/Infra/InversifyExpressUtils/AnnotatedHealthCheckController'
|
||||
@@ -29,12 +25,17 @@ import { SyncingServer } from '../src/Infra/gRPC/SyncingServer'
|
||||
import { SyncItems } from '../src/Domain/UseCase/Syncing/SyncItems/SyncItems'
|
||||
import { SyncResponseFactoryResolverInterface } from '../src/Domain/Item/SyncResponse/SyncResponseFactoryResolverInterface'
|
||||
import { SyncResponse20200115 } from '../src/Domain/Item/SyncResponse/SyncResponse20200115'
|
||||
import { CheckForTrafficAbuse } from '../src/Domain/UseCase/Syncing/CheckForTrafficAbuse/CheckForTrafficAbuse'
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
const requestPayloadLimit = env.get('HTTP_REQUEST_PAYLOAD_LIMIT_MEGABYTES', true)
|
||||
? `${+env.get('HTTP_REQUEST_PAYLOAD_LIMIT_MEGABYTES', true)}mb`
|
||||
: '50mb'
|
||||
|
||||
const server = new InversifyExpressServer(container)
|
||||
|
||||
server.setConfig((app) => {
|
||||
@@ -64,8 +65,8 @@ void container.load().then((container) => {
|
||||
}
|
||||
}))
|
||||
/* eslint-enable */
|
||||
app.use(json({ limit: '50mb' }))
|
||||
app.use(urlencoded({ extended: true, limit: '50mb', parameterLimit: 5000 }))
|
||||
app.use(json({ limit: requestPayloadLimit }))
|
||||
app.use(urlencoded({ extended: true, limit: requestPayloadLimit, parameterLimit: 5000 }))
|
||||
app.use(cors())
|
||||
})
|
||||
|
||||
@@ -114,6 +115,14 @@ void container.load().then((container) => {
|
||||
container.get<SyncItems>(TYPES.Sync_SyncItems),
|
||||
container.get<SyncResponseFactoryResolverInterface>(TYPES.Sync_SyncResponseFactoryResolver),
|
||||
container.get<MapperInterface<SyncResponse20200115, SyncResponse>>(TYPES.Sync_SyncResponseGRPCMapper),
|
||||
container.get<CheckForTrafficAbuse>(TYPES.Sync_CheckForTrafficAbuse),
|
||||
container.get<boolean>(TYPES.Sync_STRICT_ABUSE_PROTECTION),
|
||||
container.get<number>(TYPES.Sync_ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES),
|
||||
container.get<number>(TYPES.Sync_ITEM_OPERATIONS_ABUSE_THRESHOLD),
|
||||
container.get<number>(TYPES.Sync_FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD),
|
||||
container.get<number>(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD),
|
||||
container.get<number>(TYPES.Sync_FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD),
|
||||
container.get<number>(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES),
|
||||
container.get<winston.Logger>(TYPES.Sync_Logger),
|
||||
)
|
||||
|
||||
@@ -146,14 +155,6 @@ void container.load().then((container) => {
|
||||
logger.info('gRPC server closed')
|
||||
}
|
||||
})
|
||||
sdk
|
||||
.shutdown()
|
||||
.then(() => {
|
||||
logger.info('OpenTelemetry SDK shut down')
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`Failed to shut down OpenTelemetry SDK: ${error.message}`)
|
||||
})
|
||||
})
|
||||
|
||||
logger.info(`Server started on port ${process.env.PORT}`)
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { Logger } from 'winston'
|
||||
import { CloudWatchClient, PutMetricDataCommand } from '@aws-sdk/client-cloudwatch'
|
||||
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { MetricsStoreInterface } from '../src/Domain/Metrics/MetricsStoreInterface'
|
||||
import { Metric } from '../src/Domain/Metrics/Metric'
|
||||
import { Time, TimerInterface } from '@standardnotes/time'
|
||||
|
||||
const sendStatistics = async (
|
||||
metricsStore: MetricsStoreInterface,
|
||||
timer: TimerInterface,
|
||||
awsRegion: string,
|
||||
): Promise<void> => {
|
||||
const cloudwatchClient = new CloudWatchClient({
|
||||
region: awsRegion,
|
||||
})
|
||||
|
||||
const minutesToProcess = 30
|
||||
|
||||
const metricsToProcess = [Metric.NAMES.ItemCreated, Metric.NAMES.ItemUpdated]
|
||||
|
||||
for (const metricToProcess of metricsToProcess) {
|
||||
for (let i = 0; i <= minutesToProcess; i++) {
|
||||
const dateNMinutesAgo = timer.getUTCDateNMinutesAgo(minutesToProcess - i)
|
||||
const timestamp = timer.convertDateToMicroseconds(dateNMinutesAgo)
|
||||
|
||||
const statistics = await metricsStore.getMetricsSummary(
|
||||
metricToProcess,
|
||||
timestamp,
|
||||
timestamp + Time.MicrosecondsInAMinute,
|
||||
)
|
||||
|
||||
if (statistics.sampleCount === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
await cloudwatchClient.send(
|
||||
new PutMetricDataCommand({
|
||||
Namespace: 'SyncingServer',
|
||||
MetricData: [
|
||||
{
|
||||
MetricName: metricToProcess,
|
||||
Timestamp: dateNMinutesAgo,
|
||||
StatisticValues: {
|
||||
Maximum: statistics.max,
|
||||
Minimum: statistics.min,
|
||||
SampleCount: statistics.sampleCount,
|
||||
Sum: statistics.sum,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const userMetricsToProcess = [Metric.NAMES.ItemOperation, Metric.NAMES.ContentSizeUtilized]
|
||||
for (const metricToProcess of userMetricsToProcess) {
|
||||
for (let i = 0; i <= minutesToProcess; i++) {
|
||||
const dateNMinutesAgo = timer.getUTCDateNMinutesAgo(minutesToProcess - i)
|
||||
const timestamp = timer.convertDateToMicroseconds(dateNMinutesAgo)
|
||||
|
||||
const statistics = await metricsStore.getUserBasedMetricsSummary(metricToProcess, timestamp)
|
||||
|
||||
if (statistics.sampleCount === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
await cloudwatchClient.send(
|
||||
new PutMetricDataCommand({
|
||||
Namespace: 'SyncingServer',
|
||||
MetricData: [
|
||||
{
|
||||
MetricName: metricToProcess,
|
||||
Timestamp: dateNMinutesAgo,
|
||||
StatisticValues: {
|
||||
Maximum: statistics.max,
|
||||
Minimum: statistics.min,
|
||||
SampleCount: statistics.sampleCount,
|
||||
Sum: statistics.sum,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
const logger: Logger = container.get(TYPES.Sync_Logger)
|
||||
|
||||
logger.info('Starting statistics sending')
|
||||
|
||||
const metricsStore = container.get<MetricsStoreInterface>(TYPES.Sync_MetricsStore)
|
||||
const timer = container.get<TimerInterface>(TYPES.Sync_Timer)
|
||||
const awsRegion = env.get('SNS_AWS_REGION', true)
|
||||
|
||||
Promise.resolve(sendStatistics(metricsStore, timer, awsRegion))
|
||||
.then(() => {
|
||||
logger.info('Finished statistics sending')
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error while sending statistics', error)
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
@@ -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/statistics.js')))
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true })
|
||||
|
||||
exports.default = index
|
||||
@@ -5,15 +5,17 @@ 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
|
||||
;;
|
||||
|
||||
'statistics' )
|
||||
exec node docker/entrypoint-statistics.js
|
||||
;;
|
||||
|
||||
* )
|
||||
echo "[Docker] Unknown command"
|
||||
;;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.129.11",
|
||||
"version": "1.134.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -32,6 +32,7 @@
|
||||
"migrate": "yarn clean && yarn build && yarn typeorm migration:run -d dist/src/Bootstrap/DataSource.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-cloudwatch": "^3.485.0",
|
||||
"@aws-sdk/client-s3": "^3.484.0",
|
||||
"@aws-sdk/client-sns": "^3.484.0",
|
||||
"@aws-sdk/client-sqs": "^3.484.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as winston from 'winston'
|
||||
import Redis from 'ioredis'
|
||||
import { Container, interfaces } from 'inversify'
|
||||
|
||||
import { Env } from './Env'
|
||||
@@ -162,6 +163,10 @@ import { SyncResponse } from '@standardnotes/grpc'
|
||||
import { SyncResponseGRPCMapper } from '../Mapping/gRPC/SyncResponseGRPCMapper'
|
||||
import { AccountDeletionVerificationRequestedEventHandler } from '../Domain/Handler/AccountDeletionVerificationRequestedEventHandler'
|
||||
import { SendEventToClients } from '../Domain/UseCase/Syncing/SendEventToClients/SendEventToClients'
|
||||
import { MetricsStoreInterface } from '../Domain/Metrics/MetricsStoreInterface'
|
||||
import { RedisMetricStore } from '../Infra/Redis/RedisMetricStore'
|
||||
import { DummyMetricStore } from '../Infra/Dummy/DummyMetricStore'
|
||||
import { CheckForTrafficAbuse } from '../Domain/UseCase/Syncing/CheckForTrafficAbuse/CheckForTrafficAbuse'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
|
||||
@@ -211,6 +216,20 @@ export class ContainerConfigLoader {
|
||||
const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
|
||||
const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted'
|
||||
const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting
|
||||
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
|
||||
|
||||
if (!isConfiguredForInMemoryCache) {
|
||||
const redisUrl = env.get('REDIS_URL')
|
||||
const isRedisInClusterMode = redisUrl.indexOf(',') > 0
|
||||
let redis
|
||||
if (isRedisInClusterMode) {
|
||||
redis = new Redis.Cluster(redisUrl.split(','))
|
||||
} else {
|
||||
redis = new Redis(redisUrl)
|
||||
}
|
||||
|
||||
container.bind(TYPES.Sync_Redis).toConstantValue(redis)
|
||||
}
|
||||
|
||||
container
|
||||
.bind<boolean>(TYPES.Sync_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING)
|
||||
@@ -454,6 +473,49 @@ export class ContainerConfigLoader {
|
||||
})
|
||||
|
||||
// env vars
|
||||
container
|
||||
.bind(TYPES.Sync_STRICT_ABUSE_PROTECTION)
|
||||
.toConstantValue(env.get('STRICT_ABUSE_PROTECTION', true) === 'true')
|
||||
container
|
||||
.bind(TYPES.Sync_ITEM_OPERATIONS_ABUSE_THRESHOLD)
|
||||
.toConstantValue(
|
||||
env.get('ITEM_OPERATIONS_ABUSE_THRESHOLD', true) ? +env.get('ITEM_OPERATIONS_ABUSE_THRESHOLD', true) : 1000,
|
||||
)
|
||||
container
|
||||
.bind(TYPES.Sync_FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD)
|
||||
.toConstantValue(
|
||||
env.get('FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD', true)
|
||||
? +env.get('FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD', true)
|
||||
: 500,
|
||||
)
|
||||
container
|
||||
.bind(TYPES.Sync_ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES)
|
||||
.toConstantValue(
|
||||
env.get('ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES', true)
|
||||
? +env.get('ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES', true)
|
||||
: 5,
|
||||
)
|
||||
container
|
||||
.bind(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD)
|
||||
.toConstantValue(
|
||||
env.get('UPLOAD_BANDWIDTH_ABUSE_THRESHOLD', true)
|
||||
? +env.get('UPLOAD_BANDWIDTH_ABUSE_THRESHOLD', true)
|
||||
: 100_000_000,
|
||||
)
|
||||
container
|
||||
.bind(TYPES.Sync_FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD)
|
||||
.toConstantValue(
|
||||
env.get('FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD', true)
|
||||
? +env.get('FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD', true)
|
||||
: 50_000_000,
|
||||
)
|
||||
container
|
||||
.bind(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES)
|
||||
.toConstantValue(
|
||||
env.get('UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES', true)
|
||||
? +env.get('UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES', true)
|
||||
: 5,
|
||||
)
|
||||
container.bind(TYPES.Sync_AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
|
||||
container
|
||||
.bind(TYPES.Sync_REVISIONS_FREQUENCY)
|
||||
@@ -533,6 +595,20 @@ export class ContainerConfigLoader {
|
||||
),
|
||||
)
|
||||
|
||||
if (isConfiguredForInMemoryCache) {
|
||||
container.bind<MetricsStoreInterface>(TYPES.Sync_MetricsStore).toConstantValue(new DummyMetricStore())
|
||||
} else {
|
||||
container
|
||||
.bind<MetricsStoreInterface>(TYPES.Sync_MetricsStore)
|
||||
.toConstantValue(
|
||||
new RedisMetricStore(
|
||||
container.get<Redis>(TYPES.Sync_Redis),
|
||||
container.get<TimerInterface>(TYPES.Sync_Timer),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// use cases
|
||||
container
|
||||
.bind<GetItems>(TYPES.Sync_GetItems)
|
||||
@@ -554,6 +630,7 @@ export class ContainerConfigLoader {
|
||||
container.get(TYPES.Sync_Timer),
|
||||
container.get(TYPES.Sync_DomainEventPublisher),
|
||||
container.get(TYPES.Sync_DomainEventFactory),
|
||||
container.get<MetricsStoreInterface>(TYPES.Sync_MetricsStore),
|
||||
),
|
||||
)
|
||||
container
|
||||
@@ -609,6 +686,7 @@ export class ContainerConfigLoader {
|
||||
container.get<DetermineSharedVaultOperationOnItem>(TYPES.Sync_DetermineSharedVaultOperationOnItem),
|
||||
container.get<AddNotificationsForUsers>(TYPES.Sync_AddNotificationsForUsers),
|
||||
container.get<RemoveNotificationsForUser>(TYPES.Sync_RemoveNotificationsForUser),
|
||||
container.get<MetricsStoreInterface>(TYPES.Sync_MetricsStore),
|
||||
),
|
||||
)
|
||||
container
|
||||
@@ -903,6 +981,15 @@ export class ContainerConfigLoader {
|
||||
context.container.get(TYPES.Sync_SyncResponseFactory20200115),
|
||||
)
|
||||
})
|
||||
container
|
||||
.bind<CheckForTrafficAbuse>(TYPES.Sync_CheckForTrafficAbuse)
|
||||
.toConstantValue(
|
||||
new CheckForTrafficAbuse(
|
||||
container.get<MetricsStoreInterface>(TYPES.Sync_MetricsStore),
|
||||
container.get<TimerInterface>(TYPES.Sync_Timer),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
|
||||
// Handlers
|
||||
container
|
||||
@@ -1064,11 +1151,20 @@ export class ContainerConfigLoader {
|
||||
.bind<BaseItemsController>(TYPES.Sync_BaseItemsController)
|
||||
.toConstantValue(
|
||||
new BaseItemsController(
|
||||
container.get<CheckForTrafficAbuse>(TYPES.Sync_CheckForTrafficAbuse),
|
||||
container.get<SyncItems>(TYPES.Sync_SyncItems),
|
||||
container.get<CheckIntegrity>(TYPES.Sync_CheckIntegrity),
|
||||
container.get<GetItem>(TYPES.Sync_GetItem),
|
||||
container.get<MapperInterface<Item, ItemHttpRepresentation>>(TYPES.Sync_ItemHttpMapper),
|
||||
container.get<SyncResponseFactoryResolverInterface>(TYPES.Sync_SyncResponseFactoryResolver),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
container.get<boolean>(TYPES.Sync_STRICT_ABUSE_PROTECTION),
|
||||
container.get<number>(TYPES.Sync_ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES),
|
||||
container.get<number>(TYPES.Sync_ITEM_OPERATIONS_ABUSE_THRESHOLD),
|
||||
container.get<number>(TYPES.Sync_FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD),
|
||||
container.get<number>(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD),
|
||||
container.get<number>(TYPES.Sync_FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD),
|
||||
container.get<number>(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES),
|
||||
container.get<ControllerContainerInterface>(TYPES.Sync_ControllerContainer),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -42,6 +42,17 @@ const TYPES = {
|
||||
Sync_VALET_TOKEN_SECRET: Symbol.for('Sync_VALET_TOKEN_SECRET'),
|
||||
Sync_VALET_TOKEN_TTL: Symbol.for('Sync_VALET_TOKEN_TTL'),
|
||||
Sync_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING: Symbol.for('Sync_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING'),
|
||||
Sync_STRICT_ABUSE_PROTECTION: Symbol.for('Sync_STRICT_ABUSE_PROTECTION'),
|
||||
Sync_ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES: Symbol.for(
|
||||
'Sync_ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES',
|
||||
),
|
||||
Sync_ITEM_OPERATIONS_ABUSE_THRESHOLD: Symbol.for('Sync_ITEM_OPERATIONS_ABUSE_THRESHOLD'),
|
||||
Sync_FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD: Symbol.for('Sync_FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD'),
|
||||
Sync_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD: Symbol.for('Sync_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD'),
|
||||
Sync_FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD: Symbol.for('Sync_FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD'),
|
||||
Sync_UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES: Symbol.for(
|
||||
'Sync_UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES',
|
||||
),
|
||||
// use cases
|
||||
Sync_SyncItems: Symbol.for('Sync_SyncItems'),
|
||||
Sync_CheckIntegrity: Symbol.for('Sync_CheckIntegrity'),
|
||||
@@ -85,6 +96,7 @@ const TYPES = {
|
||||
Sync_TransferSharedVault: Symbol.for('Sync_TransferSharedVault'),
|
||||
Sync_TransferSharedVaultItems: Symbol.for('Sync_TransferSharedVaultItems'),
|
||||
Sync_DumpItem: Symbol.for('Sync_DumpItem'),
|
||||
Sync_CheckForTrafficAbuse: Symbol.for('Sync_CheckForTrafficAbuse'),
|
||||
// Handlers
|
||||
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
|
||||
Sync_AccountDeletionVerificationRequestedEventHandler: Symbol.for(
|
||||
@@ -98,6 +110,7 @@ const TYPES = {
|
||||
Sync_SharedVaultFileMovedEventHandler: Symbol.for('Sync_SharedVaultFileMovedEventHandler'),
|
||||
Sync_SharedVaultRemovedEventHandler: Symbol.for('Sync_SharedVaultRemovedEventHandler'),
|
||||
// Services
|
||||
Sync_MetricsStore: Symbol.for('Sync_MetricsStore'),
|
||||
Sync_ContentDecoder: Symbol.for('Sync_ContentDecoder'),
|
||||
Sync_DomainEventPublisher: Symbol.for('Sync_DomainEventPublisher'),
|
||||
Sync_DomainEventSubscriber: Symbol.for('Sync_DomainEventSubscriber'),
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
DomainEventPublisherInterface,
|
||||
EmailBackupRequestedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { EmailLevel } from '@standardnotes/domain-core'
|
||||
import { EmailLevel, Uuid } from '@standardnotes/domain-core'
|
||||
import { Logger } from 'winston'
|
||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||
import { ItemBackupServiceInterface } from '../Item/ItemBackupServiceInterface'
|
||||
@@ -32,6 +32,17 @@ export class EmailBackupRequestedEventHandler implements DomainEventHandlerInter
|
||||
event: EmailBackupRequestedEvent,
|
||||
itemRepository: ItemRepositoryInterface,
|
||||
): Promise<void> {
|
||||
const userUuidOrError = Uuid.create(event.payload.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
this.logger.error('User uuid is invalid', {
|
||||
userId: event.payload.userUuid,
|
||||
codeTag: 'EmailBackupRequestedEventHandler',
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const itemQuery: ItemQuery = {
|
||||
userUuid: event.payload.userUuid,
|
||||
sortBy: 'updated_at_timestamp',
|
||||
@@ -42,6 +53,7 @@ export class EmailBackupRequestedEventHandler implements DomainEventHandlerInter
|
||||
const itemUuidBundles = await this.itemTransferCalculator.computeItemUuidBundlesToFetch(
|
||||
itemContentSizeDescriptors,
|
||||
this.emailAttachmentMaxByteSize,
|
||||
userUuid,
|
||||
)
|
||||
|
||||
const backupFileNames: string[] = []
|
||||
|
||||
@@ -4,13 +4,18 @@ import { Logger } from 'winston'
|
||||
|
||||
import { ItemTransferCalculator } from './ItemTransferCalculator'
|
||||
import { ItemContentSizeDescriptor } from './ItemContentSizeDescriptor'
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
describe('ItemTransferCalculator', () => {
|
||||
let logger: Logger
|
||||
|
||||
const createCalculator = () => new ItemTransferCalculator(logger)
|
||||
|
||||
let userUuid: Uuid
|
||||
|
||||
beforeEach(() => {
|
||||
userUuid = Uuid.create('00000000-0000-0000-0000-000000000000').getValue()
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.warn = jest.fn()
|
||||
})
|
||||
@@ -23,7 +28,7 @@ describe('ItemTransferCalculator', () => {
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', 20).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidsToFetch(itemContentSizeDescriptors, 50)
|
||||
const result = await createCalculator().computeItemUuidsToFetch(itemContentSizeDescriptors, 50, userUuid)
|
||||
|
||||
expect(result).toEqual({
|
||||
uuids: [
|
||||
@@ -42,7 +47,7 @@ describe('ItemTransferCalculator', () => {
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', 20).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidsToFetch(itemContentSizeDescriptors, 40)
|
||||
const result = await createCalculator().computeItemUuidsToFetch(itemContentSizeDescriptors, 40, userUuid)
|
||||
|
||||
expect(result).toEqual({
|
||||
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
|
||||
@@ -57,7 +62,7 @@ describe('ItemTransferCalculator', () => {
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', null).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidsToFetch(itemContentSizeDescriptors, 50)
|
||||
const result = await createCalculator().computeItemUuidsToFetch(itemContentSizeDescriptors, 50, userUuid)
|
||||
|
||||
expect(result).toEqual({
|
||||
uuids: [
|
||||
@@ -76,7 +81,7 @@ describe('ItemTransferCalculator', () => {
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', 20).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidsToFetch(itemContentSizeDescriptors, 40)
|
||||
const result = await createCalculator().computeItemUuidsToFetch(itemContentSizeDescriptors, 40, userUuid)
|
||||
|
||||
expect(result).toEqual({
|
||||
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
|
||||
@@ -93,7 +98,7 @@ describe('ItemTransferCalculator', () => {
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', 20).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(itemContentSizeDescriptors, 50)
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(itemContentSizeDescriptors, 50, userUuid)
|
||||
|
||||
expect(result).toEqual([
|
||||
[
|
||||
@@ -111,7 +116,7 @@ describe('ItemTransferCalculator', () => {
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', 20).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(itemContentSizeDescriptors, 40)
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(itemContentSizeDescriptors, 40, userUuid)
|
||||
|
||||
expect(result).toEqual([
|
||||
['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
|
||||
@@ -126,7 +131,7 @@ describe('ItemTransferCalculator', () => {
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', null).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(itemContentSizeDescriptors, 50)
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(itemContentSizeDescriptors, 50, userUuid)
|
||||
|
||||
expect(result).toEqual([
|
||||
[
|
||||
@@ -144,7 +149,7 @@ describe('ItemTransferCalculator', () => {
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', 20).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(itemContentSizeDescriptors, 40)
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(itemContentSizeDescriptors, 40, userUuid)
|
||||
|
||||
expect(result).toEqual([
|
||||
['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Logger } from 'winston'
|
||||
|
||||
import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
|
||||
import { ItemContentSizeDescriptor } from './ItemContentSizeDescriptor'
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
export class ItemTransferCalculator implements ItemTransferCalculatorInterface {
|
||||
constructor(private logger: Logger) {}
|
||||
@@ -9,6 +10,7 @@ export class ItemTransferCalculator implements ItemTransferCalculatorInterface {
|
||||
async computeItemUuidsToFetch(
|
||||
itemContentSizeDescriptors: ItemContentSizeDescriptor[],
|
||||
bytesTransferLimit: number,
|
||||
userUuid: Uuid,
|
||||
): Promise<{ uuids: Array<string>; transferLimitBreachedBeforeEndOfItems: boolean }> {
|
||||
const itemUuidsToFetch = []
|
||||
let totalContentSizeInBytes = 0
|
||||
@@ -24,6 +26,7 @@ export class ItemTransferCalculator implements ItemTransferCalculatorInterface {
|
||||
bytesTransferLimit,
|
||||
itemUuidsToFetch,
|
||||
itemContentSizeDescriptors,
|
||||
userUuid,
|
||||
})
|
||||
|
||||
if (transferLimitBreached) {
|
||||
@@ -41,6 +44,7 @@ export class ItemTransferCalculator implements ItemTransferCalculatorInterface {
|
||||
async computeItemUuidBundlesToFetch(
|
||||
itemContentSizeDescriptors: ItemContentSizeDescriptor[],
|
||||
bytesTransferLimit: number,
|
||||
userUuid: Uuid,
|
||||
): Promise<Array<Array<string>>> {
|
||||
let itemUuidsToFetch = []
|
||||
let totalContentSizeInBytes = 0
|
||||
@@ -56,6 +60,7 @@ export class ItemTransferCalculator implements ItemTransferCalculatorInterface {
|
||||
bytesTransferLimit,
|
||||
itemUuidsToFetch,
|
||||
itemContentSizeDescriptors,
|
||||
userUuid,
|
||||
})
|
||||
|
||||
if (transferLimitBreached) {
|
||||
@@ -77,15 +82,20 @@ export class ItemTransferCalculator implements ItemTransferCalculatorInterface {
|
||||
bytesTransferLimit: number
|
||||
itemUuidsToFetch: Array<string>
|
||||
itemContentSizeDescriptors: ItemContentSizeDescriptor[]
|
||||
userUuid: Uuid
|
||||
}): boolean {
|
||||
const transferLimitBreached = dto.totalContentSizeInBytes >= dto.bytesTransferLimit
|
||||
const transferLimitBreachedAtFirstItem =
|
||||
transferLimitBreached && dto.itemUuidsToFetch.length === 1 && dto.itemContentSizeDescriptors.length > 1
|
||||
|
||||
if (transferLimitBreachedAtFirstItem) {
|
||||
this.logger.warn(
|
||||
`Item ${dto.itemUuidsToFetch[0]} is breaching the content size transfer limit: ${dto.bytesTransferLimit}`,
|
||||
)
|
||||
this.logger.warn('Item is breaching the content size transfer limit at first item in the bundle to fetch.', {
|
||||
codeTag: 'ItemTransferCalculator',
|
||||
itemUuid: dto.itemUuidsToFetch[0],
|
||||
totalContentSizeInBytes: dto.totalContentSizeInBytes,
|
||||
bytesTransferLimit: dto.bytesTransferLimit,
|
||||
userId: dto.userUuid.value,
|
||||
})
|
||||
}
|
||||
|
||||
return transferLimitBreached && !transferLimitBreachedAtFirstItem
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { ItemContentSizeDescriptor } from './ItemContentSizeDescriptor'
|
||||
|
||||
export interface ItemTransferCalculatorInterface {
|
||||
computeItemUuidsToFetch(
|
||||
itemContentSizeDescriptors: ItemContentSizeDescriptor[],
|
||||
bytesTransferLimit: number,
|
||||
userUuid: Uuid,
|
||||
): Promise<{ uuids: Array<string>; transferLimitBreachedBeforeEndOfItems: boolean }>
|
||||
computeItemUuidBundlesToFetch(
|
||||
itemContentSizeDescriptors: ItemContentSizeDescriptor[],
|
||||
bytesTransferLimit: number,
|
||||
userUuid: Uuid,
|
||||
): Promise<Array<Array<string>>>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Metric } from './Metric'
|
||||
|
||||
describe('Metric', () => {
|
||||
it('should create a value object', () => {
|
||||
const valueOrError = Metric.create({ name: 'ItemCreated', timestamp: 0 })
|
||||
|
||||
expect(valueOrError.isFailed()).toBeFalsy()
|
||||
expect(valueOrError.getValue().props.name).toEqual('ItemCreated')
|
||||
})
|
||||
|
||||
it('should not create an invalid value object', () => {
|
||||
const valueOrError = Metric.create({ name: 'InvalidMetricName', timestamp: 0 })
|
||||
|
||||
expect(valueOrError.isFailed()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Result, ValueObject } from '@standardnotes/domain-core'
|
||||
|
||||
import { MetricProps } from './MetricProps'
|
||||
|
||||
export class Metric extends ValueObject<MetricProps> {
|
||||
static readonly NAMES = {
|
||||
ItemCreated: 'ItemCreated',
|
||||
ItemUpdated: 'ItemUpdated',
|
||||
ContentSizeUtilized: 'ContentSizeUtilized',
|
||||
ItemOperation: 'ItemOperation',
|
||||
}
|
||||
|
||||
static create(props: MetricProps): Result<Metric> {
|
||||
const isValidName = Object.values(this.NAMES).includes(props.name)
|
||||
if (!isValidName) {
|
||||
return Result.fail<Metric>(`Invalid metric name: ${props.name}`)
|
||||
} else {
|
||||
return Result.ok<Metric>(new Metric(props))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface MetricProps {
|
||||
name: string
|
||||
timestamp: number
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { Metric } from './Metric'
|
||||
import { MetricsSummary } from './MetricsSummary'
|
||||
|
||||
export interface MetricsStoreInterface {
|
||||
storeMetric(metric: Metric): Promise<void>
|
||||
storeUserBasedMetric(metric: Metric, value: number, userUuid: Uuid): Promise<void>
|
||||
getUserBasedMetricsSummaryWithinTimeRange(dto: {
|
||||
metricName: string
|
||||
userUuid: Uuid
|
||||
from: Date
|
||||
to: Date
|
||||
}): Promise<MetricsSummary>
|
||||
getUserBasedMetricsSummary(name: string, timestamp: number): Promise<MetricsSummary>
|
||||
getMetricsSummary(name: string, from: number, to: number): Promise<MetricsSummary>
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface MetricsSummary {
|
||||
sum: number
|
||||
max: number
|
||||
min: number
|
||||
sampleCount: number
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { MetricsStoreInterface } from '../../../Metrics/MetricsStoreInterface'
|
||||
import { CheckForTrafficAbuse } from './CheckForTrafficAbuse'
|
||||
import { MetricsSummary } from '../../../Metrics/MetricsSummary'
|
||||
import { Metric } from '../../../Metrics/Metric'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('CheckForTrafficAbuse', () => {
|
||||
let metricsStore: MetricsStoreInterface
|
||||
let timer: TimerInterface
|
||||
let timeframeLengthInMinutes: number
|
||||
let threshold: number
|
||||
let logger: Logger
|
||||
|
||||
const createUseCase = () => new CheckForTrafficAbuse(metricsStore, timer, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
|
||||
const metricsSummary: MetricsSummary = {
|
||||
sum: 101,
|
||||
max: 0,
|
||||
min: 0,
|
||||
sampleCount: 0,
|
||||
}
|
||||
|
||||
metricsStore = {} as jest.Mocked<MetricsStoreInterface>
|
||||
metricsStore.getUserBasedMetricsSummaryWithinTimeRange = jest.fn().mockReturnValue(metricsSummary)
|
||||
|
||||
timer = {} as TimerInterface
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(0)
|
||||
timer.getUTCDateNMinutesAgo = jest.fn().mockReturnValue(new Date(0))
|
||||
timer.getUTCDate = jest.fn().mockReturnValue(new Date(10))
|
||||
|
||||
timeframeLengthInMinutes = 5
|
||||
|
||||
threshold = 100
|
||||
})
|
||||
|
||||
it('returns a failure result if the user uuid is invalid', async () => {
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: 'invalid',
|
||||
metricToCheck: Metric.NAMES.ItemOperation,
|
||||
timeframeLengthInMinutes,
|
||||
threshold,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('return a failure result if the metric name is invalid', async () => {
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
metricToCheck: 'invalid',
|
||||
timeframeLengthInMinutes,
|
||||
threshold,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('returns a failure result if the metric summary is above the threshold', async () => {
|
||||
const metricsSummary: MetricsSummary = {
|
||||
sum: 101,
|
||||
max: 0,
|
||||
min: 0,
|
||||
sampleCount: 0,
|
||||
}
|
||||
|
||||
metricsStore.getUserBasedMetricsSummaryWithinTimeRange = jest.fn().mockReturnValue(metricsSummary)
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
metricToCheck: Metric.NAMES.ItemOperation,
|
||||
timeframeLengthInMinutes,
|
||||
threshold,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('returns a success result if the metric summary is below the threshold', async () => {
|
||||
const metricsSummary: MetricsSummary = {
|
||||
sum: 99,
|
||||
max: 0,
|
||||
min: 0,
|
||||
sampleCount: 0,
|
||||
}
|
||||
|
||||
metricsStore.getUserBasedMetricsSummaryWithinTimeRange = jest.fn().mockReturnValue(metricsSummary)
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
metricToCheck: Metric.NAMES.ItemOperation,
|
||||
timeframeLengthInMinutes,
|
||||
threshold,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
})
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user