mirror of
https://github.com/standardnotes/server
synced 2026-03-28 00:01:13 -04:00
Compare commits
24 Commits
@standardn
...
update-cod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84cbeaf3be | ||
|
|
578ce0e74e | ||
|
|
532be7c358 | ||
|
|
d406272f07 | ||
|
|
9de3352885 | ||
|
|
8575d20f7b | ||
|
|
102d4b1e8a | ||
|
|
1a57c247b2 | ||
|
|
dbb0e4a974 | ||
|
|
5c02435ee4 | ||
|
|
0a1e555b13 | ||
|
|
be668d7d7a | ||
|
|
87e50ec941 | ||
|
|
6d7ca1b926 | ||
|
|
00bfaaa53d | ||
|
|
f939caf2d9 | ||
|
|
0f3615ee65 | ||
|
|
567bcf26b5 | ||
|
|
9d49764b84 | ||
|
|
5c9f493b67 | ||
|
|
4fe8e9a79f | ||
|
|
f975dd9697 | ||
|
|
10832f7001 | ||
|
|
86b050865f |
5
.github/ci.env
vendored
5
.github/ci.env
vendored
@@ -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
|
||||||
@@ -28,3 +31,5 @@ AUTH_SERVER_ENCRYPTION_SERVER_KEY=1087415dfde3093797f9a7ca93a49e7d7aa1861735eb0d
|
|||||||
VALET_TOKEN_SECRET=4b886819ebe1e908077c6cae96311b48a8416bd60cc91c03060e15bdf6b30d1f
|
VALET_TOKEN_SECRET=4b886819ebe1e908077c6cae96311b48a8416bd60cc91c03060e15bdf6b30d1f
|
||||||
|
|
||||||
SYNCING_SERVER_CONTENT_SIZE_TRANSFER_LIMIT=100000
|
SYNCING_SERVER_CONTENT_SIZE_TRANSFER_LIMIT=100000
|
||||||
|
|
||||||
|
HTTP_REQUEST_PAYLOAD_LIMIT_MEGABYTES=1
|
||||||
|
|||||||
36
.github/workflows/common-server-application.yml
vendored
36
.github/workflows/common-server-application.yml
vendored
@@ -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
|
||||||
|
|||||||
7
.github/workflows/e2e-home-server.yml
vendored
7
.github/workflows/e2e-home-server.yml
vendored
@@ -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'
|
||||||
@@ -71,6 +71,7 @@ jobs:
|
|||||||
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=100000" >> 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
|
||||||
|
|||||||
6
.github/workflows/e2e-self-hosted.yml
vendored
6
.github/workflows/e2e-self-hosted.yml
vendored
@@ -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
|
||||||
|
|||||||
12
.github/workflows/pr.yml
vendored
12
.github/workflows/pr.yml
vendored
@@ -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'
|
||||||
|
|||||||
10
.github/workflows/publish.yml
vendored
10
.github/workflows/publish.yml
vendored
@@ -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'
|
||||||
|
|||||||
89
.pnp.cjs
generated
89
.pnp.cjs
generated
@@ -6356,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"],\
|
||||||
@@ -6396,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"],\
|
||||||
@@ -6407,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"],\
|
||||||
@@ -6457,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"],\
|
||||||
@@ -6468,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"],\
|
||||||
@@ -6479,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"],\
|
||||||
@@ -6689,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"],\
|
||||||
@@ -6790,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"],\
|
||||||
@@ -6809,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"],\
|
||||||
@@ -6826,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"],\
|
||||||
@@ -6977,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"],\
|
||||||
@@ -7057,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"],\
|
||||||
@@ -7237,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/",\
|
||||||
@@ -8497,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", [\
|
||||||
@@ -9608,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": [\
|
||||||
@@ -9616,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/",\
|
||||||
@@ -10864,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": [\
|
||||||
@@ -13783,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"],\
|
||||||
@@ -16836,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],\
|
||||||
@@ -16928,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.
BIN
.yarn/cache/@types-cookie-parser-npm-1.4.6-27287e1e43-b1bbb17bc4.zip
vendored
Normal file
BIN
.yarn/cache/@types-cookie-parser-npm-1.4.6-27287e1e43-b1bbb17bc4.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/axios-npm-1.6.7-d7b9974d1b-a1932b089e.zip
vendored
Normal file
BIN
.yarn/cache/axios-npm-1.6.7-d7b9974d1b-a1932b089e.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/cookie-npm-0.4.1-cc5e2ebb42-0f2defd60a.zip
vendored
Normal file
BIN
.yarn/cache/cookie-npm-0.4.1-cc5e2ebb42-0f2defd60a.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/cookie-parser-npm-1.4.6-a68f84d02a-1e5a63aa82.zip
vendored
Normal file
BIN
.yarn/cache/cookie-parser-npm-1.4.6-a68f84d02a-1e5a63aa82.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/follow-redirects-npm-1.15.5-9d14db76ca-d467f13c1c.zip
vendored
Normal file
BIN
.yarn/cache/follow-redirects-npm-1.15.5-9d14db76ca-d467f13c1c.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/fsevents-patch-19706e7e35-10.zip
vendored
BIN
.yarn/cache/fsevents-patch-19706e7e35-10.zip
vendored
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/mysql2-npm-3.9.7-8fe89e50ac-7f43b17cc0.zip
vendored
Normal file
BIN
.yarn/cache/mysql2-npm-3.9.7-8fe89e50ac-7f43b17cc0.zip
vendored
Normal file
Binary file not shown.
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [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)
|
## [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
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|||||||
@@ -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" ]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/analytics",
|
"name": "@standardnotes/analytics",
|
||||||
"version": "2.34.15",
|
"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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -3,6 +3,16 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.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)
|
# [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
|
### Features
|
||||||
|
|||||||
@@ -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" ]
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -47,9 +48,24 @@ void container.load().then((container) => {
|
|||||||
? `${+env.get('HTTP_REQUEST_PAYLOAD_LIMIT_MEGABYTES', true)}mb`
|
? `${+env.get('HTTP_REQUEST_PAYLOAD_LIMIT_MEGABYTES', true)}mb`
|
||||||
: '50mb'
|
: '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()
|
||||||
@@ -77,13 +93,57 @@ void container.load().then((container) => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
app.use(cookieParser())
|
||||||
|
|
||||||
app.use(json({ limit: requestPayloadLimit }))
|
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: '*',
|
||||||
@@ -92,13 +152,12 @@ 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
|
const locals = response.locals as ResponseLocals
|
||||||
|
|
||||||
logger.error(`${error.stack}`, {
|
logger.error(`${error.stack}`, {
|
||||||
|
origin: request.headers.origin,
|
||||||
codeTag: 'server.ts',
|
codeTag: 'server.ts',
|
||||||
method: request.method,
|
method: request.method,
|
||||||
url: request.url,
|
url: request.url,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/api-gateway",
|
"name": "@standardnotes/api-gateway",
|
||||||
"version": "1.90.0",
|
"version": "1.92.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
@@ -41,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",
|
||||||
@@ -55,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",
|
||||||
|
|||||||
@@ -142,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
|
||||||
@@ -157,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)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export const TYPES = {
|
|||||||
ApiGateway_SNS: Symbol.for('ApiGateway_SNS'),
|
ApiGateway_SNS: Symbol.for('ApiGateway_SNS'),
|
||||||
ApiGateway_DomainEventPublisher: Symbol.for('ApiGateway_DomainEventPublisher'),
|
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_TOPIC_ARN: Symbol.for('ApiGateway_SNS_TOPIC_ARN'),
|
||||||
ApiGateway_SNS_AWS_REGION: Symbol.for('ApiGateway_SNS_AWS_REGION'),
|
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'),
|
||||||
@@ -24,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'),
|
||||||
|
|||||||
@@ -42,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)) {
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export class GRPCWebSocketAuthMiddleware extends BaseMiddleware {
|
|||||||
roles: decodedToken.roles,
|
roles: decodedToken.roles,
|
||||||
isFreeUser: decodedToken.roles.length === 1 && decodedToken.roles[0].name === RoleName.NAMES.CoreUser,
|
isFreeUser: decodedToken.roles.length === 1 && decodedToken.roles[0].name === RoleName.NAMES.CoreUser,
|
||||||
readOnlyAccess: decodedToken.session?.readonly_access ?? false,
|
readOnlyAccess: decodedToken.session?.readonly_access ?? false,
|
||||||
|
hasContentLimit: decodedToken.hasContentLimit,
|
||||||
} as ResponseLocals)
|
} as ResponseLocals)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
|
|||||||
@@ -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([
|
||||||
|
|||||||
@@ -26,4 +26,5 @@ export interface ResponseLocals {
|
|||||||
sharedVaultOwnerContext?: {
|
sharedVaultOwnerContext?: {
|
||||||
upload_bytes_limit: number
|
upload_bytes_limit: number
|
||||||
}
|
}
|
||||||
|
hasContentLimit: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
controller,
|
controller,
|
||||||
httpDelete,
|
httpDelete,
|
||||||
httpGet,
|
httpGet,
|
||||||
httpPatch,
|
|
||||||
httpPost,
|
httpPost,
|
||||||
httpPut,
|
httpPut,
|
||||||
results,
|
results,
|
||||||
@@ -39,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(
|
||||||
@@ -86,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'),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +131,20 @@ export class UsersController extends BaseHttpController {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@httpPut('/:userUuid/subscription-settings', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
|
||||||
|
async putSubscriptionSetting(request: Request, response: Response): Promise<void> {
|
||||||
|
await this.httpService.callAuthServer(
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
this.endpointResolver.resolveEndpointOrMethodIdentifier(
|
||||||
|
'PUT',
|
||||||
|
'users/:userUuid/subscription-settings',
|
||||||
|
request.params.userUuid,
|
||||||
|
),
|
||||||
|
request.body,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@httpGet('/:userUuid/settings/:settingName', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
|
@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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -10,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,
|
||||||
|
|||||||
@@ -28,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
|
||||||
@@ -58,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
|
||||||
@@ -186,9 +222,18 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
|||||||
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']
|
||||||
|
|
||||||
|
headers.cookie = request.headers.cookie as string
|
||||||
|
|
||||||
if ('authToken' in locals && locals.authToken) {
|
if ('authToken' in locals && locals.authToken) {
|
||||||
headers['X-Auth-Token'] = locals.authToken
|
headers['X-Auth-Token'] = locals.authToken
|
||||||
}
|
}
|
||||||
@@ -340,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) => {
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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'],
|
||||||
@@ -48,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'],
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ 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'
|
||||||
@@ -30,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,
|
||||||
@@ -90,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 } }
|
||||||
@@ -99,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
|
||||||
@@ -265,6 +303,8 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
|
|||||||
delete headers.host
|
delete headers.host
|
||||||
delete headers['content-length']
|
delete headers['content-length']
|
||||||
|
|
||||||
|
headers.cookie = request.headers.cookie as string
|
||||||
|
|
||||||
if ('authToken' in locals && locals.authToken) {
|
if ('authToken' in locals && locals.authToken) {
|
||||||
headers['X-Auth-Token'] = locals.authToken
|
headers['X-Auth-Token'] = locals.authToken
|
||||||
}
|
}
|
||||||
@@ -435,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) => {
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export class GRPCSyncingServerServiceProxy {
|
|||||||
metadata.set('x-session-uuid', locals.session.uuid)
|
metadata.set('x-session-uuid', locals.session.uuid)
|
||||||
}
|
}
|
||||||
metadata.set('x-is-free-user', 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) {
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
## [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)
|
## [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
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|||||||
@@ -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" ]
|
||||||
|
|||||||
74
packages/auth/bin/fix_subscriptions.ts
Normal file
74
packages/auth/bin/fix_subscriptions.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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'
|
||||||
@@ -53,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())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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}`)
|
||||||
|
|||||||
11
packages/auth/docker/entrypoint-fix-subscriptions.js
Normal file
11
packages/auth/docker/entrypoint-fix-subscriptions.js
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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"')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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`')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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`')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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`')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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`')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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`')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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") ')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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") ')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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`')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/auth-server",
|
"name": "@standardnotes/auth-server",
|
||||||
"version": "1.177.20",
|
"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",
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -285,6 +285,20 @@ import { RenewSharedSubscriptions } from '../Domain/UseCase/RenewSharedSubscript
|
|||||||
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 { 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') {}
|
||||||
@@ -329,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)
|
||||||
@@ -596,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)
|
||||||
@@ -632,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
|
||||||
@@ -651,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
|
||||||
@@ -678,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)
|
||||||
@@ -690,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
|
||||||
@@ -739,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)
|
||||||
@@ -779,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)
|
||||||
@@ -818,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)
|
||||||
@@ -952,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),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -996,10 +1122,36 @@ export class ContainerConfigLoader {
|
|||||||
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
|
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
|
||||||
container.get<TimerInterface>(TYPES.Auth_Timer),
|
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||||
container.get<GetSetting>(TYPES.Auth_GetSetting),
|
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.get<winston.Logger>(TYPES.Auth_Logger),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
container.bind<SignIn>(TYPES.Auth_SignIn).to(SignIn)
|
container
|
||||||
|
.bind<VerifyHumanInteraction>(TYPES.Auth_VerifyHumanInteraction)
|
||||||
|
.toConstantValue(
|
||||||
|
new VerifyHumanInteraction(
|
||||||
|
container.get(TYPES.Auth_HUMAN_VERIFICATION_ENABLED),
|
||||||
|
container.get<CaptchaServerInterface>(TYPES.Auth_CaptchaServer),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
container
|
||||||
|
.bind<SignIn>(TYPES.Auth_SignIn)
|
||||||
|
.toConstantValue(
|
||||||
|
new SignIn(
|
||||||
|
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||||
|
container.get<AuthResponseFactoryResolverInterface>(TYPES.Auth_AuthResponseFactoryResolver),
|
||||||
|
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
|
||||||
|
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
|
||||||
|
container.get<SessionServiceInterface>(TYPES.Auth_SessionService),
|
||||||
|
container.get<PKCERepositoryInterface>(TYPES.Auth_PKCERepository),
|
||||||
|
container.get<CrypterInterface>(TYPES.Auth_Crypter),
|
||||||
|
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||||
|
container.get<number>(TYPES.Auth_MAX_LOGIN_ATTEMPTS),
|
||||||
|
container.get<LockRepositoryInterface>(TYPES.Auth_LockRepository),
|
||||||
|
container.get<VerifyHumanInteraction>(TYPES.Auth_VerifyHumanInteraction),
|
||||||
|
),
|
||||||
|
)
|
||||||
container
|
container
|
||||||
.bind<VerifyMFA>(TYPES.Auth_VerifyMFA)
|
.bind<VerifyMFA>(TYPES.Auth_VerifyMFA)
|
||||||
.toConstantValue(
|
.toConstantValue(
|
||||||
@@ -1016,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(
|
||||||
@@ -1028,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(
|
||||||
@@ -1129,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
|
||||||
@@ -1261,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),
|
||||||
@@ -1336,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
|
||||||
@@ -1579,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)],
|
||||||
@@ -1620,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) {
|
||||||
@@ -1652,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),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1726,6 +1909,7 @@ export class ContainerConfigLoader {
|
|||||||
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),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1733,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
|
||||||
@@ -1760,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),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1787,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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
46
packages/auth/src/Domain/Api/ApiVersion.spec.ts
Normal file
46
packages/auth/src/Domain/Api/ApiVersion.spec.ts
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
packages/auth/src/Domain/Api/ApiVersionProps.ts
Normal file
3
packages/auth/src/Domain/Api/ApiVersionProps.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface ApiVersionProps {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
10
packages/auth/src/Domain/Auth/AuthResponseCreationResult.ts
Normal file
10
packages/auth/src/Domain/Auth/AuthResponseCreationResult.ts
Normal 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 }
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { Logger } from 'winston'
|
|||||||
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
|
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
|
||||||
import { User } from '../User/User'
|
import { User } from '../User/User'
|
||||||
import { AuthResponseFactory20161215 } from './AuthResponseFactory20161215'
|
import { AuthResponseFactory20161215 } from './AuthResponseFactory20161215'
|
||||||
|
import { ApiVersion } from '../Api/ApiVersion'
|
||||||
|
|
||||||
describe('AuthResponseFactory20161215', () => {
|
describe('AuthResponseFactory20161215', () => {
|
||||||
let userProjector: ProjectorInterface<User>
|
let userProjector: ProjectorInterface<User>
|
||||||
@@ -32,13 +33,13 @@ describe('AuthResponseFactory20161215', () => {
|
|||||||
it('should create a 20161215 auth response', async () => {
|
it('should create a 20161215 auth response', async () => {
|
||||||
const result = await createFactory().createResponse({
|
const result = await createFactory().createResponse({
|
||||||
user,
|
user,
|
||||||
apiVersion: '20161215',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20161215).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
ephemeralSession: false,
|
ephemeralSession: false,
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.response).toEqual({
|
expect(result.legacyResponse).toEqual({
|
||||||
user: { foo: 'bar' },
|
user: { foo: 'bar' },
|
||||||
token: 'foobar',
|
token: 'foobar',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ import TYPES from '../../Bootstrap/Types'
|
|||||||
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
|
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
|
||||||
|
|
||||||
import { User } from '../User/User'
|
import { User } from '../User/User'
|
||||||
import { AuthResponse20161215 } from './AuthResponse20161215'
|
|
||||||
import { AuthResponse20200115 } from './AuthResponse20200115'
|
|
||||||
import { AuthResponseFactoryInterface } from './AuthResponseFactoryInterface'
|
import { AuthResponseFactoryInterface } from './AuthResponseFactoryInterface'
|
||||||
import { Session } from '../Session/Session'
|
import { AuthResponseCreationResult } from './AuthResponseCreationResult'
|
||||||
|
import { ApiVersion } from '../Api/ApiVersion'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class AuthResponseFactory20161215 implements AuthResponseFactoryInterface {
|
export class AuthResponseFactory20161215 implements AuthResponseFactoryInterface {
|
||||||
@@ -23,11 +22,13 @@ export class AuthResponseFactory20161215 implements AuthResponseFactoryInterface
|
|||||||
|
|
||||||
async createResponse(dto: {
|
async createResponse(dto: {
|
||||||
user: User
|
user: User
|
||||||
apiVersion: string
|
apiVersion: ApiVersion
|
||||||
userAgent: string
|
userAgent: string
|
||||||
ephemeralSession: boolean
|
ephemeralSession: boolean
|
||||||
readonlyAccess: boolean
|
readonlyAccess: boolean
|
||||||
}): Promise<{ response: AuthResponse20161215 | AuthResponse20200115; session?: Session }> {
|
snjs?: string
|
||||||
|
application?: string
|
||||||
|
}): Promise<AuthResponseCreationResult> {
|
||||||
this.logger.debug(`Creating JWT auth response for user ${dto.user.uuid}`)
|
this.logger.debug(`Creating JWT auth response for user ${dto.user.uuid}`)
|
||||||
|
|
||||||
const data: SessionTokenData = {
|
const data: SessionTokenData = {
|
||||||
@@ -40,7 +41,7 @@ export class AuthResponseFactory20161215 implements AuthResponseFactoryInterface
|
|||||||
this.logger.debug(`Created JWT token for user ${dto.user.uuid}: ${token}`)
|
this.logger.debug(`Created JWT token for user ${dto.user.uuid}: ${token}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
response: {
|
legacyResponse: {
|
||||||
user: this.userProjector.projectSimple(dto.user) as {
|
user: this.userProjector.projectSimple(dto.user) as {
|
||||||
uuid: string
|
uuid: string
|
||||||
email: string
|
email: string
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Logger } from 'winston'
|
|||||||
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
|
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
|
||||||
import { User } from '../User/User'
|
import { User } from '../User/User'
|
||||||
import { AuthResponseFactory20190520 } from './AuthResponseFactory20190520'
|
import { AuthResponseFactory20190520 } from './AuthResponseFactory20190520'
|
||||||
|
import { ApiVersion } from '../Api/ApiVersion'
|
||||||
|
|
||||||
describe('AuthResponseFactory20190520', () => {
|
describe('AuthResponseFactory20190520', () => {
|
||||||
let userProjector: ProjectorInterface<User>
|
let userProjector: ProjectorInterface<User>
|
||||||
@@ -31,13 +32,13 @@ describe('AuthResponseFactory20190520', () => {
|
|||||||
it('should create a 20161215 auth response', async () => {
|
it('should create a 20161215 auth response', async () => {
|
||||||
const result = await createFactory().createResponse({
|
const result = await createFactory().createResponse({
|
||||||
user,
|
user,
|
||||||
apiVersion: '20161215',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20161215).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
ephemeralSession: false,
|
ephemeralSession: false,
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.response).toEqual({
|
expect(result.legacyResponse).toEqual({
|
||||||
user: { foo: 'bar' },
|
user: { foo: 'bar' },
|
||||||
token: 'foobar',
|
token: 'foobar',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { AuthResponseFactory20200115 } from './AuthResponseFactory20200115'
|
|||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||||
import { Session } from '../Session/Session'
|
import { Session } from '../Session/Session'
|
||||||
|
import { ApiVersion } from '../Api/ApiVersion'
|
||||||
|
|
||||||
describe('AuthResponseFactory20200115', () => {
|
describe('AuthResponseFactory20200115', () => {
|
||||||
let sessionService: SessionServiceInterface
|
let sessionService: SessionServiceInterface
|
||||||
@@ -51,10 +52,10 @@ describe('AuthResponseFactory20200115', () => {
|
|||||||
sessionService = {} as jest.Mocked<SessionServiceInterface>
|
sessionService = {} as jest.Mocked<SessionServiceInterface>
|
||||||
sessionService.createNewSessionForUser = jest
|
sessionService.createNewSessionForUser = jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockReturnValue({ sessionHttpRepresentation: sessionPayload, session: {} as jest.Mocked<Session> })
|
.mockReturnValue({ sessionHttpRepresentation: sessionPayload, sessionBody: {} as jest.Mocked<Session> })
|
||||||
sessionService.createNewEphemeralSessionForUser = jest
|
sessionService.createNewEphemeralSessionForUser = jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockReturnValue({ sessionHttpRepresentation: sessionPayload, session: {} as jest.Mocked<Session> })
|
.mockReturnValue({ sessionHttpRepresentation: sessionPayload, sessionBody: {} as jest.Mocked<Session> })
|
||||||
|
|
||||||
keyParamsFactory = {} as jest.Mocked<KeyParamsFactoryInterface>
|
keyParamsFactory = {} as jest.Mocked<KeyParamsFactoryInterface>
|
||||||
keyParamsFactory.create = jest.fn().mockReturnValue({
|
keyParamsFactory.create = jest.fn().mockReturnValue({
|
||||||
@@ -83,13 +84,13 @@ describe('AuthResponseFactory20200115', () => {
|
|||||||
|
|
||||||
const result = await createFactory().createResponse({
|
const result = await createFactory().createResponse({
|
||||||
user,
|
user,
|
||||||
apiVersion: '20161215',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20161215).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
ephemeralSession: false,
|
ephemeralSession: false,
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.response).toEqual({
|
expect(result.legacyResponse).toEqual({
|
||||||
user: { foo: 'bar' },
|
user: { foo: 'bar' },
|
||||||
token: expect.any(String),
|
token: expect.any(String),
|
||||||
})
|
})
|
||||||
@@ -100,18 +101,18 @@ describe('AuthResponseFactory20200115', () => {
|
|||||||
|
|
||||||
const result = await createFactory().createResponse({
|
const result = await createFactory().createResponse({
|
||||||
user,
|
user,
|
||||||
apiVersion: '20200115',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
ephemeralSession: false,
|
ephemeralSession: false,
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.response).toEqual({
|
expect(result.response).toEqual({
|
||||||
key_params: {
|
keyParams: {
|
||||||
key1: 'value1',
|
key1: 'value1',
|
||||||
key2: 'value2',
|
key2: 'value2',
|
||||||
},
|
},
|
||||||
session: {
|
sessionBody: {
|
||||||
access_token: 'access_token',
|
access_token: 'access_token',
|
||||||
refresh_token: 'refresh_token',
|
refresh_token: 'refresh_token',
|
||||||
access_expiration: 123,
|
access_expiration: 123,
|
||||||
@@ -131,18 +132,18 @@ describe('AuthResponseFactory20200115', () => {
|
|||||||
|
|
||||||
const result = await createFactory().createResponse({
|
const result = await createFactory().createResponse({
|
||||||
user,
|
user,
|
||||||
apiVersion: '20200115',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
ephemeralSession: false,
|
ephemeralSession: false,
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.response).toEqual({
|
expect(result.response).toEqual({
|
||||||
key_params: {
|
keyParams: {
|
||||||
key1: 'value1',
|
key1: 'value1',
|
||||||
key2: 'value2',
|
key2: 'value2',
|
||||||
},
|
},
|
||||||
session: {
|
sessionBody: {
|
||||||
access_token: 'access_token',
|
access_token: 'access_token',
|
||||||
refresh_token: 'refresh_token',
|
refresh_token: 'refresh_token',
|
||||||
access_expiration: 123,
|
access_expiration: 123,
|
||||||
@@ -160,18 +161,18 @@ describe('AuthResponseFactory20200115', () => {
|
|||||||
|
|
||||||
const result = await createFactory().createResponse({
|
const result = await createFactory().createResponse({
|
||||||
user,
|
user,
|
||||||
apiVersion: '20200115',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
ephemeralSession: true,
|
ephemeralSession: true,
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.response).toEqual({
|
expect(result.response).toEqual({
|
||||||
key_params: {
|
keyParams: {
|
||||||
key1: 'value1',
|
key1: 'value1',
|
||||||
key2: 'value2',
|
key2: 'value2',
|
||||||
},
|
},
|
||||||
session: {
|
sessionBody: {
|
||||||
access_token: 'access_token',
|
access_token: 'access_token',
|
||||||
refresh_token: 'refresh_token',
|
refresh_token: 'refresh_token',
|
||||||
access_expiration: 123,
|
access_expiration: 123,
|
||||||
@@ -192,23 +193,23 @@ describe('AuthResponseFactory20200115', () => {
|
|||||||
...sessionPayload,
|
...sessionPayload,
|
||||||
readonly_access: true,
|
readonly_access: true,
|
||||||
},
|
},
|
||||||
session: {} as jest.Mocked<Session>,
|
sessionBody: {} as jest.Mocked<Session>,
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await createFactory().createResponse({
|
const result = await createFactory().createResponse({
|
||||||
user,
|
user,
|
||||||
apiVersion: '20200115',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
ephemeralSession: false,
|
ephemeralSession: false,
|
||||||
readonlyAccess: true,
|
readonlyAccess: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.response).toEqual({
|
expect(result.response).toEqual({
|
||||||
key_params: {
|
keyParams: {
|
||||||
key1: 'value1',
|
key1: 'value1',
|
||||||
key2: 'value2',
|
key2: 'value2',
|
||||||
},
|
},
|
||||||
session: {
|
sessionBody: {
|
||||||
access_token: 'access_token',
|
access_token: 'access_token',
|
||||||
refresh_token: 'refresh_token',
|
refresh_token: 'refresh_token',
|
||||||
access_expiration: 123,
|
access_expiration: 123,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
TokenEncoderInterface,
|
TokenEncoderInterface,
|
||||||
} from '@standardnotes/security'
|
} from '@standardnotes/security'
|
||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import { SessionBody } from '@standardnotes/responses'
|
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@@ -17,9 +16,9 @@ import { User } from '../User/User'
|
|||||||
import { AuthResponseFactory20190520 } from './AuthResponseFactory20190520'
|
import { AuthResponseFactory20190520 } from './AuthResponseFactory20190520'
|
||||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||||
|
|
||||||
import { AuthResponse20161215 } from './AuthResponse20161215'
|
import { SessionCreationResult } from '../Session/SessionCreationResult'
|
||||||
import { AuthResponse20200115 } from './AuthResponse20200115'
|
import { AuthResponseCreationResult } from './AuthResponseCreationResult'
|
||||||
import { Session } from '../Session/Session'
|
import { ApiVersion } from '../Api/ApiVersion'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class AuthResponseFactory20200115 extends AuthResponseFactory20190520 {
|
export class AuthResponseFactory20200115 extends AuthResponseFactory20190520 {
|
||||||
@@ -37,11 +36,13 @@ export class AuthResponseFactory20200115 extends AuthResponseFactory20190520 {
|
|||||||
|
|
||||||
override async createResponse(dto: {
|
override async createResponse(dto: {
|
||||||
user: User
|
user: User
|
||||||
apiVersion: string
|
apiVersion: ApiVersion
|
||||||
userAgent: string
|
userAgent: string
|
||||||
ephemeralSession: boolean
|
ephemeralSession: boolean
|
||||||
readonlyAccess: boolean
|
readonlyAccess: boolean
|
||||||
}): Promise<{ response: AuthResponse20161215 | AuthResponse20200115; session?: Session }> {
|
snjs?: string
|
||||||
|
application?: string
|
||||||
|
}): Promise<AuthResponseCreationResult> {
|
||||||
if (!dto.user.supportsSessions()) {
|
if (!dto.user.supportsSessions()) {
|
||||||
this.logger.debug(`User ${dto.user.uuid} does not support sessions. Falling back to JWT auth response`)
|
this.logger.debug(`User ${dto.user.uuid} does not support sessions. Falling back to JWT auth response`)
|
||||||
|
|
||||||
@@ -50,29 +51,31 @@ export class AuthResponseFactory20200115 extends AuthResponseFactory20190520 {
|
|||||||
|
|
||||||
const sessionCreationResult = await this.createSession(dto)
|
const sessionCreationResult = await this.createSession(dto)
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug('Created session payload for user', {
|
||||||
'Created session payload for user %s: %O',
|
userId: dto.user.uuid,
|
||||||
dto.user.uuid,
|
session: sessionCreationResult,
|
||||||
sessionCreationResult.sessionHttpRepresentation,
|
})
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
response: {
|
response: {
|
||||||
session: sessionCreationResult.sessionHttpRepresentation,
|
sessionBody: sessionCreationResult.sessionHttpRepresentation,
|
||||||
key_params: this.keyParamsFactory.create(dto.user, true),
|
keyParams: this.keyParamsFactory.create(dto.user, true),
|
||||||
user: this.userProjector.projectSimple(dto.user) as SimpleUserProjection,
|
user: this.userProjector.projectSimple(dto.user) as SimpleUserProjection,
|
||||||
},
|
},
|
||||||
session: sessionCreationResult.session,
|
session: sessionCreationResult.session,
|
||||||
|
cookies: sessionCreationResult.sessionCookieRepresentation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createSession(dto: {
|
private async createSession(dto: {
|
||||||
user: User
|
user: User
|
||||||
apiVersion: string
|
apiVersion: ApiVersion
|
||||||
userAgent: string
|
userAgent: string
|
||||||
ephemeralSession: boolean
|
ephemeralSession: boolean
|
||||||
readonlyAccess: boolean
|
readonlyAccess: boolean
|
||||||
}): Promise<{ sessionHttpRepresentation: SessionBody; session: Session }> {
|
snjs?: string
|
||||||
|
application?: string
|
||||||
|
}): Promise<SessionCreationResult> {
|
||||||
if (dto.ephemeralSession) {
|
if (dto.ephemeralSession) {
|
||||||
return this.sessionService.createNewEphemeralSessionForUser(dto)
|
return this.sessionService.createNewEphemeralSessionForUser(dto)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { Session } from '../Session/Session'
|
import { ApiVersion } from '../Api/ApiVersion'
|
||||||
import { User } from '../User/User'
|
import { User } from '../User/User'
|
||||||
import { AuthResponse20161215 } from './AuthResponse20161215'
|
import { AuthResponseCreationResult } from './AuthResponseCreationResult'
|
||||||
import { AuthResponse20200115 } from './AuthResponse20200115'
|
|
||||||
|
|
||||||
export interface AuthResponseFactoryInterface {
|
export interface AuthResponseFactoryInterface {
|
||||||
createResponse(dto: {
|
createResponse(dto: {
|
||||||
user: User
|
user: User
|
||||||
apiVersion: string
|
apiVersion: ApiVersion
|
||||||
userAgent: string
|
userAgent: string
|
||||||
ephemeralSession: boolean
|
ephemeralSession: boolean
|
||||||
readonlyAccess: boolean
|
readonlyAccess: boolean
|
||||||
}): Promise<{ response: AuthResponse20161215 | AuthResponse20200115; session?: Session }>
|
snjs?: string
|
||||||
|
application?: string
|
||||||
|
}): Promise<AuthResponseCreationResult>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { AuthResponseFactory20161215 } from './AuthResponseFactory20161215'
|
|||||||
import { AuthResponseFactory20190520 } from './AuthResponseFactory20190520'
|
import { AuthResponseFactory20190520 } from './AuthResponseFactory20190520'
|
||||||
import { AuthResponseFactory20200115 } from './AuthResponseFactory20200115'
|
import { AuthResponseFactory20200115 } from './AuthResponseFactory20200115'
|
||||||
import { AuthResponseFactoryResolver } from './AuthResponseFactoryResolver'
|
import { AuthResponseFactoryResolver } from './AuthResponseFactoryResolver'
|
||||||
|
import { ApiVersion } from '../Api/ApiVersion'
|
||||||
|
|
||||||
describe('AuthResponseFactoryResolver', () => {
|
describe('AuthResponseFactoryResolver', () => {
|
||||||
let authResponseFactory20161215: AuthResponseFactory20161215
|
let authResponseFactory20161215: AuthResponseFactory20161215
|
||||||
@@ -30,18 +31,26 @@ describe('AuthResponseFactoryResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should resolve 2016 response factory', () => {
|
it('should resolve 2016 response factory', () => {
|
||||||
expect(createResolver().resolveAuthResponseFactoryVersion('20161215')).toEqual(authResponseFactory20161215)
|
expect(
|
||||||
|
createResolver().resolveAuthResponseFactoryVersion(ApiVersion.create(ApiVersion.VERSIONS.v20161215).getValue()),
|
||||||
|
).toEqual(authResponseFactory20161215)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should resolve 2019 response factory', () => {
|
it('should resolve 2019 response factory', () => {
|
||||||
expect(createResolver().resolveAuthResponseFactoryVersion('20190520')).toEqual(authResponseFactory20190520)
|
expect(
|
||||||
|
createResolver().resolveAuthResponseFactoryVersion(ApiVersion.create(ApiVersion.VERSIONS.v20190520).getValue()),
|
||||||
|
).toEqual(authResponseFactory20190520)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should resolve 2020 response factory', () => {
|
it('should resolve 2020 response factory', () => {
|
||||||
expect(createResolver().resolveAuthResponseFactoryVersion('20200115')).toEqual(authResponseFactory20200115)
|
expect(
|
||||||
|
createResolver().resolveAuthResponseFactoryVersion(ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue()),
|
||||||
|
).toEqual(authResponseFactory20200115)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should resolve 2016 response factory as default', () => {
|
it('should resolve 2024 response factory', () => {
|
||||||
expect(createResolver().resolveAuthResponseFactoryVersion('')).toEqual(authResponseFactory20161215)
|
expect(
|
||||||
|
createResolver().resolveAuthResponseFactoryVersion(ApiVersion.create(ApiVersion.VERSIONS.v20240226).getValue()),
|
||||||
|
).toEqual(authResponseFactory20200115)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ export class AuthResponseFactoryResolver implements AuthResponseFactoryResolverI
|
|||||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
resolveAuthResponseFactoryVersion(apiVersion: string): AuthResponseFactoryInterface {
|
resolveAuthResponseFactoryVersion(apiVersion: ApiVersion): AuthResponseFactoryInterface {
|
||||||
this.logger.debug(`Resolving auth response factory for api version: ${apiVersion}`)
|
this.logger.debug(`Resolving auth response factory for api version: ${apiVersion.value}`)
|
||||||
|
|
||||||
switch (apiVersion) {
|
switch (apiVersion.value) {
|
||||||
case ApiVersion.v20190520:
|
case ApiVersion.VERSIONS.v20190520:
|
||||||
return this.authResponseFactory20190520
|
return this.authResponseFactory20190520
|
||||||
case ApiVersion.v20200115:
|
case ApiVersion.VERSIONS.v20200115:
|
||||||
|
case ApiVersion.VERSIONS.v20240226:
|
||||||
return this.authResponseFactory20200115
|
return this.authResponseFactory20200115
|
||||||
default:
|
default:
|
||||||
return this.authResponseFactory20161215
|
return this.authResponseFactory20161215
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { ApiVersion } from '../Api/ApiVersion'
|
||||||
import { AuthResponseFactoryInterface } from './AuthResponseFactoryInterface'
|
import { AuthResponseFactoryInterface } from './AuthResponseFactoryInterface'
|
||||||
|
|
||||||
export interface AuthResponseFactoryResolverInterface {
|
export interface AuthResponseFactoryResolverInterface {
|
||||||
resolveAuthResponseFactoryVersion(apiVersion: string): AuthResponseFactoryInterface
|
resolveAuthResponseFactoryVersion(apiVersion: ApiVersion): AuthResponseFactoryInterface
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ export type AuthenticationMethod = {
|
|||||||
user: User | null
|
user: User | null
|
||||||
claims?: Record<string, unknown>
|
claims?: Record<string, unknown>
|
||||||
session?: Session
|
session?: Session
|
||||||
|
givenTokensWereInCooldown?: boolean
|
||||||
revokedSession?: RevokedSession
|
revokedSession?: RevokedSession
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,19 +10,29 @@ import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
|||||||
|
|
||||||
import { AuthenticationMethodResolver } from './AuthenticationMethodResolver'
|
import { AuthenticationMethodResolver } from './AuthenticationMethodResolver'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
import { GetSessionFromToken } from '../UseCase/GetSessionFromToken/GetSessionFromToken'
|
||||||
|
import { Result } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
describe('AuthenticationMethodResolver', () => {
|
describe('AuthenticationMethodResolver', () => {
|
||||||
let userRepository: UserRepositoryInterface
|
let userRepository: UserRepositoryInterface
|
||||||
let sessionService: SessionServiceInterface
|
let sessionService: SessionServiceInterface
|
||||||
let sessionTokenDecoder: TokenDecoderInterface<SessionTokenData>
|
let sessionTokenDecoder: TokenDecoderInterface<SessionTokenData>
|
||||||
let fallbackTokenDecoder: TokenDecoderInterface<SessionTokenData>
|
let fallbackTokenDecoder: TokenDecoderInterface<SessionTokenData>
|
||||||
|
let getSessionFromToken: GetSessionFromToken
|
||||||
let user: User
|
let user: User
|
||||||
let session: Session
|
let session: Session
|
||||||
let revokedSession: RevokedSession
|
let revokedSession: RevokedSession
|
||||||
let logger: Logger
|
let logger: Logger
|
||||||
|
|
||||||
const createResolver = () =>
|
const createResolver = () =>
|
||||||
new AuthenticationMethodResolver(userRepository, sessionService, sessionTokenDecoder, fallbackTokenDecoder, logger)
|
new AuthenticationMethodResolver(
|
||||||
|
userRepository,
|
||||||
|
sessionService,
|
||||||
|
sessionTokenDecoder,
|
||||||
|
fallbackTokenDecoder,
|
||||||
|
getSessionFromToken,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
logger = {} as jest.Mocked<Logger>
|
logger = {} as jest.Mocked<Logger>
|
||||||
@@ -41,10 +51,12 @@ describe('AuthenticationMethodResolver', () => {
|
|||||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
|
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
|
||||||
|
|
||||||
sessionService = {} as jest.Mocked<SessionServiceInterface>
|
sessionService = {} as jest.Mocked<SessionServiceInterface>
|
||||||
sessionService.getSessionFromToken = jest.fn().mockReturnValue({ session: undefined, isEphemeral: false })
|
|
||||||
sessionService.getRevokedSessionFromToken = jest.fn()
|
sessionService.getRevokedSessionFromToken = jest.fn()
|
||||||
sessionService.markRevokedSessionAsReceived = jest.fn().mockReturnValue(revokedSession)
|
sessionService.markRevokedSessionAsReceived = jest.fn().mockReturnValue(revokedSession)
|
||||||
|
|
||||||
|
getSessionFromToken = {} as jest.Mocked<GetSessionFromToken>
|
||||||
|
getSessionFromToken.execute = jest.fn().mockReturnValue(Result.fail('No session found.'))
|
||||||
|
|
||||||
sessionTokenDecoder = {} as jest.Mocked<TokenDecoderInterface<SessionTokenData>>
|
sessionTokenDecoder = {} as jest.Mocked<TokenDecoderInterface<SessionTokenData>>
|
||||||
sessionTokenDecoder.decodeToken = jest.fn()
|
sessionTokenDecoder.decodeToken = jest.fn()
|
||||||
|
|
||||||
@@ -55,7 +67,12 @@ describe('AuthenticationMethodResolver', () => {
|
|||||||
it('should resolve jwt authentication method', async () => {
|
it('should resolve jwt authentication method', async () => {
|
||||||
sessionTokenDecoder.decodeToken = jest.fn().mockReturnValue({ user_uuid: '00000000-0000-0000-0000-000000000000' })
|
sessionTokenDecoder.decodeToken = jest.fn().mockReturnValue({ user_uuid: '00000000-0000-0000-0000-000000000000' })
|
||||||
|
|
||||||
expect(await createResolver().resolve('test')).toEqual({
|
expect(
|
||||||
|
await createResolver().resolve({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
claims: {
|
claims: {
|
||||||
user_uuid: '00000000-0000-0000-0000-000000000000',
|
user_uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
},
|
},
|
||||||
@@ -67,31 +84,56 @@ describe('AuthenticationMethodResolver', () => {
|
|||||||
it('should not resolve jwt authentication method with invalid user uuid', async () => {
|
it('should not resolve jwt authentication method with invalid user uuid', async () => {
|
||||||
sessionTokenDecoder.decodeToken = jest.fn().mockReturnValue({ user_uuid: 'invalid' })
|
sessionTokenDecoder.decodeToken = jest.fn().mockReturnValue({ user_uuid: 'invalid' })
|
||||||
|
|
||||||
expect(await createResolver().resolve('test')).toBeUndefined
|
expect(
|
||||||
|
await createResolver().resolve({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
}),
|
||||||
|
).toBeUndefined
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should resolve session authentication method', async () => {
|
it('should resolve session authentication method', async () => {
|
||||||
sessionService.getSessionFromToken = jest.fn().mockReturnValue({ session, isEphemeral: false })
|
getSessionFromToken.execute = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(Result.ok({ session, isEphemeral: false, givenTokensWereInCooldown: false }))
|
||||||
|
|
||||||
expect(await createResolver().resolve('test')).toEqual({
|
expect(
|
||||||
|
await createResolver().resolve({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
session,
|
session,
|
||||||
type: 'session_token',
|
type: 'session_token',
|
||||||
user,
|
user,
|
||||||
|
givenTokensWereInCooldown: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not resolve session authentication method with invalid user uuid on session', async () => {
|
it('should not resolve session authentication method with invalid user uuid on session', async () => {
|
||||||
sessionService.getSessionFromToken = jest
|
getSessionFromToken.execute = jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockReturnValue({ session: { userUuid: 'invalid' }, isEphemeral: false })
|
.mockReturnValue(
|
||||||
|
Result.ok({ session: { userUuid: 'invalid' }, isEphemeral: false, givenTokensWereInCooldown: false }),
|
||||||
|
)
|
||||||
|
|
||||||
expect(await createResolver().resolve('test')).toBeUndefined
|
expect(
|
||||||
|
await createResolver().resolve({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
}),
|
||||||
|
).toBeUndefined
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should resolve archvied session authentication method', async () => {
|
it('should resolve archvied session authentication method', async () => {
|
||||||
sessionService.getRevokedSessionFromToken = jest.fn().mockReturnValue(revokedSession)
|
sessionService.getRevokedSessionFromToken = jest.fn().mockReturnValue(revokedSession)
|
||||||
|
|
||||||
expect(await createResolver().resolve('test')).toEqual({
|
expect(
|
||||||
|
await createResolver().resolve({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
revokedSession,
|
revokedSession,
|
||||||
type: 'revoked',
|
type: 'revoked',
|
||||||
user: null,
|
user: null,
|
||||||
@@ -101,6 +143,11 @@ describe('AuthenticationMethodResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should indicated that authentication method cannot be resolved', async () => {
|
it('should indicated that authentication method cannot be resolved', async () => {
|
||||||
expect(await createResolver().resolve('test')).toBeUndefined
|
expect(
|
||||||
|
await createResolver().resolve({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
}),
|
||||||
|
).toBeUndefined
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,30 +1,39 @@
|
|||||||
import { SessionTokenData, TokenDecoderInterface } from '@standardnotes/security'
|
import { SessionTokenData, TokenDecoderInterface } from '@standardnotes/security'
|
||||||
import { inject, injectable } from 'inversify'
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
|
||||||
import { SessionServiceInterface } from '../Session/SessionServiceInterface'
|
import { SessionServiceInterface } from '../Session/SessionServiceInterface'
|
||||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
import { AuthenticationMethod } from './AuthenticationMethod'
|
import { AuthenticationMethod } from './AuthenticationMethod'
|
||||||
import { AuthenticationMethodResolverInterface } from './AuthenticationMethodResolverInterface'
|
import { AuthenticationMethodResolverInterface } from './AuthenticationMethodResolverInterface'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { Uuid } from '@standardnotes/domain-core'
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
import { GetSessionFromToken } from '../UseCase/GetSessionFromToken/GetSessionFromToken'
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class AuthenticationMethodResolver implements AuthenticationMethodResolverInterface {
|
export class AuthenticationMethodResolver implements AuthenticationMethodResolverInterface {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TYPES.Auth_UserRepository) private userRepository: UserRepositoryInterface,
|
private userRepository: UserRepositoryInterface,
|
||||||
@inject(TYPES.Auth_SessionService) private sessionService: SessionServiceInterface,
|
private sessionService: SessionServiceInterface,
|
||||||
@inject(TYPES.Auth_SessionTokenDecoder) private sessionTokenDecoder: TokenDecoderInterface<SessionTokenData>,
|
private sessionTokenDecoder: TokenDecoderInterface<SessionTokenData>,
|
||||||
@inject(TYPES.Auth_FallbackSessionTokenDecoder)
|
|
||||||
private fallbackSessionTokenDecoder: TokenDecoderInterface<SessionTokenData>,
|
private fallbackSessionTokenDecoder: TokenDecoderInterface<SessionTokenData>,
|
||||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
private getSessionFromToken: GetSessionFromToken,
|
||||||
|
private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async resolve(token: string): Promise<AuthenticationMethod | undefined> {
|
async resolve(dto: {
|
||||||
let decodedToken: SessionTokenData | undefined = this.sessionTokenDecoder.decodeToken(token)
|
authTokenFromHeaders: string
|
||||||
|
authCookies?: Map<string, string[]>
|
||||||
|
requestMetadata: {
|
||||||
|
url: string
|
||||||
|
method: string
|
||||||
|
snjs?: string
|
||||||
|
application?: string
|
||||||
|
userAgent?: string
|
||||||
|
secChUa?: string
|
||||||
|
}
|
||||||
|
}): Promise<AuthenticationMethod | undefined> {
|
||||||
|
let decodedToken: SessionTokenData | undefined = this.sessionTokenDecoder.decodeToken(dto.authTokenFromHeaders)
|
||||||
if (decodedToken === undefined) {
|
if (decodedToken === undefined) {
|
||||||
this.logger.debug('Could not decode token with primary decoder, trying fallback decoder.')
|
this.logger.debug('Could not decode token with primary decoder, trying fallback decoder.')
|
||||||
|
|
||||||
decodedToken = this.fallbackSessionTokenDecoder.decodeToken(token)
|
decodedToken = this.fallbackSessionTokenDecoder.decodeToken(dto.authTokenFromHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decodedToken) {
|
if (decodedToken) {
|
||||||
@@ -47,8 +56,10 @@ export class AuthenticationMethodResolver implements AuthenticationMethodResolve
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { session } = await this.sessionService.getSessionFromToken(token)
|
const resultOrError = await this.getSessionFromToken.execute(dto)
|
||||||
if (session) {
|
if (!resultOrError.isFailed()) {
|
||||||
|
const { session, givenTokensWereInCooldown } = resultOrError.getValue()
|
||||||
|
|
||||||
this.logger.debug('Token decoded successfully. Session found.')
|
this.logger.debug('Token decoded successfully. Session found.')
|
||||||
|
|
||||||
const userUuidOrError = Uuid.create(session.userUuid)
|
const userUuidOrError = Uuid.create(session.userUuid)
|
||||||
@@ -61,10 +72,11 @@ export class AuthenticationMethodResolver implements AuthenticationMethodResolve
|
|||||||
type: 'session_token',
|
type: 'session_token',
|
||||||
user: await this.userRepository.findOneByUuid(userUuid),
|
user: await this.userRepository.findOneByUuid(userUuid),
|
||||||
session: session,
|
session: session,
|
||||||
|
givenTokensWereInCooldown: givenTokensWereInCooldown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const revokedSession = await this.sessionService.getRevokedSessionFromToken(token)
|
const revokedSession = await this.sessionService.getRevokedSessionFromToken(dto.authTokenFromHeaders)
|
||||||
if (revokedSession) {
|
if (revokedSession) {
|
||||||
this.logger.debug('Token decoded successfully. Revoked session found.')
|
this.logger.debug('Token decoded successfully. Revoked session found.')
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
import { AuthenticationMethod } from './AuthenticationMethod'
|
import { AuthenticationMethod } from './AuthenticationMethod'
|
||||||
|
|
||||||
export interface AuthenticationMethodResolverInterface {
|
export interface AuthenticationMethodResolverInterface {
|
||||||
resolve(token: string): Promise<AuthenticationMethod | undefined>
|
resolve(dto: {
|
||||||
|
authTokenFromHeaders: string
|
||||||
|
authCookies?: Map<string, string[]>
|
||||||
|
requestMetadata: {
|
||||||
|
url: string
|
||||||
|
method: string
|
||||||
|
snjs?: string
|
||||||
|
application?: string
|
||||||
|
userAgent?: string
|
||||||
|
secChUa?: string
|
||||||
|
}
|
||||||
|
}): Promise<AuthenticationMethod | undefined>
|
||||||
}
|
}
|
||||||
|
|||||||
28
packages/auth/src/Domain/Auth/Cookies/CookieFactory.ts
Normal file
28
packages/auth/src/Domain/Auth/Cookies/CookieFactory.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { CookieFactoryInterface } from './CookieFactoryInterface'
|
||||||
|
|
||||||
|
export class CookieFactory implements CookieFactoryInterface {
|
||||||
|
constructor(
|
||||||
|
private sameSite: 'None' | 'Lax' | 'Strict',
|
||||||
|
private domain: string,
|
||||||
|
private secure: boolean,
|
||||||
|
private partitioned: boolean,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
createCookieHeaderValue(dto: {
|
||||||
|
sessionUuid: string
|
||||||
|
accessToken: string
|
||||||
|
refreshToken: string
|
||||||
|
refreshTokenExpiration: Date
|
||||||
|
}): string[] {
|
||||||
|
return [
|
||||||
|
`access_token_${dto.sessionUuid}=${dto.accessToken}; HttpOnly;${this.secure ? 'Secure; ' : ' '}Path=/;${
|
||||||
|
this.partitioned ? 'Partitioned; ' : ' '
|
||||||
|
}SameSite=${this.sameSite}; Domain=${this.domain}; Expires=${dto.refreshTokenExpiration.toUTCString()};`,
|
||||||
|
`refresh_token_${dto.sessionUuid}=${dto.refreshToken}; HttpOnly;${
|
||||||
|
this.secure ? 'Secure; ' : ' '
|
||||||
|
}Path=/v1/sessions/refresh;${this.partitioned ? 'Partitioned; ' : ' '}SameSite=${this.sameSite}; Domain=${
|
||||||
|
this.domain
|
||||||
|
}; Expires=${dto.refreshTokenExpiration.toUTCString()};`,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export interface CookieFactoryInterface {
|
||||||
|
createCookieHeaderValue(dto: {
|
||||||
|
sessionUuid: string
|
||||||
|
accessToken: string
|
||||||
|
refreshToken: string
|
||||||
|
refreshTokenExpiration: Date
|
||||||
|
}): string[]
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
export const html = (userEmail: string, offlineSubscriptionDashboardUrl: string) => `<div class="sn-component">
|
import { safeHtml } from '@standardnotes/common'
|
||||||
|
|
||||||
|
export const html = (userEmail: string, offlineSubscriptionDashboardUrl: string) => safeHtml`<div class="sn-component">
|
||||||
<div class="sk-panel static">
|
<div class="sk-panel static">
|
||||||
<div class="sk-panel-content">
|
<div class="sk-panel-content">
|
||||||
<div class="sk-panel-section">
|
<div class="sk-panel-section">
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export const html = (inviterIdentifier: string, inviteUuid: string) => `<p>Hello,</p>
|
import { safeHtml } from '@standardnotes/common'
|
||||||
|
|
||||||
|
export const html = (inviterIdentifier: string, inviteUuid: string) => safeHtml`<p>Hello,</p>
|
||||||
<p>You've been invited to join a Standard Notes premium subscription at no cost. ${inviterIdentifier} has invited you to share the benefits of their subscription plan.</p>
|
<p>You've been invited to join a Standard Notes premium subscription at no cost. ${inviterIdentifier} has invited you to share the benefits of their subscription plan.</p>
|
||||||
<p>
|
<p>
|
||||||
<a href='https://app.standardnotes.com/?accept-subscription-invite=${inviteUuid}'>Accept Invite</a>
|
<a href='https://app.standardnotes.com/?accept-subscription-invite=${inviteUuid}'>Accept Invite</a>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export const html = (newEmail: string) => `
|
import { safeHtml } from '@standardnotes/common'
|
||||||
|
|
||||||
|
export const html = (newEmail: string) => safeHtml`
|
||||||
<p>Hello,</p>
|
<p>Hello,</p>
|
||||||
|
|
||||||
<p>We are writing to inform you that your request to update your email address has been successfully processed. The email address associated with your Standard Notes account has now been changed to the following:</p>
|
<p>We are writing to inform you that your request to update your email address has been successfully processed. The email address associated with your Standard Notes account has now been changed to the following:</p>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export const html = () => `
|
import { safeHtml } from '@standardnotes/common'
|
||||||
|
|
||||||
|
export const html = () => safeHtml`
|
||||||
<p>Hello,</p>
|
<p>Hello,</p>
|
||||||
|
|
||||||
<p>You've been invited to join a shared vault. This shared workspace will help you collaborate and securely manage notes and files.</p>
|
<p>You've been invited to join a shared vault. This shared workspace will help you collaborate and securely manage notes and files.</p>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export const html = (email: string, device: string, browser: string, timeAndDate: string) => `
|
import { safeHtml } from '@standardnotes/common'
|
||||||
|
|
||||||
|
export const html = (email: string, device: string, browser: string, timeAndDate: string) => safeHtml`
|
||||||
<div>
|
<div>
|
||||||
<p>Hello,</p>
|
<p>Hello,</p>
|
||||||
<p>We've detected a new sign-in to your account ${email}</p>
|
<p>We've detected a new sign-in to your account ${email}</p>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
SessionRefreshedEvent,
|
SessionRefreshedEvent,
|
||||||
AccountDeletionVerificationRequestedEvent,
|
AccountDeletionVerificationRequestedEvent,
|
||||||
FileQuotaRecalculationRequestedEvent,
|
FileQuotaRecalculationRequestedEvent,
|
||||||
|
SubscriptionStateRequestedEvent,
|
||||||
} from '@standardnotes/domain-events'
|
} from '@standardnotes/domain-events'
|
||||||
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
|
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
@@ -34,6 +35,21 @@ import { KeyParamsData } from '@standardnotes/responses'
|
|||||||
@injectable()
|
@injectable()
|
||||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||||
constructor(@inject(TYPES.Auth_Timer) private timer: TimerInterface) {}
|
constructor(@inject(TYPES.Auth_Timer) private timer: TimerInterface) {}
|
||||||
|
createSubscriptionStateRequestedEvent(dto: { userEmail: string }): SubscriptionStateRequestedEvent {
|
||||||
|
return {
|
||||||
|
type: 'SUBSCRIPTION_STATE_REQUESTED',
|
||||||
|
createdAt: this.timer.getUTCDate(),
|
||||||
|
meta: {
|
||||||
|
correlation: {
|
||||||
|
userIdentifier: dto.userEmail,
|
||||||
|
userIdentifierType: 'email',
|
||||||
|
},
|
||||||
|
origin: DomainEventService.Auth,
|
||||||
|
},
|
||||||
|
payload: dto,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createFileQuotaRecalculationRequestedEvent(dto: { userUuid: string }): FileQuotaRecalculationRequestedEvent {
|
createFileQuotaRecalculationRequestedEvent(dto: { userUuid: string }): FileQuotaRecalculationRequestedEvent {
|
||||||
return {
|
return {
|
||||||
type: 'FILE_QUOTA_RECALCULATION_REQUESTED',
|
type: 'FILE_QUOTA_RECALCULATION_REQUESTED',
|
||||||
@@ -289,12 +305,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createEmailBackupRequestedEvent(
|
createEmailBackupRequestedEvent(userUuid: string, keyParams: KeyParamsData): EmailBackupRequestedEvent {
|
||||||
userUuid: string,
|
|
||||||
muteEmailsSettingUuid: string,
|
|
||||||
userHasEmailsMuted: boolean,
|
|
||||||
keyParams: KeyParamsData,
|
|
||||||
): EmailBackupRequestedEvent {
|
|
||||||
return {
|
return {
|
||||||
type: 'EMAIL_BACKUP_REQUESTED',
|
type: 'EMAIL_BACKUP_REQUESTED',
|
||||||
createdAt: this.timer.getUTCDate(),
|
createdAt: this.timer.getUTCDate(),
|
||||||
@@ -307,8 +318,6 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
|||||||
},
|
},
|
||||||
payload: {
|
payload: {
|
||||||
userUuid,
|
userUuid,
|
||||||
userHasEmailsMuted,
|
|
||||||
muteEmailsSettingUuid,
|
|
||||||
keyParams,
|
keyParams,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ import {
|
|||||||
SessionRefreshedEvent,
|
SessionRefreshedEvent,
|
||||||
AccountDeletionVerificationRequestedEvent,
|
AccountDeletionVerificationRequestedEvent,
|
||||||
FileQuotaRecalculationRequestedEvent,
|
FileQuotaRecalculationRequestedEvent,
|
||||||
|
SubscriptionStateRequestedEvent,
|
||||||
} from '@standardnotes/domain-events'
|
} from '@standardnotes/domain-events'
|
||||||
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
|
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
|
||||||
import { KeyParamsData } from '@standardnotes/responses'
|
import { KeyParamsData } from '@standardnotes/responses'
|
||||||
|
|
||||||
export interface DomainEventFactoryInterface {
|
export interface DomainEventFactoryInterface {
|
||||||
|
createSubscriptionStateRequestedEvent(dto: { userEmail: string }): SubscriptionStateRequestedEvent
|
||||||
createFileQuotaRecalculationRequestedEvent(dto: { userUuid: string }): FileQuotaRecalculationRequestedEvent
|
createFileQuotaRecalculationRequestedEvent(dto: { userUuid: string }): FileQuotaRecalculationRequestedEvent
|
||||||
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: JSONString }): WebSocketMessageRequestedEvent
|
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: JSONString }): WebSocketMessageRequestedEvent
|
||||||
createEmailRequestedEvent(dto: {
|
createEmailRequestedEvent(dto: {
|
||||||
@@ -41,12 +43,7 @@ export interface DomainEventFactoryInterface {
|
|||||||
email: string
|
email: string
|
||||||
protocolVersion: ProtocolVersion
|
protocolVersion: ProtocolVersion
|
||||||
}): UserRegisteredEvent
|
}): UserRegisteredEvent
|
||||||
createEmailBackupRequestedEvent(
|
createEmailBackupRequestedEvent(userUuid: string, keyParams: KeyParamsData): EmailBackupRequestedEvent
|
||||||
userUuid: string,
|
|
||||||
muteEmailsSettingUuid: string,
|
|
||||||
userHasEmailsMuted: boolean,
|
|
||||||
keyParams: KeyParamsData,
|
|
||||||
): EmailBackupRequestedEvent
|
|
||||||
createAccountDeletionRequestedEvent(dto: {
|
createAccountDeletionRequestedEvent(dto: {
|
||||||
userUuid: string
|
userUuid: string
|
||||||
email: string
|
email: string
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { inject, injectable } from 'inversify'
|
|||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
|
export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
|
||||||
@@ -12,9 +13,20 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
|||||||
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
||||||
@inject(TYPES.Auth_OfflineUserSubscriptionRepository)
|
@inject(TYPES.Auth_OfflineUserSubscriptionRepository)
|
||||||
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
|
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
|
||||||
|
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionCancelledEvent): Promise<void> {
|
async handle(event: SubscriptionCancelledEvent): Promise<void> {
|
||||||
|
if (!event.payload.subscriptionId) {
|
||||||
|
this.logger.error('Subscription ID is missing', {
|
||||||
|
codeTag: 'SubscriptionCancelledEventHandler.handle',
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
userId: event.payload.userEmail,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (event.payload.offline) {
|
if (event.payload.offline) {
|
||||||
await this.updateOfflineSubscriptionCancelled(event.payload.subscriptionId, event.payload.timestamp)
|
await this.updateOfflineSubscriptionCancelled(event.payload.subscriptionId, event.payload.timestamp)
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,16 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionExpiredEvent): Promise<void> {
|
async handle(event: SubscriptionExpiredEvent): Promise<void> {
|
||||||
|
if (!event.payload.subscriptionId) {
|
||||||
|
this.logger.error('Subscription ID is missing', {
|
||||||
|
codeTag: 'SubscriptionExpiredEventHandler.handle',
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
userId: event.payload.userEmail,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (event.payload.offline) {
|
if (event.payload.offline) {
|
||||||
await this.updateOfflineSubscriptionEndsAt(event.payload.subscriptionId, event.payload.timestamp)
|
await this.updateOfflineSubscriptionEndsAt(event.payload.subscriptionId, event.payload.timestamp)
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,16 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionPurchasedEvent): Promise<void> {
|
async handle(event: SubscriptionPurchasedEvent): Promise<void> {
|
||||||
|
if (!event.payload.subscriptionId) {
|
||||||
|
this.logger.error('Subscription ID is missing', {
|
||||||
|
codeTag: 'SubscriptionPurchasedEventHandler.handle',
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
userId: event.payload.userEmail,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (event.payload.offline) {
|
if (event.payload.offline) {
|
||||||
const offlineUserSubscription = await this.createOfflineSubscription(
|
const offlineUserSubscription = await this.createOfflineSubscription(
|
||||||
event.payload.subscriptionId,
|
event.payload.subscriptionId,
|
||||||
|
|||||||
@@ -22,6 +22,16 @@ export class SubscriptionReassignedEventHandler implements DomainEventHandlerInt
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionReassignedEvent): Promise<void> {
|
async handle(event: SubscriptionReassignedEvent): Promise<void> {
|
||||||
|
if (!event.payload.subscriptionId) {
|
||||||
|
this.logger.error('Subscription ID is missing', {
|
||||||
|
codeTag: 'SubscriptionReassignedEventHandler.handle',
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
userId: event.payload.userEmail,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const usernameOrError = Username.create(event.payload.userEmail)
|
const usernameOrError = Username.create(event.payload.userEmail)
|
||||||
if (usernameOrError.isFailed()) {
|
if (usernameOrError.isFailed()) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -22,6 +22,16 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionRefundedEvent): Promise<void> {
|
async handle(event: SubscriptionRefundedEvent): Promise<void> {
|
||||||
|
if (!event.payload.subscriptionId) {
|
||||||
|
this.logger.error('Subscription ID is missing', {
|
||||||
|
codeTag: 'SubscriptionRefundedEventHandler.handle',
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
userId: event.payload.userEmail,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (event.payload.offline) {
|
if (event.payload.offline) {
|
||||||
await this.updateOfflineSubscriptionEndsAt(event.payload.subscriptionId, event.payload.timestamp)
|
await this.updateOfflineSubscriptionEndsAt(event.payload.subscriptionId, event.payload.timestamp)
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,16 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionRenewedEvent): Promise<void> {
|
async handle(event: SubscriptionRenewedEvent): Promise<void> {
|
||||||
|
if (!event.payload.subscriptionId) {
|
||||||
|
this.logger.error('Subscription ID is missing', {
|
||||||
|
codeTag: 'SubscriptionRenewedEventHandler.handle',
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
userId: event.payload.userEmail,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (event.payload.offline) {
|
if (event.payload.offline) {
|
||||||
const offlineUserSubscription = await this.offlineUserSubscriptionRepository.findOneBySubscriptionId(
|
const offlineUserSubscription = await this.offlineUserSubscriptionRepository.findOneBySubscriptionId(
|
||||||
event.payload.subscriptionId,
|
event.payload.subscriptionId,
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
import { DomainEventHandlerInterface, SubscriptionStateFetchedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
|
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||||
|
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||||
|
|
||||||
|
export class SubscriptionStateFetchedEventHandler implements DomainEventHandlerInterface {
|
||||||
|
constructor(
|
||||||
|
private userRepository: UserRepositoryInterface,
|
||||||
|
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
||||||
|
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
|
||||||
|
private logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async handle(event: SubscriptionStateFetchedEvent): Promise<void> {
|
||||||
|
if (!event.payload.subscriptionId) {
|
||||||
|
this.logger.error('Subscription ID is missing', {
|
||||||
|
codeTag: 'SubscriptionStateFetchedEventHandler.handle',
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
userId: event.payload.userEmail,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info('Subscription state update fetched', {
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (event.payload.offline) {
|
||||||
|
this.logger.info('Updating offline subscription', {
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
})
|
||||||
|
|
||||||
|
const subscription = await this.offlineUserSubscriptionRepository.findOneByEmailAndSubscriptionId(
|
||||||
|
event.payload.userEmail,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
if (!subscription) {
|
||||||
|
this.logger.error('Offline subscription not found', {
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription.planName = event.payload.subscriptionName
|
||||||
|
subscription.email = event.payload.userEmail
|
||||||
|
subscription.endsAt = event.payload.subscriptionExpiresAt
|
||||||
|
subscription.cancelled = event.payload.canceled
|
||||||
|
if (subscription.subscriptionId !== event.payload.subscriptionId) {
|
||||||
|
this.logger.warn('Subscription IDs do not match', {
|
||||||
|
previousSubscriptionId: subscription.subscriptionId,
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
subscription.subscriptionId = event.payload.subscriptionId
|
||||||
|
|
||||||
|
await this.offlineUserSubscriptionRepository.save(subscription)
|
||||||
|
|
||||||
|
this.logger.info('Offline subscription updated', {
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const usernameOrError = Username.create(event.payload.userEmail)
|
||||||
|
if (usernameOrError.isFailed()) {
|
||||||
|
this.logger.warn(`Could not update subscription: ${usernameOrError.getError()}`, {
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const username = usernameOrError.getValue()
|
||||||
|
|
||||||
|
const user = await this.userRepository.findOneByUsernameOrEmail(username)
|
||||||
|
|
||||||
|
if (user === null) {
|
||||||
|
this.logger.warn(`Could not find user with email: ${username.value}`, {
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info('Updating subscription', {
|
||||||
|
userId: user.uuid,
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
})
|
||||||
|
|
||||||
|
const subscription = await this.userSubscriptionRepository.findOneByUserUuidAndSubscriptionId(user.uuid, 0)
|
||||||
|
if (!subscription) {
|
||||||
|
this.logger.error('Subscription not found', {
|
||||||
|
userId: user.uuid,
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription.planName = event.payload.subscriptionName
|
||||||
|
subscription.endsAt = event.payload.subscriptionExpiresAt
|
||||||
|
subscription.cancelled = event.payload.canceled
|
||||||
|
if (subscription.subscriptionId !== event.payload.subscriptionId) {
|
||||||
|
this.logger.warn('Subscription IDs do not match', {
|
||||||
|
previousSubscriptionId: subscription.subscriptionId,
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
subscription.subscriptionId = event.payload.subscriptionId
|
||||||
|
|
||||||
|
await this.userSubscriptionRepository.save(subscription)
|
||||||
|
|
||||||
|
this.logger.info('Subscription updated to current state', {
|
||||||
|
userId: user.uuid,
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user