Compare commits

..

43 Commits

Author SHA1 Message Date
standardci
d406272f07 chore(release): publish new version
- @standardnotes/analytics@2.34.17
 - @standardnotes/api-gateway@1.92.1
 - @standardnotes/auth-server@1.178.5
 - @standardnotes/common@1.52.3
 - @standardnotes/domain-core@1.41.2
 - @standardnotes/domain-events-infra@1.23.4
 - @standardnotes/domain-events@2.141.1
 - @standardnotes/files-server@1.38.2
 - @standardnotes/grpc@1.4.2
 - @standardnotes/home-server@1.23.1
 - @standardnotes/predicates@1.8.2
 - @standardnotes/revisions-server@1.51.18
 - @standardnotes/scheduler-server@1.27.22
 - @standardnotes/security@1.17.4
 - @standardnotes/settings@1.23.3
 - @standardnotes/sncrypto-node@1.16.3
 - @standardnotes/syncing-server@1.136.4
 - @standardnotes/time@1.19.1
 - @standardnotes/websockets-server@1.22.13
2024-06-18 10:06:54 +00:00
Karol Sójko
9de3352885 fix(home-server): bump version 2024-06-18 11:46:16 +02:00
Karol Sójko
8575d20f7b fix: bump versions on packages 2024-06-18 10:33:39 +02:00
Karol Sójko
102d4b1e8a fix(api-gateway): bump version 2024-06-18 10:09:04 +02:00
Karol Sójko
1a57c247b2 chore: release latest changes (#1056)
* chore: release latest changes

* update yarn lockfile

* remove stale files

* fix ci env

* remove mysql command overwrite

* remove mysql overwrite from example

* fix cookie cooldown in memory
2024-06-18 09:29:24 +02:00
standardci
dbb0e4a974 chore(release): publish new version
- @standardnotes/api-gateway@1.91.0
 - @standardnotes/files-server@1.38.0
 - @standardnotes/home-server@1.23.0
2024-03-20 15:04:32 +00:00
Karol Sójko
5c02435ee4 feat: add CORS_ORIGIN_STRICT_MODE_ENABLED env var to determine if CORS origin should be restricted 2024-03-20 15:59:43 +01:00
standardci
0a1e555b13 chore(release): publish new version
- @standardnotes/api-gateway@1.90.3
 - @standardnotes/home-server@1.22.68
2024-03-18 10:22:49 +00:00
Karol Sójko
be668d7d7a fix(api-gateway): response headers cors issue - fixes #1046 2024-03-18 11:17:52 +01:00
standardci
87e50ec941 chore(release): publish new version
- @standardnotes/api-gateway@1.90.2
 - @standardnotes/files-server@1.37.12
 - @standardnotes/home-server@1.22.67
2024-03-18 08:48:11 +00:00
Karol Sójko
6d7ca1b926 fix: cors issues on clients - fixes #1046 (#1049) 2024-03-18 09:43:58 +01:00
standardci
00bfaaa53d chore(release): publish new version
- @standardnotes/auth-server@1.178.3
 - @standardnotes/home-server@1.22.66
2024-03-18 08:12:46 +00:00
Karol Sójko
f939caf2d9 fix(auth): allow registration on new api versions - fixes #1046 (#1048) 2024-03-18 09:08:16 +01:00
standardci
0f3615ee65 chore(release): publish new version
- @standardnotes/auth-server@1.178.2
 - @standardnotes/home-server@1.22.65
 - @standardnotes/syncing-server@1.136.2
2024-03-15 10:25:31 +00:00
Karol Sójko
567bcf26b5 tmp: disable e2e and deployment to ecs 2024-03-15 11:20:38 +01:00
Karol Sójko
9d49764b84 fix: allow handling of new api version 2024-03-15 11:17:46 +01:00
standardci
5c9f493b67 chore(release): publish new version
- @standardnotes/auth-server@1.178.1
 - @standardnotes/home-server@1.22.64
2024-02-09 18:01:17 +00:00
Mo
4fe8e9a79f fix: allow expired offline subscriptions to receive dashboard emails (#1041) 2024-02-09 11:39:47 -06:00
Karol Sójko
f975dd9697 fix: e2e params for max http request payload size (#1037) 2024-02-02 13:06:52 +01:00
standardci
10832f7001 chore(release): publish new version
- @standardnotes/analytics@2.34.16
 - @standardnotes/api-gateway@1.90.1
 - @standardnotes/auth-server@1.178.0
 - @standardnotes/domain-events-infra@1.23.3
 - @standardnotes/domain-events@2.141.0
 - @standardnotes/files-server@1.37.11
 - @standardnotes/home-server@1.22.63
 - @standardnotes/revisions-server@1.51.16
 - @standardnotes/scheduler-server@1.27.21
 - @standardnotes/syncing-server@1.136.1
 - @standardnotes/websockets-server@1.22.12
2024-01-19 10:38:11 +00:00
Karol Sójko
86b050865f feat(auth): add script for fixing subscriptions with missing id state (#1030)
* fix(auth): add subscription id safe guards on handlers

* feat(domain-events): add subscription state events

* feat(domain-events): add subscription state events

* feat(auth): add handling of subscription state fetched events

* feat(auth): add script for fixing subscriptions state
2024-01-19 11:17:33 +01:00
standardci
6f07aaf87a chore(release): publish new version
- @standardnotes/analytics@2.34.15
 - @standardnotes/api-gateway@1.90.0
 - @standardnotes/auth-server@1.177.20
 - @standardnotes/domain-events-infra@1.23.2
 - @standardnotes/domain-events@2.140.0
 - @standardnotes/files-server@1.37.10
 - @standardnotes/home-server@1.22.62
 - @standardnotes/revisions-server@1.51.15
 - @standardnotes/scheduler-server@1.27.20
 - @standardnotes/syncing-server@1.136.0
 - @standardnotes/websockets-server@1.22.11
2024-01-18 13:19:12 +00:00
Karol Sójko
634e8bd2d0 feat: add content sizes fixing upon grpc resource exhausted error (#1029) 2024-01-18 13:58:28 +01:00
standardci
6853dfbf66 chore(release): publish new version
- @standardnotes/api-gateway@1.89.20
 - @standardnotes/home-server@1.22.61
2024-01-18 11:08:11 +00:00
Karol Sójko
136cf252a1 fix(api-gateway): add codetag metadata to error logs 2024-01-18 11:47:26 +01:00
standardci
cad28ebba5 chore(release): publish new version
- @standardnotes/auth-server@1.177.19
 - @standardnotes/home-server@1.22.60
2024-01-17 12:54:44 +00:00
Karol Sójko
460fdf9eaf fix(auth): add server daily email backup permission for all versions of core user role (#1028) 2024-01-17 13:34:00 +01:00
standardci
bec1b502ad chore(release): publish new version
- @standardnotes/home-server@1.22.59
 - @standardnotes/syncing-server@1.135.0
2024-01-17 10:48:28 +00:00
Karol Sójko
70bbf11db5 feat(syncing-server): add procedure to recalculate content sizes (#1027) 2024-01-17 11:27:26 +01:00
standardci
c00c7becae chore(release): publish new version
- @standardnotes/home-server@1.22.58
 - @standardnotes/syncing-server@1.134.1
2024-01-16 10:41:23 +00:00
Karol Sójko
89dc6c19bf fix(syncing-server): missing item operations metric store expiry 2024-01-16 11:20:35 +01:00
standardci
972a91d59f chore(release): publish new version
- @standardnotes/auth-server@1.177.18
 - @standardnotes/home-server@1.22.57
2024-01-15 12:09:42 +00:00
Karol Sójko
045358ddbf fix(auth): add renewal for shared offline subscriptions 2024-01-15 12:42:54 +01:00
Karol Sójko
c7217a92ba fix(auth): add more logs to syncing subscription 2024-01-15 12:39:27 +01:00
standardci
3da7a21cde chore(release): publish new version
- @standardnotes/auth-server@1.177.17
 - @standardnotes/home-server@1.22.56
2024-01-15 10:27:24 +00:00
Karol Sójko
351e18f638 fix(auth): add debug logs for subscription sync requested event 2024-01-15 11:06:19 +01:00
standardci
4f2129c4e0 chore(release): publish new version
- @standardnotes/auth-server@1.177.16
 - @standardnotes/home-server@1.22.55
2024-01-15 09:44:23 +00:00
Karol Sójko
d7a1c667dd fix(auth): update shared subscriptions upon subscription sync (#1022) 2024-01-15 10:23:51 +01:00
standardci
4de0bfa36d chore(release): publish new version
- @standardnotes/home-server@1.22.54
 - @standardnotes/syncing-server@1.134.0
2024-01-12 15:06:26 +00:00
Karol Sójko
0443de88ce feat(syncing-server): reduced abuse thresholds for free users (#1021) 2024-01-12 15:45:00 +01:00
Karol Sójko
f830bac873 fix: reduce the transfer limit on e2e tests (#1020) 2024-01-11 13:12:33 +01:00
standardci
517ae5ded9 chore(release): publish new version
- @standardnotes/api-gateway@1.89.19
 - @standardnotes/files-server@1.37.9
 - @standardnotes/home-server@1.22.53
 - @standardnotes/syncing-server@1.133.6
2024-01-10 14:40:54 +00:00
Karol Sójko
6062f85000 fix: add dedicated http code response upon a request with too large payload (#1019)
* fix: add dedicated http code response upon a request with too large payload

* fix error log
2024-01-10 15:19:26 +01:00
328 changed files with 7735 additions and 1777 deletions

7
.github/ci.env vendored
View File

@@ -17,6 +17,9 @@ SYNCING_SERVER_LOG_LEVEL=debug
FILES_SERVER_LOG_LEVEL=debug
REVISIONS_SERVER_LOG_LEVEL=debug
API_GATEWAY_LOG_LEVEL=debug
COOKIE_DOMAIN=localhost
COOKIE_SECURE=false
COOKIE_PARTITIONED=false
MYSQL_DATABASE=standard_notes_db
MYSQL_USER=std_notes_user
@@ -27,4 +30,6 @@ 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
HTTP_REQUEST_PAYLOAD_LIMIT_MEGABYTES=1

View File

@@ -42,26 +42,26 @@ jobs:
workspace_name: ${{ inputs.workspace_name }}
secrets: inherit
deploy-web:
if: ${{ inputs.deploy_web }}
# deploy-web:
# if: ${{ inputs.deploy_web }}
needs: publish
# needs: publish
name: Deploy Web
uses: standardnotes/server/.github/workflows/common-deploy.yml@main
with:
service_name: ${{ inputs.service_name }}
docker_image: ${{ inputs.service_name }}:${{ github.sha }}
secrets: inherit
# name: Deploy Web
# uses: standardnotes/server/.github/workflows/common-deploy.yml@main
# with:
# service_name: ${{ inputs.service_name }}
# docker_image: ${{ inputs.service_name }}:${{ github.sha }}
# secrets: inherit
deploy-worker:
if: ${{ inputs.deploy_worker }}
# deploy-worker:
# if: ${{ inputs.deploy_worker }}
needs: publish
# needs: publish
name: Deploy Worker
uses: standardnotes/server/.github/workflows/common-deploy.yml@main
with:
service_name: ${{ inputs.service_name }}-worker
docker_image: ${{ inputs.service_name }}:${{ github.sha }}
secrets: inherit
# name: Deploy Worker
# uses: standardnotes/server/.github/workflows/common-deploy.yml@main
# with:
# service_name: ${{ inputs.service_name }}-worker
# docker_image: ${{ inputs.service_name }}:${{ github.sha }}
# secrets: inherit

View File

@@ -70,7 +70,8 @@ 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 "HTTP_REQUEST_PAYLOAD_LIMIT_MEGABYTES=1" >> 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

494
.pnp.cjs generated
View File

@@ -466,6 +466,54 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.490.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-client-sns-npm-3.490.0-2cd839225d-4d8875521c.zip/node_modules/@aws-sdk/client-sns/",\
"packageDependencies": [\
["@aws-sdk/client-sns", "npm:3.490.0"],\
["@aws-crypto/sha256-browser", "npm:3.0.0"],\
["@aws-crypto/sha256-js", "npm:3.0.0"],\
["@aws-sdk/client-sts", "npm:3.490.0"],\
["@aws-sdk/core", "npm:3.490.0"],\
["@aws-sdk/credential-provider-node", "npm:3.490.0"],\
["@aws-sdk/middleware-host-header", "npm:3.489.0"],\
["@aws-sdk/middleware-logger", "npm:3.489.0"],\
["@aws-sdk/middleware-recursion-detection", "npm:3.489.0"],\
["@aws-sdk/middleware-signing", "npm:3.489.0"],\
["@aws-sdk/middleware-user-agent", "npm:3.489.0"],\
["@aws-sdk/region-config-resolver", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.0"],\
["@aws-sdk/util-endpoints", "npm:3.489.0"],\
["@aws-sdk/util-user-agent-browser", "npm:3.489.0"],\
["@aws-sdk/util-user-agent-node", "virtual:26ec4a89785e0643103d1dd3b2a86d8c63d7fd76dbfb0e516f1dc429fef4581a7306b382504a8b85e8fb995888356d6341786deec607cb64b29957c728540295#npm:3.489.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"],\
["fast-xml-parser", "npm:4.2.5"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@aws-sdk/client-sqs", [\
@@ -607,6 +655,50 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.490.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-client-sso-npm-3.490.0-26ec4a8978-785147e3c2.zip/node_modules/@aws-sdk/client-sso/",\
"packageDependencies": [\
["@aws-sdk/client-sso", "npm:3.490.0"],\
["@aws-crypto/sha256-browser", "npm:3.0.0"],\
["@aws-crypto/sha256-js", "npm:3.0.0"],\
["@aws-sdk/core", "npm:3.490.0"],\
["@aws-sdk/middleware-host-header", "npm:3.489.0"],\
["@aws-sdk/middleware-logger", "npm:3.489.0"],\
["@aws-sdk/middleware-recursion-detection", "npm:3.489.0"],\
["@aws-sdk/middleware-user-agent", "npm:3.489.0"],\
["@aws-sdk/region-config-resolver", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.0"],\
["@aws-sdk/util-endpoints", "npm:3.489.0"],\
["@aws-sdk/util-user-agent-browser", "npm:3.489.0"],\
["@aws-sdk/util-user-agent-node", "virtual:26ec4a89785e0643103d1dd3b2a86d8c63d7fd76dbfb0e516f1dc429fef4581a7306b382504a8b85e8fb995888356d6341786deec607cb64b29957c728540295#npm:3.489.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", [\
@@ -703,6 +795,53 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.490.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-client-sts-npm-3.490.0-f3cd7f7c70-19d1b98694.zip/node_modules/@aws-sdk/client-sts/",\
"packageDependencies": [\
["@aws-sdk/client-sts", "npm:3.490.0"],\
["@aws-crypto/sha256-browser", "npm:3.0.0"],\
["@aws-crypto/sha256-js", "npm:3.0.0"],\
["@aws-sdk/core", "npm:3.490.0"],\
["@aws-sdk/credential-provider-node", "npm:3.490.0"],\
["@aws-sdk/middleware-host-header", "npm:3.489.0"],\
["@aws-sdk/middleware-logger", "npm:3.489.0"],\
["@aws-sdk/middleware-recursion-detection", "npm:3.489.0"],\
["@aws-sdk/middleware-user-agent", "npm:3.489.0"],\
["@aws-sdk/region-config-resolver", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.0"],\
["@aws-sdk/util-endpoints", "npm:3.489.0"],\
["@aws-sdk/util-user-agent-browser", "npm:3.489.0"],\
["@aws-sdk/util-user-agent-node", "virtual:26ec4a89785e0643103d1dd3b2a86d8c63d7fd76dbfb0e516f1dc429fef4581a7306b382504a8b85e8fb995888356d6341786deec607cb64b29957c728540295#npm:3.489.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", [\
@@ -731,6 +870,19 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.490.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-core-npm-3.490.0-3725a806be-3e81f37825.zip/node_modules/@aws-sdk/core/",\
"packageDependencies": [\
["@aws-sdk/core", "npm:3.490.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", [\
@@ -755,6 +907,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-env-npm-3.489.0-e86b20f5e4-95ab96ee49.zip/node_modules/@aws-sdk/credential-provider-env/",\
"packageDependencies": [\
["@aws-sdk/credential-provider-env", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.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", [\
@@ -791,6 +954,23 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.490.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-ini-npm-3.490.0-51f9d0faff-4e4cd2633a.zip/node_modules/@aws-sdk/credential-provider-ini/",\
"packageDependencies": [\
["@aws-sdk/credential-provider-ini", "npm:3.490.0"],\
["@aws-sdk/credential-provider-env", "npm:3.489.0"],\
["@aws-sdk/credential-provider-process", "npm:3.489.0"],\
["@aws-sdk/credential-provider-sso", "npm:3.490.0"],\
["@aws-sdk/credential-provider-web-identity", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.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", [\
@@ -829,6 +1009,24 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.490.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-node-npm-3.490.0-3cbe0ec5e6-2f8141c3e1.zip/node_modules/@aws-sdk/credential-provider-node/",\
"packageDependencies": [\
["@aws-sdk/credential-provider-node", "npm:3.490.0"],\
["@aws-sdk/credential-provider-env", "npm:3.489.0"],\
["@aws-sdk/credential-provider-ini", "npm:3.490.0"],\
["@aws-sdk/credential-provider-process", "npm:3.489.0"],\
["@aws-sdk/credential-provider-sso", "npm:3.490.0"],\
["@aws-sdk/credential-provider-web-identity", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.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", [\
@@ -855,6 +1053,18 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-process-npm-3.489.0-9370bfd061-42f4f5f21d.zip/node_modules/@aws-sdk/credential-provider-process/",\
"packageDependencies": [\
["@aws-sdk/credential-provider-process", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.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", [\
@@ -885,6 +1095,20 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.490.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-sso-npm-3.490.0-9acb6828c0-ef2eff8fbc.zip/node_modules/@aws-sdk/credential-provider-sso/",\
"packageDependencies": [\
["@aws-sdk/credential-provider-sso", "npm:3.490.0"],\
["@aws-sdk/client-sso", "npm:3.490.0"],\
["@aws-sdk/token-providers", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.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", [\
@@ -909,6 +1133,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-web-identity-npm-3.489.0-002a8c8ade-911bc3fd28.zip/node_modules/@aws-sdk/credential-provider-web-identity/",\
"packageDependencies": [\
["@aws-sdk/credential-provider-web-identity", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.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", [\
@@ -979,6 +1214,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-middleware-host-header-npm-3.489.0-10c65ea2e3-3f80f71691.zip/node_modules/@aws-sdk/middleware-host-header/",\
"packageDependencies": [\
["@aws-sdk/middleware-host-header", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.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", [\
@@ -1013,6 +1259,16 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-middleware-logger-npm-3.489.0-ba04fd0161-0bbf9d08c7.zip/node_modules/@aws-sdk/middleware-logger/",\
"packageDependencies": [\
["@aws-sdk/middleware-logger", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.0"],\
["@smithy/types", "npm:2.8.0"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@aws-sdk/middleware-recursion-detection", [\
@@ -1037,6 +1293,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-middleware-recursion-detection-npm-3.489.0-2cefe5fc6b-91eb0b3b46.zip/node_modules/@aws-sdk/middleware-recursion-detection/",\
"packageDependencies": [\
["@aws-sdk/middleware-recursion-detection", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.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", [\
@@ -1099,6 +1366,20 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-middleware-signing-npm-3.489.0-722d97a2fd-6fedba4569.zip/node_modules/@aws-sdk/middleware-signing/",\
"packageDependencies": [\
["@aws-sdk/middleware-signing", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.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", [\
@@ -1137,6 +1418,18 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-middleware-user-agent-npm-3.489.0-4a9e57c5ff-51fc7a8a03.zip/node_modules/@aws-sdk/middleware-user-agent/",\
"packageDependencies": [\
["@aws-sdk/middleware-user-agent", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.0"],\
["@aws-sdk/util-endpoints", "npm:3.489.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", [\
@@ -1163,6 +1456,19 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-region-config-resolver-npm-3.489.0-0721047a4a-045a630c94.zip/node_modules/@aws-sdk/region-config-resolver/",\
"packageDependencies": [\
["@aws-sdk/region-config-resolver", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.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", [\
@@ -1268,6 +1574,50 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-token-providers-npm-3.489.0-05002406d6-ee7a20eff7.zip/node_modules/@aws-sdk/token-providers/",\
"packageDependencies": [\
["@aws-sdk/token-providers", "npm:3.489.0"],\
["@aws-crypto/sha256-browser", "npm:3.0.0"],\
["@aws-crypto/sha256-js", "npm:3.0.0"],\
["@aws-sdk/middleware-host-header", "npm:3.489.0"],\
["@aws-sdk/middleware-logger", "npm:3.489.0"],\
["@aws-sdk/middleware-recursion-detection", "npm:3.489.0"],\
["@aws-sdk/middleware-user-agent", "npm:3.489.0"],\
["@aws-sdk/region-config-resolver", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.0"],\
["@aws-sdk/util-endpoints", "npm:3.489.0"],\
["@aws-sdk/util-user-agent-browser", "npm:3.489.0"],\
["@aws-sdk/util-user-agent-node", "virtual:26ec4a89785e0643103d1dd3b2a86d8c63d7fd76dbfb0e516f1dc429fef4581a7306b382504a8b85e8fb995888356d6341786deec607cb64b29957c728540295#npm:3.489.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", [\
@@ -1296,6 +1646,15 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-types-npm-3.489.0-f0f748fbaa-48778dad14.zip/node_modules/@aws-sdk/types/",\
"packageDependencies": [\
["@aws-sdk/types", "npm:3.489.0"],\
["@smithy/types", "npm:2.8.0"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@aws-sdk/util-arn-parser", [\
@@ -1328,6 +1687,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-util-endpoints-npm-3.489.0-1f2dd7e944-68f921982f.zip/node_modules/@aws-sdk/util-endpoints/",\
"packageDependencies": [\
["@aws-sdk/util-endpoints", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.0"],\
["@smithy/types", "npm:2.8.0"],\
["@smithy/util-endpoints", "npm:1.0.8"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@aws-sdk/util-locate-window", [\
@@ -1362,6 +1732,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-util-user-agent-browser-npm-3.489.0-ffb94f7b1d-2bb414b8d8.zip/node_modules/@aws-sdk/util-user-agent-browser/",\
"packageDependencies": [\
["@aws-sdk/util-user-agent-browser", "npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.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", [\
@@ -1379,6 +1760,30 @@ const RAW_RUNTIME_STATE =
],\
"linkType": "SOFT"\
}],\
["npm:3.489.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-util-user-agent-node-npm-3.489.0-082349e8a9-95dc1e07b6.zip/node_modules/@aws-sdk/util-user-agent-node/",\
"packageDependencies": [\
["@aws-sdk/util-user-agent-node", "npm:3.489.0"]\
],\
"linkType": "SOFT"\
}],\
["virtual:26ec4a89785e0643103d1dd3b2a86d8c63d7fd76dbfb0e516f1dc429fef4581a7306b382504a8b85e8fb995888356d6341786deec607cb64b29957c728540295#npm:3.489.0", {\
"packageLocation": "./.yarn/__virtual__/@aws-sdk-util-user-agent-node-virtual-73c334651c/0/cache/@aws-sdk-util-user-agent-node-npm-3.489.0-082349e8a9-95dc1e07b6.zip/node_modules/@aws-sdk/util-user-agent-node/",\
"packageDependencies": [\
["@aws-sdk/util-user-agent-node", "virtual:26ec4a89785e0643103d1dd3b2a86d8c63d7fd76dbfb0e516f1dc429fef4581a7306b382504a8b85e8fb995888356d6341786deec607cb64b29957c728540295#npm:3.489.0"],\
["@aws-sdk/types", "npm:3.489.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: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": [\
@@ -5951,7 +6356,7 @@ const RAW_RUNTIME_STATE =
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mixpanel", "npm:0.17.0"],\
["mysql2", "npm:3.3.3"],\
["mysql2", "npm:3.9.7"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
@@ -5983,6 +6388,7 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./packages/api-gateway/",\
"packageDependencies": [\
["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\
["@aws-sdk/client-sns", "npm:3.490.0"],\
["@grpc/grpc-js", "npm:1.9.13"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
@@ -5990,6 +6396,7 @@ const RAW_RUNTIME_STATE =
["@standardnotes/grpc", "workspace:packages/grpc"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/time", "workspace:packages/time"],\
["@types/cookie-parser", "npm:1.4.6"],\
["@types/cors", "npm:2.8.13"],\
["@types/express", "npm:4.17.17"],\
["@types/ioredis", "npm:5.0.0"],\
@@ -6001,6 +6408,7 @@ const RAW_RUNTIME_STATE =
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["agentkeepalive", "npm:4.5.0"],\
["axios", "npm:1.6.1"],\
["cookie-parser", "npm:1.4.6"],\
["cors", "npm:2.8.5"],\
["dotenv", "npm:16.1.3"],\
["eslint", "npm:8.41.0"],\
@@ -6051,6 +6459,7 @@ const RAW_RUNTIME_STATE =
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
["@standardnotes/time", "workspace:packages/time"],\
["@types/bcryptjs", "npm:2.4.2"],\
["@types/cookie-parser", "npm:1.4.6"],\
["@types/cors", "npm:2.8.13"],\
["@types/express", "npm:4.17.17"],\
["@types/ioredis", "npm:5.0.0"],\
@@ -6062,7 +6471,10 @@ const RAW_RUNTIME_STATE =
["@types/uuid", "npm:9.0.3"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["agentkeepalive", "npm:4.5.0"],\
["axios", "npm:1.6.7"],\
["bcryptjs", "npm:2.4.3"],\
["cookie-parser", "npm:1.4.6"],\
["cors", "npm:2.8.5"],\
["dayjs", "npm:1.11.7"],\
["dotenv", "npm:16.1.3"],\
@@ -6073,7 +6485,7 @@ const RAW_RUNTIME_STATE =
["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\
["mysql2", "npm:3.9.7"],\
["otplib", "npm:12.0.1"],\
["prettier", "npm:3.0.3"],\
["prettyjson", "npm:1.2.5"],\
@@ -6283,10 +6695,12 @@ const RAW_RUNTIME_STATE =
["@standardnotes/files-server", "workspace:packages/files"],\
["@standardnotes/revisions-server", "workspace:packages/revisions"],\
["@standardnotes/syncing-server", "workspace:packages/syncing-server"],\
["@types/cookie-parser", "npm:1.4.6"],\
["@types/cors", "npm:2.8.13"],\
["@types/express", "npm:4.17.17"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["cookie-parser", "npm:1.4.6"],\
["cors", "npm:2.8.5"],\
["dotenv", "npm:16.1.3"],\
["eslint", "npm:8.41.0"],\
@@ -6384,7 +6798,7 @@ const RAW_RUNTIME_STATE =
["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\
["mysql2", "npm:3.9.7"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.2.1"],\
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
@@ -6420,7 +6834,7 @@ const RAW_RUNTIME_STATE =
["inversify", "npm:6.0.1"],\
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\
["mysql2", "npm:3.9.7"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
@@ -6571,7 +6985,7 @@ const RAW_RUNTIME_STATE =
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["jsonwebtoken", "npm:9.0.0"],\
["mysql2", "npm:3.3.3"],\
["mysql2", "npm:3.9.7"],\
["prettier", "npm:3.0.3"],\
["prettyjson", "npm:1.2.5"],\
["reflect-metadata", "npm:0.2.1"],\
@@ -6651,7 +7065,7 @@ const RAW_RUNTIME_STATE =
["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\
["mysql2", "npm:3.9.7"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
@@ -6831,6 +7245,16 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@types/cookie-parser", [\
["npm:1.4.6", {\
"packageLocation": "./.yarn/cache/@types-cookie-parser-npm-1.4.6-27287e1e43-b1bbb17bc4.zip/node_modules/@types/cookie-parser/",\
"packageDependencies": [\
["@types/cookie-parser", "npm:1.4.6"],\
["@types/express", "npm:4.17.17"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/cors", [\
["npm:2.8.13", {\
"packageLocation": "./.yarn/cache/@types-cors-npm-2.8.13-4b8ac1068f-7ef197ea19.zip/node_modules/@types/cors/",\
@@ -8091,6 +8515,16 @@ const RAW_RUNTIME_STATE =
["proxy-from-env", "npm:1.1.0"]\
],\
"linkType": "HARD"\
}],\
["npm:1.6.7", {\
"packageLocation": "./.yarn/cache/axios-npm-1.6.7-d7b9974d1b-a1932b089e.zip/node_modules/axios/",\
"packageDependencies": [\
["axios", "npm:1.6.7"],\
["follow-redirects", "virtual:d7b9974d1bba76881cc57a280a16dd4914416a6fc4923c2efbb6328057412974da1e719cef1530b7a62b97d85d828f7e1d49b5f6de3b5b0854d49902ec87827c#npm:1.15.5"],\
["form-data", "npm:4.0.0"],\
["proxy-from-env", "npm:1.1.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["babel-jest", [\
@@ -9202,6 +9636,13 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["cookie", [\
["npm:0.4.1", {\
"packageLocation": "./.yarn/cache/cookie-npm-0.4.1-cc5e2ebb42-0f2defd60a.zip/node_modules/cookie/",\
"packageDependencies": [\
["cookie", "npm:0.4.1"]\
],\
"linkType": "HARD"\
}],\
["npm:0.5.0", {\
"packageLocation": "./.yarn/cache/cookie-npm-0.5.0-e2d58a161a-aae7911ddc.zip/node_modules/cookie/",\
"packageDependencies": [\
@@ -9210,6 +9651,17 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["cookie-parser", [\
["npm:1.4.6", {\
"packageLocation": "./.yarn/cache/cookie-parser-npm-1.4.6-a68f84d02a-1e5a63aa82.zip/node_modules/cookie-parser/",\
"packageDependencies": [\
["cookie-parser", "npm:1.4.6"],\
["cookie", "npm:0.4.1"],\
["cookie-signature", "npm:1.0.6"]\
],\
"linkType": "HARD"\
}]\
]],\
["cookie-signature", [\
["npm:1.0.6", {\
"packageLocation": "./.yarn/cache/cookie-signature-npm-1.0.6-93f325f7f0-f4e1b0a98a.zip/node_modules/cookie-signature/",\
@@ -10458,6 +10910,26 @@ const RAW_RUNTIME_STATE =
],\
"linkType": "SOFT"\
}],\
["npm:1.15.5", {\
"packageLocation": "./.yarn/cache/follow-redirects-npm-1.15.5-9d14db76ca-d467f13c1c.zip/node_modules/follow-redirects/",\
"packageDependencies": [\
["follow-redirects", "npm:1.15.5"]\
],\
"linkType": "SOFT"\
}],\
["virtual:d7b9974d1bba76881cc57a280a16dd4914416a6fc4923c2efbb6328057412974da1e719cef1530b7a62b97d85d828f7e1d49b5f6de3b5b0854d49902ec87827c#npm:1.15.5", {\
"packageLocation": "./.yarn/__virtual__/follow-redirects-virtual-393395f3f6/0/cache/follow-redirects-npm-1.15.5-9d14db76ca-d467f13c1c.zip/node_modules/follow-redirects/",\
"packageDependencies": [\
["follow-redirects", "virtual:d7b9974d1bba76881cc57a280a16dd4914416a6fc4923c2efbb6328057412974da1e719cef1530b7a62b97d85d828f7e1d49b5f6de3b5b0854d49902ec87827c#npm:1.15.5"],\
["@types/debug", null],\
["debug", null]\
],\
"packagePeers": [\
"@types/debug",\
"debug"\
],\
"linkType": "HARD"\
}],\
["virtual:ffaff76449f02e83712a7d24e03c564489516739c78ebeffb0fbcdb3893ad9a0e48504f9acfa70fe6f16debe9c8dabde3679d63bf648278ea98a5ff38cf77a9e#npm:1.15.2", {\
"packageLocation": "./.yarn/__virtual__/follow-redirects-virtual-c2d5794c26/0/cache/follow-redirects-npm-1.15.2-1ec1dd82be-8be0d39919.zip/node_modules/follow-redirects/",\
"packageDependencies": [\
@@ -13377,10 +13849,10 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["mysql2", [\
["npm:3.3.3", {\
"packageLocation": "./.yarn/cache/mysql2-npm-3.3.3-d2fe8cf512-4bf7ace8f1.zip/node_modules/mysql2/",\
["npm:3.9.7", {\
"packageLocation": "./.yarn/cache/mysql2-npm-3.9.7-8fe89e50ac-7f43b17cc0.zip/node_modules/mysql2/",\
"packageDependencies": [\
["mysql2", "npm:3.3.3"],\
["mysql2", "npm:3.9.7"],\
["denque", "npm:2.1.0"],\
["generate-function", "npm:2.3.1"],\
["iconv-lite", "npm:0.6.3"],\
@@ -16430,7 +16902,7 @@ const RAW_RUNTIME_STATE =
["mkdirp", "npm:2.1.6"],\
["mongodb", null],\
["mssql", null],\
["mysql2", "npm:3.3.3"],\
["mysql2", "npm:3.9.7"],\
["oracledb", null],\
["pg", null],\
["pg-native", null],\
@@ -16522,7 +16994,7 @@ const RAW_RUNTIME_STATE =
["mkdirp", "npm:2.1.6"],\
["mongodb", null],\
["mssql", null],\
["mysql2", "npm:3.3.3"],\
["mysql2", "npm:3.9.7"],\
["oracledb", null],\
["pg", null],\
["pg-native", null],\

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -54,7 +54,6 @@ services:
ports:
- 3306
restart: unless-stopped
command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
volumes:
- ./data/mysql:/var/lib/mysql
- ./data/import:/docker-entrypoint-initdb.d

View File

@@ -39,7 +39,6 @@ services:
expose:
- 3306
restart: unless-stopped
command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
volumes:
- ./data/mysql:/var/lib/mysql
- ./data/import:/docker-entrypoint-initdb.d

View File

@@ -21,7 +21,7 @@
"publish": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
"postversion": "./scripts/push-tags-one-by-one.sh",
"e2e": "yarn build && PORT=3123 yarn workspace @standardnotes/home-server start",
"start": "yarn workspace @standardnotes/home-server run build && yarn workspace @standardnotes/home-server start"
"start": "yarn build && yarn workspace @standardnotes/home-server start"
},
"devDependencies": {
"@commitlint/cli": "^17.0.2",
@@ -39,7 +39,7 @@
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
},
"packageManager": "yarn@4.0.2",
"packageManager": "yarn@4.1.0",
"dependenciesMeta": {
"grpc-tools@1.12.4": {
"unplugged": true

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.34.17](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.16...@standardnotes/analytics@2.34.17) (2024-06-18)
**Note:** Version bump only for package @standardnotes/analytics
## [2.34.16](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.15...@standardnotes/analytics@2.34.16) (2024-01-19)
**Note:** Version bump only for package @standardnotes/analytics
## [2.34.15](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.14...@standardnotes/analytics@2.34.15) (2024-01-18)
**Note:** Version bump only for package @standardnotes/analytics
## [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

View File

@@ -10,6 +10,12 @@ RUN corepack enable
COPY ./ /workspace
WORKDIR /workspace
RUN yarn install --immutable
RUN yarn build
WORKDIR /workspace/packages/analytics
ENTRYPOINT [ "/workspace/packages/analytics/docker/entrypoint.sh" ]

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.34.14",
"version": "2.34.17",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -24,7 +24,7 @@
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=2",
"worker": "yarn node dist/bin/worker.js",
"report": "yarn node dist/bin/report.js",
"setup:env": "cp .env.sample .env",
@@ -57,7 +57,7 @@
"inversify": "^6.0.1",
"ioredis": "^5.2.4",
"mixpanel": "^0.17.0",
"mysql2": "^3.0.1",
"mysql2": "^3.9.7",
"reflect-metadata": "^0.2.1",
"typeorm": "^0.3.17",
"winston": "^3.8.1"

View File

@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.92.1](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.91.0...@standardnotes/api-gateway@1.92.1) (2024-06-18)
### Bug Fixes
* **api-gateway:** bump version ([102d4b1](https://github.com/standardnotes/server/commit/102d4b1e8ab000fc97d01c621654b6fc65e37d32))
## [1.90.1](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.90.0...@standardnotes/api-gateway@1.90.1) (2024-01-19)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.90.0](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.20...@standardnotes/api-gateway@1.90.0) (2024-01-18)
### Features
* add content sizes fixing upon grpc resource exhausted error ([#1029](https://github.com/standardnotes/server/issues/1029)) ([634e8bd](https://github.com/standardnotes/server/commit/634e8bd2d0f055abbda1150587ab9a444281e600))
## [1.89.20](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.19...@standardnotes/api-gateway@1.89.20) (2024-01-18)
### Bug Fixes
* **api-gateway:** add codetag metadata to error logs ([136cf25](https://github.com/standardnotes/server/commit/136cf252a134efe7d99f79d8622c43dbebbb5ac8))
## [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

View File

@@ -10,6 +10,12 @@ RUN corepack enable
COPY ./ /workspace
WORKDIR /workspace
RUN yarn install --immutable
RUN yarn build
WORKDIR /workspace/packages/api-gateway
ENTRYPOINT [ "/workspace/packages/api-gateway/docker/entrypoint.sh" ]

View File

@@ -27,6 +27,7 @@ import '../src/Controller/v2/RevisionsControllerV2'
import helmet from 'helmet'
import * as cors from 'cors'
import * as cookieParser from 'cookie-parser'
import { text, json, Request, Response, NextFunction } from 'express'
import * as winston from 'winston'
// eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -43,9 +44,28 @@ 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 logger: winston.Logger = container.get(TYPES.ApiGateway_Logger)
const server = new InversifyExpressServer(container)
server.setConfig((app) => {
app.use((request: Request, _response: Response, next: NextFunction) => {
if (request.hostname.includes('standardnotes.org')) {
logger.warn('Request is using deprecated domain', {
origin: request.headers.origin,
method: request.method,
url: request.url,
snjs: request.headers['x-snjs-version'],
application: request.headers['x-application-version'],
})
}
next()
})
app.use((_request: Request, response: Response, next: NextFunction) => {
response.setHeader('X-API-Gateway-Version', container.get(TYPES.ApiGateway_VERSION))
next()
@@ -73,13 +93,57 @@ void container.load().then((container) => {
}),
)
app.use(json({ limit: '50mb' }))
app.use(cookieParser())
app.use(json({ limit: requestPayloadLimit }))
app.use(
text({
type: ['text/plain', 'application/x-www-form-urlencoded', 'application/x-www-form-urlencoded; charset=utf-8'],
}),
)
app.use(cors())
const corsAllowedOrigins = container.get<string[]>(TYPES.ApiGateway_CORS_ALLOWED_ORIGINS)
app.use(
cors({
credentials: true,
exposedHeaders: ['x-captcha-required'],
origin: (requestOrigin: string | undefined, callback: (err: Error | null, origin?: string[]) => void) => {
const originStrictModeEnabled = env.get('CORS_ORIGIN_STRICT_MODE_ENABLED', true)
? env.get('CORS_ORIGIN_STRICT_MODE_ENABLED', true) === 'true'
: false
if (!originStrictModeEnabled) {
callback(null, [requestOrigin as string])
return
}
const requstOriginIsNotFilled = !requestOrigin || requestOrigin === 'null'
const requestOriginatesFromTheDesktopApp = requestOrigin?.startsWith('file://')
const requestOriginatesFromClipperForFirefox = requestOrigin?.startsWith('moz-extension://')
const requestOriginatesFromSelfHostedAppOnHttpPort = requestOrigin === 'http://localhost'
const requestOriginatesFromSelfHostedAppOnCustomPort = requestOrigin?.match(/http:\/\/localhost:\d+/) !== null
const requestOriginatesFromSelfHostedApp =
requestOriginatesFromSelfHostedAppOnHttpPort || requestOriginatesFromSelfHostedAppOnCustomPort
const requestIsWhitelisted =
corsAllowedOrigins.length === 0 ||
requstOriginIsNotFilled ||
requestOriginatesFromTheDesktopApp ||
requestOriginatesFromClipperForFirefox ||
requestOriginatesFromSelfHostedApp
if (requestIsWhitelisted) {
callback(null, [requestOrigin as string])
} else {
if (corsAllowedOrigins.includes(requestOrigin)) {
callback(null, [requestOrigin])
} else {
callback(new Error('Not allowed by CORS', { cause: 'origin not allowed' }))
}
}
},
}),
)
app.use(
robots({
UserAgent: '*',
@@ -88,13 +152,13 @@ void container.load().then((container) => {
)
})
const logger: winston.Logger = container.get(TYPES.ApiGateway_Logger)
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}`, {
origin: request.headers.origin,
codeTag: 'server.ts',
method: request.method,
url: request.url,
snjs: request.headers['x-snjs-version'],
@@ -107,6 +171,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:

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.89.18",
"version": "1.92.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -31,6 +31,7 @@
"start": "yarn node dist/bin/server.js"
},
"dependencies": {
"@aws-sdk/client-sns": "^3.490.0",
"@grpc/grpc-js": "^1.9.13",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*",
@@ -40,6 +41,7 @@
"@standardnotes/time": "workspace:*",
"agentkeepalive": "^4.5.0",
"axios": "^1.6.1",
"cookie-parser": "^1.4.6",
"cors": "2.8.5",
"dotenv": "^16.0.1",
"express": "^4.18.2",
@@ -54,6 +56,7 @@
"winston": "^3.8.1"
},
"devDependencies": {
"@types/cookie-parser": "^1",
"@types/cors": "^2.8.9",
"@types/express": "^4.17.14",
"@types/ioredis": "^5.0.0",

View File

@@ -1,6 +1,7 @@
import * as winston from 'winston'
import * as AgentKeepAlive from 'agentkeepalive'
import * as grpc from '@grpc/grpc-js'
import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
import axios, { AxiosInstance } from 'axios'
import Redis from 'ioredis'
import { Container } from 'inversify'
@@ -29,6 +30,10 @@ import { SyncResponseHttpRepresentation } from '../Mapping/Sync/Http/SyncRespons
import { SyncRequestGRPCMapper } from '../Mapping/Sync/GRPC/SyncRequestGRPCMapper'
import { SyncResponseGRPCMapper } from '../Mapping/Sync/GRPC/SyncResponseGRPCMapper'
import { GRPCWebSocketAuthMiddleware } from '../Controller/GRPCWebSocketAuthMiddleware'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { SNSDomainEventPublisher } from '@standardnotes/domain-events-infra'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { DomainEventFactory } from '../Event/DomainEventFactory'
export class ContainerConfigLoader {
async load(configuration?: {
@@ -51,6 +56,34 @@ export class ContainerConfigLoader {
.bind<boolean>(TYPES.ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING)
.toConstantValue(isConfiguredForHomeServerOrSelfHosting)
if (!isConfiguredForHomeServerOrSelfHosting) {
const snsConfig: SNSClientConfig = {
region: env.get('SNS_AWS_REGION', true),
}
if (env.get('SNS_ENDPOINT', true)) {
snsConfig.endpoint = env.get('SNS_ENDPOINT', true)
}
if (env.get('SNS_ACCESS_KEY_ID', true) && env.get('SNS_SECRET_ACCESS_KEY', true)) {
snsConfig.credentials = {
accessKeyId: env.get('SNS_ACCESS_KEY_ID', true),
secretAccessKey: env.get('SNS_SECRET_ACCESS_KEY', true),
}
}
const snsClient = new SNSClient(snsConfig)
container.bind<SNSClient>(TYPES.ApiGateway_SNS).toConstantValue(snsClient)
container.bind(TYPES.ApiGateway_SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN', true))
container
.bind<DomainEventPublisherInterface>(TYPES.ApiGateway_DomainEventPublisher)
.toConstantValue(
new SNSDomainEventPublisher(
container.get(TYPES.ApiGateway_SNS),
container.get(TYPES.ApiGateway_SNS_TOPIC_ARN),
),
)
}
const winstonFormatters = [winston.format.splat(), winston.format.json()]
let logger: winston.Logger
@@ -109,6 +142,10 @@ export class ContainerConfigLoader {
.bind(TYPES.ApiGateway_CROSS_SERVICE_TOKEN_CACHE_TTL)
.toConstantValue(+env.get('CROSS_SERVICE_TOKEN_CACHE_TTL', true))
container.bind(TYPES.ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER).toConstantValue(isConfiguredForHomeServer)
container
.bind<string[]>(TYPES.ApiGateway_CORS_ALLOWED_ORIGINS)
.toConstantValue(env.get('CORS_ALLOWED_ORIGINS', true) ? env.get('CORS_ALLOWED_ORIGINS', true).split(',') : [])
container.bind<string>(TYPES.ApiGateway_CAPTCHA_UI_URL).toConstantValue(env.get('CAPTCHA_UI_URL', true))
// Middleware
container
@@ -124,14 +161,14 @@ export class ContainerConfigLoader {
// Services
container.bind<TimerInterface>(TYPES.ApiGateway_Timer).toConstantValue(new Timer())
if (isConfiguredForHomeServer) {
if (isConfiguredForInMemoryCache) {
container
.bind<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache)
.toConstantValue(new InMemoryCrossServiceTokenCache(container.get(TYPES.ApiGateway_Timer)))
} else {
container
.bind<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache)
.to(RedisCrossServiceTokenCache)
.toConstantValue(new RedisCrossServiceTokenCache(container.get(TYPES.ApiGateway_Redis)))
}
container
.bind<EndpointResolverInterface>(TYPES.ApiGateway_EndpointResolver)
@@ -192,6 +229,10 @@ export class ContainerConfigLoader {
.bind<MapperInterface<SyncResponse, SyncResponseHttpRepresentation>>(TYPES.Mapper_SyncResponseGRPCMapper)
.toConstantValue(new SyncResponseGRPCMapper())
container
.bind<DomainEventFactoryInterface>(TYPES.ApiGateway_DomainEventFactory)
.toConstantValue(new DomainEventFactory(container.get<TimerInterface>(TYPES.ApiGateway_Timer)))
container
.bind<GRPCSyncingServerServiceProxy>(TYPES.ApiGateway_GRPCSyncingServerServiceProxy)
.toConstantValue(
@@ -202,6 +243,10 @@ export class ContainerConfigLoader {
TYPES.Mapper_SyncResponseGRPCMapper,
),
container.get<winston.Logger>(TYPES.ApiGateway_Logger),
container.get<DomainEventFactoryInterface>(TYPES.ApiGateway_DomainEventFactory),
isConfiguredForHomeServerOrSelfHosting
? undefined
: container.get<DomainEventPublisherInterface>(TYPES.ApiGateway_DomainEventPublisher),
),
)
container

View File

@@ -2,7 +2,12 @@ export const TYPES = {
ApiGateway_Logger: Symbol.for('ApiGateway_Logger'),
ApiGateway_Redis: Symbol.for('ApiGateway_Redis'),
ApiGateway_HTTPClient: Symbol.for('ApiGateway_HTTPClient'),
ApiGateway_SNS: Symbol.for('ApiGateway_SNS'),
ApiGateway_DomainEventPublisher: Symbol.for('ApiGateway_DomainEventPublisher'),
// env vars
ApiGateway_CORS_ALLOWED_ORIGINS: Symbol.for('ApiGateway_CORS_ALLOWED_ORIGINS'),
ApiGateway_SNS_TOPIC_ARN: Symbol.for('ApiGateway_SNS_TOPIC_ARN'),
ApiGateway_SNS_AWS_REGION: Symbol.for('ApiGateway_SNS_AWS_REGION'),
ApiGateway_SYNCING_SERVER_JS_URL: Symbol.for('ApiGateway_SYNCING_SERVER_JS_URL'),
ApiGateway_AUTH_SERVER_URL: Symbol.for('ApiGateway_AUTH_SERVER_URL'),
ApiGateway_AUTH_SERVER_GRPC_URL: Symbol.for('ApiGateway_AUTH_SERVER_GRPC_URL'),
@@ -20,6 +25,7 @@ export const TYPES = {
ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING: Symbol.for(
'ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING',
),
ApiGateway_CAPTCHA_UI_URL: Symbol.for('ApiGateway_CAPTCHA_UI_URL'),
// Middleware
ApiGateway_RequiredCrossServiceTokenMiddleware: Symbol.for('ApiGateway_RequiredCrossServiceTokenMiddleware'),
ApiGateway_OptionalCrossServiceTokenMiddleware: Symbol.for('ApiGateway_OptionalCrossServiceTokenMiddleware'),
@@ -29,6 +35,7 @@ export const TYPES = {
Mapper_SyncRequestGRPCMapper: Symbol.for('Mapper_SyncRequestGRPCMapper'),
Mapper_SyncResponseGRPCMapper: Symbol.for('Mapper_SyncResponseGRPCMapper'),
// Services
ApiGateway_DomainEventFactory: Symbol.for('ApiGateway_DomainEventFactory'),
ApiGateway_GRPCSyncingServerServiceProxy: Symbol.for('ApiGateway_GRPCSyncingServerServiceProxy'),
ApiGateway_ServiceProxy: Symbol.for('ApiGateway_ServiceProxy'),
ApiGateway_CrossServiceTokenCache: Symbol.for('ApiGateway_CrossServiceTokenCache'),

View File

@@ -42,9 +42,33 @@ export abstract class AuthMiddleware extends BaseMiddleware {
}
if (crossServiceToken === null) {
const cookiesFromHeaders = new Map<string, string[]>()
request.headers.cookie?.split(';').forEach((cookie) => {
const parts = cookie.split('=')
if (parts.length === 2) {
const existingCookies = cookiesFromHeaders.get(parts[0].trim())
if (existingCookies) {
existingCookies.push(parts[1].trim())
cookiesFromHeaders.set(parts[0].trim(), existingCookies)
} else {
cookiesFromHeaders.set(parts[0].trim(), [parts[1].trim()])
}
}
})
const authResponse = await this.serviceProxy.validateSession({
authorization: authHeaderValue,
sharedVaultOwnerContext: sharedVaultOwnerContextHeaderValue,
headers: {
authorization: authHeaderValue.replace('Bearer ', ''),
sharedVaultOwnerContext: sharedVaultOwnerContextHeaderValue,
},
requestMetadata: {
snjs: request.headers['x-snjs-version'] as string,
application: request.headers['x-application-version'] as string,
url: request.url,
method: request.method,
userAgent: request.headers['user-agent'],
secChUa: request.headers['sec-ch-ua'] as string,
},
cookies: cookiesFromHeaders,
})
if (!this.handleSessionValidationResponse(authResponse, response, next)) {

View File

@@ -100,6 +100,7 @@ export class GRPCWebSocketAuthMiddleware extends BaseMiddleware {
roles: decodedToken.roles,
isFreeUser: decodedToken.roles.length === 1 && decodedToken.roles[0].name === RoleName.NAMES.CoreUser,
readOnlyAccess: decodedToken.session?.readonly_access ?? false,
hasContentLimit: decodedToken.hasContentLimit,
} as ResponseLocals)
} catch (error) {
this.logger.error(

View File

@@ -20,8 +20,6 @@ export class LegacyController extends BaseHttpController {
['DELETE:/session', 'DELETE:session'],
['DELETE:/session/all', 'DELETE:session/all'],
['POST:/session/refresh', 'POST:session/refresh'],
['POST:/auth/sign_in', 'POST:auth/sign_in'],
['GET:/auth/params', 'GET:auth/params'],
])
this.PARAMETRIZED_AUTH_ROUTES = new Map([

View File

@@ -26,4 +26,5 @@ export interface ResponseLocals {
sharedVaultOwnerContext?: {
upload_bytes_limit: number
}
hasContentLimit: boolean
}

View File

@@ -4,12 +4,14 @@ import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-exp
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
import { JsonResult } from 'inversify-express-utils/lib/results'
@controller('/v1')
export class ActionsController extends BaseHttpController {
constructor(
@inject(TYPES.ApiGateway_ServiceProxy) private serviceProxy: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_CAPTCHA_UI_URL) private captchaUIUrl: string,
) {
super()
}
@@ -19,7 +21,7 @@ export class ActionsController extends BaseHttpController {
await this.serviceProxy.callAuthServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('POST', 'auth/sign_in'),
this.endpointResolver.resolveEndpointOrMethodIdentifier('POST', 'auth/pkce_sign_in'),
request.body,
)
}
@@ -29,7 +31,7 @@ export class ActionsController extends BaseHttpController {
await this.serviceProxy.callAuthServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('GET', 'auth/params'),
this.endpointResolver.resolveEndpointOrMethodIdentifier('POST', 'auth/pkce_params'),
request.body,
)
}
@@ -83,4 +85,11 @@ export class ActionsController extends BaseHttpController {
request.body,
)
}
@httpGet('/meta')
async serverMetadata(): Promise<JsonResult> {
return this.json({
captchaUIUrl: this.captchaUIUrl,
})
}
}

View File

@@ -6,7 +6,6 @@ import {
controller,
httpDelete,
httpGet,
httpPatch,
httpPost,
httpPut,
results,
@@ -39,16 +38,6 @@ export class UsersController extends BaseHttpController {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/send-activation-code', request.body)
}
@httpPatch('/:userId', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async updateUser(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('PATCH', 'users/:userId', request.params.userId),
request.body,
)
}
@httpPut('/:userUuid/password', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async changePassword(request: Request, response: Response): Promise<void> {
this.logger.debug(
@@ -86,7 +75,7 @@ export class UsersController extends BaseHttpController {
await this.httpService.callAuthServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('GET', 'auth/params'),
this.endpointResolver.resolveEndpointOrMethodIdentifier('POST', 'auth/pkce_params'),
)
}
@@ -142,6 +131,20 @@ export class UsersController extends BaseHttpController {
)
}
@httpPut('/:userUuid/subscription-settings', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async putSubscriptionSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier(
'PUT',
'users/:userUuid/subscription-settings',
request.params.userUuid,
),
request.body,
)
}
@httpGet('/:userUuid/settings/:settingName', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async getSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(

View File

@@ -0,0 +1,23 @@
import { TimerInterface } from '@standardnotes/time'
import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
import { ContentSizesFixRequestedEvent, DomainEventService } from '@standardnotes/domain-events'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(private timer: TimerInterface) {}
createContentSizesFixRequestedEvent(dto: { userUuid: string }): ContentSizesFixRequestedEvent {
return {
type: 'CONTENT_SIZES_FIX_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Auth,
},
payload: dto,
}
}
}

View File

@@ -0,0 +1,5 @@
import { ContentSizesFixRequestedEvent } from '@standardnotes/domain-events'
export interface DomainEventFactoryInterface {
createContentSizesFixRequestedEvent(dto: { userUuid: string }): ContentSizesFixRequestedEvent
}

View File

@@ -1,15 +1,12 @@
import { inject, injectable } from 'inversify'
import * as IORedis from 'ioredis'
import { TYPES } from '../../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../../Service/Cache/CrossServiceTokenCacheInterface'
@injectable()
export class RedisCrossServiceTokenCache implements CrossServiceTokenCacheInterface {
private readonly PREFIX = 'cst'
private readonly USER_CST_PREFIX = 'user-cst'
constructor(@inject(TYPES.ApiGateway_Redis) private redisClient: IORedis.Redis) {}
constructor(private redisClient: IORedis.Redis) {}
async set(dto: {
key: string

View File

@@ -10,23 +10,44 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
private filesServerUrl: string,
) {}
async validateSession(
async validateSession(dto: {
headers: {
authorization: string
sharedVaultOwnerContext?: string
},
_retryAttempt?: number,
): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
}
cookies?: Map<string, string[]>
snjs?: string
application?: string
retryAttempt?: number
}): Promise<{
status: number
data: unknown
headers: {
contentType: string
}
}> {
const authService = this.serviceContainer.get(ServiceIdentifier.create(ServiceIdentifier.NAMES.Auth).getValue())
if (!authService) {
throw new Error('Auth service not found')
}
let stringOfCookies = ''
for (const cookieName of dto.cookies?.keys() ?? []) {
for (const cookieValue of dto.cookies?.get(cookieName) as string[]) {
stringOfCookies += `${cookieName}=${cookieValue}; `
}
}
const serviceResponse = (await authService.handleRequest(
{
body: {
authTokenFromHeaders: dto.headers.authorization,
sharedVaultOwnerContext: dto.headers.sharedVaultOwnerContext,
},
headers: {
authorization: headers.authorization,
'x-shared-vault-owner-context': headers.sharedVaultOwnerContext,
'x-snjs-version': dto.snjs,
'x-application-version': dto.application,
cookie: stringOfCookies.trim(),
},
} as never,
{} as never,

View File

@@ -28,20 +28,51 @@ export class HttpServiceProxy implements ServiceProxyInterface {
@inject(TYPES.ApiGateway_Timer) private timer: TimerInterface,
) {}
async validateSession(
async validateSession(dto: {
headers: {
authorization: string
sharedVaultOwnerContext?: string
},
retryAttempt?: number,
): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
}
requestMetadata: {
url: string
method: string
snjs?: string
application?: string
userAgent?: string
secChUa?: string
}
cookies?: Map<string, string[]>
retryAttempt?: number
}): Promise<{
status: number
data: unknown
headers: {
contentType: string
}
}> {
try {
let stringOfCookies = ''
for (const cookieName of dto.cookies?.keys() ?? []) {
for (const cookieValue of dto.cookies?.get(cookieName) as string[]) {
stringOfCookies += `${cookieName}=${cookieValue}; `
}
}
const authResponse = await this.httpClient.request({
method: 'POST',
headers: {
Authorization: headers.authorization,
Accept: 'application/json',
'x-shared-vault-owner-context': headers.sharedVaultOwnerContext,
Cookie: stringOfCookies.trim(),
'x-snjs-version': dto.requestMetadata.snjs,
'x-application-version': dto.requestMetadata.application,
'x-origin-user-agent': dto.requestMetadata.userAgent,
'x-origin-sec-ch-ua': dto.requestMetadata.secChUa,
'x-origin-url': dto.requestMetadata.url,
'x-origin-method': dto.requestMetadata.method,
},
data: {
authTokenFromHeaders: dto.headers.authorization,
sharedVaultOwnerContext: dto.headers.sharedVaultOwnerContext,
},
validateStatus: (status: number) => {
return status >= 200 && status < 500
@@ -58,13 +89,18 @@ export class HttpServiceProxy implements ServiceProxyInterface {
}
} catch (error) {
const requestDidNotMakeIt = this.requestTimedOutOrDidNotReachDestination(error as Record<string, unknown>)
const tooManyRetryAttempts = retryAttempt && retryAttempt > 2
const tooManyRetryAttempts = dto.retryAttempt && dto.retryAttempt > 2
if (!tooManyRetryAttempts && requestDidNotMakeIt) {
await this.timer.sleep(50)
const nextRetryAttempt = retryAttempt ? retryAttempt + 1 : 1
const nextRetryAttempt = dto.retryAttempt ? dto.retryAttempt + 1 : 1
return this.validateSession(headers, nextRetryAttempt)
return this.validateSession({
headers: dto.headers,
cookies: dto.cookies,
requestMetadata: dto.requestMetadata,
retryAttempt: nextRetryAttempt,
})
}
throw error
@@ -186,9 +222,18 @@ export class HttpServiceProxy implements ServiceProxyInterface {
headers[headerName] = request.headers[headerName] as string
}
headers['x-origin-url'] = request.url
headers['x-origin-method'] = request.method
headers['x-snjs-version'] = request.headers['x-snjs-version'] as string
headers['x-application-version'] = request.headers['x-application-version'] as string
headers['x-origin-user-agent'] = request.headers['user-agent'] as string
headers['x-origin-sec-ch-ua'] = request.headers['sec-ch-ua'] as string
delete headers.host
delete headers['content-length']
headers.cookie = request.headers.cookie as string
if ('authToken' in locals && locals.authToken) {
headers['X-Auth-Token'] = locals.authToken
}
@@ -340,13 +385,11 @@ export class HttpServiceProxy implements ServiceProxyInterface {
private applyResponseHeaders(serviceResponse: AxiosResponse, response: Response): void {
const returnedHeadersFromUnderlyingService = [
'access-control-allow-methods',
'access-control-allow-origin',
'access-control-expose-headers',
'authorization',
'content-type',
'x-ssjs-version',
'x-auth-version',
'authorization',
'set-cookie',
'access-control-expose-headers',
'x-captcha-required',
]
returnedHeadersFromUnderlyingService.map((headerName) => {

View File

@@ -49,13 +49,22 @@ export interface ServiceProxyInterface {
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void>
validateSession(
validateSession(dto: {
headers: {
authorization: string
sharedVaultOwnerContext?: string
},
retryAttempt?: number,
): Promise<{
}
requestMetadata: {
url: string
method: string
snjs?: string
application?: string
userAgent?: string
secChUa?: string
}
cookies?: Map<string, string[]>
retryAttempt?: number
}): Promise<{
status: number
data: unknown
headers: {

View File

@@ -7,8 +7,6 @@ export class EndpointResolver implements EndpointResolverInterface {
// Auth Middleware
['[POST]:sessions/validate', 'auth.sessions.validate'],
// Actions Controller
['[POST]:auth/sign_in', 'auth.signIn'],
['[GET]:auth/params', 'auth.params'],
['[POST]:auth/sign_out', 'auth.signOut'],
['[POST]:auth/recovery/codes', 'auth.generateRecoveryCodes'],
['[POST]:auth/recovery/login', 'auth.signInWithRecoveryCodes'],
@@ -48,6 +46,7 @@ export class EndpointResolver implements EndpointResolverInterface {
['[PUT]:users/:userUuid/settings', 'auth.users.updateSetting'],
['[GET]:users/:userUuid/settings/:settingName', 'auth.users.getSetting'],
['[DELETE]:users/:userUuid/settings/:settingName', 'auth.users.deleteSetting'],
['[PUT]:users/:userUuid/subscription-settings', 'auth.users.updateSubscriptionSetting'],
['[GET]:users/:userUuid/subscription-settings/:subscriptionSettingName', 'auth.users.getSubscriptionSetting'],
['[GET]:users/:userUuid/features', 'auth.users.getFeatures'],
['[GET]:users/:userUuid/subscription', 'auth.users.getSubscription'],

View File

@@ -2,7 +2,7 @@ import { AxiosInstance, AxiosError, AxiosResponse, Method } from 'axios'
import { Request, Response } from 'express'
import { Logger } from 'winston'
import { TimerInterface } from '@standardnotes/time'
import { IAuthClient, AuthorizationHeader, SessionValidationResponse } from '@standardnotes/grpc'
import { Cookie, IAuthClient, RequestValidationOptions, SessionValidationResponse } from '@standardnotes/grpc'
import * as grpc from '@grpc/grpc-js'
import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface'
@@ -30,23 +30,56 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
private gRPCSyncingServerServiceProxy: GRPCSyncingServerServiceProxy,
) {}
async validateSession(
async validateSession(dto: {
headers: {
authorization: string
sharedVaultOwnerContext?: string
},
retryAttempt?: number,
): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
}
requestMetadata: {
url: string
method: string
snjs?: string
application?: string
userAgent?: string
secChUa?: string
}
cookies?: Map<string, string[]>
retryAttempt?: number
}): Promise<{
status: number
data: unknown
headers: {
contentType: string
}
}> {
const promise = new Promise((resolve, reject) => {
try {
const request = new AuthorizationHeader()
request.setBearerToken(headers.authorization)
const request = new RequestValidationOptions()
request.setBearerToken(dto.headers.authorization)
const metadata = new grpc.Metadata()
metadata.set('x-shared-vault-owner-context', headers.sharedVaultOwnerContext ?? '')
for (const cookieName of dto.cookies?.keys() ?? []) {
for (const cookieValue of dto.cookies?.get(cookieName) as string[]) {
const cookie = new Cookie()
cookie.setName(cookieName)
cookie.setValue(cookieValue)
request.addCookie(cookie)
}
}
if (dto.headers.sharedVaultOwnerContext) {
request.setSharedVaultOwnerContext(dto.headers.sharedVaultOwnerContext)
}
this.logger.debug('[GRPCServiceProxy] Validating session via gRPC')
const metadata = new grpc.Metadata()
metadata.set('x-snjs-version', dto.requestMetadata.snjs as string)
metadata.set('x-application-version', dto.requestMetadata.application as string)
metadata.set('x-origin-user-agent', dto.requestMetadata.userAgent as string)
metadata.set('x-origin-sec-ch-ua', dto.requestMetadata.secChUa as string)
metadata.set('x-origin-url', dto.requestMetadata.url)
metadata.set('x-origin-method', dto.requestMetadata.method)
this.authClient.validate(
request,
metadata,
@@ -90,8 +123,8 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
try {
const result = await promise
if (retryAttempt) {
this.logger.debug(`Request to Auth Server succeeded after ${retryAttempt} retries`)
if (dto.retryAttempt) {
this.logger.info(`Request to Auth Server succeeded after ${dto.retryAttempt} retries`)
}
return result as { status: number; data: unknown; headers: { contentType: string } }
@@ -99,15 +132,20 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
const requestDidNotMakeIt =
'code' in (error as Record<string, unknown>) && (error as Record<string, unknown>).code === Status.UNAVAILABLE
const tooManyRetryAttempts = retryAttempt && retryAttempt > 2
const tooManyRetryAttempts = dto.retryAttempt && dto.retryAttempt > 2
if (!tooManyRetryAttempts && requestDidNotMakeIt) {
await this.timer.sleep(50)
const nextRetryAttempt = retryAttempt ? retryAttempt + 1 : 1
const nextRetryAttempt = dto.retryAttempt ? dto.retryAttempt + 1 : 1
this.logger.debug(`Retrying request to Auth Server for the ${nextRetryAttempt} time`)
this.logger.warn(`Retrying request to Auth Server for the ${nextRetryAttempt} time`)
return this.validateSession(headers, nextRetryAttempt)
return this.validateSession({
headers: dto.headers,
cookies: dto.cookies,
requestMetadata: dto.requestMetadata,
retryAttempt: nextRetryAttempt,
})
}
throw error
@@ -265,6 +303,8 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
delete headers.host
delete headers['content-length']
headers.cookie = request.headers.cookie as string
if ('authToken' in locals && locals.authToken) {
headers['X-Auth-Token'] = locals.authToken
}
@@ -435,13 +475,11 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
private applyResponseHeaders(serviceResponse: AxiosResponse, response: Response): void {
const returnedHeadersFromUnderlyingService = [
'access-control-allow-methods',
'access-control-allow-origin',
'access-control-expose-headers',
'authorization',
'content-type',
'x-ssjs-version',
'x-auth-version',
'authorization',
'set-cookie',
'access-control-expose-headers',
'x-captcha-required',
]
returnedHeadersFromUnderlyingService.map((headerName) => {

View File

@@ -1,12 +1,14 @@
import { Request, Response } from 'express'
import { ISyncingClient, SyncRequest, SyncResponse } from '@standardnotes/grpc'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { MapperInterface } from '@standardnotes/domain-core'
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 { SyncResponseHttpRepresentation } from '../../Mapping/Sync/Http/SyncResponseHttpRepresentation'
import { ResponseLocals } from '../../Controller/ResponseLocals'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
export class GRPCSyncingServerServiceProxy {
constructor(
@@ -14,6 +16,8 @@ export class GRPCSyncingServerServiceProxy {
private syncRequestGRPCMapper: MapperInterface<Record<string, unknown>, SyncRequest>,
private syncResponseGRPCMapper: MapperInterface<SyncResponse, SyncResponseHttpRepresentation>,
private logger: Logger,
private domainEventFactory: DomainEventFactoryInterface,
private domainEventPublisher?: DomainEventPublisherInterface,
) {}
async sync(
@@ -41,6 +45,7 @@ export class GRPCSyncingServerServiceProxy {
metadata.set('x-session-uuid', locals.session.uuid)
}
metadata.set('x-is-free-user', locals.isFreeUser ? 'true' : 'false')
metadata.set('x-has-content-limit', locals.hasContentLimit ? 'true' : 'false')
this.syncingClient.syncItems(syncRequest, metadata, (error, syncResponse) => {
if (error) {
@@ -59,6 +64,12 @@ export class GRPCSyncingServerServiceProxy {
})
}
if (error.code === Status.RESOURCE_EXHAUSTED && this.domainEventPublisher !== undefined) {
void this.domainEventPublisher.publish(
this.domainEventFactory.createContentSizesFixRequestedEvent({ userUuid: locals.user.uuid }),
)
}
return reject(error)
}

View File

@@ -29,6 +29,11 @@ CACHE_TYPE=redis
DISABLE_USER_REGISTRATION=false
COOKIE_DOMAIN=
COOKIE_SAME_SITE=
COOKIE_SECURE=
COOKIE_PARTITIONED=
ACCESS_TOKEN_AGE=5184000
REFRESH_TOKEN_AGE=31556926
@@ -49,6 +54,10 @@ VALET_TOKEN_TTL=
WEB_SOCKET_CONNECTION_TOKEN_SECRET=
# Human verfication
CAPTCHA_SERVER_URL=
CAPTCHA_UI_URL=
# (Optional) U2F Setup
U2F_RELYING_PARTY_ID=
U2F_RELYING_PARTY_NAME=

View File

@@ -3,6 +3,47 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.178.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.178.3...@standardnotes/auth-server@1.178.5) (2024-06-18)
### Bug Fixes
* bump versions on packages ([8575d20](https://github.com/standardnotes/server/commit/8575d20f7b79f5220da7cced0041ae12b72e1e49))
# [1.178.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.20...@standardnotes/auth-server@1.178.0) (2024-01-19)
### Features
* **auth:** add script for fixing subscriptions with missing id state ([#1030](https://github.com/standardnotes/server/issues/1030)) ([86b0508](https://github.com/standardnotes/server/commit/86b050865f8090ed33d5ce05528ff0e1e23657ef))
## [1.177.20](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.19...@standardnotes/auth-server@1.177.20) (2024-01-18)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.177.19](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.18...@standardnotes/auth-server@1.177.19) (2024-01-17)
### Bug Fixes
* **auth:** add server daily email backup permission for all versions of core user role ([#1028](https://github.com/standardnotes/server/issues/1028)) ([460fdf9](https://github.com/standardnotes/server/commit/460fdf9eafe2db629637ba481f2b135ed21560b9))
## [1.177.18](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.17...@standardnotes/auth-server@1.177.18) (2024-01-15)
### Bug Fixes
* **auth:** add more logs to syncing subscription ([c7217a9](https://github.com/standardnotes/server/commit/c7217a92ba89d8b5f4963a832aa7561dd146ca0d))
* **auth:** add renewal for shared offline subscriptions ([045358d](https://github.com/standardnotes/server/commit/045358ddbf300996a23bba8d6945b1d7b5f6e862))
## [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

View File

@@ -10,6 +10,12 @@ RUN corepack enable
COPY ./ /workspace
WORKDIR /workspace
RUN yarn install --immutable
RUN yarn build
WORKDIR /workspace/packages/auth
ENTRYPOINT [ "/workspace/packages/auth/docker/entrypoint.sh" ]

View File

@@ -0,0 +1,74 @@
import 'reflect-metadata'
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { UserSubscriptionRepositoryInterface } from '../src/Domain/Subscription/UserSubscriptionRepositoryInterface'
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
import { Uuid } from '@standardnotes/domain-core'
const fixSubscriptions = async (
userRepository: UserRepositoryInterface,
userSubscriptionRepository: UserSubscriptionRepositoryInterface,
domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface,
): Promise<void> => {
const subscriptions = await userSubscriptionRepository.findBySubscriptionId(0)
for (const subscription of subscriptions) {
const userUuidOrError = Uuid.create(subscription.userUuid)
if (userUuidOrError.isFailed()) {
continue
}
const userUuid = userUuidOrError.getValue()
const user = await userRepository.findOneByUuid(userUuid)
if (!user) {
continue
}
await domainEventPublisher.publish(
domainEventFactory.createSubscriptionStateRequestedEvent({
userEmail: user.email,
}),
)
}
}
const container = new ContainerConfigLoader('worker')
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Auth_Logger)
logger.info('Starting to fix subscriptions with missing subscriptionId ...')
const userRepository = container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository)
const userSubscriptionRepository = container.get<UserSubscriptionRepositoryInterface>(
TYPES.Auth_UserSubscriptionRepository,
)
const domainEventFactory = container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory)
const domainEventPublisher = container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher)
Promise.resolve(
fixSubscriptions(userRepository, userSubscriptionRepository, domainEventFactory, domainEventPublisher),
)
.then(() => {
logger.info('Finished fixing subscriptions with missing subscriptionId.')
process.exit(0)
})
.catch((error) => {
logger.error('Failed to fix subscriptions with missing subscriptionId.', {
error: error.message,
stack: error.stack,
})
process.exit(1)
})
})

View File

@@ -20,6 +20,7 @@ import '../src/Infra/InversifyExpressUtils/AnnotatedHealthCheckController'
import '../src/Infra/InversifyExpressUtils/AnnotatedFeaturesController'
import * as cors from 'cors'
import * as cookieParser from 'cookie-parser'
import * as grpc from '@grpc/grpc-js'
import { urlencoded, json, Request, Response, NextFunction } from 'express'
import * as winston from 'winston'
@@ -53,6 +54,7 @@ void container.load().then((container) => {
})
app.use(json())
app.use(urlencoded({ extended: true }))
app.use(cookieParser())
app.use(cors())
})

View File

@@ -9,28 +9,23 @@ import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
import { MuteFailedBackupsEmailsOption } from '@standardnotes/settings'
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
import { PermissionName } from '@standardnotes/features'
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
import { GetUserKeyParams } from '../src/Domain/UseCase/GetUserKeyParams/GetUserKeyParams'
import { Email, SettingName } from '@standardnotes/domain-core'
import { Email } from '@standardnotes/domain-core'
const inputArgs = process.argv.slice(2)
const backupEmail = inputArgs[0]
const requestBackups = async (
userRepository: UserRepositoryInterface,
settingRepository: SettingRepositoryInterface,
roleService: RoleServiceInterface,
domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface,
getUserKeyParamsUseCase: GetUserKeyParams,
): Promise<void> => {
const permissionName = PermissionName.DailyEmailBackup
const muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails
const muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
const emailOrError = Email.create(backupEmail)
if (emailOrError.isFailed()) {
@@ -48,24 +43,13 @@ const requestBackups = async (
throw new Error(`User ${backupEmail} is not permitted for email backups`)
}
let userHasEmailsMuted = false
const emailsMutedSetting = await settingRepository.findOneByNameAndUserUuid(muteEmailsSettingName, user.uuid)
if (emailsMutedSetting !== null && emailsMutedSetting.props.value !== null) {
userHasEmailsMuted = emailsMutedSetting.props.value === muteEmailsSettingValue
}
const keyParamsResponse = await getUserKeyParamsUseCase.execute({
userUuid: user.uuid,
authenticated: false,
})
await domainEventPublisher.publish(
domainEventFactory.createEmailBackupRequestedEvent(
user.uuid,
emailsMutedSetting?.id.toString() as string,
userHasEmailsMuted,
keyParamsResponse.keyParams,
),
domainEventFactory.createEmailBackupRequestedEvent(user.uuid, keyParamsResponse.keyParams),
)
return
@@ -82,7 +66,6 @@ void container.load().then((container) => {
logger.info(`Starting email backup requesting for ${backupEmail} ...`)
const settingRepository: SettingRepositoryInterface = container.get(TYPES.Auth_SettingRepository)
const userRepository: UserRepositoryInterface = container.get(TYPES.Auth_UserRepository)
const roleService: RoleServiceInterface = container.get(TYPES.Auth_RoleService)
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.Auth_DomainEventFactory)
@@ -90,14 +73,7 @@ void container.load().then((container) => {
const getUserKeyParamsUseCase: GetUserKeyParams = container.get(TYPES.Auth_GetUserKeyParams)
Promise.resolve(
requestBackups(
userRepository,
settingRepository,
roleService,
domainEventFactory,
domainEventPublisher,
getUserKeyParamsUseCase,
),
requestBackups(userRepository, roleService, domainEventFactory, domainEventPublisher, getUserKeyParamsUseCase),
)
.then(() => {
logger.info(`Email backup requesting complete for ${backupEmail}`)

View File

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

View File

@@ -42,6 +42,10 @@ case "$COMMAND" in
exec node docker/entrypoint-fix-roles.js
;;
'fix-subscriptions' )
exec node docker/entrypoint-fix-subscriptions.js
;;
'delete-accounts' )
FILE_NAME=$1 && shift 1
MODE=$1 && shift 1

View File

@@ -0,0 +1,22 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class EnableEmailBackupsForAll1705493201352 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Core User v1 Permissions
await queryRunner.query(
'INSERT INTO `role_permissions` (role_uuid, permission_uuid) VALUES \
("bde42e26-628c-44e6-9d76-21b08954b0bf", "eb0575a2-6e26-49e3-9501-f2e75d7dbda3") \
',
)
// Core User v2 Permissions
await queryRunner.query(
'INSERT INTO `role_permissions` (role_uuid, permission_uuid) VALUES \
("23bf88ca-bee1-4a4c-adf0-b7a48749eea7", "eb0575a2-6e26-49e3-9501-f2e75d7dbda3") \
',
)
}
public async down(): Promise<void> {
return
}
}

View File

@@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class UserRolesContentLimit1707759514236 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'INSERT INTO `permissions` (uuid, name) VALUES ("f8b4ced2-6a59-49f8-9ade-416a5f5ffc61", "server:content-limit")',
)
await queryRunner.query(
'INSERT INTO `roles` (uuid, name, version) VALUES ("ab2e15c9-9252-43f3-829c-6f0af3315791", "CORE_USER", 4)',
)
await queryRunner.query(
'INSERT INTO `role_permissions` (permission_uuid, role_uuid) VALUES \
("b04a7670-934e-4ab1-b8a3-0f27ff159511", "ab2e15c9-9252-43f3-829c-6f0af3315791"), \
("eb0575a2-6e26-49e3-9501-f2e75d7dbda3", "ab2e15c9-9252-43f3-829c-6f0af3315791"), \
("f8b4ced2-6a59-49f8-9ade-416a5f5ffc61", "ab2e15c9-9252-43f3-829c-6f0af3315791") \
',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DELETE FROM `role_permissions` WHERE role_uuid="ab2e15c9-9252-43f3-829c-6f0af3315791"')
await queryRunner.query('DELETE FROM `permissions` WHERE uuid="f8b4ced2-6a59-49f8-9ade-416a5f5ffc61"')
await queryRunner.query('DELETE FROM `roles` WHERE uuid="ab2e15c9-9252-43f3-829c-6f0af3315791"')
}
}

View File

@@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSessionVersion1707813542369 implements MigrationInterface {
name = 'AddSessionVersion1707813542369'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `sessions` ADD `version` smallint NULL DEFAULT 1')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `sessions` DROP COLUMN `version`')
}
}

View File

@@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSessionPrivateIdentifier1709133001993 implements MigrationInterface {
name = 'AddSessionPrivateIdentifier1709133001993'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"ALTER TABLE `sessions` ADD `private_identifier` varchar(36) NULL COMMENT 'Used to identify a session without exposing the UUID in client-side cookies.'",
)
await queryRunner.query('CREATE INDEX `index_sessions_on_private_identifier` ON `sessions` (`private_identifier`)')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_sessions_on_private_identifier` ON `sessions`')
await queryRunner.query('ALTER TABLE `sessions` DROP COLUMN `private_identifier`')
}
}

View File

@@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddRevokedSessionPrivateIdentifier1709206805226 implements MigrationInterface {
name = 'AddRevokedSessionPrivateIdentifier1709206805226'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"ALTER TABLE `revoked_sessions` ADD `private_identifier` varchar(36) NULL COMMENT 'Used to identify a session without exposing the UUID in client-side cookies.'",
)
await queryRunner.query(
'CREATE INDEX `index_revoked_sessions_on_private_identifier` ON `revoked_sessions` (`private_identifier`)',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_revoked_sessions_on_private_identifier` ON `revoked_sessions`')
await queryRunner.query('ALTER TABLE `revoked_sessions` DROP COLUMN `private_identifier`')
}
}

View File

@@ -0,0 +1,15 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddApplicationAndSnjsToSessions1710236132439 implements MigrationInterface {
name = 'AddApplicationAndSnjsToSessions1710236132439'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `sessions` ADD `application` varchar(255) NULL')
await queryRunner.query('ALTER TABLE `sessions` ADD `snjs` varchar(255) NULL')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `sessions` DROP COLUMN `snjs`')
await queryRunner.query('ALTER TABLE `sessions` DROP COLUMN `application`')
}
}

View File

@@ -0,0 +1,22 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class EnableEmailBackupsForAll1705493490376 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Core User v1 Permissions
await queryRunner.query(
'INSERT INTO `role_permissions` (role_uuid, permission_uuid) VALUES \
("bde42e26-628c-44e6-9d76-21b08954b0bf", "eb0575a2-6e26-49e3-9501-f2e75d7dbda3") \
',
)
// Core User v2 Permissions
await queryRunner.query(
'INSERT INTO `role_permissions` (role_uuid, permission_uuid) VALUES \
("23bf88ca-bee1-4a4c-adf0-b7a48749eea7", "eb0575a2-6e26-49e3-9501-f2e75d7dbda3") \
',
)
}
public async down(): Promise<void> {
return
}
}

View File

@@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class UserRolesContentLimit1707759514236 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'INSERT INTO `permissions` (uuid, name) VALUES ("f8b4ced2-6a59-49f8-9ade-416a5f5ffc61", "server:content-limit")',
)
await queryRunner.query(
'INSERT INTO `roles` (uuid, name, version) VALUES ("ab2e15c9-9252-43f3-829c-6f0af3315791", "CORE_USER", 4)',
)
await queryRunner.query(
'INSERT INTO `role_permissions` (permission_uuid, role_uuid) VALUES \
("b04a7670-934e-4ab1-b8a3-0f27ff159511", "ab2e15c9-9252-43f3-829c-6f0af3315791"), \
("eb0575a2-6e26-49e3-9501-f2e75d7dbda3", "ab2e15c9-9252-43f3-829c-6f0af3315791"), \
("f8b4ced2-6a59-49f8-9ade-416a5f5ffc61", "ab2e15c9-9252-43f3-829c-6f0af3315791") \
',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DELETE FROM `role_permissions` WHERE role_uuid="ab2e15c9-9252-43f3-829c-6f0af3315791"')
await queryRunner.query('DELETE FROM `permissions` WHERE uuid="f8b4ced2-6a59-49f8-9ade-416a5f5ffc61"')
await queryRunner.query('DELETE FROM `roles` WHERE uuid="ab2e15c9-9252-43f3-829c-6f0af3315791"')
}
}

View File

@@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSessionVersion1707813542369 implements MigrationInterface {
name = 'AddSessionVersion1707813542369'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `sessions` ADD `version` smallint NULL DEFAULT 1')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `sessions` DROP COLUMN `version`')
}
}

View File

@@ -0,0 +1,37 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSessionPrivateIdentifier1709133169237 implements MigrationInterface {
name = 'AddSessionPrivateIdentifier1709133169237'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "index_sessions_on_updated_at"')
await queryRunner.query('DROP INDEX "index_sessions_on_user_uuid"')
await queryRunner.query(
'CREATE TABLE "temporary_sessions" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(255), "hashed_access_token" varchar(255) NOT NULL, "hashed_refresh_token" varchar(255) NOT NULL, "access_expiration" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "refresh_expiration" datetime NOT NULL, "api_version" varchar(255), "user_agent" text, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL, "readonly_access" tinyint NOT NULL DEFAULT (0), "version" smallint DEFAULT (1), "private_identifier" varchar(36))',
)
await queryRunner.query(
'INSERT INTO "temporary_sessions"("uuid", "user_uuid", "hashed_access_token", "hashed_refresh_token", "access_expiration", "refresh_expiration", "api_version", "user_agent", "created_at", "updated_at", "readonly_access", "version") SELECT "uuid", "user_uuid", "hashed_access_token", "hashed_refresh_token", "access_expiration", "refresh_expiration", "api_version", "user_agent", "created_at", "updated_at", "readonly_access", "version" FROM "sessions"',
)
await queryRunner.query('DROP TABLE "sessions"')
await queryRunner.query('ALTER TABLE "temporary_sessions" RENAME TO "sessions"')
await queryRunner.query('CREATE INDEX "index_sessions_on_updated_at" ON "sessions" ("updated_at") ')
await queryRunner.query('CREATE INDEX "index_sessions_on_user_uuid" ON "sessions" ("user_uuid") ')
await queryRunner.query('CREATE INDEX "index_sessions_on_private_identifier" ON "sessions" ("private_identifier") ')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "index_sessions_on_private_identifier"')
await queryRunner.query('DROP INDEX "index_sessions_on_user_uuid"')
await queryRunner.query('DROP INDEX "index_sessions_on_updated_at"')
await queryRunner.query('ALTER TABLE "sessions" RENAME TO "temporary_sessions"')
await queryRunner.query(
'CREATE TABLE "sessions" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(255), "hashed_access_token" varchar(255) NOT NULL, "hashed_refresh_token" varchar(255) NOT NULL, "access_expiration" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "refresh_expiration" datetime NOT NULL, "api_version" varchar(255), "user_agent" text, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL, "readonly_access" tinyint NOT NULL DEFAULT (0), "version" smallint DEFAULT (1))',
)
await queryRunner.query(
'INSERT INTO "sessions"("uuid", "user_uuid", "hashed_access_token", "hashed_refresh_token", "access_expiration", "refresh_expiration", "api_version", "user_agent", "created_at", "updated_at", "readonly_access", "version") SELECT "uuid", "user_uuid", "hashed_access_token", "hashed_refresh_token", "access_expiration", "refresh_expiration", "api_version", "user_agent", "created_at", "updated_at", "readonly_access", "version" FROM "temporary_sessions"',
)
await queryRunner.query('DROP TABLE "temporary_sessions"')
await queryRunner.query('CREATE INDEX "index_sessions_on_user_uuid" ON "sessions" ("user_uuid") ')
await queryRunner.query('CREATE INDEX "index_sessions_on_updated_at" ON "sessions" ("updated_at") ')
}
}

View File

@@ -0,0 +1,34 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddRevokedSessionPrivateIdentifier1709208455658 implements MigrationInterface {
name = 'AddRevokedSessionPrivateIdentifier1709208455658'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "index_revoked_sessions_on_user_uuid"')
await queryRunner.query(
'CREATE TABLE "temporary_revoked_sessions" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "received" tinyint NOT NULL DEFAULT (0), "created_at" datetime NOT NULL, "received_at" datetime, "user_agent" text, "api_version" varchar(255), "private_identifier" varchar(36), CONSTRAINT "FK_edaf18faca67e682be39b5ecae5" FOREIGN KEY ("user_uuid") REFERENCES "users" ("uuid") ON DELETE CASCADE ON UPDATE NO ACTION)',
)
await queryRunner.query(
'INSERT INTO "temporary_revoked_sessions"("uuid", "user_uuid", "received", "created_at", "received_at", "user_agent", "api_version") SELECT "uuid", "user_uuid", "received", "created_at", "received_at", "user_agent", "api_version" FROM "revoked_sessions"',
)
await queryRunner.query('DROP TABLE "revoked_sessions"')
await queryRunner.query('ALTER TABLE "temporary_revoked_sessions" RENAME TO "revoked_sessions"')
await queryRunner.query('CREATE INDEX "index_revoked_sessions_on_user_uuid" ON "revoked_sessions" ("user_uuid") ')
await queryRunner.query(
'CREATE INDEX "index_revoked_sessions_on_private_identifier" ON "revoked_sessions" ("private_identifier") ',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "index_revoked_sessions_on_user_uuid"')
await queryRunner.query('ALTER TABLE "revoked_sessions" RENAME TO "temporary_revoked_sessions"')
await queryRunner.query(
'CREATE TABLE "revoked_sessions" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "received" tinyint NOT NULL DEFAULT (0), "created_at" datetime NOT NULL, "received_at" datetime, "user_agent" text, "api_version" varchar(255), CONSTRAINT "FK_edaf18faca67e682be39b5ecae5" FOREIGN KEY ("user_uuid") REFERENCES "users" ("uuid") ON DELETE CASCADE ON UPDATE NO ACTION)',
)
await queryRunner.query(
'INSERT INTO "revoked_sessions"("uuid", "user_uuid", "received", "created_at", "received_at", "user_agent", "api_version") SELECT "uuid", "user_uuid", "received", "created_at", "received_at", "user_agent", "api_version" FROM "temporary_revoked_sessions"',
)
await queryRunner.query('DROP TABLE "temporary_revoked_sessions"')
await queryRunner.query('CREATE INDEX "index_revoked_sessions_on_user_uuid" ON "revoked_sessions" ("user_uuid") ')
}
}

View File

@@ -0,0 +1,15 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddApplicationAndSnjsToSessions1710236132439 implements MigrationInterface {
name = 'AddApplicationAndSnjsToSessions1710236132439'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `sessions` ADD `application` varchar(255) NULL')
await queryRunner.query('ALTER TABLE `sessions` ADD `snjs` varchar(255) NULL')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `sessions` DROP COLUMN `snjs`')
await queryRunner.query('ALTER TABLE `sessions` DROP COLUMN `application`')
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.177.15",
"version": "1.178.5",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -24,8 +24,7 @@
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --fix --ext .ts",
"pretest": "yarn lint && yarn build",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=2",
"start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js",
"cleanup": "yarn node dist/bin/cleanup.js",
@@ -60,7 +59,10 @@
"@standardnotes/sncrypto-common": "^1.13.4",
"@standardnotes/sncrypto-node": "workspace:*",
"@standardnotes/time": "workspace:*",
"agentkeepalive": "^4.5.0",
"axios": "^1.6.7",
"bcryptjs": "2.4.3",
"cookie-parser": "^1.4.6",
"cors": "2.8.5",
"dayjs": "^1.11.6",
"dotenv": "^16.0.1",
@@ -68,7 +70,7 @@
"inversify": "^6.0.1",
"inversify-express-utils": "^6.4.3",
"ioredis": "^5.2.4",
"mysql2": "^3.0.1",
"mysql2": "^3.9.7",
"otplib": "12.0.1",
"prettyjson": "^1.2.5",
"reflect-metadata": "^0.2.1",
@@ -80,6 +82,7 @@
},
"devDependencies": {
"@types/bcryptjs": "^2.4.2",
"@types/cookie-parser": "^1",
"@types/cors": "^2.8.9",
"@types/express": "^4.17.14",
"@types/ioredis": "^5.0.0",

View File

@@ -1,6 +1,8 @@
import * as winston from 'winston'
import * as AgentKeepAlive from 'agentkeepalive'
import Redis from 'ioredis'
import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
import axios, { AxiosInstance } from 'axios'
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
import { S3Client } from '@aws-sdk/client-s3'
import { Container } from 'inversify'
@@ -36,13 +38,11 @@ import { AuthResponseFactoryResolver } from '../Domain/Auth/AuthResponseFactoryR
import { ClearLoginAttempts } from '../Domain/UseCase/ClearLoginAttempts'
import { IncreaseLoginAttempts } from '../Domain/UseCase/IncreaseLoginAttempts'
import { GetUserKeyParams } from '../Domain/UseCase/GetUserKeyParams/GetUserKeyParams'
import { UpdateUser } from '../Domain/UseCase/UpdateUser'
import { RedisEphemeralSessionRepository } from '../Infra/Redis/RedisEphemeralSessionRepository'
import { GetActiveSessionsForUser } from '../Domain/UseCase/GetActiveSessionsForUser'
import { DeleteOtherSessionsForUser } from '../Domain/UseCase/DeleteOtherSessionsForUser'
import { DeleteSessionForUser } from '../Domain/UseCase/DeleteSessionForUser'
import { Register } from '../Domain/UseCase/Register'
import { LockRepository } from '../Infra/Redis/LockRepository'
import { TypeORMRevokedSessionRepository } from '../Infra/TypeORM/TypeORMRevokedSessionRepository'
import { AuthenticationMethodResolver } from '../Domain/Auth/AuthenticationMethodResolver'
import { RevokedSession } from '../Domain/Session/RevokedSession'
@@ -285,6 +285,20 @@ import { RenewSharedSubscriptions } from '../Domain/UseCase/RenewSharedSubscript
import { FixStorageQuotaForUser } from '../Domain/UseCase/FixStorageQuotaForUser/FixStorageQuotaForUser'
import { FileQuotaRecalculatedEventHandler } from '../Domain/Handler/FileQuotaRecalculatedEventHandler'
import { SessionServiceInterface } from '../Domain/Session/SessionServiceInterface'
import { SubscriptionStateFetchedEventHandler } from '../Domain/Handler/SubscriptionStateFetchedEventHandler'
import { CaptchaServerInterface } from '../Domain/HumanVerification/CaptchaServerInterface'
import { VerifyHumanInteraction } from '../Domain/UseCase/VerifyHumanInteraction/VerifyHumanInteraction'
import { HttpCaptchaServer } from '../Infra/Http/HumanVerification/HttpCaptchaServer'
import { CookieFactoryInterface } from '../Domain/Auth/Cookies/CookieFactoryInterface'
import { CookieFactory } from '../Domain/Auth/Cookies/CookieFactory'
import { RedisLockRepository } from '../Infra/Redis/RedisLockRepository'
import { DeleteSessionByToken } from '../Domain/UseCase/DeleteSessionByToken/DeleteSessionByToken'
import { GetSessionFromToken } from '../Domain/UseCase/GetSessionFromToken/GetSessionFromToken'
import { CooldownSessionTokens } from '../Domain/UseCase/CooldownSessionTokens/CooldownSessionTokens'
import { SessionTokensCooldownRepositoryInterface } from '../Domain/Session/SessionTokensCooldownRepositoryInterface'
import { RedisSessionTokensCooldownRepository } from '../Infra/Redis/RedisSessionTokensCooldownRepository'
import { InMemorySessionTokensCooldownRepository } from '../Infra/InMemory/InMemorySessionTokensCooldownRepository'
import { GetCooldownSessionTokens } from '../Domain/UseCase/GetCooldownSessionTokens/GetCooldownSessionTokens'
export class ContainerConfigLoader {
constructor(private mode: 'server' | 'worker' = 'server') {}
@@ -329,6 +343,8 @@ export class ContainerConfigLoader {
const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted'
const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
const captchaServerUrl = env.get('CAPTCHA_SERVER_URL', true)
const captchaUIUrl = env.get('CAPTCHA_UI_URL', true)
container
.bind<boolean>(TYPES.Auth_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING)
@@ -596,9 +612,17 @@ export class ContainerConfigLoader {
container
.bind(TYPES.Auth_MAX_LOGIN_ATTEMPTS)
.toConstantValue(env.get('MAX_LOGIN_ATTEMPTS', true) ? +env.get('MAX_LOGIN_ATTEMPTS', true) : 6)
container
.bind(TYPES.Auth_MAX_CAPTCHA_LOGIN_ATTEMPTS)
.toConstantValue(env.get('MAX_CAPTCHA_LOGIN_ATTEMPTS', true) ? +env.get('MAX_CAPTCHA_LOGIN_ATTEMPTS', true) : 6)
container
.bind(TYPES.Auth_FAILED_LOGIN_LOCKOUT)
.toConstantValue(env.get('FAILED_LOGIN_LOCKOUT', true) ? +env.get('FAILED_LOGIN_LOCKOUT', true) : 3600)
container
.bind(TYPES.Auth_FAILED_LOGIN_CAPTCHA_LOCKOUT)
.toConstantValue(
env.get('FAILED_LOGIN_CAPTCHA_LOCKOUT', true) ? +env.get('FAILED_LOGIN_CAPTCHA_LOCKOUT', true) : 86400,
)
container.bind(TYPES.Auth_PSEUDO_KEY_PARAMS_KEY).toConstantValue(env.get('PSEUDO_KEY_PARAMS_KEY'))
container
.bind(TYPES.Auth_EPHEMERAL_SESSION_AGE)
@@ -632,6 +656,10 @@ export class ContainerConfigLoader {
container
.bind(TYPES.Auth_READONLY_USERS)
.toConstantValue(env.get('READONLY_USERS', true) ? env.get('READONLY_USERS', true).split(',') : [])
container.bind(TYPES.Auth_CAPTCHA_SERVER_URL).toConstantValue(captchaServerUrl)
container.bind(TYPES.Auth_CAPTCHA_UI_URL).toConstantValue(captchaUIUrl)
container.bind<boolean>(TYPES.Auth_HUMAN_VERIFICATION_ENABLED).toConstantValue(!!captchaServerUrl && !!captchaUIUrl)
container.bind<boolean>(TYPES.Auth_FORCE_LEGACY_SESSIONS).toConstantValue(env.get('E2E_TESTING', true) === 'true')
if (isConfiguredForInMemoryCache) {
container
@@ -651,6 +679,7 @@ export class ContainerConfigLoader {
container.get(TYPES.Auth_Timer),
container.get(TYPES.Auth_MAX_LOGIN_ATTEMPTS),
container.get(TYPES.Auth_FAILED_LOGIN_LOCKOUT),
container.get(TYPES.Auth_FAILED_LOGIN_CAPTCHA_LOCKOUT),
),
)
container
@@ -678,9 +707,21 @@ export class ContainerConfigLoader {
container.get(TYPES.Auth_Timer),
),
)
container
.bind<SessionTokensCooldownRepositoryInterface>(TYPES.Auth_SessionTokensCooldownRepository)
.toConstantValue(new InMemorySessionTokensCooldownRepository())
} else {
container.bind<PKCERepositoryInterface>(TYPES.Auth_PKCERepository).to(RedisPKCERepository)
container.bind<LockRepositoryInterface>(TYPES.Auth_LockRepository).to(LockRepository)
container
.bind<LockRepositoryInterface>(TYPES.Auth_LockRepository)
.toConstantValue(
new RedisLockRepository(
container.get<Redis>(TYPES.Auth_Redis),
container.get<number>(TYPES.Auth_MAX_LOGIN_ATTEMPTS),
container.get<number>(TYPES.Auth_FAILED_LOGIN_LOCKOUT),
container.get<number>(TYPES.Auth_FAILED_LOGIN_CAPTCHA_LOCKOUT),
),
)
container
.bind<EphemeralSessionRepositoryInterface>(TYPES.Auth_EphemeralSessionRepository)
.to(RedisEphemeralSessionRepository)
@@ -690,6 +731,9 @@ export class ContainerConfigLoader {
container
.bind<SubscriptionTokenRepositoryInterface>(TYPES.Auth_SubscriptionTokenRepository)
.to(RedisSubscriptionTokenRepository)
container
.bind<SessionTokensCooldownRepositoryInterface>(TYPES.Auth_SessionTokensCooldownRepository)
.toConstantValue(new RedisSessionTokensCooldownRepository(container.get<Redis>(TYPES.Auth_Redis)))
}
container
@@ -739,6 +783,41 @@ export class ContainerConfigLoader {
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
container.get<string[]>(TYPES.Auth_READONLY_USERS),
container.get<GetSetting>(TYPES.Auth_GetSetting),
container.get<boolean>(TYPES.Auth_FORCE_LEGACY_SESSIONS),
),
)
container
.bind<GetCooldownSessionTokens>(TYPES.Auth_GetCooldownSessionTokens)
.toConstantValue(
new GetCooldownSessionTokens(
container.get<SessionTokensCooldownRepositoryInterface>(TYPES.Auth_SessionTokensCooldownRepository),
),
)
container
.bind<GetSessionFromToken>(TYPES.Auth_GetSessionFromToken)
.toConstantValue(
new GetSessionFromToken(
container.get<SessionRepositoryInterface>(TYPES.Auth_SessionRepository),
container.get<EphemeralSessionRepositoryInterface>(TYPES.Auth_EphemeralSessionRepository),
container.get<GetCooldownSessionTokens>(TYPES.Auth_GetCooldownSessionTokens),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container
.bind<DeleteSessionByToken>(TYPES.Auth_DeleteSessionByToken)
.toConstantValue(
new DeleteSessionByToken(
container.get<GetSessionFromToken>(TYPES.Auth_GetSessionFromToken),
container.get<SessionRepositoryInterface>(TYPES.Auth_SessionRepository),
container.get<EphemeralSessionRepositoryInterface>(TYPES.Auth_EphemeralSessionRepository),
),
)
container
.bind<CooldownSessionTokens>(TYPES.Auth_CooldownSessionTokens)
.toConstantValue(
new CooldownSessionTokens(
env.get('COOLDOWN_SESSION_TOKENS_TTL', true) ? +env.get('COOLDOWN_SESSION_TOKENS_TTL', true) : 120,
container.get<SessionTokensCooldownRepositoryInterface>(TYPES.Auth_SessionTokensCooldownRepository),
),
)
container.bind<AuthResponseFactory20161215>(TYPES.Auth_AuthResponseFactory20161215).to(AuthResponseFactory20161215)
@@ -779,7 +858,16 @@ export class ContainerConfigLoader {
.toConstantValue(new TokenEncoder<ValetTokenData>(container.get(TYPES.Auth_VALET_TOKEN_SECRET)))
container
.bind<AuthenticationMethodResolver>(TYPES.Auth_AuthenticationMethodResolver)
.to(AuthenticationMethodResolver)
.toConstantValue(
new AuthenticationMethodResolver(
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
container.get<SessionServiceInterface>(TYPES.Auth_SessionService),
container.get<TokenDecoderInterface<SessionTokenData>>(TYPES.Auth_SessionTokenDecoder),
container.get<TokenDecoderInterface<SessionTokenData>>(TYPES.Auth_FallbackSessionTokenDecoder),
container.get<GetSessionFromToken>(TYPES.Auth_GetSessionFromToken),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container.bind<DomainEventFactory>(TYPES.Auth_DomainEventFactory).to(DomainEventFactory)
container
.bind<SettingsAssociationServiceInterface>(TYPES.Auth_SettingsAssociationService)
@@ -818,6 +906,43 @@ export class ContainerConfigLoader {
.bind<SelectorInterface<boolean>>(TYPES.Auth_BooleanSelector)
.toConstantValue(new DeterministicSelector<boolean>())
const httpAgentKeepAliveTimeout = env.get('HTTP_AGENT_KEEP_ALIVE_TIMEOUT', true)
? +env.get('HTTP_AGENT_KEEP_ALIVE_TIMEOUT', true)
: 4_000
container.bind<AxiosInstance>(TYPES.Auth_HTTPClient).toConstantValue(
axios.create({
httpAgent: new AgentKeepAlive({
keepAlive: true,
timeout: 2 * httpAgentKeepAliveTimeout,
freeSocketTimeout: httpAgentKeepAliveTimeout,
}),
}),
)
container
.bind<CaptchaServerInterface>(TYPES.Auth_CaptchaServer)
.toConstantValue(
new HttpCaptchaServer(
container.get(TYPES.Auth_Logger),
container.get(TYPES.Auth_HTTPClient),
container.get(TYPES.Auth_CAPTCHA_SERVER_URL),
),
)
container
.bind<CookieFactoryInterface>(TYPES.Auth_CookieFactory)
.toConstantValue(
new CookieFactory(
['None', 'Lax', 'Strict'].includes(env.get('COOKIE_SAME_SITE', true))
? (env.get('COOKIE_SAME_SITE', true) as 'None' | 'Lax' | 'Strict')
: 'None',
env.get('COOKIE_DOMAIN', true) ?? 'standardnotes.com',
env.get('COOKIE_SECURE', true) ? env.get('COOKIE_SECURE', true) === 'true' : true,
env.get('COOKIE_PARTITIONED', true) ? env.get('COOKIE_PARTITIONED', true) === 'true' : true,
),
)
// Middleware
container.bind<SessionMiddleware>(TYPES.Auth_SessionMiddleware).to(SessionMiddleware)
container.bind<LockMiddleware>(TYPES.Auth_LockMiddleware).to(LockMiddleware)
@@ -952,6 +1077,7 @@ export class ContainerConfigLoader {
new SetSubscriptionSettingValue(
container.get<SubscriptionSettingRepositoryInterface>(TYPES.Auth_SubscriptionSettingRepository),
container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting),
container.get<SettingsAssociationServiceInterface>(TYPES.Auth_SettingsAssociationService),
container.get<TimerInterface>(TYPES.Auth_Timer),
),
)
@@ -996,10 +1122,36 @@ export class ContainerConfigLoader {
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
container.get<TimerInterface>(TYPES.Auth_Timer),
container.get<GetSetting>(TYPES.Auth_GetSetting),
container.get<CooldownSessionTokens>(TYPES.Auth_CooldownSessionTokens),
container.get<GetSessionFromToken>(TYPES.Auth_GetSessionFromToken),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container.bind<SignIn>(TYPES.Auth_SignIn).to(SignIn)
container
.bind<VerifyHumanInteraction>(TYPES.Auth_VerifyHumanInteraction)
.toConstantValue(
new VerifyHumanInteraction(
container.get(TYPES.Auth_HUMAN_VERIFICATION_ENABLED),
container.get<CaptchaServerInterface>(TYPES.Auth_CaptchaServer),
),
)
container
.bind<SignIn>(TYPES.Auth_SignIn)
.toConstantValue(
new SignIn(
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
container.get<AuthResponseFactoryResolverInterface>(TYPES.Auth_AuthResponseFactoryResolver),
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
container.get<SessionServiceInterface>(TYPES.Auth_SessionService),
container.get<PKCERepositoryInterface>(TYPES.Auth_PKCERepository),
container.get<CrypterInterface>(TYPES.Auth_Crypter),
container.get<winston.Logger>(TYPES.Auth_Logger),
container.get<number>(TYPES.Auth_MAX_LOGIN_ATTEMPTS),
container.get<LockRepositoryInterface>(TYPES.Auth_LockRepository),
container.get<VerifyHumanInteraction>(TYPES.Auth_VerifyHumanInteraction),
),
)
container
.bind<VerifyMFA>(TYPES.Auth_VerifyMFA)
.toConstantValue(
@@ -1016,8 +1168,24 @@ export class ContainerConfigLoader {
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container.bind<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts).to(ClearLoginAttempts)
container.bind<IncreaseLoginAttempts>(TYPES.Auth_IncreaseLoginAttempts).to(IncreaseLoginAttempts)
container
.bind<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts)
.toConstantValue(
new ClearLoginAttempts(
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
container.get<LockRepositoryInterface>(TYPES.Auth_LockRepository),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container
.bind<IncreaseLoginAttempts>(TYPES.Auth_IncreaseLoginAttempts)
.toConstantValue(
new IncreaseLoginAttempts(
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
container.get<LockRepositoryInterface>(TYPES.Auth_LockRepository),
container.get<number>(TYPES.Auth_MAX_LOGIN_ATTEMPTS),
),
)
container
.bind<GetUserKeyParamsRecovery>(TYPES.Auth_GetUserKeyParamsRecovery)
.toConstantValue(
@@ -1028,7 +1196,6 @@ export class ContainerConfigLoader {
container.get<GetSetting>(TYPES.Auth_GetSetting),
),
)
container.bind<UpdateUser>(TYPES.Auth_UpdateUser).to(UpdateUser)
container
.bind<ApplyDefaultSettings>(TYPES.Auth_ApplyDefaultSettings)
.toConstantValue(
@@ -1129,6 +1296,9 @@ export class ContainerConfigLoader {
container.get<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts),
container.get<DeleteSetting>(TYPES.Auth_DeleteSetting),
container.get<AuthenticatorRepositoryInterface>(TYPES.Auth_AuthenticatorRepository),
container.get<number>(TYPES.Auth_MAX_LOGIN_ATTEMPTS),
container.get<LockRepositoryInterface>(TYPES.Auth_LockRepository),
container.get<VerifyHumanInteraction>(TYPES.Auth_VerifyHumanInteraction),
),
)
container
@@ -1261,7 +1431,6 @@ export class ContainerConfigLoader {
.toConstantValue(
new TriggerEmailBackupForUser(
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
container.get<GetSetting>(TYPES.Auth_GetSetting),
container.get<GetUserKeyParams>(TYPES.Auth_GetUserKeyParams),
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
@@ -1336,15 +1505,9 @@ export class ContainerConfigLoader {
.bind<AuthController>(TYPES.Auth_AuthController)
.toConstantValue(
new AuthController(
container.get(TYPES.Auth_ClearLoginAttempts),
container.get(TYPES.Auth_Register),
container.get(TYPES.Auth_DomainEventPublisher),
container.get(TYPES.Auth_DomainEventFactory),
container.get(TYPES.Auth_SignInWithRecoveryCodes),
container.get(TYPES.Auth_GetUserKeyParamsRecovery),
container.get(TYPES.Auth_GenerateRecoveryCodes),
container.get(TYPES.Auth_Logger),
container.get(TYPES.Auth_SessionService),
container.get<GetUserKeyParamsRecovery>(TYPES.Auth_GetUserKeyParamsRecovery),
container.get<GenerateRecoveryCodes>(TYPES.Auth_GenerateRecoveryCodes),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container
@@ -1421,6 +1584,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),
),
)
@@ -1578,6 +1742,16 @@ export class ContainerConfigLoader {
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container
.bind<SubscriptionStateFetchedEventHandler>(TYPES.Auth_SubscriptionStateFetchedEventHandler)
.toConstantValue(
new SubscriptionStateFetchedEventHandler(
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
container.get<OfflineUserSubscriptionRepositoryInterface>(TYPES.Auth_OfflineUserSubscriptionRepository),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Auth_AccountDeletionRequestedEventHandler)],
@@ -1619,6 +1793,7 @@ export class ContainerConfigLoader {
'FILE_QUOTA_RECALCULATED',
container.get<FileQuotaRecalculatedEventHandler>(TYPES.Auth_FileQuotaRecalculatedEventHandler),
],
['SUBSCRIPTION_STATE_FETCHED', container.get(TYPES.Auth_SubscriptionStateFetchedEventHandler)],
])
if (isConfiguredForHomeServer) {
@@ -1651,14 +1826,23 @@ export class ContainerConfigLoader {
.bind<BaseAuthController>(TYPES.Auth_BaseAuthController)
.toConstantValue(
new BaseAuthController(
container.get(TYPES.Auth_VerifyMFA),
container.get(TYPES.Auth_SignIn),
container.get(TYPES.Auth_GetUserKeyParams),
container.get(TYPES.Auth_ClearLoginAttempts),
container.get(TYPES.Auth_IncreaseLoginAttempts),
container.get(TYPES.Auth_Logger),
container.get(TYPES.Auth_AuthController),
container.get(TYPES.Auth_ControllerContainer),
container.get<VerifyMFA>(TYPES.Auth_VerifyMFA),
container.get<SignIn>(TYPES.Auth_SignIn),
container.get<GetUserKeyParams>(TYPES.Auth_GetUserKeyParams),
container.get<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts),
container.get<IncreaseLoginAttempts>(TYPES.Auth_IncreaseLoginAttempts),
container.get<winston.Logger>(TYPES.Auth_Logger),
container.get<AuthController>(TYPES.Auth_AuthController),
container.get<Register>(TYPES.Auth_Register),
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
container.get<SessionServiceInterface>(TYPES.Auth_SessionService),
container.get<VerifyHumanInteraction>(TYPES.Auth_VerifyHumanInteraction),
container.get<CookieFactoryInterface>(TYPES.Auth_CookieFactory),
container.get<SignInWithRecoveryCodes>(TYPES.Auth_SignInWithRecoveryCodes),
container.get<DeleteSessionByToken>(TYPES.Auth_DeleteSessionByToken),
container.get<string>(TYPES.Auth_CAPTCHA_UI_URL),
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
),
)
@@ -1725,6 +1909,7 @@ export class ContainerConfigLoader {
container.get<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts),
container.get<IncreaseLoginAttempts>(TYPES.Auth_IncreaseLoginAttempts),
container.get<ChangeCredentials>(TYPES.Auth_ChangeCredentials),
container.get<CookieFactoryInterface>(TYPES.Auth_CookieFactory),
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
),
)
@@ -1732,11 +1917,12 @@ export class ContainerConfigLoader {
.bind<BaseAdminController>(TYPES.Auth_BaseAdminController)
.toConstantValue(
new BaseAdminController(
container.get(TYPES.Auth_DeleteSetting),
container.get(TYPES.Auth_UserRepository),
container.get(TYPES.Auth_CreateSubscriptionToken),
container.get(TYPES.Auth_CreateOfflineSubscriptionToken),
container.get(TYPES.Auth_ControllerContainer),
container.get<DeleteSetting>(TYPES.Auth_DeleteSetting),
container.get<GetSetting>(TYPES.Auth_GetSetting),
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
container.get<CreateSubscriptionToken>(TYPES.Auth_CreateSubscriptionToken),
container.get<CreateOfflineSubscriptionToken>(TYPES.Auth_CreateOfflineSubscriptionToken),
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
),
)
container
@@ -1759,9 +1945,12 @@ export class ContainerConfigLoader {
new BaseSubscriptionSettingsController(
container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting),
container.get<GetSharedOrRegularSubscriptionForUser>(TYPES.Auth_GetSharedOrRegularSubscriptionForUser),
container.get<SetSubscriptionSettingValue>(TYPES.Auth_SetSubscriptionSettingValue),
container.get<TriggerPostSettingUpdateActions>(TYPES.Auth_TriggerPostSettingUpdateActions),
container.get<MapperInterface<SubscriptionSetting, SubscriptionSettingHttpRepresentation>>(
TYPES.Auth_SubscriptionSettingHttpMapper,
),
container.get<winston.Logger>(TYPES.Auth_Logger),
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
),
)
@@ -1786,10 +1975,11 @@ export class ContainerConfigLoader {
.bind<BaseSessionController>(TYPES.Auth_BaseSessionController)
.toConstantValue(
new BaseSessionController(
container.get(TYPES.Auth_DeleteSessionForUser),
container.get(TYPES.Auth_DeleteOtherSessionsForUser),
container.get(TYPES.Auth_RefreshSessionToken),
container.get(TYPES.Auth_ControllerContainer),
container.get<DeleteSessionForUser>(TYPES.Auth_DeleteSessionForUser),
container.get<DeleteOtherSessionsForUser>(TYPES.Auth_DeleteOtherSessionsForUser),
container.get<RefreshSessionToken>(TYPES.Auth_RefreshSessionToken),
container.get<CookieFactoryInterface>(TYPES.Auth_CookieFactory),
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
),
)
container

View File

@@ -34,6 +34,7 @@ const TYPES = {
Auth_UserSubscriptionRepository: Symbol.for('Auth_UserSubscriptionRepository'),
Auth_OfflineUserSubscriptionRepository: Symbol.for('Auth_OfflineUserSubscriptionRepository'),
Auth_SubscriptionTokenRepository: Symbol.for('Auth_SubscriptionTokenRepository'),
Auth_SessionTokensCooldownRepository: Symbol.for('Auth_SessionTokensCooldownRepository'),
Auth_OfflineSubscriptionTokenRepository: Symbol.for('Auth_OfflineSubscriptionTokenRepository'),
Auth_SharedSubscriptionInvitationRepository: Symbol.for('Auth_SharedSubscriptionInvitationRepository'),
Auth_PKCERepository: Symbol.for('Auth_PKCERepository'),
@@ -84,7 +85,9 @@ const TYPES = {
Auth_REFRESH_TOKEN_AGE: Symbol.for('Auth_REFRESH_TOKEN_AGE'),
Auth_EPHEMERAL_SESSION_AGE: Symbol.for('Auth_EPHEMERAL_SESSION_AGE'),
Auth_MAX_LOGIN_ATTEMPTS: Symbol.for('Auth_MAX_LOGIN_ATTEMPTS'),
Auth_MAX_CAPTCHA_LOGIN_ATTEMPTS: Symbol.for('Auth_MAX_CAPTCHA_LOGIN_ATTEMPTS'),
Auth_FAILED_LOGIN_LOCKOUT: Symbol.for('Auth_FAILED_LOGIN_LOCKOUT'),
Auth_FAILED_LOGIN_CAPTCHA_LOCKOUT: Symbol.for('Auth_FAILED_LOGIN_CAPTCHA_LOCKOUT'),
Auth_PSEUDO_KEY_PARAMS_KEY: Symbol.for('Auth_PSEUDO_KEY_PARAMS_KEY'),
Auth_REDIS_URL: Symbol.for('Auth_REDIS_URL'),
Auth_DISABLE_USER_REGISTRATION: Symbol.for('Auth_DISABLE_USER_REGISTRATION'),
@@ -100,6 +103,10 @@ const TYPES = {
Auth_U2F_REQUIRE_USER_VERIFICATION: Symbol.for('Auth_U2F_REQUIRE_USER_VERIFICATION'),
Auth_READONLY_USERS: Symbol.for('Auth_READONLY_USERS'),
Auth_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING: Symbol.for('Auth_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING'),
Auth_CAPTCHA_SERVER_URL: Symbol.for('Auth_CAPTCHA_SERVER_URL'),
Auth_CAPTCHA_UI_URL: Symbol.for('Auth_CAPTCHA_UI_URL'),
Auth_HUMAN_VERIFICATION_ENABLED: Symbol.for('Auth_HUMAN_VERIFICATION_ENABLED'),
Auth_FORCE_LEGACY_SESSIONS: Symbol.for('Auth_FORCE_LEGACY_SESSIONS'),
// use cases
Auth_AuthenticateUser: Symbol.for('Auth_AuthenticateUser'),
Auth_AuthenticateRequest: Symbol.for('Auth_AuthenticateRequest'),
@@ -109,7 +116,6 @@ const TYPES = {
Auth_ClearLoginAttempts: Symbol.for('Auth_ClearLoginAttempts'),
Auth_IncreaseLoginAttempts: Symbol.for('Auth_IncreaseLoginAttempts'),
Auth_GetUserKeyParams: Symbol.for('Auth_GetUserKeyParams'),
Auth_UpdateUser: Symbol.for('Auth_UpdateUser'),
Auth_Register: Symbol.for('Auth_Register'),
Auth_GetActiveSessionsForUser: Symbol.for('Auth_GetActiveSessionsForUser'),
Auth_DeleteOtherSessionsForUser: Symbol.for('Auth_DeleteOtherSessionsForUser'),
@@ -158,6 +164,10 @@ const TYPES = {
Auth_ApplyDefaultSettings: Symbol.for('Auth_ApplyDefaultSettings'),
Auth_ActivatePremiumFeatures: Symbol.for('Auth_ActivatePremiumFeatures'),
Auth_SignInWithRecoveryCodes: Symbol.for('Auth_SignInWithRecoveryCodes'),
Auth_GetSessionFromToken: Symbol.for('Auth_GetSessionFromToken'),
Auth_DeleteSessionByToken: Symbol.for('Auth_DeleteSessionByToken'),
Auth_CooldownSessionTokens: Symbol.for('Auth_CooldownSessionTokens'),
Auth_GetCooldownSessionTokens: Symbol.for('Auth_GetCooldownSessionTokens'),
Auth_GetUserKeyParamsRecovery: Symbol.for('Auth_GetUserKeyParamsRecovery'),
Auth_UpdateStorageQuotaUsedForUser: Symbol.for('Auth_UpdateStorageQuotaUsedForUser'),
Auth_AddSharedVaultUser: Symbol.for('Auth_AddSharedVaultUser'),
@@ -171,6 +181,7 @@ const TYPES = {
Auth_DeleteAccountsFromCSVFile: Symbol.for('Auth_DeleteAccountsFromCSVFile'),
Auth_RenewSharedSubscriptions: Symbol.for('Auth_RenewSharedSubscriptions'),
Auth_FixStorageQuotaForUser: Symbol.for('Auth_FixStorageQuotaForUser'),
Auth_VerifyHumanInteraction: Symbol.for('Auth_VerifyHumanInteraction'),
// Handlers
Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'),
Auth_AccountDeletionVerificationPassedEventHandler: Symbol.for('Auth_AccountDeletionVerificationPassedEventHandler'),
@@ -205,7 +216,9 @@ const TYPES = {
),
Auth_UserInvitedToSharedVaultEventHandler: Symbol.for('Auth_UserInvitedToSharedVaultEventHandler'),
Auth_FileQuotaRecalculatedEventHandler: Symbol.for('Auth_FileQuotaRecalculatedEventHandler'),
Auth_SubscriptionStateFetchedEventHandler: Symbol.for('Auth_SubscriptionStateFetchedEventHandler'),
// Services
Auth_CookieFactory: Symbol.for('Auth_CookieFactory'),
Auth_DeviceDetector: Symbol.for('Auth_DeviceDetector'),
Auth_SessionService: Symbol.for('Auth_SessionService'),
Auth_OfflineSettingService: Symbol.for('Auth_OfflineSettingService'),
@@ -258,6 +271,8 @@ const TYPES = {
Auth_BaseListedController: Symbol.for('Auth_BaseListedController'),
Auth_BaseFeaturesController: Symbol.for('Auth_BaseFeaturesController'),
Auth_CSVFileReader: Symbol.for('Auth_CSVFileReader'),
Auth_CaptchaServer: Symbol.for('Auth_CaptchaServer'),
Auth_HTTPClient: Symbol.for('Auth_HTTPClient'),
}
export default TYPES

View File

@@ -1,149 +0,0 @@
import 'reflect-metadata'
import { DomainEventInterface, DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { AuthController } from './AuthController'
import { ClearLoginAttempts } from '../Domain/UseCase/ClearLoginAttempts'
import { User } from '../Domain/User/User'
import { Register } from '../Domain/UseCase/Register'
import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
import { KeyParamsOrigination, ProtocolVersion } from '@standardnotes/common'
import { SignInWithRecoveryCodes } from '../Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes'
import { GetUserKeyParamsRecovery } from '../Domain/UseCase/GetUserKeyParamsRecovery/GetUserKeyParamsRecovery'
import { GenerateRecoveryCodes } from '../Domain/UseCase/GenerateRecoveryCodes/GenerateRecoveryCodes'
import { Logger } from 'winston'
import { SessionServiceInterface } from '../Domain/Session/SessionServiceInterface'
import { ApiVersion } from '../Domain/Api/ApiVersion'
describe('AuthController', () => {
let clearLoginAttempts: ClearLoginAttempts
let register: Register
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
let event: DomainEventInterface
let user: User
let doSignInWithRecoveryCodes: SignInWithRecoveryCodes
let getUserKeyParamsRecovery: GetUserKeyParamsRecovery
let doGenerateRecoveryCodes: GenerateRecoveryCodes
let logger: Logger
let sessionService: SessionServiceInterface
const createController = () =>
new AuthController(
clearLoginAttempts,
register,
domainEventPublisher,
domainEventFactory,
doSignInWithRecoveryCodes,
getUserKeyParamsRecovery,
doGenerateRecoveryCodes,
logger,
sessionService,
)
beforeEach(() => {
register = {} as jest.Mocked<Register>
register.execute = jest.fn()
user = {} as jest.Mocked<User>
user.email = 'test@test.te'
clearLoginAttempts = {} as jest.Mocked<ClearLoginAttempts>
clearLoginAttempts.execute = jest.fn()
event = {} as jest.Mocked<DomainEventInterface>
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createUserRegisteredEvent = jest.fn().mockReturnValue(event)
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
sessionService = {} as jest.Mocked<SessionServiceInterface>
sessionService.deleteSessionByToken = jest.fn().mockReturnValue('1-2-3')
})
it('should register a user', async () => {
register.execute = jest.fn().mockReturnValue({ success: true, authResponse: { user } })
const response = await createController().register({
email: 'test@test.te',
password: 'asdzxc',
version: ProtocolVersion.V004,
api: ApiVersion.v20200115,
origination: KeyParamsOrigination.Registration,
userAgent: 'Google Chrome',
identifier: 'test@test.te',
pw_nonce: '11',
ephemeral: false,
})
expect(register.execute).toHaveBeenCalledWith({
apiVersion: '20200115',
kpOrigination: 'registration',
updatedWithUserAgent: 'Google Chrome',
ephemeralSession: false,
version: '004',
email: 'test@test.te',
password: 'asdzxc',
pwNonce: '11',
})
expect(domainEventPublisher.publish).toHaveBeenCalledWith(event)
expect(response.status).toEqual(200)
expect(response.data).toEqual({ user: { email: 'test@test.te' } })
})
it('should not register a user if request param is missing', async () => {
const response = await createController().register({
email: 'test@test.te',
password: '',
version: ProtocolVersion.V004,
api: ApiVersion.v20200115,
origination: KeyParamsOrigination.Registration,
userAgent: 'Google Chrome',
identifier: 'test@test.te',
pw_nonce: '11',
ephemeral: false,
})
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(response.status).toEqual(400)
})
it('should respond with error if registering a user fails', async () => {
register.execute = jest.fn().mockReturnValue({ success: false, errorMessage: 'Something bad happened' })
const response = await createController().register({
email: 'test@test.te',
password: 'test',
version: ProtocolVersion.V004,
api: ApiVersion.v20200115,
origination: KeyParamsOrigination.Registration,
userAgent: 'Google Chrome',
identifier: 'test@test.te',
pw_nonce: '11',
ephemeral: false,
})
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(response.status).toEqual(400)
})
it('should throw error on the delete user method as it is still a part of the payments server', async () => {
let caughtError = null
try {
await createController().deleteAccount({} as never)
} catch (error) {
caughtError = error
}
expect(caughtError).not.toBeNull()
})
})

View File

@@ -1,42 +1,23 @@
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import {
UserRegistrationRequestParams,
UserServerInterface,
UserDeletionResponseBody,
UserRegistrationResponseBody,
UserUpdateRequestParams,
} from '@standardnotes/api'
import { ErrorTag, HttpResponse, HttpStatusCode } from '@standardnotes/responses'
import { ProtocolVersion } from '@standardnotes/common'
import { UserDeletionResponseBody, UserUpdateRequestParams } from '@standardnotes/api'
import { HttpResponse, HttpStatusCode } from '@standardnotes/responses'
import { ClearLoginAttempts } from '../Domain/UseCase/ClearLoginAttempts'
import { Register } from '../Domain/UseCase/Register'
import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
import { SignInWithRecoveryCodes } from '../Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes'
import { SignInWithRecoveryCodesRequestParams } from '../Infra/Http/Request/SignInWithRecoveryCodesRequestParams'
import { GetUserKeyParamsRecovery } from '../Domain/UseCase/GetUserKeyParamsRecovery/GetUserKeyParamsRecovery'
import { RecoveryKeyParamsRequestParams } from '../Infra/Http/Request/RecoveryKeyParamsRequestParams'
import { SignInWithRecoveryCodesResponseBody } from '../Infra/Http/Response/SignInWithRecoveryCodesResponseBody'
import { RecoveryKeyParamsResponseBody } from '../Infra/Http/Response/RecoveryKeyParamsResponseBody'
import { GenerateRecoveryCodesResponseBody } from '../Infra/Http/Response/GenerateRecoveryCodesResponseBody'
import { GenerateRecoveryCodes } from '../Domain/UseCase/GenerateRecoveryCodes/GenerateRecoveryCodes'
import { GenerateRecoveryCodesRequestParams } from '../Infra/Http/Request/GenerateRecoveryCodesRequestParams'
import { Logger } from 'winston'
import { SessionServiceInterface } from '../Domain/Session/SessionServiceInterface'
import { ApiVersion } from '../Domain/Api/ApiVersion'
import { UserUpdateResponse } from '@standardnotes/api/dist/Domain/Response/User/UserUpdateResponse'
export class AuthController implements UserServerInterface {
/**
* DEPRECATED: This controller is deprecated and will be removed in the future.
*/
export class AuthController {
constructor(
private clearLoginAttempts: ClearLoginAttempts,
private registerUser: Register,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private doSignInWithRecoveryCodes: SignInWithRecoveryCodes,
private getUserKeyParamsRecovery: GetUserKeyParamsRecovery,
private doGenerateRecoveryCodes: GenerateRecoveryCodes,
private logger: Logger,
private sessionService: SessionServiceInterface,
) {}
async update(_params: UserUpdateRequestParams): Promise<HttpResponse<UserUpdateResponse>> {
@@ -47,57 +28,6 @@ export class AuthController implements UserServerInterface {
throw new Error('This method is implemented on the payments server.')
}
async register(params: UserRegistrationRequestParams): Promise<HttpResponse<UserRegistrationResponseBody>> {
if (!params.email || !params.password) {
return {
status: HttpStatusCode.BadRequest,
data: {
error: {
message: 'Please enter an email and a password to register.',
},
},
}
}
const registerResult = await this.registerUser.execute({
email: params.email,
password: params.password,
updatedWithUserAgent: params.userAgent as string,
apiVersion: params.api,
ephemeralSession: params.ephemeral,
pwNonce: params.pw_nonce,
kpOrigination: params.origination,
kpCreated: params.created,
version: params.version,
})
if (!registerResult.success) {
return {
status: HttpStatusCode.BadRequest,
data: {
error: {
message: registerResult.errorMessage,
},
},
}
}
await this.clearLoginAttempts.execute({ email: registerResult.authResponse.user.email as string })
await this.domainEventPublisher.publish(
this.domainEventFactory.createUserRegisteredEvent({
userUuid: <string>registerResult.authResponse.user.uuid,
email: <string>registerResult.authResponse.user.email,
protocolVersion: (<string>registerResult.authResponse.user.protocolVersion) as ProtocolVersion,
}),
)
return {
status: HttpStatusCode.Success,
data: registerResult.authResponse,
}
}
async generateRecoveryCodes(
params: GenerateRecoveryCodesRequestParams,
): Promise<HttpResponse<GenerateRecoveryCodesResponseBody>> {
@@ -124,62 +54,11 @@ export class AuthController implements UserServerInterface {
}
}
async signInWithRecoveryCodes(
params: SignInWithRecoveryCodesRequestParams,
): Promise<HttpResponse<SignInWithRecoveryCodesResponseBody>> {
if (params.apiVersion !== ApiVersion.v20200115) {
return {
status: HttpStatusCode.BadRequest,
data: {
error: {
message: 'Invalid API version.',
},
},
}
}
const result = await this.doSignInWithRecoveryCodes.execute({
userAgent: params.userAgent,
username: params.username,
password: params.password,
codeVerifier: params.codeVerifier,
recoveryCodes: params.recoveryCodes,
})
if (result.isFailed()) {
this.logger.debug(`Failed to sign in with recovery codes: ${result.getError()}`)
return {
status: HttpStatusCode.Unauthorized,
data: {
error: {
message: 'Invalid login credentials.',
},
},
}
}
return {
status: HttpStatusCode.Success,
data: result.getValue(),
}
}
async recoveryKeyParams(
params: RecoveryKeyParamsRequestParams,
): Promise<HttpResponse<RecoveryKeyParamsResponseBody>> {
if (params.apiVersion !== ApiVersion.v20200115) {
return {
status: HttpStatusCode.BadRequest,
data: {
error: {
message: 'Invalid API version.',
},
},
}
}
const result = await this.getUserKeyParamsRecovery.execute({
apiVersion: params.apiVersion,
username: params.username,
codeChallenge: params.codeChallenge,
recoveryCodes: params.recoveryCodes,
@@ -205,33 +84,4 @@ export class AuthController implements UserServerInterface {
},
}
}
async signOut(params: Record<string, unknown>): Promise<HttpResponse> {
if (params.readOnlyAccess) {
return {
status: HttpStatusCode.Unauthorized,
data: {
error: {
tag: ErrorTag.ReadOnlyAccess,
message: 'Session has read-only access.',
},
},
}
}
const userUuid = await this.sessionService.deleteSessionByToken(
(params.authorizationHeader as string).replace('Bearer ', ''),
)
let headers = undefined
if (userUuid !== null) {
headers = new Map([['x-invalidate-cache', userUuid]])
}
return {
status: HttpStatusCode.NoContent,
data: {},
headers,
}
}
}

View File

@@ -53,7 +53,10 @@ describe('SubscriptionInvitesController', () => {
invitations: [],
})
const result = await createController().listInvites({ api: ApiVersion.v20200115, inviterEmail: 'test@test.te' })
const result = await createController().listInvites({
api: ApiVersion.VERSIONS.v20200115,
inviterEmail: 'test@test.te',
})
expect(listSharedSubscriptionInvitations.execute).toHaveBeenCalledWith({
inviterEmail: 'test@test.te',
@@ -68,7 +71,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().cancelInvite({
api: ApiVersion.v20200115,
api: ApiVersion.VERSIONS.v20200115,
inviteUuid: '1-2-3',
inviterEmail: 'test@test.te',
})
@@ -87,7 +90,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().cancelInvite({
api: ApiVersion.v20200115,
api: ApiVersion.VERSIONS.v20200115,
inviteUuid: '1-2-3',
})
@@ -100,7 +103,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().declineInvite({
api: ApiVersion.v20200115,
api: ApiVersion.VERSIONS.v20200115,
inviteUuid: '1-2-3',
})
@@ -117,7 +120,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().declineInvite({
api: ApiVersion.v20200115,
api: ApiVersion.VERSIONS.v20200115,
inviteUuid: '1-2-3',
})
@@ -134,7 +137,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().acceptInvite({
api: ApiVersion.v20200115,
api: ApiVersion.VERSIONS.v20200115,
inviteUuid: '1-2-3',
})
@@ -151,7 +154,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().acceptInvite({
api: ApiVersion.v20200115,
api: ApiVersion.VERSIONS.v20200115,
inviteUuid: '1-2-3',
})
@@ -168,7 +171,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().invite({
api: ApiVersion.v20200115,
api: ApiVersion.VERSIONS.v20200115,
identifier: 'invitee@test.te',
inviterUuid: '1-2-3',
inviterEmail: 'test@test.te',
@@ -187,7 +190,7 @@ describe('SubscriptionInvitesController', () => {
it('should not invite to user subscription if the identifier is missing in request', async () => {
const result = await createController().invite({
api: ApiVersion.v20200115,
api: ApiVersion.VERSIONS.v20200115,
identifier: '',
inviterUuid: '1-2-3',
inviterEmail: 'test@test.te',
@@ -205,7 +208,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().invite({
api: ApiVersion.v20200115,
api: ApiVersion.VERSIONS.v20200115,
identifier: 'invitee@test.te',
inviterUuid: '1-2-3',
inviterEmail: 'test@test.te',

View File

@@ -0,0 +1,46 @@
import { ApiVersion } from './ApiVersion'
describe('ApiVersion', () => {
it('should create a value object', () => {
const valueOrError = ApiVersion.create(ApiVersion.VERSIONS.v20200115)
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().value).toEqual('20200115')
})
it('should not create an invalid value object', () => {
for (const value of ['', undefined, null, 0, 'SOME_VERSION']) {
const valueOrError = ApiVersion.create(value as string)
expect(valueOrError.isFailed()).toBeTruthy()
}
})
it('should tell if the version is supported for registration', () => {
const version = ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue()
expect(version.isSupportedForRegistration()).toBeTruthy()
const version2 = ApiVersion.create(ApiVersion.VERSIONS.v20240226).getValue()
expect(version2.isSupportedForRegistration()).toBeTruthy()
const version3 = ApiVersion.create(ApiVersion.VERSIONS.v20161215).getValue()
expect(version3.isSupportedForRegistration()).toBeFalsy()
})
it('should tell if the version is supported for recovery sign in', () => {
const version = ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue()
expect(version.isSupportedForRecoverySignIn()).toBeTruthy()
const version2 = ApiVersion.create(ApiVersion.VERSIONS.v20240226).getValue()
expect(version2.isSupportedForRecoverySignIn()).toBeTruthy()
const version3 = ApiVersion.create(ApiVersion.VERSIONS.v20161215).getValue()
expect(version3.isSupportedForRecoverySignIn()).toBeFalsy()
})
})

View File

@@ -1,5 +1,37 @@
export enum ApiVersion {
v20161215 = '20161215',
v20190520 = '20190520',
v20200115 = '20200115',
import { Result, ValueObject } from '@standardnotes/domain-core'
import { ApiVersionProps } from './ApiVersionProps'
export class ApiVersion extends ValueObject<ApiVersionProps> {
static readonly VERSIONS = {
v20161215: '20161215',
v20190520: '20190520',
v20200115: '20200115',
v20240226: '20240226',
}
get value(): string {
return this.props.value
}
private constructor(props: ApiVersionProps) {
super(props)
}
static create(version: string): Result<ApiVersion> {
const isValidVersion = Object.values(this.VERSIONS).includes(version)
if (!isValidVersion) {
return Result.fail(`Invalid api version: ${version}`)
} else {
return Result.ok(new ApiVersion({ value: version }))
}
}
isSupportedForRegistration(): boolean {
return [ApiVersion.VERSIONS.v20200115, ApiVersion.VERSIONS.v20240226].includes(this.props.value)
}
isSupportedForRecoverySignIn(): boolean {
return [ApiVersion.VERSIONS.v20200115, ApiVersion.VERSIONS.v20240226].includes(this.props.value)
}
}

View File

@@ -0,0 +1,3 @@
export interface ApiVersionProps {
value: string
}

View File

@@ -3,6 +3,6 @@ import { KeyParamsData, SessionBody } from '@standardnotes/responses'
import { AuthResponse } from './AuthResponse'
export interface AuthResponse20200115 extends AuthResponse {
session: SessionBody
key_params: KeyParamsData
sessionBody: SessionBody
keyParams: KeyParamsData
}

View File

@@ -0,0 +1,10 @@
import { Session } from '../Session/Session'
import { AuthResponse20161215 } from './AuthResponse20161215'
import { AuthResponse20200115 } from './AuthResponse20200115'
export interface AuthResponseCreationResult {
response?: AuthResponse20200115
legacyResponse?: AuthResponse20161215
session?: Session
cookies?: { accessToken: string; refreshToken: string }
}

View File

@@ -6,6 +6,7 @@ import { Logger } from 'winston'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { User } from '../User/User'
import { AuthResponseFactory20161215 } from './AuthResponseFactory20161215'
import { ApiVersion } from '../Api/ApiVersion'
describe('AuthResponseFactory20161215', () => {
let userProjector: ProjectorInterface<User>
@@ -32,13 +33,13 @@ describe('AuthResponseFactory20161215', () => {
it('should create a 20161215 auth response', async () => {
const result = await createFactory().createResponse({
user,
apiVersion: '20161215',
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20161215).getValue(),
userAgent: 'Google Chrome',
ephemeralSession: false,
readonlyAccess: false,
})
expect(result.response).toEqual({
expect(result.legacyResponse).toEqual({
user: { foo: 'bar' },
token: 'foobar',
})

View File

@@ -8,10 +8,9 @@ import TYPES from '../../Bootstrap/Types'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { User } from '../User/User'
import { AuthResponse20161215 } from './AuthResponse20161215'
import { AuthResponse20200115 } from './AuthResponse20200115'
import { AuthResponseFactoryInterface } from './AuthResponseFactoryInterface'
import { Session } from '../Session/Session'
import { AuthResponseCreationResult } from './AuthResponseCreationResult'
import { ApiVersion } from '../Api/ApiVersion'
@injectable()
export class AuthResponseFactory20161215 implements AuthResponseFactoryInterface {
@@ -23,11 +22,13 @@ export class AuthResponseFactory20161215 implements AuthResponseFactoryInterface
async createResponse(dto: {
user: User
apiVersion: string
apiVersion: ApiVersion
userAgent: string
ephemeralSession: boolean
readonlyAccess: boolean
}): Promise<{ response: AuthResponse20161215 | AuthResponse20200115; session?: Session }> {
snjs?: string
application?: string
}): Promise<AuthResponseCreationResult> {
this.logger.debug(`Creating JWT auth response for user ${dto.user.uuid}`)
const data: SessionTokenData = {
@@ -40,7 +41,7 @@ export class AuthResponseFactory20161215 implements AuthResponseFactoryInterface
this.logger.debug(`Created JWT token for user ${dto.user.uuid}: ${token}`)
return {
response: {
legacyResponse: {
user: this.userProjector.projectSimple(dto.user) as {
uuid: string
email: string

View File

@@ -5,6 +5,7 @@ import { Logger } from 'winston'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { User } from '../User/User'
import { AuthResponseFactory20190520 } from './AuthResponseFactory20190520'
import { ApiVersion } from '../Api/ApiVersion'
describe('AuthResponseFactory20190520', () => {
let userProjector: ProjectorInterface<User>
@@ -31,13 +32,13 @@ describe('AuthResponseFactory20190520', () => {
it('should create a 20161215 auth response', async () => {
const result = await createFactory().createResponse({
user,
apiVersion: '20161215',
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20161215).getValue(),
userAgent: 'Google Chrome',
ephemeralSession: false,
readonlyAccess: false,
})
expect(result.response).toEqual({
expect(result.legacyResponse).toEqual({
user: { foo: 'bar' },
token: 'foobar',
})

View File

@@ -12,6 +12,7 @@ import { AuthResponseFactory20200115 } from './AuthResponseFactory20200115'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { Session } from '../Session/Session'
import { ApiVersion } from '../Api/ApiVersion'
describe('AuthResponseFactory20200115', () => {
let sessionService: SessionServiceInterface
@@ -51,10 +52,10 @@ describe('AuthResponseFactory20200115', () => {
sessionService = {} as jest.Mocked<SessionServiceInterface>
sessionService.createNewSessionForUser = jest
.fn()
.mockReturnValue({ sessionHttpRepresentation: sessionPayload, session: {} as jest.Mocked<Session> })
.mockReturnValue({ sessionHttpRepresentation: sessionPayload, sessionBody: {} as jest.Mocked<Session> })
sessionService.createNewEphemeralSessionForUser = jest
.fn()
.mockReturnValue({ sessionHttpRepresentation: sessionPayload, session: {} as jest.Mocked<Session> })
.mockReturnValue({ sessionHttpRepresentation: sessionPayload, sessionBody: {} as jest.Mocked<Session> })
keyParamsFactory = {} as jest.Mocked<KeyParamsFactoryInterface>
keyParamsFactory.create = jest.fn().mockReturnValue({
@@ -83,13 +84,13 @@ describe('AuthResponseFactory20200115', () => {
const result = await createFactory().createResponse({
user,
apiVersion: '20161215',
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20161215).getValue(),
userAgent: 'Google Chrome',
ephemeralSession: false,
readonlyAccess: false,
})
expect(result.response).toEqual({
expect(result.legacyResponse).toEqual({
user: { foo: 'bar' },
token: expect.any(String),
})
@@ -100,18 +101,18 @@ describe('AuthResponseFactory20200115', () => {
const result = await createFactory().createResponse({
user,
apiVersion: '20200115',
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
userAgent: 'Google Chrome',
ephemeralSession: false,
readonlyAccess: false,
})
expect(result.response).toEqual({
key_params: {
keyParams: {
key1: 'value1',
key2: 'value2',
},
session: {
sessionBody: {
access_token: 'access_token',
refresh_token: 'refresh_token',
access_expiration: 123,
@@ -131,18 +132,18 @@ describe('AuthResponseFactory20200115', () => {
const result = await createFactory().createResponse({
user,
apiVersion: '20200115',
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
userAgent: 'Google Chrome',
ephemeralSession: false,
readonlyAccess: false,
})
expect(result.response).toEqual({
key_params: {
keyParams: {
key1: 'value1',
key2: 'value2',
},
session: {
sessionBody: {
access_token: 'access_token',
refresh_token: 'refresh_token',
access_expiration: 123,
@@ -160,18 +161,18 @@ describe('AuthResponseFactory20200115', () => {
const result = await createFactory().createResponse({
user,
apiVersion: '20200115',
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
userAgent: 'Google Chrome',
ephemeralSession: true,
readonlyAccess: false,
})
expect(result.response).toEqual({
key_params: {
keyParams: {
key1: 'value1',
key2: 'value2',
},
session: {
sessionBody: {
access_token: 'access_token',
refresh_token: 'refresh_token',
access_expiration: 123,
@@ -192,23 +193,23 @@ describe('AuthResponseFactory20200115', () => {
...sessionPayload,
readonly_access: true,
},
session: {} as jest.Mocked<Session>,
sessionBody: {} as jest.Mocked<Session>,
})
const result = await createFactory().createResponse({
user,
apiVersion: '20200115',
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
userAgent: 'Google Chrome',
ephemeralSession: false,
readonlyAccess: true,
})
expect(result.response).toEqual({
key_params: {
keyParams: {
key1: 'value1',
key2: 'value2',
},
session: {
sessionBody: {
access_token: 'access_token',
refresh_token: 'refresh_token',
access_expiration: 123,

View File

@@ -4,7 +4,6 @@ import {
TokenEncoderInterface,
} from '@standardnotes/security'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { SessionBody } from '@standardnotes/responses'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
@@ -17,9 +16,9 @@ import { User } from '../User/User'
import { AuthResponseFactory20190520 } from './AuthResponseFactory20190520'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { AuthResponse20161215 } from './AuthResponse20161215'
import { AuthResponse20200115 } from './AuthResponse20200115'
import { Session } from '../Session/Session'
import { SessionCreationResult } from '../Session/SessionCreationResult'
import { AuthResponseCreationResult } from './AuthResponseCreationResult'
import { ApiVersion } from '../Api/ApiVersion'
@injectable()
export class AuthResponseFactory20200115 extends AuthResponseFactory20190520 {
@@ -37,11 +36,13 @@ export class AuthResponseFactory20200115 extends AuthResponseFactory20190520 {
override async createResponse(dto: {
user: User
apiVersion: string
apiVersion: ApiVersion
userAgent: string
ephemeralSession: boolean
readonlyAccess: boolean
}): Promise<{ response: AuthResponse20161215 | AuthResponse20200115; session?: Session }> {
snjs?: string
application?: string
}): Promise<AuthResponseCreationResult> {
if (!dto.user.supportsSessions()) {
this.logger.debug(`User ${dto.user.uuid} does not support sessions. Falling back to JWT auth response`)
@@ -50,29 +51,31 @@ export class AuthResponseFactory20200115 extends AuthResponseFactory20190520 {
const sessionCreationResult = await this.createSession(dto)
this.logger.debug(
'Created session payload for user %s: %O',
dto.user.uuid,
sessionCreationResult.sessionHttpRepresentation,
)
this.logger.debug('Created session payload for user', {
userId: dto.user.uuid,
session: sessionCreationResult,
})
return {
response: {
session: sessionCreationResult.sessionHttpRepresentation,
key_params: this.keyParamsFactory.create(dto.user, true),
sessionBody: sessionCreationResult.sessionHttpRepresentation,
keyParams: this.keyParamsFactory.create(dto.user, true),
user: this.userProjector.projectSimple(dto.user) as SimpleUserProjection,
},
session: sessionCreationResult.session,
cookies: sessionCreationResult.sessionCookieRepresentation,
}
}
private async createSession(dto: {
user: User
apiVersion: string
apiVersion: ApiVersion
userAgent: string
ephemeralSession: boolean
readonlyAccess: boolean
}): Promise<{ sessionHttpRepresentation: SessionBody; session: Session }> {
snjs?: string
application?: string
}): Promise<SessionCreationResult> {
if (dto.ephemeralSession) {
return this.sessionService.createNewEphemeralSessionForUser(dto)
}

View File

@@ -1,14 +1,15 @@
import { Session } from '../Session/Session'
import { ApiVersion } from '../Api/ApiVersion'
import { User } from '../User/User'
import { AuthResponse20161215 } from './AuthResponse20161215'
import { AuthResponse20200115 } from './AuthResponse20200115'
import { AuthResponseCreationResult } from './AuthResponseCreationResult'
export interface AuthResponseFactoryInterface {
createResponse(dto: {
user: User
apiVersion: string
apiVersion: ApiVersion
userAgent: string
ephemeralSession: boolean
readonlyAccess: boolean
}): Promise<{ response: AuthResponse20161215 | AuthResponse20200115; session?: Session }>
snjs?: string
application?: string
}): Promise<AuthResponseCreationResult>
}

View File

@@ -5,6 +5,7 @@ import { AuthResponseFactory20161215 } from './AuthResponseFactory20161215'
import { AuthResponseFactory20190520 } from './AuthResponseFactory20190520'
import { AuthResponseFactory20200115 } from './AuthResponseFactory20200115'
import { AuthResponseFactoryResolver } from './AuthResponseFactoryResolver'
import { ApiVersion } from '../Api/ApiVersion'
describe('AuthResponseFactoryResolver', () => {
let authResponseFactory20161215: AuthResponseFactory20161215
@@ -30,18 +31,26 @@ describe('AuthResponseFactoryResolver', () => {
})
it('should resolve 2016 response factory', () => {
expect(createResolver().resolveAuthResponseFactoryVersion('20161215')).toEqual(authResponseFactory20161215)
expect(
createResolver().resolveAuthResponseFactoryVersion(ApiVersion.create(ApiVersion.VERSIONS.v20161215).getValue()),
).toEqual(authResponseFactory20161215)
})
it('should resolve 2019 response factory', () => {
expect(createResolver().resolveAuthResponseFactoryVersion('20190520')).toEqual(authResponseFactory20190520)
expect(
createResolver().resolveAuthResponseFactoryVersion(ApiVersion.create(ApiVersion.VERSIONS.v20190520).getValue()),
).toEqual(authResponseFactory20190520)
})
it('should resolve 2020 response factory', () => {
expect(createResolver().resolveAuthResponseFactoryVersion('20200115')).toEqual(authResponseFactory20200115)
expect(
createResolver().resolveAuthResponseFactoryVersion(ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue()),
).toEqual(authResponseFactory20200115)
})
it('should resolve 2016 response factory as default', () => {
expect(createResolver().resolveAuthResponseFactoryVersion('')).toEqual(authResponseFactory20161215)
it('should resolve 2024 response factory', () => {
expect(
createResolver().resolveAuthResponseFactoryVersion(ApiVersion.create(ApiVersion.VERSIONS.v20240226).getValue()),
).toEqual(authResponseFactory20200115)
})
})

View File

@@ -17,13 +17,14 @@ export class AuthResponseFactoryResolver implements AuthResponseFactoryResolverI
@inject(TYPES.Auth_Logger) private logger: Logger,
) {}
resolveAuthResponseFactoryVersion(apiVersion: string): AuthResponseFactoryInterface {
this.logger.debug(`Resolving auth response factory for api version: ${apiVersion}`)
resolveAuthResponseFactoryVersion(apiVersion: ApiVersion): AuthResponseFactoryInterface {
this.logger.debug(`Resolving auth response factory for api version: ${apiVersion.value}`)
switch (apiVersion) {
case ApiVersion.v20190520:
switch (apiVersion.value) {
case ApiVersion.VERSIONS.v20190520:
return this.authResponseFactory20190520
case ApiVersion.v20200115:
case ApiVersion.VERSIONS.v20200115:
case ApiVersion.VERSIONS.v20240226:
return this.authResponseFactory20200115
default:
return this.authResponseFactory20161215

View File

@@ -1,5 +1,6 @@
import { ApiVersion } from '../Api/ApiVersion'
import { AuthResponseFactoryInterface } from './AuthResponseFactoryInterface'
export interface AuthResponseFactoryResolverInterface {
resolveAuthResponseFactoryVersion(apiVersion: string): AuthResponseFactoryInterface
resolveAuthResponseFactoryVersion(apiVersion: ApiVersion): AuthResponseFactoryInterface
}

View File

@@ -7,5 +7,6 @@ export type AuthenticationMethod = {
user: User | null
claims?: Record<string, unknown>
session?: Session
givenTokensWereInCooldown?: boolean
revokedSession?: RevokedSession
}

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