Compare commits

..

62 Commits

Author SHA1 Message Date
Aman Harwara
84cbeaf3be chore: release latest code 2025-04-29 13:07:38 +02:00
Karol Sójko
578ce0e74e Fix puppeteer sandbox issue (#1077)
Force merging for the rules to apply in basic workflows
2025-04-29 13:05:12 +02:00
Aman Harwara
532be7c358 chore: upgrade github actions (#1076)
Some checks failed
E2E Test Suite / E2E (push) Has been cancelled
Publish Packages / build (push) Has been cancelled
Publish Packages / lint (push) Has been cancelled
Publish Packages / test (push) Has been cancelled
Publish Packages / E2E Base Suite (push) Has been cancelled
Publish Packages / E2E Vaults Suite (push) Has been cancelled
Publish Packages / Publish Self Hosting Docker Image (push) Has been cancelled
Publish Packages / publish-services (push) Has been cancelled
2025-04-25 20:03:20 +05:30
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
standardci
e2205c3849 chore(release): publish new version
- @standardnotes/auth-server@1.177.15
 - @standardnotes/home-server@1.22.52
2024-01-09 09:17:45 +00:00
Karol Sójko
0b46eff16e fix(auth): check for user agent persisting on session during a session refresh (#1016) 2024-01-09 09:57:02 +01:00
standardci
df67982bca chore(release): publish new version
- @standardnotes/api-gateway@1.89.18
 - @standardnotes/auth-server@1.177.14
 - @standardnotes/home-server@1.22.51
 - @standardnotes/syncing-server@1.133.5
2024-01-08 12:25:14 +00:00
Karol Sójko
d44866b3c0 chore: add types for response locals (#1015) 2024-01-08 13:04:25 +01:00
standardci
6ee18bffe6 chore(release): publish new version
- @standardnotes/home-server@1.22.50
 - @standardnotes/syncing-server@1.133.4
2024-01-08 05:37:21 +00:00
Karol Sójko
a881dd2d79 fix(syncing-server): disable ot tracing 2024-01-08 06:16:59 +01:00
standardci
b767e1f072 chore(release): publish new version
- @standardnotes/home-server@1.22.49
 - @standardnotes/syncing-server@1.133.3
2024-01-05 12:28:02 +00:00
Karol Sójko
e3cb1faba4 fix(syncing-server): add traffic abuse check in gRPC coms 2024-01-05 13:07:00 +01:00
Karol Sójko
5c5f988055 fix(syncing-server): remove excessive debug logs 2024-01-05 13:07:00 +01:00
standardci
c7d2adf091 chore(release): publish new version
- @standardnotes/home-server@1.22.48
 - @standardnotes/syncing-server@1.133.2
2024-01-05 12:06:26 +00:00
Karol Sójko
a4ad37f309 fix(syncing-server): add debug logs to redis metrics store 2024-01-05 12:45:54 +01:00
Karol Sójko
73c2cc1222 fix(syncing-server): add metadata to transfer breach logs 2024-01-05 12:45:54 +01:00
standardci
9380900aaf chore(release): publish new version
- @standardnotes/home-server@1.22.47
 - @standardnotes/syncing-server@1.133.1
2024-01-05 11:43:04 +00:00
Karol Sójko
02f4d5c717 fix(syncing-server): metadata in logs 2024-01-05 12:22:28 +01:00
Karol Sójko
1f4b26d269 fix(syncing-server): add debug logs for checking traffic abuse 2024-01-05 12:21:24 +01:00
Karol Sójko
e253825da6 fix(syncing-server): error message 2024-01-05 12:09:34 +01:00
387 changed files with 8837 additions and 2092 deletions

7
.github/ci.env vendored
View File

@@ -17,6 +17,9 @@ SYNCING_SERVER_LOG_LEVEL=debug
FILES_SERVER_LOG_LEVEL=debug FILES_SERVER_LOG_LEVEL=debug
REVISIONS_SERVER_LOG_LEVEL=debug REVISIONS_SERVER_LOG_LEVEL=debug
API_GATEWAY_LOG_LEVEL=debug API_GATEWAY_LOG_LEVEL=debug
COOKIE_DOMAIN=localhost
COOKIE_SECURE=false
COOKIE_PARTITIONED=false
MYSQL_DATABASE=standard_notes_db MYSQL_DATABASE=standard_notes_db
MYSQL_USER=std_notes_user MYSQL_USER=std_notes_user
@@ -27,4 +30,6 @@ AUTH_JWT_SECRET=f95259c5e441f5a4646d76422cfb3df4c4488842901aa50b6c51b8be2e0040e9
AUTH_SERVER_ENCRYPTION_SERVER_KEY=1087415dfde3093797f9a7ca93a49e7d7aa1861735eb0d32aae9c303b8c3d060 AUTH_SERVER_ENCRYPTION_SERVER_KEY=1087415dfde3093797f9a7ca93a49e7d7aa1861735eb0d32aae9c303b8c3d060
VALET_TOKEN_SECRET=4b886819ebe1e908077c6cae96311b48a8416bd60cc91c03060e15bdf6b30d1f 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 }} workspace_name: ${{ inputs.workspace_name }}
secrets: inherit secrets: inherit
deploy-web: # deploy-web:
if: ${{ inputs.deploy_web }} # if: ${{ inputs.deploy_web }}
needs: publish # needs: publish
name: Deploy Web # name: Deploy Web
uses: standardnotes/server/.github/workflows/common-deploy.yml@main # uses: standardnotes/server/.github/workflows/common-deploy.yml@main
with: # with:
service_name: ${{ inputs.service_name }} # service_name: ${{ inputs.service_name }}
docker_image: ${{ inputs.service_name }}:${{ github.sha }} # docker_image: ${{ inputs.service_name }}:${{ github.sha }}
secrets: inherit # secrets: inherit
deploy-worker: # deploy-worker:
if: ${{ inputs.deploy_worker }} # if: ${{ inputs.deploy_worker }}
needs: publish # needs: publish
name: Deploy Worker # name: Deploy Worker
uses: standardnotes/server/.github/workflows/common-deploy.yml@main # uses: standardnotes/server/.github/workflows/common-deploy.yml@main
with: # with:
service_name: ${{ inputs.service_name }}-worker # service_name: ${{ inputs.service_name }}-worker
docker_image: ${{ inputs.service_name }}:${{ github.sha }} # docker_image: ${{ inputs.service_name }}:${{ github.sha }}
secrets: inherit # secrets: inherit

View File

@@ -46,7 +46,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Node - name: Set up Node
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
@@ -70,7 +70,8 @@ jobs:
echo "ACCESS_TOKEN_AGE=4" >> packages/home-server/.env echo "ACCESS_TOKEN_AGE=4" >> packages/home-server/.env
echo "REFRESH_TOKEN_AGE=10" >> packages/home-server/.env echo "REFRESH_TOKEN_AGE=10" >> packages/home-server/.env
echo "REVISIONS_FREQUENCY=2" >> 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_HOST=localhost" >> packages/home-server/.env
echo "DB_PORT=3306" >> packages/home-server/.env echo "DB_PORT=3306" >> packages/home-server/.env
echo "DB_DATABASE=standardnotes" >> packages/home-server/.env echo "DB_DATABASE=standardnotes" >> packages/home-server/.env
@@ -93,11 +94,11 @@ jobs:
run: for i in {1..30}; do curl -s http://localhost:3123/healthcheck && break || sleep 1; done run: for i in {1..30}; do curl -s http://localhost:3123/healthcheck && break || sleep 1; done
- name: Run E2E Test Suite - name: Run E2E Test Suite
run: yarn dlx mocha-headless-chrome --timeout 3600000 -f http://localhost:9001/mocha/test.html?suite=${{ inputs.suite }} run: yarn dlx mocha-headless-chrome --timeout 3600000 -a no-sandbox -a disable-setuid-sandbox -f http://localhost:9001/mocha/test.html?suite=${{ inputs.suite }}
- name: Archive failed run logs - name: Archive failed run logs
if: ${{ failure() }} if: ${{ failure() }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: home-server-failure-logs-${{ inputs.suite }}-${{ matrix.db_type }}-${{ matrix.cache_type }} name: home-server-failure-logs-${{ inputs.suite }}-${{ matrix.db_type }}-${{ matrix.cache_type }}
retention-days: 5 retention-days: 5

View File

@@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Node - name: Set up Node
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
@@ -57,11 +57,11 @@ jobs:
run: docker/is-available.sh http://localhost:3123 $(pwd)/logs run: docker/is-available.sh http://localhost:3123 $(pwd)/logs
- name: Run E2E Test Suite - name: Run E2E Test Suite
run: yarn dlx mocha-headless-chrome --timeout 3600000 -f http://localhost:9001/mocha/test.html?suite=${{ inputs.suite }} run: yarn dlx mocha-headless-chrome --timeout 3600000 -a no-sandbox -a disable-setuid-sandbox -f http://localhost:9001/mocha/test.html?suite=${{ inputs.suite }}
- name: Archive failed run logs - name: Archive failed run logs
if: ${{ failure() }} if: ${{ failure() }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: self-hosted-failure-logs-${{ inputs.suite }} name: self-hosted-failure-logs-${{ inputs.suite }}
retention-days: 5 retention-days: 5

View File

@@ -13,14 +13,14 @@ jobs:
- name: Cache build - name: Cache build
id: cache-build id: cache-build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: | path: |
packages/**/dist packages/**/dist
key: ${{ runner.os }}-build-${{ github.sha }} key: ${{ runner.os }}-build-${{ github.sha }}
- name: Set up Node - name: Set up Node
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
@@ -41,14 +41,14 @@ jobs:
- name: Cache build - name: Cache build
id: cache-build id: cache-build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: | path: |
packages/**/dist packages/**/dist
key: ${{ runner.os }}-build-${{ github.sha }} key: ${{ runner.os }}-build-${{ github.sha }}
- name: Set up Node - name: Set up Node
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
@@ -73,14 +73,14 @@ jobs:
- name: Cache build - name: Cache build
id: cache-build id: cache-build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: | path: |
packages/**/dist packages/**/dist
key: ${{ runner.os }}-build-${{ github.sha }} key: ${{ runner.os }}-build-${{ github.sha }}
- name: Set up Node - name: Set up Node
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc' node-version-file: '.nvmrc'

View File

@@ -16,7 +16,7 @@ jobs:
- name: Cache build - name: Cache build
id: cache-build id: cache-build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: | path: |
packages/**/dist packages/**/dist
@@ -44,7 +44,7 @@ jobs:
- name: Cache build - name: Cache build
id: cache-build id: cache-build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: | path: |
packages/**/dist packages/**/dist
@@ -76,7 +76,7 @@ jobs:
- name: Cache build - name: Cache build
id: cache-build id: cache-build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: | path: |
packages/**/dist packages/**/dist
@@ -134,7 +134,7 @@ jobs:
- name: Cache build - name: Cache build
id: cache-build id: cache-build
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: | path: |
packages/**/dist packages/**/dist
@@ -154,7 +154,7 @@ jobs:
git_commit_gpgsign: true git_commit_gpgsign: true
- name: Set up Node - name: Set up Node
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc' node-version-file: '.nvmrc'

495
.pnp.cjs generated
View File

@@ -466,6 +466,54 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/client-sqs", [\
@@ -607,6 +655,50 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/client-sts", [\
@@ -703,6 +795,53 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/core", [\
@@ -731,6 +870,19 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/credential-provider-env", [\
@@ -755,6 +907,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/credential-provider-ini", [\
@@ -791,6 +954,23 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/credential-provider-node", [\
@@ -829,6 +1009,24 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/credential-provider-process", [\
@@ -855,6 +1053,18 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/credential-provider-sso", [\
@@ -885,6 +1095,20 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/credential-provider-web-identity", [\
@@ -909,6 +1133,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/middleware-bucket-endpoint", [\
@@ -979,6 +1214,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/middleware-location-constraint", [\
@@ -1013,6 +1259,16 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/middleware-recursion-detection", [\
@@ -1037,6 +1293,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/middleware-sdk-s3", [\
@@ -1099,6 +1366,20 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/middleware-ssec", [\
@@ -1137,6 +1418,18 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/region-config-resolver", [\
@@ -1163,6 +1456,19 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/signature-v4-multi-region", [\
@@ -1268,6 +1574,50 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/types", [\
@@ -1296,6 +1646,15 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/util-arn-parser", [\
@@ -1328,6 +1687,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/util-locate-window", [\
@@ -1362,6 +1732,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\ ["tslib", "npm:2.5.2"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["@aws-sdk/util-user-agent-node", [\
@@ -1379,6 +1760,30 @@ const RAW_RUNTIME_STATE =
],\ ],\
"linkType": "SOFT"\ "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", {\ ["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/",\ "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": [\ "packageDependencies": [\
@@ -5951,7 +6356,7 @@ const RAW_RUNTIME_STATE =
["ioredis", "npm:5.3.2"],\ ["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\ ["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mixpanel", "npm:0.17.0"],\ ["mixpanel", "npm:0.17.0"],\
["mysql2", "npm:3.3.3"],\ ["mysql2", "npm:3.9.7"],\
["prettier", "npm:3.0.3"],\ ["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.2.1"],\ ["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\ ["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
@@ -5983,6 +6388,7 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./packages/api-gateway/",\ "packageLocation": "./packages/api-gateway/",\
"packageDependencies": [\ "packageDependencies": [\
["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\ ["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\
["@aws-sdk/client-sns", "npm:3.490.0"],\
["@grpc/grpc-js", "npm:1.9.13"],\ ["@grpc/grpc-js", "npm:1.9.13"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\ ["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\ ["@standardnotes/domain-events", "workspace:packages/domain-events"],\
@@ -5990,6 +6396,7 @@ const RAW_RUNTIME_STATE =
["@standardnotes/grpc", "workspace:packages/grpc"],\ ["@standardnotes/grpc", "workspace:packages/grpc"],\
["@standardnotes/security", "workspace:packages/security"],\ ["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/time", "workspace:packages/time"],\ ["@standardnotes/time", "workspace:packages/time"],\
["@types/cookie-parser", "npm:1.4.6"],\
["@types/cors", "npm:2.8.13"],\ ["@types/cors", "npm:2.8.13"],\
["@types/express", "npm:4.17.17"],\ ["@types/express", "npm:4.17.17"],\
["@types/ioredis", "npm:5.0.0"],\ ["@types/ioredis", "npm:5.0.0"],\
@@ -6001,6 +6408,7 @@ const RAW_RUNTIME_STATE =
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\ ["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["agentkeepalive", "npm:4.5.0"],\ ["agentkeepalive", "npm:4.5.0"],\
["axios", "npm:1.6.1"],\ ["axios", "npm:1.6.1"],\
["cookie-parser", "npm:1.4.6"],\
["cors", "npm:2.8.5"],\ ["cors", "npm:2.8.5"],\
["dotenv", "npm:16.1.3"],\ ["dotenv", "npm:16.1.3"],\
["eslint", "npm:8.41.0"],\ ["eslint", "npm:8.41.0"],\
@@ -6051,6 +6459,7 @@ const RAW_RUNTIME_STATE =
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\ ["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
["@standardnotes/time", "workspace:packages/time"],\ ["@standardnotes/time", "workspace:packages/time"],\
["@types/bcryptjs", "npm:2.4.2"],\ ["@types/bcryptjs", "npm:2.4.2"],\
["@types/cookie-parser", "npm:1.4.6"],\
["@types/cors", "npm:2.8.13"],\ ["@types/cors", "npm:2.8.13"],\
["@types/express", "npm:4.17.17"],\ ["@types/express", "npm:4.17.17"],\
["@types/ioredis", "npm:5.0.0"],\ ["@types/ioredis", "npm:5.0.0"],\
@@ -6062,7 +6471,10 @@ const RAW_RUNTIME_STATE =
["@types/uuid", "npm:9.0.3"],\ ["@types/uuid", "npm:9.0.3"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\ ["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["@typescript-eslint/parser", "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"],\ ["bcryptjs", "npm:2.4.3"],\
["cookie-parser", "npm:1.4.6"],\
["cors", "npm:2.8.5"],\ ["cors", "npm:2.8.5"],\
["dayjs", "npm:1.11.7"],\ ["dayjs", "npm:1.11.7"],\
["dotenv", "npm:16.1.3"],\ ["dotenv", "npm:16.1.3"],\
@@ -6073,7 +6485,7 @@ const RAW_RUNTIME_STATE =
["inversify-express-utils", "npm:6.4.3"],\ ["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.3.2"],\ ["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\ ["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\ ["mysql2", "npm:3.9.7"],\
["otplib", "npm:12.0.1"],\ ["otplib", "npm:12.0.1"],\
["prettier", "npm:3.0.3"],\ ["prettier", "npm:3.0.3"],\
["prettyjson", "npm:1.2.5"],\ ["prettyjson", "npm:1.2.5"],\
@@ -6283,10 +6695,12 @@ const RAW_RUNTIME_STATE =
["@standardnotes/files-server", "workspace:packages/files"],\ ["@standardnotes/files-server", "workspace:packages/files"],\
["@standardnotes/revisions-server", "workspace:packages/revisions"],\ ["@standardnotes/revisions-server", "workspace:packages/revisions"],\
["@standardnotes/syncing-server", "workspace:packages/syncing-server"],\ ["@standardnotes/syncing-server", "workspace:packages/syncing-server"],\
["@types/cookie-parser", "npm:1.4.6"],\
["@types/cors", "npm:2.8.13"],\ ["@types/cors", "npm:2.8.13"],\
["@types/express", "npm:4.17.17"],\ ["@types/express", "npm:4.17.17"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\ ["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["@typescript-eslint/parser", "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"],\ ["cors", "npm:2.8.5"],\
["dotenv", "npm:16.1.3"],\ ["dotenv", "npm:16.1.3"],\
["eslint", "npm:8.41.0"],\ ["eslint", "npm:8.41.0"],\
@@ -6384,7 +6798,7 @@ const RAW_RUNTIME_STATE =
["inversify-express-utils", "npm:6.4.3"],\ ["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.3.2"],\ ["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\ ["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\ ["mysql2", "npm:3.9.7"],\
["prettier", "npm:3.0.3"],\ ["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.2.1"],\ ["reflect-metadata", "npm:0.2.1"],\
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\ ["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
@@ -6403,6 +6817,7 @@ const RAW_RUNTIME_STATE =
["@standardnotes/scheduler-server", "workspace:packages/scheduler"],\ ["@standardnotes/scheduler-server", "workspace:packages/scheduler"],\
["@aws-sdk/client-sns", "npm:3.484.0"],\ ["@aws-sdk/client-sns", "npm:3.484.0"],\
["@aws-sdk/client-sqs", "npm:3.484.0"],\ ["@aws-sdk/client-sqs", "npm:3.484.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\ ["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\ ["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\ ["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
@@ -6420,7 +6835,7 @@ const RAW_RUNTIME_STATE =
["inversify", "npm:6.0.1"],\ ["inversify", "npm:6.0.1"],\
["ioredis", "npm:5.3.2"],\ ["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\ ["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\ ["mysql2", "npm:3.9.7"],\
["prettier", "npm:3.0.3"],\ ["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.2.1"],\ ["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\ ["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
@@ -6571,7 +6986,7 @@ const RAW_RUNTIME_STATE =
["ioredis", "npm:5.3.2"],\ ["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\ ["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["jsonwebtoken", "npm:9.0.0"],\ ["jsonwebtoken", "npm:9.0.0"],\
["mysql2", "npm:3.3.3"],\ ["mysql2", "npm:3.9.7"],\
["prettier", "npm:3.0.3"],\ ["prettier", "npm:3.0.3"],\
["prettyjson", "npm:1.2.5"],\ ["prettyjson", "npm:1.2.5"],\
["reflect-metadata", "npm:0.2.1"],\ ["reflect-metadata", "npm:0.2.1"],\
@@ -6651,7 +7066,7 @@ const RAW_RUNTIME_STATE =
["inversify-express-utils", "npm:6.4.3"],\ ["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.3.2"],\ ["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\ ["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\ ["mysql2", "npm:3.9.7"],\
["prettier", "npm:3.0.3"],\ ["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.2.1"],\ ["reflect-metadata", "npm:0.2.1"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\ ["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
@@ -6831,6 +7246,16 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "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", [\ ["@types/cors", [\
["npm:2.8.13", {\ ["npm:2.8.13", {\
"packageLocation": "./.yarn/cache/@types-cors-npm-2.8.13-4b8ac1068f-7ef197ea19.zip/node_modules/@types/cors/",\ "packageLocation": "./.yarn/cache/@types-cors-npm-2.8.13-4b8ac1068f-7ef197ea19.zip/node_modules/@types/cors/",\
@@ -8091,6 +8516,16 @@ const RAW_RUNTIME_STATE =
["proxy-from-env", "npm:1.1.0"]\ ["proxy-from-env", "npm:1.1.0"]\
],\ ],\
"linkType": "HARD"\ "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", [\ ["babel-jest", [\
@@ -9202,6 +9637,13 @@ const RAW_RUNTIME_STATE =
}]\ }]\
]],\ ]],\
["cookie", [\ ["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", {\ ["npm:0.5.0", {\
"packageLocation": "./.yarn/cache/cookie-npm-0.5.0-e2d58a161a-aae7911ddc.zip/node_modules/cookie/",\ "packageLocation": "./.yarn/cache/cookie-npm-0.5.0-e2d58a161a-aae7911ddc.zip/node_modules/cookie/",\
"packageDependencies": [\ "packageDependencies": [\
@@ -9210,6 +9652,17 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "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", [\ ["cookie-signature", [\
["npm:1.0.6", {\ ["npm:1.0.6", {\
"packageLocation": "./.yarn/cache/cookie-signature-npm-1.0.6-93f325f7f0-f4e1b0a98a.zip/node_modules/cookie-signature/",\ "packageLocation": "./.yarn/cache/cookie-signature-npm-1.0.6-93f325f7f0-f4e1b0a98a.zip/node_modules/cookie-signature/",\
@@ -10458,6 +10911,26 @@ const RAW_RUNTIME_STATE =
],\ ],\
"linkType": "SOFT"\ "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", {\ ["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/",\ "packageLocation": "./.yarn/__virtual__/follow-redirects-virtual-c2d5794c26/0/cache/follow-redirects-npm-1.15.2-1ec1dd82be-8be0d39919.zip/node_modules/follow-redirects/",\
"packageDependencies": [\ "packageDependencies": [\
@@ -13377,10 +13850,10 @@ const RAW_RUNTIME_STATE =
}]\ }]\
]],\ ]],\
["mysql2", [\ ["mysql2", [\
["npm:3.3.3", {\ ["npm:3.9.7", {\
"packageLocation": "./.yarn/cache/mysql2-npm-3.3.3-d2fe8cf512-4bf7ace8f1.zip/node_modules/mysql2/",\ "packageLocation": "./.yarn/cache/mysql2-npm-3.9.7-8fe89e50ac-7f43b17cc0.zip/node_modules/mysql2/",\
"packageDependencies": [\ "packageDependencies": [\
["mysql2", "npm:3.3.3"],\ ["mysql2", "npm:3.9.7"],\
["denque", "npm:2.1.0"],\ ["denque", "npm:2.1.0"],\
["generate-function", "npm:2.3.1"],\ ["generate-function", "npm:2.3.1"],\
["iconv-lite", "npm:0.6.3"],\ ["iconv-lite", "npm:0.6.3"],\
@@ -16430,7 +16903,7 @@ const RAW_RUNTIME_STATE =
["mkdirp", "npm:2.1.6"],\ ["mkdirp", "npm:2.1.6"],\
["mongodb", null],\ ["mongodb", null],\
["mssql", null],\ ["mssql", null],\
["mysql2", "npm:3.3.3"],\ ["mysql2", "npm:3.9.7"],\
["oracledb", null],\ ["oracledb", null],\
["pg", null],\ ["pg", null],\
["pg-native", null],\ ["pg-native", null],\
@@ -16522,7 +16995,7 @@ const RAW_RUNTIME_STATE =
["mkdirp", "npm:2.1.6"],\ ["mkdirp", "npm:2.1.6"],\
["mongodb", null],\ ["mongodb", null],\
["mssql", null],\ ["mssql", null],\
["mysql2", "npm:3.3.3"],\ ["mysql2", "npm:3.9.7"],\
["oracledb", null],\ ["oracledb", null],\
["pg", null],\ ["pg", null],\
["pg-native", 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.

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,8 @@ import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
import { StatisticMeasureName } from '../Statistics/StatisticMeasureName' import { StatisticMeasureName } from '../Statistics/StatisticMeasureName'
import { Period } from '../Time/Period' import { Period } from '../Time/Period'
import { safeHtml } from '@standardnotes/common'
const countActiveUsers = (measureName: string, data: any): { yesterday: number; last30Days: number } => { const countActiveUsers = (measureName: string, data: any): { yesterday: number; last30Days: number } => {
const totalActiveUsersLast30DaysIncludingToday = data.statisticsOverTime.find( const totalActiveUsersLast30DaysIncludingToday = data.statisticsOverTime.find(
(a: { name: string; period: number }) => a.name === measureName && a.period === 27, (a: { name: string; period: number }) => a.name === measureName && a.period === 27,
@@ -567,7 +569,7 @@ export const html = (data: any, timer: TimerInterface) => {
const totalActivePlusUsers = countActiveUsers(StatisticMeasureName.NAMES.ActivePlusUsers, data) const totalActivePlusUsers = countActiveUsers(StatisticMeasureName.NAMES.ActivePlusUsers, data)
const totalActiveProUsers = countActiveUsers(StatisticMeasureName.NAMES.ActiveProUsers, data) const totalActiveProUsers = countActiveUsers(StatisticMeasureName.NAMES.ActiveProUsers, data)
return ` <div> return safeHtml` <div>
<p>Hello,</p> <p>Hello,</p>
<p> <p>
<strong>Here are some statistics from yesterday:</strong> <strong>Here are some statistics from yesterday:</strong>

View File

@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
## [1.89.17](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.16...@standardnotes/api-gateway@1.89.17) (2024-01-04) ## [1.89.17](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.89.16...@standardnotes/api-gateway@1.89.17) (2024-01-04)
### Bug Fixes ### Bug Fixes

View File

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

View File

@@ -27,6 +27,7 @@ import '../src/Controller/v2/RevisionsControllerV2'
import helmet from 'helmet' import helmet from 'helmet'
import * as cors from 'cors' import * as cors from 'cors'
import * as cookieParser from 'cookie-parser'
import { text, json, Request, Response, NextFunction } from 'express' import { text, json, Request, Response, NextFunction } from 'express'
import * as winston from 'winston' import * as winston from 'winston'
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -36,15 +37,35 @@ import { InversifyExpressServer } from 'inversify-express-utils'
import { ContainerConfigLoader } from '../src/Bootstrap/Container' import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import { TYPES } from '../src/Bootstrap/Types' import { TYPES } from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env' import { Env } from '../src/Bootstrap/Env'
import { ResponseLocals } from '../src/Controller/ResponseLocals'
const container = new ContainerConfigLoader() const container = new ContainerConfigLoader()
void container.load().then((container) => { void container.load().then((container) => {
const env: Env = new Env() const env: Env = new Env()
env.load() 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) const server = new InversifyExpressServer(container)
server.setConfig((app) => { 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) => { app.use((_request: Request, response: Response, next: NextFunction) => {
response.setHeader('X-API-Gateway-Version', container.get(TYPES.ApiGateway_VERSION)) response.setHeader('X-API-Gateway-Version', container.get(TYPES.ApiGateway_VERSION))
next() next()
@@ -72,13 +93,57 @@ void container.load().then((container) => {
}), }),
) )
app.use(json({ limit: '50mb' })) app.use(cookieParser())
app.use(json({ limit: requestPayloadLimit }))
app.use( app.use(
text({ text({
type: ['text/plain', 'application/x-www-form-urlencoded', 'application/x-www-form-urlencoded; charset=utf-8'], 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( app.use(
robots({ robots({
UserAgent: '*', UserAgent: '*',
@@ -87,16 +152,18 @@ void container.load().then((container) => {
) )
}) })
const logger: winston.Logger = container.get(TYPES.ApiGateway_Logger)
server.setErrorConfig((app) => { server.setErrorConfig((app) => {
app.use((error: Record<string, unknown>, request: Request, response: Response, _next: NextFunction) => { app.use((error: Record<string, unknown>, request: Request, response: Response, _next: NextFunction) => {
const locals = response.locals as ResponseLocals
logger.error(`${error.stack}`, { logger.error(`${error.stack}`, {
origin: request.headers.origin,
codeTag: 'server.ts',
method: request.method, method: request.method,
url: request.url, url: request.url,
snjs: request.headers['x-snjs-version'], snjs: request.headers['x-snjs-version'],
application: request.headers['x-application-version'], application: request.headers['x-application-version'],
userId: response.locals.user ? response.locals.user.uuid : undefined, userId: locals.user ? locals.user.uuid : undefined,
}) })
logger.debug( logger.debug(
`[URL: |${request.method}| ${request.url}][SNJS: ${request.headers['x-snjs-version']}][Application: ${ `[URL: |${request.method}| ${request.url}][SNJS: ${request.headers['x-snjs-version']}][Application: ${
@@ -104,6 +171,16 @@ void container.load().then((container) => {
}] Request body: ${JSON.stringify(request.body)}`, }] 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({ response.status(500).send({
error: { error: {
message: message:

View File

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

View File

@@ -1,6 +1,7 @@
import * as winston from 'winston' import * as winston from 'winston'
import * as AgentKeepAlive from 'agentkeepalive' import * as AgentKeepAlive from 'agentkeepalive'
import * as grpc from '@grpc/grpc-js' import * as grpc from '@grpc/grpc-js'
import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
import axios, { AxiosInstance } from 'axios' import axios, { AxiosInstance } from 'axios'
import Redis from 'ioredis' import Redis from 'ioredis'
import { Container } from 'inversify' import { Container } from 'inversify'
@@ -29,6 +30,10 @@ import { SyncResponseHttpRepresentation } from '../Mapping/Sync/Http/SyncRespons
import { SyncRequestGRPCMapper } from '../Mapping/Sync/GRPC/SyncRequestGRPCMapper' import { SyncRequestGRPCMapper } from '../Mapping/Sync/GRPC/SyncRequestGRPCMapper'
import { SyncResponseGRPCMapper } from '../Mapping/Sync/GRPC/SyncResponseGRPCMapper' import { SyncResponseGRPCMapper } from '../Mapping/Sync/GRPC/SyncResponseGRPCMapper'
import { GRPCWebSocketAuthMiddleware } from '../Controller/GRPCWebSocketAuthMiddleware' 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 { export class ContainerConfigLoader {
async load(configuration?: { async load(configuration?: {
@@ -51,6 +56,34 @@ export class ContainerConfigLoader {
.bind<boolean>(TYPES.ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING) .bind<boolean>(TYPES.ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING)
.toConstantValue(isConfiguredForHomeServerOrSelfHosting) .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()] const winstonFormatters = [winston.format.splat(), winston.format.json()]
let logger: winston.Logger let logger: winston.Logger
@@ -109,6 +142,10 @@ export class ContainerConfigLoader {
.bind(TYPES.ApiGateway_CROSS_SERVICE_TOKEN_CACHE_TTL) .bind(TYPES.ApiGateway_CROSS_SERVICE_TOKEN_CACHE_TTL)
.toConstantValue(+env.get('CROSS_SERVICE_TOKEN_CACHE_TTL', true)) .toConstantValue(+env.get('CROSS_SERVICE_TOKEN_CACHE_TTL', true))
container.bind(TYPES.ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER).toConstantValue(isConfiguredForHomeServer) 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 // Middleware
container container
@@ -124,14 +161,14 @@ export class ContainerConfigLoader {
// Services // Services
container.bind<TimerInterface>(TYPES.ApiGateway_Timer).toConstantValue(new Timer()) container.bind<TimerInterface>(TYPES.ApiGateway_Timer).toConstantValue(new Timer())
if (isConfiguredForHomeServer) { if (isConfiguredForInMemoryCache) {
container container
.bind<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache) .bind<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache)
.toConstantValue(new InMemoryCrossServiceTokenCache(container.get(TYPES.ApiGateway_Timer))) .toConstantValue(new InMemoryCrossServiceTokenCache(container.get(TYPES.ApiGateway_Timer)))
} else { } else {
container container
.bind<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache) .bind<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache)
.to(RedisCrossServiceTokenCache) .toConstantValue(new RedisCrossServiceTokenCache(container.get(TYPES.ApiGateway_Redis)))
} }
container container
.bind<EndpointResolverInterface>(TYPES.ApiGateway_EndpointResolver) .bind<EndpointResolverInterface>(TYPES.ApiGateway_EndpointResolver)
@@ -192,6 +229,10 @@ export class ContainerConfigLoader {
.bind<MapperInterface<SyncResponse, SyncResponseHttpRepresentation>>(TYPES.Mapper_SyncResponseGRPCMapper) .bind<MapperInterface<SyncResponse, SyncResponseHttpRepresentation>>(TYPES.Mapper_SyncResponseGRPCMapper)
.toConstantValue(new SyncResponseGRPCMapper()) .toConstantValue(new SyncResponseGRPCMapper())
container
.bind<DomainEventFactoryInterface>(TYPES.ApiGateway_DomainEventFactory)
.toConstantValue(new DomainEventFactory(container.get<TimerInterface>(TYPES.ApiGateway_Timer)))
container container
.bind<GRPCSyncingServerServiceProxy>(TYPES.ApiGateway_GRPCSyncingServerServiceProxy) .bind<GRPCSyncingServerServiceProxy>(TYPES.ApiGateway_GRPCSyncingServerServiceProxy)
.toConstantValue( .toConstantValue(
@@ -202,6 +243,10 @@ export class ContainerConfigLoader {
TYPES.Mapper_SyncResponseGRPCMapper, TYPES.Mapper_SyncResponseGRPCMapper,
), ),
container.get<winston.Logger>(TYPES.ApiGateway_Logger), container.get<winston.Logger>(TYPES.ApiGateway_Logger),
container.get<DomainEventFactoryInterface>(TYPES.ApiGateway_DomainEventFactory),
isConfiguredForHomeServerOrSelfHosting
? undefined
: container.get<DomainEventPublisherInterface>(TYPES.ApiGateway_DomainEventPublisher),
), ),
) )
container container

View File

@@ -2,7 +2,12 @@ export const TYPES = {
ApiGateway_Logger: Symbol.for('ApiGateway_Logger'), ApiGateway_Logger: Symbol.for('ApiGateway_Logger'),
ApiGateway_Redis: Symbol.for('ApiGateway_Redis'), ApiGateway_Redis: Symbol.for('ApiGateway_Redis'),
ApiGateway_HTTPClient: Symbol.for('ApiGateway_HTTPClient'), ApiGateway_HTTPClient: Symbol.for('ApiGateway_HTTPClient'),
ApiGateway_SNS: Symbol.for('ApiGateway_SNS'),
ApiGateway_DomainEventPublisher: Symbol.for('ApiGateway_DomainEventPublisher'),
// env vars // 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_SYNCING_SERVER_JS_URL: Symbol.for('ApiGateway_SYNCING_SERVER_JS_URL'),
ApiGateway_AUTH_SERVER_URL: Symbol.for('ApiGateway_AUTH_SERVER_URL'), ApiGateway_AUTH_SERVER_URL: Symbol.for('ApiGateway_AUTH_SERVER_URL'),
ApiGateway_AUTH_SERVER_GRPC_URL: Symbol.for('ApiGateway_AUTH_SERVER_GRPC_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: Symbol.for(
'ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING', 'ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING',
), ),
ApiGateway_CAPTCHA_UI_URL: Symbol.for('ApiGateway_CAPTCHA_UI_URL'),
// Middleware // Middleware
ApiGateway_RequiredCrossServiceTokenMiddleware: Symbol.for('ApiGateway_RequiredCrossServiceTokenMiddleware'), ApiGateway_RequiredCrossServiceTokenMiddleware: Symbol.for('ApiGateway_RequiredCrossServiceTokenMiddleware'),
ApiGateway_OptionalCrossServiceTokenMiddleware: Symbol.for('ApiGateway_OptionalCrossServiceTokenMiddleware'), ApiGateway_OptionalCrossServiceTokenMiddleware: Symbol.for('ApiGateway_OptionalCrossServiceTokenMiddleware'),
@@ -29,6 +35,7 @@ export const TYPES = {
Mapper_SyncRequestGRPCMapper: Symbol.for('Mapper_SyncRequestGRPCMapper'), Mapper_SyncRequestGRPCMapper: Symbol.for('Mapper_SyncRequestGRPCMapper'),
Mapper_SyncResponseGRPCMapper: Symbol.for('Mapper_SyncResponseGRPCMapper'), Mapper_SyncResponseGRPCMapper: Symbol.for('Mapper_SyncResponseGRPCMapper'),
// Services // Services
ApiGateway_DomainEventFactory: Symbol.for('ApiGateway_DomainEventFactory'),
ApiGateway_GRPCSyncingServerServiceProxy: Symbol.for('ApiGateway_GRPCSyncingServerServiceProxy'), ApiGateway_GRPCSyncingServerServiceProxy: Symbol.for('ApiGateway_GRPCSyncingServerServiceProxy'),
ApiGateway_ServiceProxy: Symbol.for('ApiGateway_ServiceProxy'), ApiGateway_ServiceProxy: Symbol.for('ApiGateway_ServiceProxy'),
ApiGateway_CrossServiceTokenCache: Symbol.for('ApiGateway_CrossServiceTokenCache'), ApiGateway_CrossServiceTokenCache: Symbol.for('ApiGateway_CrossServiceTokenCache'),

View File

@@ -8,6 +8,8 @@ import { Logger } from 'winston'
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface' import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from '../Service/Proxy/ServiceProxyInterface' import { ServiceProxyInterface } from '../Service/Proxy/ServiceProxyInterface'
import { ResponseLocals } from './ResponseLocals'
import { RoleName } from '@standardnotes/domain-core'
export abstract class AuthMiddleware extends BaseMiddleware { export abstract class AuthMiddleware extends BaseMiddleware {
constructor( constructor(
@@ -40,9 +42,33 @@ export abstract class AuthMiddleware extends BaseMiddleware {
} }
if (crossServiceToken === null) { 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({ const authResponse = await this.serviceProxy.validateSession({
authorization: authHeaderValue, headers: {
sharedVaultOwnerContext: sharedVaultOwnerContextHeaderValue, 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)) { if (!this.handleSessionValidationResponse(authResponse, response, next)) {
@@ -55,33 +81,27 @@ export abstract class AuthMiddleware extends BaseMiddleware {
crossServiceTokenFetchedFromCache = false crossServiceTokenFetchedFromCache = false
} }
response.locals.authToken = crossServiceToken const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
const decodedToken = <CrossServiceTokenData>(
verify(response.locals.authToken, this.jwtSecret, { algorithms: ['HS256'] })
)
if (this.crossServiceTokenCacheTTL && !crossServiceTokenFetchedFromCache) { if (this.crossServiceTokenCacheTTL && !crossServiceTokenFetchedFromCache) {
await this.crossServiceTokenCache.set({ await this.crossServiceTokenCache.set({
key: cacheKey, key: cacheKey,
encodedCrossServiceToken: response.locals.authToken, encodedCrossServiceToken: crossServiceToken,
expiresAtInSeconds: this.getCrossServiceTokenCacheExpireTimestamp(decodedToken), expiresAtInSeconds: this.getCrossServiceTokenCacheExpireTimestamp(decodedToken),
userUuid: decodedToken.user.uuid, userUuid: decodedToken.user.uuid,
}) })
} }
response.locals.user = decodedToken.user Object.assign(response.locals, {
response.locals.session = decodedToken.session authToken: crossServiceToken,
response.locals.roles = decodedToken.roles user: decodedToken.user,
response.locals.sharedVaultOwnerContext = decodedToken.shared_vault_owner_context session: decodedToken.session,
response.locals.readOnlyAccess = decodedToken.session?.readonly_access ?? false roles: decodedToken.roles,
if (response.locals.readOnlyAccess) { sharedVaultOwnerContext: decodedToken.shared_vault_owner_context,
this.logger.debug('User operates on read-only access', { readOnlyAccess: decodedToken.session?.readonly_access ?? false,
codeTag: 'AuthMiddleware', isFreeUser: decodedToken.roles.length === 1 && decodedToken.roles[0].name === RoleName.NAMES.CoreUser,
userId: response.locals.user.uuid, belongsToSharedVaults: decodedToken.belongs_to_shared_vaults ?? [],
}) } as ResponseLocals)
}
response.locals.belongsToSharedVaults = decodedToken.belongs_to_shared_vaults ?? []
} catch (error) { } catch (error) {
let detailedErrorMessage = (error as Error).message let detailedErrorMessage = (error as Error).message
if (error instanceof AxiosError) { if (error instanceof AxiosError) {

View File

@@ -6,6 +6,7 @@ import { verify } from 'jsonwebtoken'
import { Logger } from 'winston' import { Logger } from 'winston'
import { ConnectionValidationResponse, IAuthClient, WebsocketConnectionAuthorizationHeader } from '@standardnotes/grpc' import { ConnectionValidationResponse, IAuthClient, WebsocketConnectionAuthorizationHeader } from '@standardnotes/grpc'
import { RoleName } from '@standardnotes/domain-core' import { RoleName } from '@standardnotes/domain-core'
import { ResponseLocals } from './ResponseLocals'
export class GRPCWebSocketAuthMiddleware extends BaseMiddleware { export class GRPCWebSocketAuthMiddleware extends BaseMiddleware {
constructor( constructor(
@@ -90,15 +91,17 @@ export class GRPCWebSocketAuthMiddleware extends BaseMiddleware {
const crossServiceToken = authResponse.data.authToken as string const crossServiceToken = authResponse.data.authToken as string
response.locals.authToken = crossServiceToken
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] }) const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
response.locals.user = decodedToken.user Object.assign(response.locals, {
response.locals.session = decodedToken.session authToken: crossServiceToken,
response.locals.roles = decodedToken.roles user: decodedToken.user,
response.locals.isFreeUser = session: decodedToken.session,
decodedToken.roles.length === 1 && decodedToken.roles[0].name === RoleName.NAMES.CoreUser 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) { } catch (error) {
this.logger.error( this.logger.error(
`Could not pass the request to websocket connection validation on underlying service: ${ `Could not pass the request to websocket connection validation on underlying service: ${

View File

@@ -20,8 +20,6 @@ export class LegacyController extends BaseHttpController {
['DELETE:/session', 'DELETE:session'], ['DELETE:/session', 'DELETE:session'],
['DELETE:/session/all', 'DELETE:session/all'], ['DELETE:/session/all', 'DELETE:session/all'],
['POST:/session/refresh', 'POST:session/refresh'], ['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([ this.PARAMETRIZED_AUTH_ROUTES = new Map([

View File

@@ -0,0 +1,5 @@
export interface OfflineResponseLocals {
offlineAuthToken: string
userEmail: string
featuresToken: string
}

View File

@@ -0,0 +1,30 @@
import { Role } from '@standardnotes/security'
export interface ResponseLocals {
authToken: string
user: {
uuid: string
email: string
}
roles: Array<Role>
session?: {
uuid: string
api_version: string
created_at: string
updated_at: string
device_info: string
readonly_access: boolean
access_expiration: string
refresh_expiration: string
}
readOnlyAccess: boolean
isFreeUser: boolean
belongsToSharedVaults?: Array<{
shared_vault_uuid: string
permission: string
}>
sharedVaultOwnerContext?: {
upload_bytes_limit: number
}
hasContentLimit: boolean
}

View File

@@ -0,0 +1,5 @@
import { TokenAuthenticationMethod } from './TokenAuthenticationMethod'
export interface SubscriptionResponseLocals {
tokenAuthenticationMethod: TokenAuthenticationMethod
}

View File

@@ -7,6 +7,9 @@ import { AxiosError, AxiosInstance, AxiosResponse } from 'axios'
import { Logger } from 'winston' import { Logger } from 'winston'
import { TYPES } from '../Bootstrap/Types' import { TYPES } from '../Bootstrap/Types'
import { TokenAuthenticationMethod } from './TokenAuthenticationMethod' import { TokenAuthenticationMethod } from './TokenAuthenticationMethod'
import { ResponseLocals } from './ResponseLocals'
import { OfflineResponseLocals } from './OfflineResponseLocals'
import { SubscriptionResponseLocals } from './SubscriptionResponseLocals'
@injectable() @injectable()
export class SubscriptionTokenAuthMiddleware extends BaseMiddleware { export class SubscriptionTokenAuthMiddleware extends BaseMiddleware {
@@ -34,13 +37,16 @@ export class SubscriptionTokenAuthMiddleware extends BaseMiddleware {
return return
} }
response.locals.tokenAuthenticationMethod = email const locals = {
? TokenAuthenticationMethod.OfflineSubscriptionToken tokenAuthenticationMethod: email
: TokenAuthenticationMethod.SubscriptionToken ? TokenAuthenticationMethod.OfflineSubscriptionToken
: TokenAuthenticationMethod.SubscriptionToken,
} as SubscriptionResponseLocals
Object.assign(response.locals, locals)
try { try {
const url = const url =
response.locals.tokenAuthenticationMethod == TokenAuthenticationMethod.OfflineSubscriptionToken locals.tokenAuthenticationMethod == TokenAuthenticationMethod.OfflineSubscriptionToken
? `${this.authServerUrl}/offline/subscription-tokens/${subscriptionToken}/validate` ? `${this.authServerUrl}/offline/subscription-tokens/${subscriptionToken}/validate`
: `${this.authServerUrl}/subscription-tokens/${subscriptionToken}/validate` : `${this.authServerUrl}/subscription-tokens/${subscriptionToken}/validate`
@@ -65,7 +71,7 @@ export class SubscriptionTokenAuthMiddleware extends BaseMiddleware {
return return
} }
if (response.locals.tokenAuthenticationMethod == TokenAuthenticationMethod.OfflineSubscriptionToken) { if (locals.tokenAuthenticationMethod == TokenAuthenticationMethod.OfflineSubscriptionToken) {
this.handleOfflineAuthTokenValidationResponse(response, authResponse) this.handleOfflineAuthTokenValidationResponse(response, authResponse)
return next() return next()
@@ -101,24 +107,26 @@ export class SubscriptionTokenAuthMiddleware extends BaseMiddleware {
} }
private handleOfflineAuthTokenValidationResponse(response: Response, authResponse: AxiosResponse) { private handleOfflineAuthTokenValidationResponse(response: Response, authResponse: AxiosResponse) {
response.locals.offlineAuthToken = authResponse.data.authToken
const decodedToken = <OfflineUserTokenData>( const decodedToken = <OfflineUserTokenData>(
verify(authResponse.data.authToken, this.jwtSecret, { algorithms: ['HS256'] }) verify(authResponse.data.authToken, this.jwtSecret, { algorithms: ['HS256'] })
) )
response.locals.offlineUserEmail = decodedToken.userEmail Object.assign(response.locals, {
response.locals.offlineFeaturesToken = decodedToken.featuresToken offlineAuthToken: authResponse.data.authToken,
userEmail: decodedToken.userEmail,
featuresToken: decodedToken.featuresToken,
} as OfflineResponseLocals)
} }
private handleAuthTokenValidationResponse(response: Response, authResponse: AxiosResponse) { private handleAuthTokenValidationResponse(response: Response, authResponse: AxiosResponse) {
response.locals.authToken = authResponse.data.authToken
const decodedToken = <CrossServiceTokenData>( const decodedToken = <CrossServiceTokenData>(
verify(authResponse.data.authToken, this.jwtSecret, { algorithms: ['HS256'] }) verify(authResponse.data.authToken, this.jwtSecret, { algorithms: ['HS256'] })
) )
response.locals.user = decodedToken.user Object.assign(response.locals, {
response.locals.roles = decodedToken.roles authToken: authResponse.data.authToken,
user: decodedToken.user,
roles: decodedToken.roles,
} as ResponseLocals)
} }
} }

View File

@@ -7,6 +7,7 @@ import { AxiosError, AxiosInstance } from 'axios'
import { Logger } from 'winston' import { Logger } from 'winston'
import { TYPES } from '../Bootstrap/Types' import { TYPES } from '../Bootstrap/Types'
import { ResponseLocals } from './ResponseLocals'
@injectable() @injectable()
export class WebSocketAuthMiddleware extends BaseMiddleware { export class WebSocketAuthMiddleware extends BaseMiddleware {
@@ -55,13 +56,14 @@ export class WebSocketAuthMiddleware extends BaseMiddleware {
const crossServiceToken = authResponse.data.authToken const crossServiceToken = authResponse.data.authToken
response.locals.authToken = crossServiceToken
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] }) const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
response.locals.user = decodedToken.user Object.assign(response.locals, {
response.locals.session = decodedToken.session authToken: crossServiceToken,
response.locals.roles = decodedToken.roles user: decodedToken.user,
session: decodedToken.session,
roles: decodedToken.roles,
} as ResponseLocals)
} catch (error) { } catch (error) {
const errorMessage = (error as AxiosError).isAxiosError const errorMessage = (error as AxiosError).isAxiosError
? JSON.stringify((error as AxiosError).response?.data) ? JSON.stringify((error as AxiosError).response?.data)

View File

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

View File

@@ -6,7 +6,6 @@ import {
controller, controller,
httpDelete, httpDelete,
httpGet, httpGet,
httpPatch,
httpPost, httpPost,
httpPut, httpPut,
results, results,
@@ -16,6 +15,8 @@ import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { TokenAuthenticationMethod } from '../TokenAuthenticationMethod' import { TokenAuthenticationMethod } from '../TokenAuthenticationMethod'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
import { ResponseLocals } from '../ResponseLocals'
import { SubscriptionResponseLocals } from '../SubscriptionResponseLocals'
@controller('/v1/users') @controller('/v1/users')
export class UsersController extends BaseHttpController { export class UsersController extends BaseHttpController {
@@ -37,16 +38,6 @@ export class UsersController extends BaseHttpController {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/send-activation-code', request.body) 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) @httpPut('/:userUuid/password', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async changePassword(request: Request, response: Response): Promise<void> { async changePassword(request: Request, response: Response): Promise<void> {
this.logger.debug( this.logger.debug(
@@ -84,7 +75,7 @@ export class UsersController extends BaseHttpController {
await this.httpService.callAuthServer( await this.httpService.callAuthServer(
request, request,
response, response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('GET', 'auth/params'), this.endpointResolver.resolveEndpointOrMethodIdentifier('POST', 'auth/pkce_params'),
) )
} }
@@ -140,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) @httpGet('/:userUuid/settings/:settingName', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async getSetting(request: Request, response: Response): Promise<void> { async getSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer( await this.httpService.callAuthServer(
@@ -214,7 +219,9 @@ export class UsersController extends BaseHttpController {
@httpGet('/subscription', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware) @httpGet('/subscription', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async getSubscriptionBySubscriptionToken(request: Request, response: Response): Promise<void> { async getSubscriptionBySubscriptionToken(request: Request, response: Response): Promise<void> {
if (response.locals.tokenAuthenticationMethod === TokenAuthenticationMethod.OfflineSubscriptionToken) { const locals = response.locals as SubscriptionResponseLocals & ResponseLocals
if (locals.tokenAuthenticationMethod === TokenAuthenticationMethod.OfflineSubscriptionToken) {
await this.httpService.callAuthServer( await this.httpService.callAuthServer(
request, request,
response, response,
@@ -227,11 +234,7 @@ export class UsersController extends BaseHttpController {
await this.httpService.callAuthServer( await this.httpService.callAuthServer(
request, request,
response, response,
this.endpointResolver.resolveEndpointOrMethodIdentifier( this.endpointResolver.resolveEndpointOrMethodIdentifier('GET', 'users/:userUuid/subscription', locals.user.uuid),
'GET',
'users/:userUuid/subscription',
response.locals.user.uuid,
),
) )
} }

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 * as IORedis from 'ioredis'
import { TYPES } from '../../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../../Service/Cache/CrossServiceTokenCacheInterface' import { CrossServiceTokenCacheInterface } from '../../Service/Cache/CrossServiceTokenCacheInterface'
@injectable()
export class RedisCrossServiceTokenCache implements CrossServiceTokenCacheInterface { export class RedisCrossServiceTokenCache implements CrossServiceTokenCacheInterface {
private readonly PREFIX = 'cst' private readonly PREFIX = 'cst'
private readonly USER_CST_PREFIX = 'user-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: { async set(dto: {
key: string key: string

View File

@@ -2,6 +2,7 @@ import { Request, Response } from 'express'
import { ServiceContainerInterface, ServiceIdentifier } from '@standardnotes/domain-core' import { ServiceContainerInterface, ServiceIdentifier } from '@standardnotes/domain-core'
import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface' import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
import { ResponseLocals } from '../../Controller/ResponseLocals'
export class DirectCallServiceProxy implements ServiceProxyInterface { export class DirectCallServiceProxy implements ServiceProxyInterface {
constructor( constructor(
@@ -9,23 +10,44 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
private filesServerUrl: string, private filesServerUrl: string,
) {} ) {}
async validateSession( async validateSession(dto: {
headers: { headers: {
authorization: string authorization: string
sharedVaultOwnerContext?: string sharedVaultOwnerContext?: string
}, }
_retryAttempt?: number, cookies?: Map<string, string[]>
): Promise<{ status: number; data: unknown; headers: { contentType: 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()) const authService = this.serviceContainer.get(ServiceIdentifier.create(ServiceIdentifier.NAMES.Auth).getValue())
if (!authService) { if (!authService) {
throw new Error('Auth service not found') 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( const serviceResponse = (await authService.handleRequest(
{ {
body: {
authTokenFromHeaders: dto.headers.authorization,
sharedVaultOwnerContext: dto.headers.sharedVaultOwnerContext,
},
headers: { headers: {
authorization: headers.authorization, 'x-snjs-version': dto.snjs,
'x-shared-vault-owner-context': headers.sharedVaultOwnerContext, 'x-application-version': dto.application,
cookie: stringOfCookies.trim(),
}, },
} as never, } as never,
{} as never, {} as never,
@@ -134,11 +156,13 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
response: Response, response: Response,
serviceResponse: { statusCode: number; json: Record<string, unknown> }, serviceResponse: { statusCode: number; json: Record<string, unknown> },
): void { ): void {
const locals = response.locals as ResponseLocals
void response.status(serviceResponse.statusCode).send({ void response.status(serviceResponse.statusCode).send({
meta: { meta: {
auth: { auth: {
userUuid: response.locals.user?.uuid, userUuid: locals.user?.uuid,
roles: response.locals.roles, roles: locals.roles,
}, },
server: { server: {
filesServerUrl: this.filesServerUrl, filesServerUrl: this.filesServerUrl,

View File

@@ -8,6 +8,8 @@ import { TYPES } from '../../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface' import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface' import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
import { TimerInterface } from '@standardnotes/time' import { TimerInterface } from '@standardnotes/time'
import { ResponseLocals } from '../../Controller/ResponseLocals'
import { OfflineResponseLocals } from '../../Controller/OfflineResponseLocals'
@injectable() @injectable()
export class HttpServiceProxy implements ServiceProxyInterface { export class HttpServiceProxy implements ServiceProxyInterface {
@@ -26,20 +28,51 @@ export class HttpServiceProxy implements ServiceProxyInterface {
@inject(TYPES.ApiGateway_Timer) private timer: TimerInterface, @inject(TYPES.ApiGateway_Timer) private timer: TimerInterface,
) {} ) {}
async validateSession( async validateSession(dto: {
headers: { headers: {
authorization: string authorization: string
sharedVaultOwnerContext?: string sharedVaultOwnerContext?: string
}, }
retryAttempt?: number, requestMetadata: {
): Promise<{ status: number; data: unknown; headers: { contentType: string } }> { 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 { 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({ const authResponse = await this.httpClient.request({
method: 'POST', method: 'POST',
headers: { headers: {
Authorization: headers.authorization,
Accept: 'application/json', 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) => { validateStatus: (status: number) => {
return status >= 200 && status < 500 return status >= 200 && status < 500
@@ -56,13 +89,18 @@ export class HttpServiceProxy implements ServiceProxyInterface {
} }
} catch (error) { } catch (error) {
const requestDidNotMakeIt = this.requestTimedOutOrDidNotReachDestination(error as Record<string, unknown>) const requestDidNotMakeIt = this.requestTimedOutOrDidNotReachDestination(error as Record<string, unknown>)
const tooManyRetryAttempts = retryAttempt && retryAttempt > 2 const tooManyRetryAttempts = dto.retryAttempt && dto.retryAttempt > 2
if (!tooManyRetryAttempts && requestDidNotMakeIt) { if (!tooManyRetryAttempts && requestDidNotMakeIt) {
await this.timer.sleep(50) 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 throw error
@@ -176,21 +214,32 @@ export class HttpServiceProxy implements ServiceProxyInterface {
endpoint: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<AxiosResponse | undefined> { ): Promise<AxiosResponse | undefined> {
const locals = response.locals as ResponseLocals | OfflineResponseLocals
try { try {
const headers: Record<string, string> = {} const headers: Record<string, string> = {}
for (const headerName of Object.keys(request.headers)) { for (const headerName of Object.keys(request.headers)) {
headers[headerName] = request.headers[headerName] as string 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.host
delete headers['content-length'] delete headers['content-length']
if (response.locals.authToken) { headers.cookie = request.headers.cookie as string
headers['X-Auth-Token'] = response.locals.authToken
if ('authToken' in locals && locals.authToken) {
headers['X-Auth-Token'] = locals.authToken
} }
if (response.locals.offlineAuthToken) { if ('offlineAuthToken' in locals && locals.offlineAuthToken) {
headers['X-Auth-Offline-Token'] = response.locals.offlineAuthToken headers['X-Auth-Offline-Token'] = locals.offlineAuthToken
} }
const serviceResponse = await this.httpClient.request({ const serviceResponse = await this.httpClient.request({
@@ -222,7 +271,7 @@ export class HttpServiceProxy implements ServiceProxyInterface {
this.logger.error( this.logger.error(
`Could not pass the request to ${serverUrl}/${endpoint} on underlying service: ${detailedErrorMessage}`, `Could not pass the request to ${serverUrl}/${endpoint} on underlying service: ${detailedErrorMessage}`,
{ {
userId: response.locals.user ? response.locals.user.uuid : undefined, userId: (locals as ResponseLocals).user ? (locals as ResponseLocals).user.uuid : undefined,
}, },
) )
@@ -257,6 +306,7 @@ export class HttpServiceProxy implements ServiceProxyInterface {
endpoint: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void> { ): Promise<void> {
const locals = response.locals as ResponseLocals
const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload) const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload)
if (!serviceResponse) { if (!serviceResponse) {
@@ -274,8 +324,8 @@ export class HttpServiceProxy implements ServiceProxyInterface {
response.status(serviceResponse.status).send({ response.status(serviceResponse.status).send({
meta: { meta: {
auth: { auth: {
userUuid: response.locals.user?.uuid, userUuid: locals.user?.uuid,
roles: response.locals.roles, roles: locals.roles,
}, },
server: { server: {
filesServerUrl: this.filesServerUrl, filesServerUrl: this.filesServerUrl,
@@ -335,13 +385,11 @@ export class HttpServiceProxy implements ServiceProxyInterface {
private applyResponseHeaders(serviceResponse: AxiosResponse, response: Response): void { private applyResponseHeaders(serviceResponse: AxiosResponse, response: Response): void {
const returnedHeadersFromUnderlyingService = [ const returnedHeadersFromUnderlyingService = [
'access-control-allow-methods',
'access-control-allow-origin',
'access-control-expose-headers',
'authorization',
'content-type', 'content-type',
'x-ssjs-version', 'authorization',
'x-auth-version', 'set-cookie',
'access-control-expose-headers',
'x-captcha-required',
] ]
returnedHeadersFromUnderlyingService.map((headerName) => { returnedHeadersFromUnderlyingService.map((headerName) => {

View File

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

View File

@@ -7,8 +7,6 @@ export class EndpointResolver implements EndpointResolverInterface {
// Auth Middleware // Auth Middleware
['[POST]:sessions/validate', 'auth.sessions.validate'], ['[POST]:sessions/validate', 'auth.sessions.validate'],
// Actions Controller // Actions Controller
['[POST]:auth/sign_in', 'auth.signIn'],
['[GET]:auth/params', 'auth.params'],
['[POST]:auth/sign_out', 'auth.signOut'], ['[POST]:auth/sign_out', 'auth.signOut'],
['[POST]:auth/recovery/codes', 'auth.generateRecoveryCodes'], ['[POST]:auth/recovery/codes', 'auth.generateRecoveryCodes'],
['[POST]:auth/recovery/login', 'auth.signInWithRecoveryCodes'], ['[POST]:auth/recovery/login', 'auth.signInWithRecoveryCodes'],
@@ -40,7 +38,6 @@ export class EndpointResolver implements EndpointResolverInterface {
// Tokens Controller // Tokens Controller
['[POST]:subscription-tokens', 'auth.subscription-tokens.create'], ['[POST]:subscription-tokens', 'auth.subscription-tokens.create'],
// Users Controller // Users Controller
['[PATCH]:users/:userId', 'auth.users.update'],
['[PUT]:users/:userUuid/attributes/credentials', 'auth.users.updateCredentials'], ['[PUT]:users/:userUuid/attributes/credentials', 'auth.users.updateCredentials'],
['[DELETE]:users/:userUuid', 'auth.users.delete'], ['[DELETE]:users/:userUuid', 'auth.users.delete'],
['[POST]:listed', 'auth.users.createListedAccount'], ['[POST]:listed', 'auth.users.createListedAccount'],
@@ -49,6 +46,7 @@ export class EndpointResolver implements EndpointResolverInterface {
['[PUT]:users/:userUuid/settings', 'auth.users.updateSetting'], ['[PUT]:users/:userUuid/settings', 'auth.users.updateSetting'],
['[GET]:users/:userUuid/settings/:settingName', 'auth.users.getSetting'], ['[GET]:users/:userUuid/settings/:settingName', 'auth.users.getSetting'],
['[DELETE]:users/:userUuid/settings/:settingName', 'auth.users.deleteSetting'], ['[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/subscription-settings/:subscriptionSettingName', 'auth.users.getSubscriptionSetting'],
['[GET]:users/:userUuid/features', 'auth.users.getFeatures'], ['[GET]:users/:userUuid/features', 'auth.users.getFeatures'],
['[GET]:users/:userUuid/subscription', 'auth.users.getSubscription'], ['[GET]:users/:userUuid/subscription', 'auth.users.getSubscription'],

View File

@@ -2,13 +2,15 @@ import { AxiosInstance, AxiosError, AxiosResponse, Method } from 'axios'
import { Request, Response } from 'express' import { Request, Response } from 'express'
import { Logger } from 'winston' import { Logger } from 'winston'
import { TimerInterface } from '@standardnotes/time' 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 * as grpc from '@grpc/grpc-js'
import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface' import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface' import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
import { GRPCSyncingServerServiceProxy } from './GRPCSyncingServerServiceProxy' import { GRPCSyncingServerServiceProxy } from './GRPCSyncingServerServiceProxy'
import { Status } from '@grpc/grpc-js/build/src/constants' import { Status } from '@grpc/grpc-js/build/src/constants'
import { ResponseLocals } from '../../Controller/ResponseLocals'
import { OfflineResponseLocals } from '../../Controller/OfflineResponseLocals'
export class GRPCServiceProxy implements ServiceProxyInterface { export class GRPCServiceProxy implements ServiceProxyInterface {
constructor( constructor(
@@ -28,23 +30,56 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
private gRPCSyncingServerServiceProxy: GRPCSyncingServerServiceProxy, private gRPCSyncingServerServiceProxy: GRPCSyncingServerServiceProxy,
) {} ) {}
async validateSession( async validateSession(dto: {
headers: { headers: {
authorization: string authorization: string
sharedVaultOwnerContext?: string sharedVaultOwnerContext?: string
}, }
retryAttempt?: number, requestMetadata: {
): Promise<{ status: number; data: unknown; headers: { contentType: string } }> { 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) => { const promise = new Promise((resolve, reject) => {
try { try {
const request = new AuthorizationHeader() const request = new RequestValidationOptions()
request.setBearerToken(headers.authorization) request.setBearerToken(dto.headers.authorization)
const metadata = new grpc.Metadata() for (const cookieName of dto.cookies?.keys() ?? []) {
metadata.set('x-shared-vault-owner-context', headers.sharedVaultOwnerContext ?? '') 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') 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( this.authClient.validate(
request, request,
metadata, metadata,
@@ -88,8 +123,8 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
try { try {
const result = await promise const result = await promise
if (retryAttempt) { if (dto.retryAttempt) {
this.logger.debug(`Request to Auth Server succeeded after ${retryAttempt} retries`) this.logger.info(`Request to Auth Server succeeded after ${dto.retryAttempt} retries`)
} }
return result as { status: number; data: unknown; headers: { contentType: string } } return result as { status: number; data: unknown; headers: { contentType: string } }
@@ -97,15 +132,20 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
const requestDidNotMakeIt = const requestDidNotMakeIt =
'code' in (error as Record<string, unknown>) && (error as Record<string, unknown>).code === Status.UNAVAILABLE '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) { if (!tooManyRetryAttempts && requestDidNotMakeIt) {
await this.timer.sleep(50) 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 throw error
@@ -135,13 +175,15 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
response: Response, response: Response,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void> { ): Promise<void> {
const locals = response.locals as ResponseLocals
const result = await this.gRPCSyncingServerServiceProxy.sync(request, response, payload) const result = await this.gRPCSyncingServerServiceProxy.sync(request, response, payload)
response.status(result.status).send({ response.status(result.status).send({
meta: { meta: {
auth: { auth: {
userUuid: response.locals.user?.uuid, userUuid: locals.user?.uuid,
roles: response.locals.roles, roles: locals.roles,
}, },
server: { server: {
filesServerUrl: this.filesServerUrl, filesServerUrl: this.filesServerUrl,
@@ -250,6 +292,8 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
retryAttempt?: number, retryAttempt?: number,
): Promise<AxiosResponse | undefined> { ): Promise<AxiosResponse | undefined> {
const locals = response.locals as ResponseLocals | OfflineResponseLocals
try { try {
const headers: Record<string, string> = {} const headers: Record<string, string> = {}
for (const headerName of Object.keys(request.headers)) { for (const headerName of Object.keys(request.headers)) {
@@ -259,12 +303,14 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
delete headers.host delete headers.host
delete headers['content-length'] delete headers['content-length']
if (response.locals.authToken) { headers.cookie = request.headers.cookie as string
headers['X-Auth-Token'] = response.locals.authToken
if ('authToken' in locals && locals.authToken) {
headers['X-Auth-Token'] = locals.authToken
} }
if (response.locals.offlineAuthToken) { if ('offlineAuthToken' in locals && locals.offlineAuthToken) {
headers['X-Auth-Offline-Token'] = response.locals.offlineAuthToken headers['X-Auth-Offline-Token'] = locals.offlineAuthToken
} }
const serviceResponse = await this.httpClient.request({ const serviceResponse = await this.httpClient.request({
@@ -314,7 +360,7 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
? `Request to ${serverUrl}/${endpoint} timed out after ${retryAttempt} retries` ? `Request to ${serverUrl}/${endpoint} timed out after ${retryAttempt} retries`
: `Could not pass the request to ${serverUrl}/${endpoint} on underlying service: ${detailedErrorMessage}`, : `Could not pass the request to ${serverUrl}/${endpoint} on underlying service: ${detailedErrorMessage}`,
{ {
userId: response.locals.user ? response.locals.user.uuid : undefined, userId: (locals as ResponseLocals).user ? (locals as ResponseLocals).user.uuid : undefined,
}, },
) )
@@ -349,6 +395,8 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
endpoint: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void> { ): Promise<void> {
const locals = response.locals as ResponseLocals
const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload) const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload)
if (!serviceResponse) { if (!serviceResponse) {
@@ -366,8 +414,8 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
response.status(serviceResponse.status).send({ response.status(serviceResponse.status).send({
meta: { meta: {
auth: { auth: {
userUuid: response.locals.user?.uuid, userUuid: locals.user?.uuid,
roles: response.locals.roles, roles: locals.roles,
}, },
server: { server: {
filesServerUrl: this.filesServerUrl, filesServerUrl: this.filesServerUrl,
@@ -427,13 +475,11 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
private applyResponseHeaders(serviceResponse: AxiosResponse, response: Response): void { private applyResponseHeaders(serviceResponse: AxiosResponse, response: Response): void {
const returnedHeadersFromUnderlyingService = [ const returnedHeadersFromUnderlyingService = [
'access-control-allow-methods',
'access-control-allow-origin',
'access-control-expose-headers',
'authorization',
'content-type', 'content-type',
'x-ssjs-version', 'authorization',
'x-auth-version', 'set-cookie',
'access-control-expose-headers',
'x-captcha-required',
] ]
returnedHeadersFromUnderlyingService.map((headerName) => { returnedHeadersFromUnderlyingService.map((headerName) => {

View File

@@ -1,18 +1,23 @@
import { Request, Response } from 'express' import { Request, Response } from 'express'
import { ISyncingClient, SyncRequest, SyncResponse } from '@standardnotes/grpc' import { ISyncingClient, SyncRequest, SyncResponse } from '@standardnotes/grpc'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { MapperInterface } from '@standardnotes/domain-core' import { MapperInterface } from '@standardnotes/domain-core'
import { Metadata } from '@grpc/grpc-js' import { Metadata } from '@grpc/grpc-js'
import { SyncResponseHttpRepresentation } from '../../Mapping/Sync/Http/SyncResponseHttpRepresentation'
import { Status } from '@grpc/grpc-js/build/src/constants' import { Status } from '@grpc/grpc-js/build/src/constants'
import { Logger } from 'winston' import { Logger } from 'winston'
import { SyncResponseHttpRepresentation } from '../../Mapping/Sync/Http/SyncResponseHttpRepresentation'
import { ResponseLocals } from '../../Controller/ResponseLocals'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
export class GRPCSyncingServerServiceProxy { export class GRPCSyncingServerServiceProxy {
constructor( constructor(
private syncingClient: ISyncingClient, private syncingClient: ISyncingClient,
private syncRequestGRPCMapper: MapperInterface<Record<string, unknown>, SyncRequest>, private syncRequestGRPCMapper: MapperInterface<Record<string, unknown>, SyncRequest>,
private syncResponseGRPCMapper: MapperInterface<SyncResponse, SyncResponseHttpRepresentation>, private syncResponseGRPCMapper: MapperInterface<SyncResponse, SyncResponseHttpRepresentation>,
private logger: Logger, private logger: Logger,
private domainEventFactory: DomainEventFactoryInterface,
private domainEventPublisher?: DomainEventPublisherInterface,
) {} ) {}
async sync( async sync(
@@ -20,24 +25,27 @@ export class GRPCSyncingServerServiceProxy {
response: Response, response: Response,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<{ status: number; data: unknown }> { ): Promise<{ status: number; data: unknown }> {
const locals = response.locals as ResponseLocals
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
const syncRequest = this.syncRequestGRPCMapper.toProjection(payload as Record<string, unknown>) const syncRequest = this.syncRequestGRPCMapper.toProjection(payload as Record<string, unknown>)
const metadata = new Metadata() const metadata = new Metadata()
metadata.set('x-user-uuid', response.locals.user.uuid) metadata.set('x-user-uuid', locals.user.uuid)
metadata.set('x-snjs-version', request.headers['x-snjs-version'] as string) metadata.set('x-snjs-version', request.headers['x-snjs-version'] as string)
metadata.set('x-read-only-access', response.locals.readOnlyAccess ? 'true' : 'false') metadata.set('x-read-only-access', locals.readOnlyAccess ? 'true' : 'false')
if (response.locals.readOnlyAccess) { if (locals.readOnlyAccess) {
this.logger.debug('Syncing with read-only access', { this.logger.debug('Syncing with read-only access', {
codeTag: 'GRPCSyncingServerServiceProxy', codeTag: 'GRPCSyncingServerServiceProxy',
userId: response.locals.user.uuid, userId: locals.user.uuid,
}) })
} }
if (response.locals.session) { if (locals.session) {
metadata.set('x-session-uuid', response.locals.session.uuid) metadata.set('x-session-uuid', locals.session.uuid)
} }
metadata.set('x-is-free-user', response.locals.isFreeUser ? 'true' : 'false') metadata.set('x-is-free-user', locals.isFreeUser ? 'true' : 'false')
metadata.set('x-has-content-limit', locals.hasContentLimit ? 'true' : 'false')
this.syncingClient.syncItems(syncRequest, metadata, (error, syncResponse) => { this.syncingClient.syncItems(syncRequest, metadata, (error, syncResponse) => {
if (error) { if (error) {
@@ -52,10 +60,16 @@ export class GRPCSyncingServerServiceProxy {
if (error.code === Status.INTERNAL) { if (error.code === Status.INTERNAL) {
this.logger.error(`Internal gRPC error: ${error.message}. Payload: ${JSON.stringify(payload)}`, { this.logger.error(`Internal gRPC error: ${error.message}. Payload: ${JSON.stringify(payload)}`, {
codeTag: 'GRPCSyncingServerServiceProxy', codeTag: 'GRPCSyncingServerServiceProxy',
userId: response.locals.user.uuid, userId: locals.user.uuid,
}) })
} }
if (error.code === Status.RESOURCE_EXHAUSTED && this.domainEventPublisher !== undefined) {
void this.domainEventPublisher.publish(
this.domainEventFactory.createContentSizesFixRequestedEvent({ userUuid: locals.user.uuid }),
)
}
return reject(error) return reject(error)
} }
@@ -68,7 +82,7 @@ export class GRPCSyncingServerServiceProxy {
) { ) {
this.logger.error(`Internal gRPC error: ${JSON.stringify(error)}. Payload: ${JSON.stringify(payload)}`, { this.logger.error(`Internal gRPC error: ${JSON.stringify(error)}. Payload: ${JSON.stringify(payload)}`, {
codeTag: 'GRPCSyncingServerServiceProxy.catch', codeTag: 'GRPCSyncingServerServiceProxy.catch',
userId: response.locals.user.uuid, userId: locals.user.uuid,
}) })
} }

View File

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

View File

@@ -3,6 +3,57 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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
* **auth:** check for user agent persisting on session during a session refresh ([#1016](https://github.com/standardnotes/server/issues/1016)) ([0b46eff](https://github.com/standardnotes/server/commit/0b46eff16ea0c32cac91ead04474303500359f4f))
## [1.177.14](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.13...@standardnotes/auth-server@1.177.14) (2024-01-08)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.177.13](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.12...@standardnotes/auth-server@1.177.13) (2024-01-04) ## [1.177.13](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.12...@standardnotes/auth-server@1.177.13) (2024-01-04)
**Note:** Version bump only for package @standardnotes/auth-server **Note:** Version bump only for package @standardnotes/auth-server

View File

@@ -10,6 +10,12 @@ RUN corepack enable
COPY ./ /workspace COPY ./ /workspace
WORKDIR /workspace
RUN yarn install --immutable
RUN yarn build
WORKDIR /workspace/packages/auth WORKDIR /workspace/packages/auth
ENTRYPOINT [ "/workspace/packages/auth/docker/entrypoint.sh" ] 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 '../src/Infra/InversifyExpressUtils/AnnotatedFeaturesController'
import * as cors from 'cors' import * as cors from 'cors'
import * as cookieParser from 'cookie-parser'
import * as grpc from '@grpc/grpc-js' import * as grpc from '@grpc/grpc-js'
import { urlencoded, json, Request, Response, NextFunction } from 'express' import { urlencoded, json, Request, Response, NextFunction } from 'express'
import * as winston from 'winston' import * as winston from 'winston'
@@ -35,6 +36,7 @@ import { AuthService } from '@standardnotes/grpc'
import { AuthenticateRequest } from '../src/Domain/UseCase/AuthenticateRequest' import { AuthenticateRequest } from '../src/Domain/UseCase/AuthenticateRequest'
import { CreateCrossServiceToken } from '../src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken' import { CreateCrossServiceToken } from '../src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
import { TokenDecoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security' import { TokenDecoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security'
import { ResponseLocals } from '../src/Infra/InversifyExpressUtils/ResponseLocals'
const container = new ContainerConfigLoader() const container = new ContainerConfigLoader()
void container.load().then((container) => { void container.load().then((container) => {
@@ -52,6 +54,7 @@ void container.load().then((container) => {
}) })
app.use(json()) app.use(json())
app.use(urlencoded({ extended: true })) app.use(urlencoded({ extended: true }))
app.use(cookieParser())
app.use(cors()) app.use(cors())
}) })
@@ -59,12 +62,13 @@ void container.load().then((container) => {
server.setErrorConfig((app) => { server.setErrorConfig((app) => {
app.use((error: Record<string, unknown>, request: Request, response: Response, _next: NextFunction) => { app.use((error: Record<string, unknown>, request: Request, response: Response, _next: NextFunction) => {
const locals = response.locals as ResponseLocals
logger.error(`${error.stack}`, { logger.error(`${error.stack}`, {
method: request.method, method: request.method,
url: request.url, url: request.url,
snjs: request.headers['x-snjs-version'], snjs: request.headers['x-snjs-version'],
application: request.headers['x-application-version'], application: request.headers['x-application-version'],
userId: response.locals.user ? response.locals.user.uuid : undefined, userId: locals.user ? locals.user.uuid : undefined,
}) })
response.status(500).send({ response.status(500).send({

View File

@@ -9,28 +9,23 @@ import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env' import { Env } from '../src/Bootstrap/Env'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events' import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface' 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 { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
import { PermissionName } from '@standardnotes/features' import { PermissionName } from '@standardnotes/features'
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface' import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
import { GetUserKeyParams } from '../src/Domain/UseCase/GetUserKeyParams/GetUserKeyParams' 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 inputArgs = process.argv.slice(2)
const backupEmail = inputArgs[0] const backupEmail = inputArgs[0]
const requestBackups = async ( const requestBackups = async (
userRepository: UserRepositoryInterface, userRepository: UserRepositoryInterface,
settingRepository: SettingRepositoryInterface,
roleService: RoleServiceInterface, roleService: RoleServiceInterface,
domainEventFactory: DomainEventFactoryInterface, domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface, domainEventPublisher: DomainEventPublisherInterface,
getUserKeyParamsUseCase: GetUserKeyParams, getUserKeyParamsUseCase: GetUserKeyParams,
): Promise<void> => { ): Promise<void> => {
const permissionName = PermissionName.DailyEmailBackup const permissionName = PermissionName.DailyEmailBackup
const muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails
const muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
const emailOrError = Email.create(backupEmail) const emailOrError = Email.create(backupEmail)
if (emailOrError.isFailed()) { if (emailOrError.isFailed()) {
@@ -48,24 +43,13 @@ const requestBackups = async (
throw new Error(`User ${backupEmail} is not permitted for email backups`) 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({ const keyParamsResponse = await getUserKeyParamsUseCase.execute({
userUuid: user.uuid, userUuid: user.uuid,
authenticated: false, authenticated: false,
}) })
await domainEventPublisher.publish( await domainEventPublisher.publish(
domainEventFactory.createEmailBackupRequestedEvent( domainEventFactory.createEmailBackupRequestedEvent(user.uuid, keyParamsResponse.keyParams),
user.uuid,
emailsMutedSetting?.id.toString() as string,
userHasEmailsMuted,
keyParamsResponse.keyParams,
),
) )
return return
@@ -82,7 +66,6 @@ void container.load().then((container) => {
logger.info(`Starting email backup requesting for ${backupEmail} ...`) 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 userRepository: UserRepositoryInterface = container.get(TYPES.Auth_UserRepository)
const roleService: RoleServiceInterface = container.get(TYPES.Auth_RoleService) const roleService: RoleServiceInterface = container.get(TYPES.Auth_RoleService)
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.Auth_DomainEventFactory) 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) const getUserKeyParamsUseCase: GetUserKeyParams = container.get(TYPES.Auth_GetUserKeyParams)
Promise.resolve( Promise.resolve(
requestBackups( requestBackups(userRepository, roleService, domainEventFactory, domainEventPublisher, getUserKeyParamsUseCase),
userRepository,
settingRepository,
roleService,
domainEventFactory,
domainEventPublisher,
getUserKeyParamsUseCase,
),
) )
.then(() => { .then(() => {
logger.info(`Email backup requesting complete for ${backupEmail}`) 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 exec node docker/entrypoint-fix-roles.js
;; ;;
'fix-subscriptions' )
exec node docker/entrypoint-fix-subscriptions.js
;;
'delete-accounts' ) 'delete-accounts' )
FILE_NAME=$1 && shift 1 FILE_NAME=$1 && shift 1
MODE=$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,10 +1,10 @@
{ {
"name": "@standardnotes/auth-server", "name": "@standardnotes/auth-server",
"version": "1.177.13", "version": "1.178.5",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
"description": "Auth Server", "description": "Auth Server for SN",
"main": "dist/src/index.js", "main": "dist/src/index.js",
"typings": "dist/src/index.d.ts", "typings": "dist/src/index.d.ts",
"author": "Karol Sójko <karol@standardnotes.com>", "author": "Karol Sójko <karol@standardnotes.com>",
@@ -24,8 +24,7 @@
"build": "tsc --build", "build": "tsc --build",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"lint:fix": "eslint . --fix --ext .ts", "lint:fix": "eslint . --fix --ext .ts",
"pretest": "yarn lint && yarn build", "test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=2",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"start": "yarn node dist/bin/server.js", "start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js", "worker": "yarn node dist/bin/worker.js",
"cleanup": "yarn node dist/bin/cleanup.js", "cleanup": "yarn node dist/bin/cleanup.js",
@@ -60,7 +59,10 @@
"@standardnotes/sncrypto-common": "^1.13.4", "@standardnotes/sncrypto-common": "^1.13.4",
"@standardnotes/sncrypto-node": "workspace:*", "@standardnotes/sncrypto-node": "workspace:*",
"@standardnotes/time": "workspace:*", "@standardnotes/time": "workspace:*",
"agentkeepalive": "^4.5.0",
"axios": "^1.6.7",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"cookie-parser": "^1.4.6",
"cors": "2.8.5", "cors": "2.8.5",
"dayjs": "^1.11.6", "dayjs": "^1.11.6",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
@@ -68,7 +70,7 @@
"inversify": "^6.0.1", "inversify": "^6.0.1",
"inversify-express-utils": "^6.4.3", "inversify-express-utils": "^6.4.3",
"ioredis": "^5.2.4", "ioredis": "^5.2.4",
"mysql2": "^3.0.1", "mysql2": "^3.9.7",
"otplib": "12.0.1", "otplib": "12.0.1",
"prettyjson": "^1.2.5", "prettyjson": "^1.2.5",
"reflect-metadata": "^0.2.1", "reflect-metadata": "^0.2.1",
@@ -80,6 +82,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.2", "@types/bcryptjs": "^2.4.2",
"@types/cookie-parser": "^1",
"@types/cors": "^2.8.9", "@types/cors": "^2.8.9",
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
"@types/ioredis": "^5.0.0", "@types/ioredis": "^5.0.0",

View File

@@ -1,6 +1,8 @@
import * as winston from 'winston' import * as winston from 'winston'
import * as AgentKeepAlive from 'agentkeepalive'
import Redis from 'ioredis' import Redis from 'ioredis'
import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns' import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
import axios, { AxiosInstance } from 'axios'
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs' import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
import { S3Client } from '@aws-sdk/client-s3' import { S3Client } from '@aws-sdk/client-s3'
import { Container } from 'inversify' import { Container } from 'inversify'
@@ -36,13 +38,11 @@ import { AuthResponseFactoryResolver } from '../Domain/Auth/AuthResponseFactoryR
import { ClearLoginAttempts } from '../Domain/UseCase/ClearLoginAttempts' import { ClearLoginAttempts } from '../Domain/UseCase/ClearLoginAttempts'
import { IncreaseLoginAttempts } from '../Domain/UseCase/IncreaseLoginAttempts' import { IncreaseLoginAttempts } from '../Domain/UseCase/IncreaseLoginAttempts'
import { GetUserKeyParams } from '../Domain/UseCase/GetUserKeyParams/GetUserKeyParams' import { GetUserKeyParams } from '../Domain/UseCase/GetUserKeyParams/GetUserKeyParams'
import { UpdateUser } from '../Domain/UseCase/UpdateUser'
import { RedisEphemeralSessionRepository } from '../Infra/Redis/RedisEphemeralSessionRepository' import { RedisEphemeralSessionRepository } from '../Infra/Redis/RedisEphemeralSessionRepository'
import { GetActiveSessionsForUser } from '../Domain/UseCase/GetActiveSessionsForUser' import { GetActiveSessionsForUser } from '../Domain/UseCase/GetActiveSessionsForUser'
import { DeleteOtherSessionsForUser } from '../Domain/UseCase/DeleteOtherSessionsForUser' import { DeleteOtherSessionsForUser } from '../Domain/UseCase/DeleteOtherSessionsForUser'
import { DeleteSessionForUser } from '../Domain/UseCase/DeleteSessionForUser' import { DeleteSessionForUser } from '../Domain/UseCase/DeleteSessionForUser'
import { Register } from '../Domain/UseCase/Register' import { Register } from '../Domain/UseCase/Register'
import { LockRepository } from '../Infra/Redis/LockRepository'
import { TypeORMRevokedSessionRepository } from '../Infra/TypeORM/TypeORMRevokedSessionRepository' import { TypeORMRevokedSessionRepository } from '../Infra/TypeORM/TypeORMRevokedSessionRepository'
import { AuthenticationMethodResolver } from '../Domain/Auth/AuthenticationMethodResolver' import { AuthenticationMethodResolver } from '../Domain/Auth/AuthenticationMethodResolver'
import { RevokedSession } from '../Domain/Session/RevokedSession' import { RevokedSession } from '../Domain/Session/RevokedSession'
@@ -284,6 +284,21 @@ import { AccountDeletionVerificationPassedEventHandler } from '../Domain/Handler
import { RenewSharedSubscriptions } from '../Domain/UseCase/RenewSharedSubscriptions/RenewSharedSubscriptions' import { RenewSharedSubscriptions } from '../Domain/UseCase/RenewSharedSubscriptions/RenewSharedSubscriptions'
import { FixStorageQuotaForUser } from '../Domain/UseCase/FixStorageQuotaForUser/FixStorageQuotaForUser' import { FixStorageQuotaForUser } from '../Domain/UseCase/FixStorageQuotaForUser/FixStorageQuotaForUser'
import { FileQuotaRecalculatedEventHandler } from '../Domain/Handler/FileQuotaRecalculatedEventHandler' 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 { export class ContainerConfigLoader {
constructor(private mode: 'server' | 'worker' = 'server') {} constructor(private mode: 'server' | 'worker' = 'server') {}
@@ -328,6 +343,8 @@ export class ContainerConfigLoader {
const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted' const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted'
const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory' 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 container
.bind<boolean>(TYPES.Auth_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING) .bind<boolean>(TYPES.Auth_IS_CONFIGURED_FOR_HOME_SERVER_OR_SELF_HOSTING)
@@ -595,9 +612,17 @@ export class ContainerConfigLoader {
container container
.bind(TYPES.Auth_MAX_LOGIN_ATTEMPTS) .bind(TYPES.Auth_MAX_LOGIN_ATTEMPTS)
.toConstantValue(env.get('MAX_LOGIN_ATTEMPTS', true) ? +env.get('MAX_LOGIN_ATTEMPTS', true) : 6) .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 container
.bind(TYPES.Auth_FAILED_LOGIN_LOCKOUT) .bind(TYPES.Auth_FAILED_LOGIN_LOCKOUT)
.toConstantValue(env.get('FAILED_LOGIN_LOCKOUT', true) ? +env.get('FAILED_LOGIN_LOCKOUT', true) : 3600) .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_PSEUDO_KEY_PARAMS_KEY).toConstantValue(env.get('PSEUDO_KEY_PARAMS_KEY'))
container container
.bind(TYPES.Auth_EPHEMERAL_SESSION_AGE) .bind(TYPES.Auth_EPHEMERAL_SESSION_AGE)
@@ -631,6 +656,10 @@ export class ContainerConfigLoader {
container container
.bind(TYPES.Auth_READONLY_USERS) .bind(TYPES.Auth_READONLY_USERS)
.toConstantValue(env.get('READONLY_USERS', true) ? env.get('READONLY_USERS', true).split(',') : []) .toConstantValue(env.get('READONLY_USERS', true) ? env.get('READONLY_USERS', true).split(',') : [])
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) { if (isConfiguredForInMemoryCache) {
container container
@@ -650,6 +679,7 @@ export class ContainerConfigLoader {
container.get(TYPES.Auth_Timer), container.get(TYPES.Auth_Timer),
container.get(TYPES.Auth_MAX_LOGIN_ATTEMPTS), container.get(TYPES.Auth_MAX_LOGIN_ATTEMPTS),
container.get(TYPES.Auth_FAILED_LOGIN_LOCKOUT), container.get(TYPES.Auth_FAILED_LOGIN_LOCKOUT),
container.get(TYPES.Auth_FAILED_LOGIN_CAPTCHA_LOCKOUT),
), ),
) )
container container
@@ -677,9 +707,21 @@ export class ContainerConfigLoader {
container.get(TYPES.Auth_Timer), container.get(TYPES.Auth_Timer),
), ),
) )
container
.bind<SessionTokensCooldownRepositoryInterface>(TYPES.Auth_SessionTokensCooldownRepository)
.toConstantValue(new InMemorySessionTokensCooldownRepository())
} else { } else {
container.bind<PKCERepositoryInterface>(TYPES.Auth_PKCERepository).to(RedisPKCERepository) 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 container
.bind<EphemeralSessionRepositoryInterface>(TYPES.Auth_EphemeralSessionRepository) .bind<EphemeralSessionRepositoryInterface>(TYPES.Auth_EphemeralSessionRepository)
.to(RedisEphemeralSessionRepository) .to(RedisEphemeralSessionRepository)
@@ -689,6 +731,9 @@ export class ContainerConfigLoader {
container container
.bind<SubscriptionTokenRepositoryInterface>(TYPES.Auth_SubscriptionTokenRepository) .bind<SubscriptionTokenRepositoryInterface>(TYPES.Auth_SubscriptionTokenRepository)
.to(RedisSubscriptionTokenRepository) .to(RedisSubscriptionTokenRepository)
container
.bind<SessionTokensCooldownRepositoryInterface>(TYPES.Auth_SessionTokensCooldownRepository)
.toConstantValue(new RedisSessionTokensCooldownRepository(container.get<Redis>(TYPES.Auth_Redis)))
} }
container container
@@ -738,6 +783,41 @@ export class ContainerConfigLoader {
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository), container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
container.get<string[]>(TYPES.Auth_READONLY_USERS), container.get<string[]>(TYPES.Auth_READONLY_USERS),
container.get<GetSetting>(TYPES.Auth_GetSetting), 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) container.bind<AuthResponseFactory20161215>(TYPES.Auth_AuthResponseFactory20161215).to(AuthResponseFactory20161215)
@@ -778,7 +858,16 @@ export class ContainerConfigLoader {
.toConstantValue(new TokenEncoder<ValetTokenData>(container.get(TYPES.Auth_VALET_TOKEN_SECRET))) .toConstantValue(new TokenEncoder<ValetTokenData>(container.get(TYPES.Auth_VALET_TOKEN_SECRET)))
container container
.bind<AuthenticationMethodResolver>(TYPES.Auth_AuthenticationMethodResolver) .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<DomainEventFactory>(TYPES.Auth_DomainEventFactory).to(DomainEventFactory)
container container
.bind<SettingsAssociationServiceInterface>(TYPES.Auth_SettingsAssociationService) .bind<SettingsAssociationServiceInterface>(TYPES.Auth_SettingsAssociationService)
@@ -817,6 +906,43 @@ export class ContainerConfigLoader {
.bind<SelectorInterface<boolean>>(TYPES.Auth_BooleanSelector) .bind<SelectorInterface<boolean>>(TYPES.Auth_BooleanSelector)
.toConstantValue(new DeterministicSelector<boolean>()) .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 // Middleware
container.bind<SessionMiddleware>(TYPES.Auth_SessionMiddleware).to(SessionMiddleware) container.bind<SessionMiddleware>(TYPES.Auth_SessionMiddleware).to(SessionMiddleware)
container.bind<LockMiddleware>(TYPES.Auth_LockMiddleware).to(LockMiddleware) container.bind<LockMiddleware>(TYPES.Auth_LockMiddleware).to(LockMiddleware)
@@ -951,6 +1077,7 @@ export class ContainerConfigLoader {
new SetSubscriptionSettingValue( new SetSubscriptionSettingValue(
container.get<SubscriptionSettingRepositoryInterface>(TYPES.Auth_SubscriptionSettingRepository), container.get<SubscriptionSettingRepositoryInterface>(TYPES.Auth_SubscriptionSettingRepository),
container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting), container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting),
container.get<SettingsAssociationServiceInterface>(TYPES.Auth_SettingsAssociationService),
container.get<TimerInterface>(TYPES.Auth_Timer), container.get<TimerInterface>(TYPES.Auth_Timer),
), ),
) )
@@ -986,8 +1113,45 @@ export class ContainerConfigLoader {
.toConstantValue(new CleanupExpiredSessions(container.get(TYPES.Auth_SessionRepository))) .toConstantValue(new CleanupExpiredSessions(container.get(TYPES.Auth_SessionRepository)))
container.bind<AuthenticateUser>(TYPES.Auth_AuthenticateUser).to(AuthenticateUser) container.bind<AuthenticateUser>(TYPES.Auth_AuthenticateUser).to(AuthenticateUser)
container.bind<AuthenticateRequest>(TYPES.Auth_AuthenticateRequest).to(AuthenticateRequest) container.bind<AuthenticateRequest>(TYPES.Auth_AuthenticateRequest).to(AuthenticateRequest)
container.bind<RefreshSessionToken>(TYPES.Auth_RefreshSessionToken).to(RefreshSessionToken) container
container.bind<SignIn>(TYPES.Auth_SignIn).to(SignIn) .bind<RefreshSessionToken>(TYPES.Auth_RefreshSessionToken)
.toConstantValue(
new RefreshSessionToken(
container.get<SessionServiceInterface>(TYPES.Auth_SessionService),
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
container.get<TimerInterface>(TYPES.Auth_Timer),
container.get<GetSetting>(TYPES.Auth_GetSetting),
container.get<CooldownSessionTokens>(TYPES.Auth_CooldownSessionTokens),
container.get<GetSessionFromToken>(TYPES.Auth_GetSessionFromToken),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
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 container
.bind<VerifyMFA>(TYPES.Auth_VerifyMFA) .bind<VerifyMFA>(TYPES.Auth_VerifyMFA)
.toConstantValue( .toConstantValue(
@@ -1004,8 +1168,24 @@ export class ContainerConfigLoader {
container.get<winston.Logger>(TYPES.Auth_Logger), container.get<winston.Logger>(TYPES.Auth_Logger),
), ),
) )
container.bind<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts).to(ClearLoginAttempts) container
container.bind<IncreaseLoginAttempts>(TYPES.Auth_IncreaseLoginAttempts).to(IncreaseLoginAttempts) .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 container
.bind<GetUserKeyParamsRecovery>(TYPES.Auth_GetUserKeyParamsRecovery) .bind<GetUserKeyParamsRecovery>(TYPES.Auth_GetUserKeyParamsRecovery)
.toConstantValue( .toConstantValue(
@@ -1016,7 +1196,6 @@ export class ContainerConfigLoader {
container.get<GetSetting>(TYPES.Auth_GetSetting), container.get<GetSetting>(TYPES.Auth_GetSetting),
), ),
) )
container.bind<UpdateUser>(TYPES.Auth_UpdateUser).to(UpdateUser)
container container
.bind<ApplyDefaultSettings>(TYPES.Auth_ApplyDefaultSettings) .bind<ApplyDefaultSettings>(TYPES.Auth_ApplyDefaultSettings)
.toConstantValue( .toConstantValue(
@@ -1117,6 +1296,9 @@ export class ContainerConfigLoader {
container.get<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts), container.get<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts),
container.get<DeleteSetting>(TYPES.Auth_DeleteSetting), container.get<DeleteSetting>(TYPES.Auth_DeleteSetting),
container.get<AuthenticatorRepositoryInterface>(TYPES.Auth_AuthenticatorRepository), 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 container
@@ -1249,7 +1431,6 @@ export class ContainerConfigLoader {
.toConstantValue( .toConstantValue(
new TriggerEmailBackupForUser( new TriggerEmailBackupForUser(
container.get<RoleServiceInterface>(TYPES.Auth_RoleService), container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
container.get<GetSetting>(TYPES.Auth_GetSetting),
container.get<GetUserKeyParams>(TYPES.Auth_GetUserKeyParams), container.get<GetUserKeyParams>(TYPES.Auth_GetUserKeyParams),
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher), container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory), container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
@@ -1324,15 +1505,9 @@ export class ContainerConfigLoader {
.bind<AuthController>(TYPES.Auth_AuthController) .bind<AuthController>(TYPES.Auth_AuthController)
.toConstantValue( .toConstantValue(
new AuthController( new AuthController(
container.get(TYPES.Auth_ClearLoginAttempts), container.get<GetUserKeyParamsRecovery>(TYPES.Auth_GetUserKeyParamsRecovery),
container.get(TYPES.Auth_Register), container.get<GenerateRecoveryCodes>(TYPES.Auth_GenerateRecoveryCodes),
container.get(TYPES.Auth_DomainEventPublisher), container.get<winston.Logger>(TYPES.Auth_Logger),
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 container
@@ -1409,6 +1584,7 @@ export class ContainerConfigLoader {
container.get<SetSettingValue>(TYPES.Auth_SetSettingValue), container.get<SetSettingValue>(TYPES.Auth_SetSettingValue),
container.get<OfflineSettingServiceInterface>(TYPES.Auth_OfflineSettingService), container.get<OfflineSettingServiceInterface>(TYPES.Auth_OfflineSettingService),
container.get<ContentDecoderInterface>(TYPES.Auth_ContenDecoder), container.get<ContentDecoderInterface>(TYPES.Auth_ContenDecoder),
container.get<RenewSharedSubscriptions>(TYPES.Auth_RenewSharedSubscriptions),
container.get<winston.Logger>(TYPES.Auth_Logger), container.get<winston.Logger>(TYPES.Auth_Logger),
), ),
) )
@@ -1566,6 +1742,16 @@ export class ContainerConfigLoader {
container.get<winston.Logger>(TYPES.Auth_Logger), 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([ const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Auth_AccountDeletionRequestedEventHandler)], ['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Auth_AccountDeletionRequestedEventHandler)],
@@ -1607,6 +1793,7 @@ export class ContainerConfigLoader {
'FILE_QUOTA_RECALCULATED', 'FILE_QUOTA_RECALCULATED',
container.get<FileQuotaRecalculatedEventHandler>(TYPES.Auth_FileQuotaRecalculatedEventHandler), container.get<FileQuotaRecalculatedEventHandler>(TYPES.Auth_FileQuotaRecalculatedEventHandler),
], ],
['SUBSCRIPTION_STATE_FETCHED', container.get(TYPES.Auth_SubscriptionStateFetchedEventHandler)],
]) ])
if (isConfiguredForHomeServer) { if (isConfiguredForHomeServer) {
@@ -1639,14 +1826,23 @@ export class ContainerConfigLoader {
.bind<BaseAuthController>(TYPES.Auth_BaseAuthController) .bind<BaseAuthController>(TYPES.Auth_BaseAuthController)
.toConstantValue( .toConstantValue(
new BaseAuthController( new BaseAuthController(
container.get(TYPES.Auth_VerifyMFA), container.get<VerifyMFA>(TYPES.Auth_VerifyMFA),
container.get(TYPES.Auth_SignIn), container.get<SignIn>(TYPES.Auth_SignIn),
container.get(TYPES.Auth_GetUserKeyParams), container.get<GetUserKeyParams>(TYPES.Auth_GetUserKeyParams),
container.get(TYPES.Auth_ClearLoginAttempts), container.get<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts),
container.get(TYPES.Auth_IncreaseLoginAttempts), container.get<IncreaseLoginAttempts>(TYPES.Auth_IncreaseLoginAttempts),
container.get(TYPES.Auth_Logger), container.get<winston.Logger>(TYPES.Auth_Logger),
container.get(TYPES.Auth_AuthController), container.get<AuthController>(TYPES.Auth_AuthController),
container.get(TYPES.Auth_ControllerContainer), 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),
), ),
) )
@@ -1708,12 +1904,12 @@ export class ContainerConfigLoader {
.bind<BaseUsersController>(TYPES.Auth_BaseUsersController) .bind<BaseUsersController>(TYPES.Auth_BaseUsersController)
.toConstantValue( .toConstantValue(
new BaseUsersController( new BaseUsersController(
container.get<UpdateUser>(TYPES.Auth_UpdateUser),
container.get<DeleteAccount>(TYPES.Auth_DeleteAccount), container.get<DeleteAccount>(TYPES.Auth_DeleteAccount),
container.get<GetUserSubscription>(TYPES.Auth_GetUserSubscription), container.get<GetUserSubscription>(TYPES.Auth_GetUserSubscription),
container.get<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts), container.get<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts),
container.get<IncreaseLoginAttempts>(TYPES.Auth_IncreaseLoginAttempts), container.get<IncreaseLoginAttempts>(TYPES.Auth_IncreaseLoginAttempts),
container.get<ChangeCredentials>(TYPES.Auth_ChangeCredentials), container.get<ChangeCredentials>(TYPES.Auth_ChangeCredentials),
container.get<CookieFactoryInterface>(TYPES.Auth_CookieFactory),
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer), container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
), ),
) )
@@ -1721,11 +1917,12 @@ export class ContainerConfigLoader {
.bind<BaseAdminController>(TYPES.Auth_BaseAdminController) .bind<BaseAdminController>(TYPES.Auth_BaseAdminController)
.toConstantValue( .toConstantValue(
new BaseAdminController( new BaseAdminController(
container.get(TYPES.Auth_DeleteSetting), container.get<DeleteSetting>(TYPES.Auth_DeleteSetting),
container.get(TYPES.Auth_UserRepository), container.get<GetSetting>(TYPES.Auth_GetSetting),
container.get(TYPES.Auth_CreateSubscriptionToken), container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
container.get(TYPES.Auth_CreateOfflineSubscriptionToken), container.get<CreateSubscriptionToken>(TYPES.Auth_CreateSubscriptionToken),
container.get(TYPES.Auth_ControllerContainer), container.get<CreateOfflineSubscriptionToken>(TYPES.Auth_CreateOfflineSubscriptionToken),
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
), ),
) )
container container
@@ -1748,9 +1945,12 @@ export class ContainerConfigLoader {
new BaseSubscriptionSettingsController( new BaseSubscriptionSettingsController(
container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting), container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting),
container.get<GetSharedOrRegularSubscriptionForUser>(TYPES.Auth_GetSharedOrRegularSubscriptionForUser), container.get<GetSharedOrRegularSubscriptionForUser>(TYPES.Auth_GetSharedOrRegularSubscriptionForUser),
container.get<SetSubscriptionSettingValue>(TYPES.Auth_SetSubscriptionSettingValue),
container.get<TriggerPostSettingUpdateActions>(TYPES.Auth_TriggerPostSettingUpdateActions),
container.get<MapperInterface<SubscriptionSetting, SubscriptionSettingHttpRepresentation>>( container.get<MapperInterface<SubscriptionSetting, SubscriptionSettingHttpRepresentation>>(
TYPES.Auth_SubscriptionSettingHttpMapper, TYPES.Auth_SubscriptionSettingHttpMapper,
), ),
container.get<winston.Logger>(TYPES.Auth_Logger),
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer), container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
), ),
) )
@@ -1775,10 +1975,11 @@ export class ContainerConfigLoader {
.bind<BaseSessionController>(TYPES.Auth_BaseSessionController) .bind<BaseSessionController>(TYPES.Auth_BaseSessionController)
.toConstantValue( .toConstantValue(
new BaseSessionController( new BaseSessionController(
container.get(TYPES.Auth_DeleteSessionForUser), container.get<DeleteSessionForUser>(TYPES.Auth_DeleteSessionForUser),
container.get(TYPES.Auth_DeleteOtherSessionsForUser), container.get<DeleteOtherSessionsForUser>(TYPES.Auth_DeleteOtherSessionsForUser),
container.get(TYPES.Auth_RefreshSessionToken), container.get<RefreshSessionToken>(TYPES.Auth_RefreshSessionToken),
container.get(TYPES.Auth_ControllerContainer), container.get<CookieFactoryInterface>(TYPES.Auth_CookieFactory),
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
), ),
) )
container container

View File

@@ -34,6 +34,7 @@ const TYPES = {
Auth_UserSubscriptionRepository: Symbol.for('Auth_UserSubscriptionRepository'), Auth_UserSubscriptionRepository: Symbol.for('Auth_UserSubscriptionRepository'),
Auth_OfflineUserSubscriptionRepository: Symbol.for('Auth_OfflineUserSubscriptionRepository'), Auth_OfflineUserSubscriptionRepository: Symbol.for('Auth_OfflineUserSubscriptionRepository'),
Auth_SubscriptionTokenRepository: Symbol.for('Auth_SubscriptionTokenRepository'), Auth_SubscriptionTokenRepository: Symbol.for('Auth_SubscriptionTokenRepository'),
Auth_SessionTokensCooldownRepository: Symbol.for('Auth_SessionTokensCooldownRepository'),
Auth_OfflineSubscriptionTokenRepository: Symbol.for('Auth_OfflineSubscriptionTokenRepository'), Auth_OfflineSubscriptionTokenRepository: Symbol.for('Auth_OfflineSubscriptionTokenRepository'),
Auth_SharedSubscriptionInvitationRepository: Symbol.for('Auth_SharedSubscriptionInvitationRepository'), Auth_SharedSubscriptionInvitationRepository: Symbol.for('Auth_SharedSubscriptionInvitationRepository'),
Auth_PKCERepository: Symbol.for('Auth_PKCERepository'), Auth_PKCERepository: Symbol.for('Auth_PKCERepository'),
@@ -84,7 +85,9 @@ const TYPES = {
Auth_REFRESH_TOKEN_AGE: Symbol.for('Auth_REFRESH_TOKEN_AGE'), Auth_REFRESH_TOKEN_AGE: Symbol.for('Auth_REFRESH_TOKEN_AGE'),
Auth_EPHEMERAL_SESSION_AGE: Symbol.for('Auth_EPHEMERAL_SESSION_AGE'), Auth_EPHEMERAL_SESSION_AGE: Symbol.for('Auth_EPHEMERAL_SESSION_AGE'),
Auth_MAX_LOGIN_ATTEMPTS: Symbol.for('Auth_MAX_LOGIN_ATTEMPTS'), 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_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_PSEUDO_KEY_PARAMS_KEY: Symbol.for('Auth_PSEUDO_KEY_PARAMS_KEY'),
Auth_REDIS_URL: Symbol.for('Auth_REDIS_URL'), Auth_REDIS_URL: Symbol.for('Auth_REDIS_URL'),
Auth_DISABLE_USER_REGISTRATION: Symbol.for('Auth_DISABLE_USER_REGISTRATION'), 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_U2F_REQUIRE_USER_VERIFICATION: Symbol.for('Auth_U2F_REQUIRE_USER_VERIFICATION'),
Auth_READONLY_USERS: Symbol.for('Auth_READONLY_USERS'), 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_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 // use cases
Auth_AuthenticateUser: Symbol.for('Auth_AuthenticateUser'), Auth_AuthenticateUser: Symbol.for('Auth_AuthenticateUser'),
Auth_AuthenticateRequest: Symbol.for('Auth_AuthenticateRequest'), Auth_AuthenticateRequest: Symbol.for('Auth_AuthenticateRequest'),
@@ -109,7 +116,6 @@ const TYPES = {
Auth_ClearLoginAttempts: Symbol.for('Auth_ClearLoginAttempts'), Auth_ClearLoginAttempts: Symbol.for('Auth_ClearLoginAttempts'),
Auth_IncreaseLoginAttempts: Symbol.for('Auth_IncreaseLoginAttempts'), Auth_IncreaseLoginAttempts: Symbol.for('Auth_IncreaseLoginAttempts'),
Auth_GetUserKeyParams: Symbol.for('Auth_GetUserKeyParams'), Auth_GetUserKeyParams: Symbol.for('Auth_GetUserKeyParams'),
Auth_UpdateUser: Symbol.for('Auth_UpdateUser'),
Auth_Register: Symbol.for('Auth_Register'), Auth_Register: Symbol.for('Auth_Register'),
Auth_GetActiveSessionsForUser: Symbol.for('Auth_GetActiveSessionsForUser'), Auth_GetActiveSessionsForUser: Symbol.for('Auth_GetActiveSessionsForUser'),
Auth_DeleteOtherSessionsForUser: Symbol.for('Auth_DeleteOtherSessionsForUser'), Auth_DeleteOtherSessionsForUser: Symbol.for('Auth_DeleteOtherSessionsForUser'),
@@ -158,6 +164,10 @@ const TYPES = {
Auth_ApplyDefaultSettings: Symbol.for('Auth_ApplyDefaultSettings'), Auth_ApplyDefaultSettings: Symbol.for('Auth_ApplyDefaultSettings'),
Auth_ActivatePremiumFeatures: Symbol.for('Auth_ActivatePremiumFeatures'), Auth_ActivatePremiumFeatures: Symbol.for('Auth_ActivatePremiumFeatures'),
Auth_SignInWithRecoveryCodes: Symbol.for('Auth_SignInWithRecoveryCodes'), 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_GetUserKeyParamsRecovery: Symbol.for('Auth_GetUserKeyParamsRecovery'),
Auth_UpdateStorageQuotaUsedForUser: Symbol.for('Auth_UpdateStorageQuotaUsedForUser'), Auth_UpdateStorageQuotaUsedForUser: Symbol.for('Auth_UpdateStorageQuotaUsedForUser'),
Auth_AddSharedVaultUser: Symbol.for('Auth_AddSharedVaultUser'), Auth_AddSharedVaultUser: Symbol.for('Auth_AddSharedVaultUser'),
@@ -171,6 +181,7 @@ const TYPES = {
Auth_DeleteAccountsFromCSVFile: Symbol.for('Auth_DeleteAccountsFromCSVFile'), Auth_DeleteAccountsFromCSVFile: Symbol.for('Auth_DeleteAccountsFromCSVFile'),
Auth_RenewSharedSubscriptions: Symbol.for('Auth_RenewSharedSubscriptions'), Auth_RenewSharedSubscriptions: Symbol.for('Auth_RenewSharedSubscriptions'),
Auth_FixStorageQuotaForUser: Symbol.for('Auth_FixStorageQuotaForUser'), Auth_FixStorageQuotaForUser: Symbol.for('Auth_FixStorageQuotaForUser'),
Auth_VerifyHumanInteraction: Symbol.for('Auth_VerifyHumanInteraction'),
// Handlers // Handlers
Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'), Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'),
Auth_AccountDeletionVerificationPassedEventHandler: Symbol.for('Auth_AccountDeletionVerificationPassedEventHandler'), Auth_AccountDeletionVerificationPassedEventHandler: Symbol.for('Auth_AccountDeletionVerificationPassedEventHandler'),
@@ -205,7 +216,9 @@ const TYPES = {
), ),
Auth_UserInvitedToSharedVaultEventHandler: Symbol.for('Auth_UserInvitedToSharedVaultEventHandler'), Auth_UserInvitedToSharedVaultEventHandler: Symbol.for('Auth_UserInvitedToSharedVaultEventHandler'),
Auth_FileQuotaRecalculatedEventHandler: Symbol.for('Auth_FileQuotaRecalculatedEventHandler'), Auth_FileQuotaRecalculatedEventHandler: Symbol.for('Auth_FileQuotaRecalculatedEventHandler'),
Auth_SubscriptionStateFetchedEventHandler: Symbol.for('Auth_SubscriptionStateFetchedEventHandler'),
// Services // Services
Auth_CookieFactory: Symbol.for('Auth_CookieFactory'),
Auth_DeviceDetector: Symbol.for('Auth_DeviceDetector'), Auth_DeviceDetector: Symbol.for('Auth_DeviceDetector'),
Auth_SessionService: Symbol.for('Auth_SessionService'), Auth_SessionService: Symbol.for('Auth_SessionService'),
Auth_OfflineSettingService: Symbol.for('Auth_OfflineSettingService'), Auth_OfflineSettingService: Symbol.for('Auth_OfflineSettingService'),
@@ -258,6 +271,8 @@ const TYPES = {
Auth_BaseListedController: Symbol.for('Auth_BaseListedController'), Auth_BaseListedController: Symbol.for('Auth_BaseListedController'),
Auth_BaseFeaturesController: Symbol.for('Auth_BaseFeaturesController'), Auth_BaseFeaturesController: Symbol.for('Auth_BaseFeaturesController'),
Auth_CSVFileReader: Symbol.for('Auth_CSVFileReader'), Auth_CSVFileReader: Symbol.for('Auth_CSVFileReader'),
Auth_CaptchaServer: Symbol.for('Auth_CaptchaServer'),
Auth_HTTPClient: Symbol.for('Auth_HTTPClient'),
} }
export default TYPES 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 { UserDeletionResponseBody, UserUpdateRequestParams } from '@standardnotes/api'
import { import { HttpResponse, HttpStatusCode } from '@standardnotes/responses'
UserRegistrationRequestParams,
UserServerInterface,
UserDeletionResponseBody,
UserRegistrationResponseBody,
UserUpdateRequestParams,
} from '@standardnotes/api'
import { ErrorTag, HttpResponse, HttpStatusCode } from '@standardnotes/responses'
import { ProtocolVersion } from '@standardnotes/common'
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 { GetUserKeyParamsRecovery } from '../Domain/UseCase/GetUserKeyParamsRecovery/GetUserKeyParamsRecovery'
import { RecoveryKeyParamsRequestParams } from '../Infra/Http/Request/RecoveryKeyParamsRequestParams' import { RecoveryKeyParamsRequestParams } from '../Infra/Http/Request/RecoveryKeyParamsRequestParams'
import { SignInWithRecoveryCodesResponseBody } from '../Infra/Http/Response/SignInWithRecoveryCodesResponseBody'
import { RecoveryKeyParamsResponseBody } from '../Infra/Http/Response/RecoveryKeyParamsResponseBody' import { RecoveryKeyParamsResponseBody } from '../Infra/Http/Response/RecoveryKeyParamsResponseBody'
import { GenerateRecoveryCodesResponseBody } from '../Infra/Http/Response/GenerateRecoveryCodesResponseBody' import { GenerateRecoveryCodesResponseBody } from '../Infra/Http/Response/GenerateRecoveryCodesResponseBody'
import { GenerateRecoveryCodes } from '../Domain/UseCase/GenerateRecoveryCodes/GenerateRecoveryCodes' import { GenerateRecoveryCodes } from '../Domain/UseCase/GenerateRecoveryCodes/GenerateRecoveryCodes'
import { GenerateRecoveryCodesRequestParams } from '../Infra/Http/Request/GenerateRecoveryCodesRequestParams' import { GenerateRecoveryCodesRequestParams } from '../Infra/Http/Request/GenerateRecoveryCodesRequestParams'
import { Logger } from 'winston' 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' 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( constructor(
private clearLoginAttempts: ClearLoginAttempts,
private registerUser: Register,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private doSignInWithRecoveryCodes: SignInWithRecoveryCodes,
private getUserKeyParamsRecovery: GetUserKeyParamsRecovery, private getUserKeyParamsRecovery: GetUserKeyParamsRecovery,
private doGenerateRecoveryCodes: GenerateRecoveryCodes, private doGenerateRecoveryCodes: GenerateRecoveryCodes,
private logger: Logger, private logger: Logger,
private sessionService: SessionServiceInterface,
) {} ) {}
async update(_params: UserUpdateRequestParams): Promise<HttpResponse<UserUpdateResponse>> { 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.') 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( async generateRecoveryCodes(
params: GenerateRecoveryCodesRequestParams, params: GenerateRecoveryCodesRequestParams,
): Promise<HttpResponse<GenerateRecoveryCodesResponseBody>> { ): 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( async recoveryKeyParams(
params: RecoveryKeyParamsRequestParams, params: RecoveryKeyParamsRequestParams,
): Promise<HttpResponse<RecoveryKeyParamsResponseBody>> { ): Promise<HttpResponse<RecoveryKeyParamsResponseBody>> {
if (params.apiVersion !== ApiVersion.v20200115) {
return {
status: HttpStatusCode.BadRequest,
data: {
error: {
message: 'Invalid API version.',
},
},
}
}
const result = await this.getUserKeyParamsRecovery.execute({ const result = await this.getUserKeyParamsRecovery.execute({
apiVersion: params.apiVersion,
username: params.username, username: params.username,
codeChallenge: params.codeChallenge, codeChallenge: params.codeChallenge,
recoveryCodes: params.recoveryCodes, 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: [], 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({ expect(listSharedSubscriptionInvitations.execute).toHaveBeenCalledWith({
inviterEmail: 'test@test.te', inviterEmail: 'test@test.te',
@@ -68,7 +71,7 @@ describe('SubscriptionInvitesController', () => {
}) })
const result = await createController().cancelInvite({ const result = await createController().cancelInvite({
api: ApiVersion.v20200115, api: ApiVersion.VERSIONS.v20200115,
inviteUuid: '1-2-3', inviteUuid: '1-2-3',
inviterEmail: 'test@test.te', inviterEmail: 'test@test.te',
}) })
@@ -87,7 +90,7 @@ describe('SubscriptionInvitesController', () => {
}) })
const result = await createController().cancelInvite({ const result = await createController().cancelInvite({
api: ApiVersion.v20200115, api: ApiVersion.VERSIONS.v20200115,
inviteUuid: '1-2-3', inviteUuid: '1-2-3',
}) })
@@ -100,7 +103,7 @@ describe('SubscriptionInvitesController', () => {
}) })
const result = await createController().declineInvite({ const result = await createController().declineInvite({
api: ApiVersion.v20200115, api: ApiVersion.VERSIONS.v20200115,
inviteUuid: '1-2-3', inviteUuid: '1-2-3',
}) })
@@ -117,7 +120,7 @@ describe('SubscriptionInvitesController', () => {
}) })
const result = await createController().declineInvite({ const result = await createController().declineInvite({
api: ApiVersion.v20200115, api: ApiVersion.VERSIONS.v20200115,
inviteUuid: '1-2-3', inviteUuid: '1-2-3',
}) })
@@ -134,7 +137,7 @@ describe('SubscriptionInvitesController', () => {
}) })
const result = await createController().acceptInvite({ const result = await createController().acceptInvite({
api: ApiVersion.v20200115, api: ApiVersion.VERSIONS.v20200115,
inviteUuid: '1-2-3', inviteUuid: '1-2-3',
}) })
@@ -151,7 +154,7 @@ describe('SubscriptionInvitesController', () => {
}) })
const result = await createController().acceptInvite({ const result = await createController().acceptInvite({
api: ApiVersion.v20200115, api: ApiVersion.VERSIONS.v20200115,
inviteUuid: '1-2-3', inviteUuid: '1-2-3',
}) })
@@ -168,7 +171,7 @@ describe('SubscriptionInvitesController', () => {
}) })
const result = await createController().invite({ const result = await createController().invite({
api: ApiVersion.v20200115, api: ApiVersion.VERSIONS.v20200115,
identifier: 'invitee@test.te', identifier: 'invitee@test.te',
inviterUuid: '1-2-3', inviterUuid: '1-2-3',
inviterEmail: 'test@test.te', 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 () => { it('should not invite to user subscription if the identifier is missing in request', async () => {
const result = await createController().invite({ const result = await createController().invite({
api: ApiVersion.v20200115, api: ApiVersion.VERSIONS.v20200115,
identifier: '', identifier: '',
inviterUuid: '1-2-3', inviterUuid: '1-2-3',
inviterEmail: 'test@test.te', inviterEmail: 'test@test.te',
@@ -205,7 +208,7 @@ describe('SubscriptionInvitesController', () => {
}) })
const result = await createController().invite({ const result = await createController().invite({
api: ApiVersion.v20200115, api: ApiVersion.VERSIONS.v20200115,
identifier: 'invitee@test.te', identifier: 'invitee@test.te',
inviterUuid: '1-2-3', inviterUuid: '1-2-3',
inviterEmail: 'test@test.te', 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 { import { Result, ValueObject } from '@standardnotes/domain-core'
v20161215 = '20161215',
v20190520 = '20190520', import { ApiVersionProps } from './ApiVersionProps'
v20200115 = '20200115',
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' import { AuthResponse } from './AuthResponse'
export interface AuthResponse20200115 extends AuthResponse { export interface AuthResponse20200115 extends AuthResponse {
session: SessionBody sessionBody: SessionBody
key_params: KeyParamsData 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 }
}

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