mirror of
https://github.com/standardnotes/server
synced 2026-01-16 20:04:32 -05:00
chore: release latest changes (#1056)
* chore: release latest changes * update yarn lockfile * remove stale files * fix ci env * remove mysql command overwrite * remove mysql overwrite from example * fix cookie cooldown in memory
This commit is contained in:
3
.github/ci.env
vendored
3
.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
|
||||||
|
|||||||
34
.github/workflows/publish.yml
vendored
34
.github/workflows/publish.yml
vendored
@@ -98,32 +98,30 @@ jobs:
|
|||||||
- name: Test
|
- name: Test
|
||||||
run: yarn test
|
run: yarn test
|
||||||
|
|
||||||
# e2e-base:
|
e2e-base:
|
||||||
# needs: build
|
needs: build
|
||||||
# name: E2E Base Suite
|
name: E2E Base Suite
|
||||||
# uses: standardnotes/server/.github/workflows/common-e2e.yml@main
|
uses: standardnotes/server/.github/workflows/common-e2e.yml@main
|
||||||
# with:
|
with:
|
||||||
# snjs_image_tag: 'latest'
|
snjs_image_tag: 'latest'
|
||||||
# suite: 'base'
|
suite: 'base'
|
||||||
|
|
||||||
# e2e-vaults:
|
e2e-vaults:
|
||||||
# needs: build
|
needs: build
|
||||||
# name: E2E Vaults Suite
|
name: E2E Vaults Suite
|
||||||
# uses: standardnotes/server/.github/workflows/common-e2e.yml@main
|
uses: standardnotes/server/.github/workflows/common-e2e.yml@main
|
||||||
# with:
|
with:
|
||||||
# snjs_image_tag: 'latest'
|
snjs_image_tag: 'latest'
|
||||||
# suite: 'vaults'
|
suite: 'vaults'
|
||||||
|
|
||||||
publish-self-hosting:
|
publish-self-hosting:
|
||||||
# needs: [ test, lint, e2e-base, e2e-vaults ]
|
needs: [ test, lint, e2e-base, e2e-vaults ]
|
||||||
needs: [ test, lint ]
|
|
||||||
name: Publish Self Hosting Docker Image
|
name: Publish Self Hosting Docker Image
|
||||||
uses: standardnotes/server/.github/workflows/common-self-hosting.yml@main
|
uses: standardnotes/server/.github/workflows/common-self-hosting.yml@main
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
publish-services:
|
publish-services:
|
||||||
# needs: [ test, lint, e2e-base, e2e-vaults ]
|
needs: [ test, lint, e2e-base, e2e-vaults ]
|
||||||
needs: [ test, lint ]
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
|||||||
88
.pnp.cjs
generated
88
.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"],\
|
||||||
@@ -6826,7 +6834,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 +6985,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 +7065,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 +7245,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 +8515,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 +9636,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 +9651,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 +10910,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 +13849,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 +16902,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 +16994,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],\
|
||||||
|
|||||||
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.
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
|
||||||
|
|||||||
@@ -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" ]
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -3,24 +3,6 @@
|
|||||||
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.91.0](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.90.3...@standardnotes/api-gateway@1.91.0) (2024-03-20)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add CORS_ORIGIN_STRICT_MODE_ENABLED env var to determine if CORS origin should be restricted ([5c02435](https://github.com/standardnotes/server/commit/5c02435ee478b893747d3f9e41062aae12d7ff10))
|
|
||||||
|
|
||||||
## [1.90.3](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.90.2...@standardnotes/api-gateway@1.90.3) (2024-03-18)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **api-gateway:** response headers cors issue - fixes [#1046](https://github.com/standardnotes/server/issues/1046) ([be668d7](https://github.com/standardnotes/server/commit/be668d7d7a1d9128f625a2bfa807e6a91183b488))
|
|
||||||
|
|
||||||
## [1.90.2](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.90.1...@standardnotes/api-gateway@1.90.2) (2024-03-18)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* cors issues on clients - fixes [#1046](https://github.com/standardnotes/server/issues/1046) ([#1049](https://github.com/standardnotes/server/issues/1049)) ([6d7ca1b](https://github.com/standardnotes/server/commit/6d7ca1b926fd45d744275bd3c1f4c05b010f76c8))
|
|
||||||
|
|
||||||
## [1.90.1](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.90.0...@standardnotes/api-gateway@1.90.1) (2024-01-19)
|
## [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
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|||||||
@@ -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,15 +93,15 @@ 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'],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const corsAllowedOrigins = env.get('CORS_ALLOWED_ORIGINS', true)
|
const corsAllowedOrigins = container.get<string[]>(TYPES.ApiGateway_CORS_ALLOWED_ORIGINS)
|
||||||
? env.get('CORS_ALLOWED_ORIGINS', true).split(',')
|
|
||||||
: []
|
|
||||||
app.use(
|
app.use(
|
||||||
cors({
|
cors({
|
||||||
credentials: true,
|
credentials: true,
|
||||||
@@ -136,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.91.0",
|
"version": "1.90.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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,24 +3,6 @@
|
|||||||
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.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.178.2...@standardnotes/auth-server@1.178.3) (2024-03-18)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **auth:** allow registration on new api versions - fixes [#1046](https://github.com/standardnotes/server/issues/1046) ([#1048](https://github.com/standardnotes/server/issues/1048)) ([f939caf](https://github.com/standardnotes/server/commit/f939caf2d9a781d42989ad6e92a5c7150ff48e19))
|
|
||||||
|
|
||||||
## [1.178.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.178.1...@standardnotes/auth-server@1.178.2) (2024-03-15)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* allow handling of new api version ([9d49764](https://github.com/standardnotes/server/commit/9d49764b841e73655e19523eddf10498addc9fb4))
|
|
||||||
|
|
||||||
## [1.178.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.178.0...@standardnotes/auth-server@1.178.1) (2024-02-09)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* allow expired offline subscriptions to receive dashboard emails ([#1041](https://github.com/standardnotes/server/issues/1041)) ([4fe8e9a](https://github.com/standardnotes/server/commit/4fe8e9a79f652f3e39608d6683cb17cc08bb8717))
|
|
||||||
|
|
||||||
# [1.178.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.20...@standardnotes/auth-server@1.178.0) (2024-01-19)
|
# [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
|
### Features
|
||||||
|
|||||||
@@ -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" ]
|
||||||
|
|||||||
@@ -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}`)
|
||||||
|
|||||||
@@ -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,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/auth-server",
|
"name": "@standardnotes/auth-server",
|
||||||
"version": "1.178.3",
|
"version": "1.178.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
@@ -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'
|
||||||
@@ -286,6 +286,19 @@ import { FixStorageQuotaForUser } from '../Domain/UseCase/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 { 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') {}
|
||||||
@@ -330,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)
|
||||||
@@ -597,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)
|
||||||
@@ -633,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
|
||||||
@@ -652,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
|
||||||
@@ -679,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)
|
||||||
@@ -691,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
|
||||||
@@ -740,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)
|
||||||
@@ -780,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)
|
||||||
@@ -819,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)
|
||||||
@@ -953,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),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -997,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(
|
||||||
@@ -1017,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(
|
||||||
@@ -1029,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(
|
||||||
@@ -1130,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
|
||||||
@@ -1262,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),
|
||||||
@@ -1337,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
|
||||||
@@ -1664,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),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1738,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),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1745,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
|
||||||
@@ -1772,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),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1799,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'),
|
||||||
@@ -207,6 +218,7 @@ const TYPES = {
|
|||||||
Auth_FileQuotaRecalculatedEventHandler: Symbol.for('Auth_FileQuotaRecalculatedEventHandler'),
|
Auth_FileQuotaRecalculatedEventHandler: Symbol.for('Auth_FileQuotaRecalculatedEventHandler'),
|
||||||
Auth_SubscriptionStateFetchedEventHandler: Symbol.for('Auth_SubscriptionStateFetchedEventHandler'),
|
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'),
|
||||||
@@ -259,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,6 +1,37 @@
|
|||||||
export enum ApiVersion {
|
import { Result, ValueObject } from '@standardnotes/domain-core'
|
||||||
v20161215 = '20161215',
|
|
||||||
v20190520 = '20190520',
|
import { ApiVersionProps } from './ApiVersionProps'
|
||||||
v20200115 = '20200115',
|
|
||||||
v20240226 = '20240226',
|
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,14 +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.v20240226:
|
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[]
|
||||||
|
}
|
||||||
@@ -305,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(),
|
||||||
@@ -323,8 +318,6 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
|||||||
},
|
},
|
||||||
payload: {
|
payload: {
|
||||||
userUuid,
|
userUuid,
|
||||||
userHasEmailsMuted,
|
|
||||||
muteEmailsSettingUuid,
|
|
||||||
keyParams,
|
keyParams,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,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
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface CaptchaServerInterface {
|
||||||
|
verify(hvmToken: string): Promise<boolean>
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { EphemeralSession } from './EphemeralSession'
|
|||||||
|
|
||||||
export interface EphemeralSessionRepositoryInterface {
|
export interface EphemeralSessionRepositoryInterface {
|
||||||
findOneByUuid(uuid: string): Promise<EphemeralSession | null>
|
findOneByUuid(uuid: string): Promise<EphemeralSession | null>
|
||||||
|
findOneByPrivateIdentifier(privateIdentifier: string): Promise<EphemeralSession | null>
|
||||||
findOneByUuidAndUserUuid(uuid: string, userUuid: string): Promise<EphemeralSession | null>
|
findOneByUuidAndUserUuid(uuid: string, userUuid: string): Promise<EphemeralSession | null>
|
||||||
findAllByUserUuid(userUuid: string): Promise<Array<EphemeralSession>>
|
findAllByUserUuid(userUuid: string): Promise<Array<EphemeralSession>>
|
||||||
deleteOne(uuid: string, userUuid: string): Promise<void>
|
deleteOne(uuid: string, userUuid: string): Promise<void>
|
||||||
|
|||||||
@@ -6,6 +6,16 @@ export class RevokedSession {
|
|||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
declare uuid: string
|
declare uuid: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'private_identifier',
|
||||||
|
length: 36,
|
||||||
|
nullable: true,
|
||||||
|
type: 'varchar',
|
||||||
|
comment: 'Used to identify a session without exposing the UUID in client-side cookies.',
|
||||||
|
})
|
||||||
|
@Index('index_revoked_sessions_on_private_identifier')
|
||||||
|
declare privateIdentifier: string | null
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'user_uuid',
|
name: 'user_uuid',
|
||||||
length: 36,
|
length: 36,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { RevokedSession } from './RevokedSession'
|
|||||||
|
|
||||||
export interface RevokedSessionRepositoryInterface {
|
export interface RevokedSessionRepositoryInterface {
|
||||||
findOneByUuid(uuid: string): Promise<RevokedSession | null>
|
findOneByUuid(uuid: string): Promise<RevokedSession | null>
|
||||||
|
findOneByPrivateIdentifier(privateIdentifier: string): Promise<RevokedSession | null>
|
||||||
findAllByUserUuid(userUuid: string): Promise<Array<RevokedSession>>
|
findAllByUserUuid(userUuid: string): Promise<Array<RevokedSession>>
|
||||||
insert(revokedSession: RevokedSession): Promise<void>
|
insert(revokedSession: RevokedSession): Promise<void>
|
||||||
update(revokedSession: RevokedSession): Promise<void>
|
update(revokedSession: RevokedSession): Promise<void>
|
||||||
|
|||||||
@@ -13,6 +13,16 @@ export class Session {
|
|||||||
@Index('index_sessions_on_user_uuid')
|
@Index('index_sessions_on_user_uuid')
|
||||||
declare userUuid: string
|
declare userUuid: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'private_identifier',
|
||||||
|
length: 36,
|
||||||
|
nullable: true,
|
||||||
|
type: 'varchar',
|
||||||
|
comment: 'Used to identify a session without exposing the UUID in client-side cookies.',
|
||||||
|
})
|
||||||
|
@Index('index_sessions_on_private_identifier')
|
||||||
|
declare privateIdentifier: string | null
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'hashed_access_token',
|
name: 'hashed_access_token',
|
||||||
length: 255,
|
length: 255,
|
||||||
@@ -75,4 +85,28 @@ export class Session {
|
|||||||
default: 0,
|
default: 0,
|
||||||
})
|
})
|
||||||
declare readonlyAccess: boolean
|
declare readonlyAccess: boolean
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'version',
|
||||||
|
type: 'smallint',
|
||||||
|
nullable: true,
|
||||||
|
default: 1,
|
||||||
|
})
|
||||||
|
declare version: number | null
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'application',
|
||||||
|
type: 'varchar',
|
||||||
|
length: 255,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
declare application: string | null
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'snjs',
|
||||||
|
type: 'varchar',
|
||||||
|
length: 255,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
declare snjs: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
12
packages/auth/src/Domain/Session/SessionCreationResult.ts
Normal file
12
packages/auth/src/Domain/Session/SessionCreationResult.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { SessionBody } from '@standardnotes/responses'
|
||||||
|
|
||||||
|
import { Session } from './Session'
|
||||||
|
|
||||||
|
export interface SessionCreationResult {
|
||||||
|
sessionHttpRepresentation: SessionBody
|
||||||
|
sessionCookieRepresentation: {
|
||||||
|
accessToken: string
|
||||||
|
refreshToken: string
|
||||||
|
}
|
||||||
|
session: Session
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { Session } from './Session'
|
|||||||
|
|
||||||
export interface SessionRepositoryInterface {
|
export interface SessionRepositoryInterface {
|
||||||
findOneByUuid(uuid: string): Promise<Session | null>
|
findOneByUuid(uuid: string): Promise<Session | null>
|
||||||
|
findOneByPrivateIdentifier(privateIdentifier: string): Promise<Session | null>
|
||||||
findOneByUuidAndUserUuid(uuid: string, userUuid: string): Promise<Session | null>
|
findOneByUuidAndUserUuid(uuid: string, userUuid: string): Promise<Session | null>
|
||||||
findAllByRefreshExpirationAndUserUuid(userUuid: string): Promise<Array<Session>>
|
findAllByRefreshExpirationAndUserUuid(userUuid: string): Promise<Array<Session>>
|
||||||
findAllByUserUuid(userUuid: string): Promise<Array<Session>>
|
findAllByUserUuid(userUuid: string): Promise<Array<Session>>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ describe('SessionService', () => {
|
|||||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||||
const readonlyUsers = ['demo@standardnotes.com']
|
const readonlyUsers = ['demo@standardnotes.com']
|
||||||
|
|
||||||
const createService = () =>
|
const createService = (forceLegacySessions = false) =>
|
||||||
new SessionService(
|
new SessionService(
|
||||||
sessionRepository,
|
sessionRepository,
|
||||||
ephemeralSessionRepository,
|
ephemeralSessionRepository,
|
||||||
@@ -51,6 +51,7 @@ describe('SessionService', () => {
|
|||||||
userSubscriptionRepository,
|
userSubscriptionRepository,
|
||||||
readonlyUsers,
|
readonlyUsers,
|
||||||
getSetting,
|
getSetting,
|
||||||
|
forceLegacySessions,
|
||||||
)
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -58,16 +59,18 @@ describe('SessionService', () => {
|
|||||||
existingSession.uuid = '2e1e43'
|
existingSession.uuid = '2e1e43'
|
||||||
existingSession.userUuid = '1-2-3'
|
existingSession.userUuid = '1-2-3'
|
||||||
existingSession.userAgent = 'Chrome'
|
existingSession.userAgent = 'Chrome'
|
||||||
existingSession.apiVersion = ApiVersion.v20200115
|
existingSession.apiVersion = ApiVersion.VERSIONS.v20200115
|
||||||
existingSession.hashedAccessToken = '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'
|
existingSession.hashedAccessToken = '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'
|
||||||
existingSession.hashedRefreshToken = '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'
|
existingSession.hashedRefreshToken = '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'
|
||||||
existingSession.readonlyAccess = false
|
existingSession.readonlyAccess = false
|
||||||
|
existingSession.version = SessionService.HEADER_BASED_SESSION_VERSION
|
||||||
|
|
||||||
revokedSession = {} as jest.Mocked<RevokedSession>
|
revokedSession = {} as jest.Mocked<RevokedSession>
|
||||||
revokedSession.uuid = '2e1e43'
|
revokedSession.uuid = '2e1e43'
|
||||||
|
|
||||||
sessionRepository = {} as jest.Mocked<SessionRepositoryInterface>
|
sessionRepository = {} as jest.Mocked<SessionRepositoryInterface>
|
||||||
sessionRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
sessionRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||||
|
sessionRepository.findOneByPrivateIdentifier = jest.fn().mockReturnValue(null)
|
||||||
sessionRepository.deleteOneByUuid = jest.fn()
|
sessionRepository.deleteOneByUuid = jest.fn()
|
||||||
sessionRepository.insert = jest.fn()
|
sessionRepository.insert = jest.fn()
|
||||||
sessionRepository.update = jest.fn()
|
sessionRepository.update = jest.fn()
|
||||||
@@ -79,6 +82,7 @@ describe('SessionService', () => {
|
|||||||
ephemeralSessionRepository.insert = jest.fn()
|
ephemeralSessionRepository.insert = jest.fn()
|
||||||
ephemeralSessionRepository.update = jest.fn()
|
ephemeralSessionRepository.update = jest.fn()
|
||||||
ephemeralSessionRepository.findOneByUuid = jest.fn()
|
ephemeralSessionRepository.findOneByUuid = jest.fn()
|
||||||
|
ephemeralSessionRepository.findOneByPrivateIdentifier = jest.fn()
|
||||||
ephemeralSessionRepository.deleteOne = jest.fn()
|
ephemeralSessionRepository.deleteOne = jest.fn()
|
||||||
|
|
||||||
revokedSessionRepository = {} as jest.Mocked<RevokedSessionRepositoryInterface>
|
revokedSessionRepository = {} as jest.Mocked<RevokedSessionRepositoryInterface>
|
||||||
@@ -140,25 +144,91 @@ describe('SessionService', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should refresh access and refresh tokens for a session', async () => {
|
it('should refresh access and refresh tokens for a session', async () => {
|
||||||
expect(await createService().refreshTokens({ session: existingSession, isEphemeral: false })).toEqual({
|
expect(
|
||||||
access_expiration: 123,
|
await createService().refreshTokens({
|
||||||
access_token: expect.any(String),
|
session: existingSession,
|
||||||
refresh_token: expect.any(String),
|
isEphemeral: false,
|
||||||
refresh_expiration: 123,
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
readonly_access: false,
|
}),
|
||||||
|
).toEqual({
|
||||||
|
sessionHttpRepresentation: {
|
||||||
|
access_expiration: 123,
|
||||||
|
access_token: expect.any(String),
|
||||||
|
refresh_token: expect.any(String),
|
||||||
|
refresh_expiration: 123,
|
||||||
|
readonly_access: false,
|
||||||
|
},
|
||||||
|
sessionCookieRepresentation: {
|
||||||
|
accessToken: 'foobar',
|
||||||
|
refreshToken: 'foobar',
|
||||||
|
},
|
||||||
|
session: existingSession,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(sessionRepository.update).toHaveBeenCalled()
|
expect(sessionRepository.update).toHaveBeenCalled()
|
||||||
expect(ephemeralSessionRepository.update).not.toHaveBeenCalled()
|
expect(ephemeralSessionRepository.update).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should refresh access and refresh tokens for a session and turn it into a cookie based session', async () => {
|
||||||
|
expect(
|
||||||
|
await createService().refreshTokens({
|
||||||
|
session: existingSession,
|
||||||
|
isEphemeral: false,
|
||||||
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20240226).getValue(),
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
sessionHttpRepresentation: {
|
||||||
|
access_expiration: 123,
|
||||||
|
access_token: expect.any(String),
|
||||||
|
refresh_token: expect.any(String),
|
||||||
|
refresh_expiration: 123,
|
||||||
|
readonly_access: false,
|
||||||
|
},
|
||||||
|
sessionCookieRepresentation: {
|
||||||
|
accessToken: 'foobar',
|
||||||
|
refreshToken: 'foobar',
|
||||||
|
},
|
||||||
|
session: existingSession,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(sessionRepository.update).toHaveBeenCalledWith({
|
||||||
|
apiVersion: ApiVersion.VERSIONS.v20240226,
|
||||||
|
hashedAccessToken: expect.any(String),
|
||||||
|
hashedRefreshToken: expect.any(String),
|
||||||
|
refreshExpiration: expect.any(Date),
|
||||||
|
privateIdentifier: expect.any(String),
|
||||||
|
readonlyAccess: false,
|
||||||
|
userAgent: 'Chrome',
|
||||||
|
userUuid: '1-2-3',
|
||||||
|
uuid: expect.any(String),
|
||||||
|
version: 2,
|
||||||
|
accessExpiration: expect.any(Date),
|
||||||
|
snjs: null,
|
||||||
|
application: null,
|
||||||
|
})
|
||||||
|
expect(ephemeralSessionRepository.update).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
it('should refresh access and refresh tokens for an ephemeral session', async () => {
|
it('should refresh access and refresh tokens for an ephemeral session', async () => {
|
||||||
expect(await createService().refreshTokens({ session: existingEphemeralSession, isEphemeral: true })).toEqual({
|
expect(
|
||||||
access_expiration: 123,
|
await createService().refreshTokens({
|
||||||
access_token: expect.any(String),
|
session: existingEphemeralSession,
|
||||||
refresh_token: expect.any(String),
|
isEphemeral: true,
|
||||||
refresh_expiration: 123,
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
readonly_access: false,
|
}),
|
||||||
|
).toEqual({
|
||||||
|
sessionHttpRepresentation: {
|
||||||
|
access_expiration: 123,
|
||||||
|
access_token: expect.any(String),
|
||||||
|
refresh_token: expect.any(String),
|
||||||
|
refresh_expiration: 123,
|
||||||
|
readonly_access: false,
|
||||||
|
},
|
||||||
|
sessionCookieRepresentation: {
|
||||||
|
accessToken: 'foobar',
|
||||||
|
refreshToken: 'foobar',
|
||||||
|
},
|
||||||
|
session: existingEphemeralSession,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(sessionRepository.update).not.toHaveBeenCalled()
|
expect(sessionRepository.update).not.toHaveBeenCalled()
|
||||||
@@ -171,7 +241,7 @@ describe('SessionService', () => {
|
|||||||
|
|
||||||
const result = await createService().createNewSessionForUser({
|
const result = await createService().createNewSessionForUser({
|
||||||
user,
|
user,
|
||||||
apiVersion: '003',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
@@ -179,16 +249,137 @@ describe('SessionService', () => {
|
|||||||
expect(sessionRepository.insert).toHaveBeenCalledWith(expect.any(Session))
|
expect(sessionRepository.insert).toHaveBeenCalledWith(expect.any(Session))
|
||||||
expect(sessionRepository.insert).toHaveBeenCalledWith({
|
expect(sessionRepository.insert).toHaveBeenCalledWith({
|
||||||
accessExpiration: expect.any(Date),
|
accessExpiration: expect.any(Date),
|
||||||
apiVersion: '003',
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
createdAt: expect.any(Date),
|
createdAt: expect.any(Date),
|
||||||
hashedAccessToken: expect.any(String),
|
hashedAccessToken: expect.any(String),
|
||||||
hashedRefreshToken: expect.any(String),
|
hashedRefreshToken: expect.any(String),
|
||||||
refreshExpiration: expect.any(Date),
|
refreshExpiration: expect.any(Date),
|
||||||
|
privateIdentifier: expect.any(String),
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
|
userAgent: 'Google Chrome',
|
||||||
|
userUuid: '123',
|
||||||
|
uuid: expect.any(String),
|
||||||
|
readonlyAccess: false,
|
||||||
|
version: 1,
|
||||||
|
snjs: null,
|
||||||
|
application: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.sessionHttpRepresentation).toEqual({
|
||||||
|
access_expiration: 123,
|
||||||
|
access_token: expect.any(String),
|
||||||
|
refresh_expiration: 123,
|
||||||
|
refresh_token: expect.any(String),
|
||||||
|
readonly_access: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create new cookie based session for a user', async () => {
|
||||||
|
const user = {} as jest.Mocked<User>
|
||||||
|
user.uuid = '123'
|
||||||
|
|
||||||
|
const result = await createService().createNewSessionForUser({
|
||||||
|
user,
|
||||||
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20240226).getValue(),
|
||||||
|
userAgent: 'Google Chrome',
|
||||||
|
readonlyAccess: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(sessionRepository.insert).toHaveBeenCalledWith(expect.any(Session))
|
||||||
|
expect(sessionRepository.insert).toHaveBeenCalledWith({
|
||||||
|
accessExpiration: expect.any(Date),
|
||||||
|
apiVersion: ApiVersion.VERSIONS.v20240226,
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
hashedAccessToken: expect.any(String),
|
||||||
|
hashedRefreshToken: expect.any(String),
|
||||||
|
refreshExpiration: expect.any(Date),
|
||||||
|
privateIdentifier: expect.any(String),
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
|
userAgent: 'Google Chrome',
|
||||||
|
userUuid: '123',
|
||||||
|
uuid: expect.any(String),
|
||||||
|
readonlyAccess: false,
|
||||||
|
version: 2,
|
||||||
|
snjs: null,
|
||||||
|
application: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.sessionHttpRepresentation).toEqual({
|
||||||
|
access_expiration: 123,
|
||||||
|
access_token: expect.any(String),
|
||||||
|
refresh_expiration: 123,
|
||||||
|
refresh_token: expect.any(String),
|
||||||
|
readonly_access: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create new legacy session for a user if cookie mode is disabled', async () => {
|
||||||
|
const user = {} as jest.Mocked<User>
|
||||||
|
user.uuid = '123'
|
||||||
|
|
||||||
|
const result = await createService(true).createNewSessionForUser({
|
||||||
|
user,
|
||||||
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20240226).getValue(),
|
||||||
|
userAgent: 'Google Chrome',
|
||||||
|
readonlyAccess: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(sessionRepository.insert).toHaveBeenCalledWith(expect.any(Session))
|
||||||
|
expect(sessionRepository.insert).toHaveBeenCalledWith({
|
||||||
|
accessExpiration: expect.any(Date),
|
||||||
|
apiVersion: ApiVersion.VERSIONS.v20240226,
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
hashedAccessToken: expect.any(String),
|
||||||
|
hashedRefreshToken: expect.any(String),
|
||||||
|
refreshExpiration: expect.any(Date),
|
||||||
|
privateIdentifier: expect.any(String),
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
|
userAgent: 'Google Chrome',
|
||||||
|
userUuid: '123',
|
||||||
|
uuid: expect.any(String),
|
||||||
|
readonlyAccess: false,
|
||||||
|
version: 1,
|
||||||
|
snjs: null,
|
||||||
|
application: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.sessionHttpRepresentation).toEqual({
|
||||||
|
access_expiration: 123,
|
||||||
|
access_token: expect.any(String),
|
||||||
|
refresh_expiration: 123,
|
||||||
|
refresh_token: expect.any(String),
|
||||||
|
readonly_access: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create new session for a user', async () => {
|
||||||
|
const user = {} as jest.Mocked<User>
|
||||||
|
user.uuid = '123'
|
||||||
|
|
||||||
|
const result = await createService().createNewSessionForUser({
|
||||||
|
user,
|
||||||
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
|
userAgent: 'Google Chrome',
|
||||||
|
readonlyAccess: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(sessionRepository.insert).toHaveBeenCalledWith(expect.any(Session))
|
||||||
|
expect(sessionRepository.insert).toHaveBeenCalledWith({
|
||||||
|
accessExpiration: expect.any(Date),
|
||||||
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
hashedAccessToken: expect.any(String),
|
||||||
|
hashedRefreshToken: expect.any(String),
|
||||||
|
privateIdentifier: expect.any(String),
|
||||||
|
refreshExpiration: expect.any(Date),
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
userUuid: '123',
|
userUuid: '123',
|
||||||
uuid: expect.any(String),
|
uuid: expect.any(String),
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
|
version: 1,
|
||||||
|
snjs: null,
|
||||||
|
application: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.sessionHttpRepresentation).toEqual({
|
expect(result.sessionHttpRepresentation).toEqual({
|
||||||
@@ -207,7 +398,7 @@ describe('SessionService', () => {
|
|||||||
|
|
||||||
const result = await createService().createNewSessionForUser({
|
const result = await createService().createNewSessionForUser({
|
||||||
user,
|
user,
|
||||||
apiVersion: '003',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
@@ -215,16 +406,20 @@ describe('SessionService', () => {
|
|||||||
expect(sessionRepository.insert).toHaveBeenCalledWith(expect.any(Session))
|
expect(sessionRepository.insert).toHaveBeenCalledWith(expect.any(Session))
|
||||||
expect(sessionRepository.insert).toHaveBeenCalledWith({
|
expect(sessionRepository.insert).toHaveBeenCalledWith({
|
||||||
accessExpiration: expect.any(Date),
|
accessExpiration: expect.any(Date),
|
||||||
apiVersion: '003',
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
createdAt: expect.any(Date),
|
createdAt: expect.any(Date),
|
||||||
hashedAccessToken: expect.any(String),
|
hashedAccessToken: expect.any(String),
|
||||||
hashedRefreshToken: expect.any(String),
|
hashedRefreshToken: expect.any(String),
|
||||||
refreshExpiration: expect.any(Date),
|
refreshExpiration: expect.any(Date),
|
||||||
|
privateIdentifier: expect.any(String),
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
userUuid: '123',
|
userUuid: '123',
|
||||||
uuid: expect.any(String),
|
uuid: expect.any(String),
|
||||||
readonlyAccess: true,
|
readonlyAccess: true,
|
||||||
|
version: 1,
|
||||||
|
snjs: null,
|
||||||
|
application: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.sessionHttpRepresentation).toEqual({
|
expect(result.sessionHttpRepresentation).toEqual({
|
||||||
@@ -249,7 +444,7 @@ describe('SessionService', () => {
|
|||||||
|
|
||||||
const result = await createService().createNewSessionForUser({
|
const result = await createService().createNewSessionForUser({
|
||||||
user,
|
user,
|
||||||
apiVersion: '003',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
@@ -257,15 +452,19 @@ describe('SessionService', () => {
|
|||||||
expect(sessionRepository.insert).toHaveBeenCalledWith(expect.any(Session))
|
expect(sessionRepository.insert).toHaveBeenCalledWith(expect.any(Session))
|
||||||
expect(sessionRepository.insert).toHaveBeenCalledWith({
|
expect(sessionRepository.insert).toHaveBeenCalledWith({
|
||||||
accessExpiration: expect.any(Date),
|
accessExpiration: expect.any(Date),
|
||||||
apiVersion: '003',
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
createdAt: expect.any(Date),
|
createdAt: expect.any(Date),
|
||||||
hashedAccessToken: expect.any(String),
|
hashedAccessToken: expect.any(String),
|
||||||
hashedRefreshToken: expect.any(String),
|
hashedRefreshToken: expect.any(String),
|
||||||
refreshExpiration: expect.any(Date),
|
refreshExpiration: expect.any(Date),
|
||||||
|
privateIdentifier: expect.any(String),
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
userUuid: '123',
|
userUuid: '123',
|
||||||
uuid: expect.any(String),
|
uuid: expect.any(String),
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
|
version: 1,
|
||||||
|
snjs: null,
|
||||||
|
application: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.sessionHttpRepresentation).toEqual({
|
expect(result.sessionHttpRepresentation).toEqual({
|
||||||
@@ -284,7 +483,7 @@ describe('SessionService', () => {
|
|||||||
|
|
||||||
await createService().createNewSessionForUser({
|
await createService().createNewSessionForUser({
|
||||||
user,
|
user,
|
||||||
apiVersion: '003',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
@@ -304,7 +503,7 @@ describe('SessionService', () => {
|
|||||||
|
|
||||||
await createService().createNewSessionForUser({
|
await createService().createNewSessionForUser({
|
||||||
user,
|
user,
|
||||||
apiVersion: '003',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
@@ -325,7 +524,7 @@ describe('SessionService', () => {
|
|||||||
|
|
||||||
const result = await createService().createNewSessionForUser({
|
const result = await createService().createNewSessionForUser({
|
||||||
user,
|
user,
|
||||||
apiVersion: '003',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
@@ -353,7 +552,7 @@ describe('SessionService', () => {
|
|||||||
|
|
||||||
const result = await createService().createNewSessionForUser({
|
const result = await createService().createNewSessionForUser({
|
||||||
user,
|
user,
|
||||||
apiVersion: '003',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
@@ -381,7 +580,7 @@ describe('SessionService', () => {
|
|||||||
|
|
||||||
const result = await createService().createNewSessionForUser({
|
const result = await createService().createNewSessionForUser({
|
||||||
user,
|
user,
|
||||||
apiVersion: '003',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
@@ -406,7 +605,7 @@ describe('SessionService', () => {
|
|||||||
|
|
||||||
const result = await createService().createNewEphemeralSessionForUser({
|
const result = await createService().createNewEphemeralSessionForUser({
|
||||||
user,
|
user,
|
||||||
apiVersion: '003',
|
apiVersion: ApiVersion.create(ApiVersion.VERSIONS.v20200115).getValue(),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
})
|
})
|
||||||
@@ -414,16 +613,20 @@ describe('SessionService', () => {
|
|||||||
expect(ephemeralSessionRepository.insert).toHaveBeenCalledWith(expect.any(EphemeralSession))
|
expect(ephemeralSessionRepository.insert).toHaveBeenCalledWith(expect.any(EphemeralSession))
|
||||||
expect(ephemeralSessionRepository.insert).toHaveBeenCalledWith({
|
expect(ephemeralSessionRepository.insert).toHaveBeenCalledWith({
|
||||||
accessExpiration: expect.any(Date),
|
accessExpiration: expect.any(Date),
|
||||||
apiVersion: '003',
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
createdAt: expect.any(Date),
|
createdAt: expect.any(Date),
|
||||||
hashedAccessToken: expect.any(String),
|
hashedAccessToken: expect.any(String),
|
||||||
hashedRefreshToken: expect.any(String),
|
hashedRefreshToken: expect.any(String),
|
||||||
refreshExpiration: expect.any(Date),
|
refreshExpiration: expect.any(Date),
|
||||||
|
privateIdentifier: expect.any(String),
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
userAgent: 'Google Chrome',
|
userAgent: 'Google Chrome',
|
||||||
userUuid: '123',
|
userUuid: '123',
|
||||||
uuid: expect.any(String),
|
uuid: expect.any(String),
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
|
version: 1,
|
||||||
|
snjs: null,
|
||||||
|
application: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.sessionHttpRepresentation).toEqual({
|
expect(result.sessionHttpRepresentation).toEqual({
|
||||||
@@ -435,57 +638,6 @@ describe('SessionService', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should delete a session by token', async () => {
|
|
||||||
sessionRepository.findOneByUuid = jest.fn().mockImplementation((uuid) => {
|
|
||||||
if (uuid === '2') {
|
|
||||||
return existingSession
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
await createService().deleteSessionByToken('1:2:3')
|
|
||||||
|
|
||||||
expect(sessionRepository.deleteOneByUuid).toHaveBeenCalledWith('2e1e43')
|
|
||||||
expect(ephemeralSessionRepository.deleteOne).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should delete an ephemeral session by token', async () => {
|
|
||||||
ephemeralSessionRepository.findOneByUuid = jest.fn().mockImplementation((uuid) => {
|
|
||||||
if (uuid === '2') {
|
|
||||||
return existingEphemeralSession
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
await createService().deleteSessionByToken('1:2:3')
|
|
||||||
|
|
||||||
expect(sessionRepository.deleteOneByUuid).not.toHaveBeenCalled()
|
|
||||||
expect(ephemeralSessionRepository.deleteOne).toHaveBeenCalledWith('2-3-4', '1-2-3')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not delete a session by token if session is not found', async () => {
|
|
||||||
sessionRepository.findOneByUuid = jest.fn().mockImplementation((uuid) => {
|
|
||||||
if (uuid === '2') {
|
|
||||||
return existingSession
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
await createService().deleteSessionByToken('1:4:3')
|
|
||||||
|
|
||||||
expect(sessionRepository.deleteOneByUuid).not.toHaveBeenCalled()
|
|
||||||
expect(ephemeralSessionRepository.deleteOne).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should determine if a refresh token is valid', async () => {
|
|
||||||
expect(createService().isRefreshTokenMatchingHashedSessionToken(existingSession, '1:2:3')).toBeTruthy()
|
|
||||||
expect(createService().isRefreshTokenMatchingHashedSessionToken(existingSession, '1:2:4')).toBeFalsy()
|
|
||||||
expect(createService().isRefreshTokenMatchingHashedSessionToken(existingSession, '1:2')).toBeFalsy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return device info based on user agent', () => {
|
it('should return device info based on user agent', () => {
|
||||||
expect(createService().getDeviceInfo(existingSession)).toEqual('Chrome 69.0 on Mac 10.13')
|
expect(createService().getDeviceInfo(existingSession)).toEqual('Chrome 69.0 on Mac 10.13')
|
||||||
})
|
})
|
||||||
@@ -626,67 +778,6 @@ describe('SessionService', () => {
|
|||||||
expect(createService().getDeviceInfo(existingSession)).toEqual('Unknown Client on Unknown OS')
|
expect(createService().getDeviceInfo(existingSession)).toEqual('Unknown Client on Unknown OS')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should retrieve a session from a session token', async () => {
|
|
||||||
sessionRepository.findOneByUuid = jest.fn().mockImplementation((uuid) => {
|
|
||||||
if (uuid === '2') {
|
|
||||||
return existingSession
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
const { session, isEphemeral } = await createService().getSessionFromToken('1:2:3')
|
|
||||||
|
|
||||||
expect(session).toEqual(session)
|
|
||||||
expect(isEphemeral).toBeFalsy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should retrieve an ephemeral session from a session token', async () => {
|
|
||||||
ephemeralSessionRepository.findOneByUuid = jest.fn().mockReturnValue(existingEphemeralSession)
|
|
||||||
sessionRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
|
||||||
|
|
||||||
const { session, isEphemeral } = await createService().getSessionFromToken('1:2:3')
|
|
||||||
|
|
||||||
expect(session).toEqual(existingEphemeralSession)
|
|
||||||
expect(isEphemeral).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not retrieve a session from a session token that has access token missing', async () => {
|
|
||||||
sessionRepository.findOneByUuid = jest.fn().mockImplementation((uuid) => {
|
|
||||||
if (uuid === '2') {
|
|
||||||
return existingSession
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
const { session } = await createService().getSessionFromToken('1:2')
|
|
||||||
|
|
||||||
expect(session).toBeUndefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not retrieve a session that is missing', async () => {
|
|
||||||
sessionRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
|
||||||
|
|
||||||
const { session } = await createService().getSessionFromToken('1:2:3')
|
|
||||||
|
|
||||||
expect(session).toBeUndefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not retrieve a session from a session token that has invalid access token', async () => {
|
|
||||||
sessionRepository.findOneByUuid = jest.fn().mockImplementation((uuid) => {
|
|
||||||
if (uuid === '2') {
|
|
||||||
return existingSession
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
const { session } = await createService().getSessionFromToken('1:2:4')
|
|
||||||
|
|
||||||
expect(session).toBeUndefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should revoked a session', async () => {
|
it('should revoked a session', async () => {
|
||||||
await createService().createRevokedSession(existingSession)
|
await createService().createRevokedSession(existingSession)
|
||||||
|
|
||||||
@@ -714,4 +805,26 @@ describe('SessionService', () => {
|
|||||||
|
|
||||||
expect(result).toBeNull()
|
expect(result).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should retrieve a revoked cookie session from a session token', async () => {
|
||||||
|
revokedSessionRepository.findOneByPrivateIdentifier = jest.fn().mockReturnValue(revokedSession)
|
||||||
|
|
||||||
|
const result = await createService().getRevokedSessionFromToken('2:3')
|
||||||
|
|
||||||
|
expect(result).toEqual(revokedSession)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not retrieve a revoked session if session id is missing from token', async () => {
|
||||||
|
revokedSessionRepository.findOneByPrivateIdentifier = jest.fn().mockReturnValue(null)
|
||||||
|
|
||||||
|
const result = await createService().getRevokedSessionFromToken('2')
|
||||||
|
|
||||||
|
expect(result).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not retrieve a revoked session if session token has unrecognizable version', async () => {
|
||||||
|
const result = await createService().getRevokedSessionFromToken('3:2')
|
||||||
|
|
||||||
|
expect(result).toBeNull()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,9 +20,14 @@ import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInt
|
|||||||
import { TraceSession } from '../UseCase/TraceSession/TraceSession'
|
import { TraceSession } from '../UseCase/TraceSession/TraceSession'
|
||||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||||
import { GetSetting } from '../UseCase/GetSetting/GetSetting'
|
import { GetSetting } from '../UseCase/GetSetting/GetSetting'
|
||||||
|
import { SessionCreationResult } from './SessionCreationResult'
|
||||||
|
import { ApiVersion } from '../Api/ApiVersion'
|
||||||
|
|
||||||
export class SessionService implements SessionServiceInterface {
|
export class SessionService implements SessionServiceInterface {
|
||||||
static readonly SESSION_TOKEN_VERSION = 1
|
static readonly SESSION_TOKEN_VERSION = 1
|
||||||
|
static readonly COOKIE_SESSION_TOKEN_VERSION = 2
|
||||||
|
static readonly HEADER_BASED_SESSION_VERSION = 1
|
||||||
|
static readonly COOKIE_BASED_SESSION_VERSION = 2
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private sessionRepository: SessionRepositoryInterface,
|
private sessionRepository: SessionRepositoryInterface,
|
||||||
@@ -38,20 +43,23 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
||||||
private readonlyUsers: string[],
|
private readonlyUsers: string[],
|
||||||
private getSetting: GetSetting,
|
private getSetting: GetSetting,
|
||||||
|
private forceLegacySessions: boolean,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createNewSessionForUser(dto: {
|
async createNewSessionForUser(dto: {
|
||||||
user: User
|
user: User
|
||||||
apiVersion: string
|
apiVersion: ApiVersion
|
||||||
userAgent: string
|
userAgent: string
|
||||||
readonlyAccess: boolean
|
readonlyAccess: boolean
|
||||||
}): Promise<{ sessionHttpRepresentation: SessionBody; session: Session }> {
|
snjs?: string
|
||||||
|
application?: string
|
||||||
|
}): Promise<SessionCreationResult> {
|
||||||
const session = await this.createSession({
|
const session = await this.createSession({
|
||||||
ephemeral: false,
|
ephemeral: false,
|
||||||
...dto,
|
...dto,
|
||||||
})
|
})
|
||||||
|
|
||||||
const sessionPayload = await this.createTokens(session)
|
const sessionPayload = await this.createTokens(session, dto.apiVersion)
|
||||||
|
|
||||||
await this.sessionRepository.insert(session)
|
await this.sessionRepository.insert(session)
|
||||||
|
|
||||||
@@ -70,34 +78,49 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessionHttpRepresentation: sessionPayload,
|
...sessionPayload,
|
||||||
session,
|
session,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async createNewEphemeralSessionForUser(dto: {
|
async createNewEphemeralSessionForUser(dto: {
|
||||||
user: User
|
user: User
|
||||||
apiVersion: string
|
apiVersion: ApiVersion
|
||||||
userAgent: string
|
userAgent: string
|
||||||
readonlyAccess: boolean
|
readonlyAccess: boolean
|
||||||
}): Promise<{ sessionHttpRepresentation: SessionBody; session: Session }> {
|
snjs?: string
|
||||||
|
application?: string
|
||||||
|
}): Promise<SessionCreationResult> {
|
||||||
const ephemeralSession = await this.createSession({
|
const ephemeralSession = await this.createSession({
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
...dto,
|
...dto,
|
||||||
})
|
})
|
||||||
|
|
||||||
const sessionPayload = await this.createTokens(ephemeralSession)
|
const sessionPayload = await this.createTokens(ephemeralSession, dto.apiVersion)
|
||||||
|
|
||||||
await this.ephemeralSessionRepository.insert(ephemeralSession)
|
await this.ephemeralSessionRepository.insert(ephemeralSession)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessionHttpRepresentation: sessionPayload,
|
...sessionPayload,
|
||||||
session: ephemeralSession,
|
session: ephemeralSession,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshTokens(dto: { session: Session; isEphemeral: boolean }): Promise<SessionBody> {
|
async refreshTokens(dto: {
|
||||||
const sessionPayload = await this.createTokens(dto.session)
|
session: Session
|
||||||
|
isEphemeral: boolean
|
||||||
|
apiVersion: ApiVersion
|
||||||
|
snjs?: string
|
||||||
|
application?: string
|
||||||
|
}): Promise<SessionCreationResult> {
|
||||||
|
const sessionPayload = await this.createTokens(dto.session, dto.apiVersion)
|
||||||
|
|
||||||
|
dto.session.apiVersion = dto.apiVersion.value
|
||||||
|
dto.session.version = this.shouldOperateOnCookieBasedSessions(dto.apiVersion)
|
||||||
|
? SessionService.COOKIE_BASED_SESSION_VERSION
|
||||||
|
: SessionService.HEADER_BASED_SESSION_VERSION
|
||||||
|
dto.session.snjs = dto.snjs ?? null
|
||||||
|
dto.session.application = dto.application ?? null
|
||||||
|
|
||||||
if (dto.isEphemeral) {
|
if (dto.isEphemeral) {
|
||||||
await this.ephemeralSessionRepository.update(dto.session)
|
await this.ephemeralSessionRepository.update(dto.session)
|
||||||
@@ -105,19 +128,10 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
await this.sessionRepository.update(dto.session)
|
await this.sessionRepository.update(dto.session)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sessionPayload
|
return {
|
||||||
}
|
...sessionPayload,
|
||||||
|
session: dto.session,
|
||||||
isRefreshTokenMatchingHashedSessionToken(session: Session, token: string): boolean {
|
|
||||||
const tokenParts = token.split(':')
|
|
||||||
const refreshToken = tokenParts[2]
|
|
||||||
if (!refreshToken) {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashedRefreshToken = crypto.createHash('sha256').update(refreshToken).digest('hex')
|
|
||||||
|
|
||||||
return crypto.timingSafeEqual(Buffer.from(hashedRefreshToken), Buffer.from(session.hashedRefreshToken))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getOperatingSystemInfoFromUserAgent(userAgent: string): string {
|
getOperatingSystemInfoFromUserAgent(userAgent: string): string {
|
||||||
@@ -182,35 +196,30 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
return `${browserInfo} on ${osInfo}`
|
return `${browserInfo} on ${osInfo}`
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSessionFromToken(token: string): Promise<{ session: Session | undefined; isEphemeral: boolean }> {
|
|
||||||
const tokenParts = token.split(':')
|
|
||||||
const sessionUuid = tokenParts[1]
|
|
||||||
const accessToken = tokenParts[2]
|
|
||||||
if (!accessToken) {
|
|
||||||
return { session: undefined, isEphemeral: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
const { session, isEphemeral } = await this.getSession(sessionUuid)
|
|
||||||
if (!session) {
|
|
||||||
return { session: undefined, isEphemeral: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
const hashedAccessToken = crypto.createHash('sha256').update(accessToken).digest('hex')
|
|
||||||
if (crypto.timingSafeEqual(Buffer.from(session.hashedAccessToken), Buffer.from(hashedAccessToken))) {
|
|
||||||
return { session, isEphemeral }
|
|
||||||
}
|
|
||||||
|
|
||||||
return { session: undefined, isEphemeral: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
async getRevokedSessionFromToken(token: string): Promise<RevokedSession | null> {
|
async getRevokedSessionFromToken(token: string): Promise<RevokedSession | null> {
|
||||||
const tokenParts = token.split(':')
|
const tokenParts = token.split(':')
|
||||||
const sessionUuid = tokenParts[1]
|
const tokenVersion = parseInt(tokenParts[0])
|
||||||
if (!sessionUuid) {
|
|
||||||
return null
|
switch (tokenVersion) {
|
||||||
|
case SessionService.SESSION_TOKEN_VERSION: {
|
||||||
|
const sessionUuid = tokenParts[1]
|
||||||
|
if (!sessionUuid) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.revokedSessionRepository.findOneByUuid(sessionUuid)
|
||||||
|
}
|
||||||
|
case SessionService.COOKIE_SESSION_TOKEN_VERSION: {
|
||||||
|
const privateIdentifier = tokenParts[1]
|
||||||
|
if (!privateIdentifier) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.revokedSessionRepository.findOneByPrivateIdentifier(privateIdentifier)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.revokedSessionRepository.findOneByUuid(sessionUuid)
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
async markRevokedSessionAsReceived(revokedSession: RevokedSession): Promise<RevokedSession> {
|
async markRevokedSessionAsReceived(revokedSession: RevokedSession): Promise<RevokedSession> {
|
||||||
@@ -222,22 +231,6 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
return revokedSession
|
return revokedSession
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteSessionByToken(token: string): Promise<string | null> {
|
|
||||||
const { session, isEphemeral } = await this.getSessionFromToken(token)
|
|
||||||
|
|
||||||
if (session) {
|
|
||||||
if (isEphemeral) {
|
|
||||||
await this.ephemeralSessionRepository.deleteOne(session.uuid, session.userUuid)
|
|
||||||
} else {
|
|
||||||
await this.sessionRepository.deleteOneByUuid(session.uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
return session.userUuid
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
async createRevokedSession(session: Session): Promise<RevokedSession> {
|
async createRevokedSession(session: Session): Promise<RevokedSession> {
|
||||||
const revokedSession = new RevokedSession()
|
const revokedSession = new RevokedSession()
|
||||||
revokedSession.uuid = session.uuid
|
revokedSession.uuid = session.uuid
|
||||||
@@ -245,6 +238,7 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
revokedSession.createdAt = this.timer.getUTCDate()
|
revokedSession.createdAt = this.timer.getUTCDate()
|
||||||
revokedSession.apiVersion = session.apiVersion
|
revokedSession.apiVersion = session.apiVersion
|
||||||
revokedSession.userAgent = session.userAgent
|
revokedSession.userAgent = session.userAgent
|
||||||
|
revokedSession.privateIdentifier = session.privateIdentifier
|
||||||
|
|
||||||
await this.revokedSessionRepository.insert(revokedSession)
|
await this.revokedSessionRepository.insert(revokedSession)
|
||||||
|
|
||||||
@@ -253,23 +247,31 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
|
|
||||||
private async createSession(dto: {
|
private async createSession(dto: {
|
||||||
user: User
|
user: User
|
||||||
apiVersion: string
|
apiVersion: ApiVersion
|
||||||
userAgent: string
|
userAgent: string
|
||||||
ephemeral: boolean
|
ephemeral: boolean
|
||||||
readonlyAccess: boolean
|
readonlyAccess: boolean
|
||||||
|
snjs?: string
|
||||||
|
application?: string
|
||||||
}): Promise<Session> {
|
}): Promise<Session> {
|
||||||
let session = new Session()
|
let session = new Session()
|
||||||
if (dto.ephemeral) {
|
if (dto.ephemeral) {
|
||||||
session = new EphemeralSession()
|
session = new EphemeralSession()
|
||||||
}
|
}
|
||||||
session.uuid = uuidv4()
|
session.uuid = uuidv4()
|
||||||
|
session.privateIdentifier = await this.cryptoNode.generateRandomKey(128)
|
||||||
if (await this.isLoggingUserAgentEnabledOnSessions(dto.user)) {
|
if (await this.isLoggingUserAgentEnabledOnSessions(dto.user)) {
|
||||||
session.userAgent = dto.userAgent
|
session.userAgent = dto.userAgent
|
||||||
}
|
}
|
||||||
|
session.snjs = dto.snjs ?? null
|
||||||
|
session.application = dto.application ?? null
|
||||||
session.userUuid = dto.user.uuid
|
session.userUuid = dto.user.uuid
|
||||||
session.apiVersion = dto.apiVersion
|
session.apiVersion = dto.apiVersion.value
|
||||||
session.createdAt = this.timer.getUTCDate()
|
session.createdAt = this.timer.getUTCDate()
|
||||||
session.updatedAt = this.timer.getUTCDate()
|
session.updatedAt = this.timer.getUTCDate()
|
||||||
|
session.version = this.shouldOperateOnCookieBasedSessions(dto.apiVersion)
|
||||||
|
? SessionService.COOKIE_BASED_SESSION_VERSION
|
||||||
|
: SessionService.HEADER_BASED_SESSION_VERSION
|
||||||
|
|
||||||
const userIsReadonly = this.readonlyUsers.includes(dto.user.email)
|
const userIsReadonly = this.readonlyUsers.includes(dto.user.email)
|
||||||
session.readonlyAccess = userIsReadonly || dto.readonlyAccess
|
session.readonlyAccess = userIsReadonly || dto.readonlyAccess
|
||||||
@@ -277,22 +279,16 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getSession(uuid: string): Promise<{
|
private async createTokens(
|
||||||
session: Session | null
|
session: Session,
|
||||||
isEphemeral: boolean
|
apiVersion: ApiVersion,
|
||||||
}> {
|
): Promise<{
|
||||||
let session = await this.ephemeralSessionRepository.findOneByUuid(uuid)
|
sessionHttpRepresentation: SessionBody
|
||||||
let isEphemeral = true
|
sessionCookieRepresentation: {
|
||||||
|
accessToken: string
|
||||||
if (!session) {
|
refreshToken: string
|
||||||
session = await this.sessionRepository.findOneByUuid(uuid)
|
|
||||||
isEphemeral = false
|
|
||||||
}
|
}
|
||||||
|
}> {
|
||||||
return { session, isEphemeral }
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createTokens(session: Session): Promise<SessionBody> {
|
|
||||||
const accessToken = this.cryptoNode.base64URLEncode(await this.cryptoNode.generateRandomKey(48))
|
const accessToken = this.cryptoNode.base64URLEncode(await this.cryptoNode.generateRandomKey(48))
|
||||||
const refreshToken = this.cryptoNode.base64URLEncode(await this.cryptoNode.generateRandomKey(48))
|
const refreshToken = this.cryptoNode.base64URLEncode(await this.cryptoNode.generateRandomKey(48))
|
||||||
|
|
||||||
@@ -305,13 +301,29 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
const refreshTokenExpiration = dayjs.utc().add(this.refreshTokenAge, 'second').toDate()
|
const refreshTokenExpiration = dayjs.utc().add(this.refreshTokenAge, 'second').toDate()
|
||||||
session.accessExpiration = accessTokenExpiration
|
session.accessExpiration = accessTokenExpiration
|
||||||
session.refreshExpiration = refreshTokenExpiration
|
session.refreshExpiration = refreshTokenExpiration
|
||||||
|
if (!session.privateIdentifier) {
|
||||||
|
session.privateIdentifier = await this.cryptoNode.generateRandomKey(128)
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessTokenForHeaderPurposes = this.shouldOperateOnCookieBasedSessions(apiVersion)
|
||||||
|
? `${SessionService.COOKIE_SESSION_TOKEN_VERSION}:${session.privateIdentifier}`
|
||||||
|
: `${SessionService.SESSION_TOKEN_VERSION}:${session.uuid}:${accessToken}`
|
||||||
|
const refreshTokenForHeaderPurposes = this.shouldOperateOnCookieBasedSessions(apiVersion)
|
||||||
|
? `${SessionService.COOKIE_SESSION_TOKEN_VERSION}:${session.privateIdentifier}`
|
||||||
|
: `${SessionService.SESSION_TOKEN_VERSION}:${session.uuid}:${refreshToken}`
|
||||||
|
|
||||||
return {
|
return {
|
||||||
access_token: `${SessionService.SESSION_TOKEN_VERSION}:${session.uuid}:${accessToken}`,
|
sessionHttpRepresentation: {
|
||||||
refresh_token: `${SessionService.SESSION_TOKEN_VERSION}:${session.uuid}:${refreshToken}`,
|
access_token: accessTokenForHeaderPurposes,
|
||||||
access_expiration: this.timer.convertStringDateToMilliseconds(accessTokenExpiration.toString()),
|
refresh_token: refreshTokenForHeaderPurposes,
|
||||||
refresh_expiration: this.timer.convertStringDateToMilliseconds(refreshTokenExpiration.toString()),
|
access_expiration: this.timer.convertStringDateToMilliseconds(accessTokenExpiration.toString()),
|
||||||
readonly_access: session.readonlyAccess,
|
refresh_expiration: this.timer.convertStringDateToMilliseconds(refreshTokenExpiration.toString()),
|
||||||
|
readonly_access: session.readonlyAccess,
|
||||||
|
},
|
||||||
|
sessionCookieRepresentation: {
|
||||||
|
accessToken,
|
||||||
|
refreshToken,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,4 +341,12 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
|
|
||||||
return loggingSetting.decryptedValue === LogSessionUserAgentOption.Enabled
|
return loggingSetting.decryptedValue === LogSessionUserAgentOption.Enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private shouldOperateOnCookieBasedSessions(apiVersion: ApiVersion): boolean {
|
||||||
|
if (this.forceLegacySessions) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiVersion.VERSIONS.v20240226 === apiVersion.value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,35 @@
|
|||||||
import { SessionBody } from '@standardnotes/responses'
|
|
||||||
import { User } from '../User/User'
|
import { User } from '../User/User'
|
||||||
import { RevokedSession } from './RevokedSession'
|
import { RevokedSession } from './RevokedSession'
|
||||||
import { Session } from './Session'
|
import { Session } from './Session'
|
||||||
|
import { SessionCreationResult } from './SessionCreationResult'
|
||||||
|
import { ApiVersion } from '../Api/ApiVersion'
|
||||||
|
|
||||||
export interface SessionServiceInterface {
|
export interface SessionServiceInterface {
|
||||||
createNewSessionForUser(dto: {
|
createNewSessionForUser(dto: {
|
||||||
user: User
|
user: User
|
||||||
apiVersion: string
|
apiVersion: ApiVersion
|
||||||
userAgent: string
|
userAgent: string
|
||||||
readonlyAccess: boolean
|
readonlyAccess: boolean
|
||||||
}): Promise<{ sessionHttpRepresentation: SessionBody; session: Session }>
|
snjs?: string
|
||||||
|
application?: string
|
||||||
|
}): Promise<SessionCreationResult>
|
||||||
createNewEphemeralSessionForUser(dto: {
|
createNewEphemeralSessionForUser(dto: {
|
||||||
user: User
|
user: User
|
||||||
apiVersion: string
|
apiVersion: ApiVersion
|
||||||
userAgent: string
|
userAgent: string
|
||||||
readonlyAccess: boolean
|
readonlyAccess: boolean
|
||||||
}): Promise<{ sessionHttpRepresentation: SessionBody; session: Session }>
|
snjs?: string
|
||||||
refreshTokens(dto: { session: Session; isEphemeral: boolean }): Promise<SessionBody>
|
application?: string
|
||||||
getSessionFromToken(token: string): Promise<{ session: Session | undefined; isEphemeral: boolean }>
|
}): Promise<SessionCreationResult>
|
||||||
|
refreshTokens(dto: {
|
||||||
|
session: Session
|
||||||
|
isEphemeral: boolean
|
||||||
|
apiVersion: ApiVersion
|
||||||
|
snjs?: string
|
||||||
|
application?: string
|
||||||
|
}): Promise<SessionCreationResult>
|
||||||
getRevokedSessionFromToken(token: string): Promise<RevokedSession | null>
|
getRevokedSessionFromToken(token: string): Promise<RevokedSession | null>
|
||||||
markRevokedSessionAsReceived(revokedSession: RevokedSession): Promise<RevokedSession>
|
markRevokedSessionAsReceived(revokedSession: RevokedSession): Promise<RevokedSession>
|
||||||
deleteSessionByToken(token: string): Promise<string | null>
|
|
||||||
isRefreshTokenMatchingHashedSessionToken(session: Session, token: string): boolean
|
|
||||||
getDeviceInfo(session: Session): string
|
getDeviceInfo(session: Session): string
|
||||||
getOperatingSystemInfoFromUserAgent(userAgent: string): string
|
getOperatingSystemInfoFromUserAgent(userAgent: string): string
|
||||||
getBrowserInfoFromUserAgent(userAgent: string): string
|
getBrowserInfoFromUserAgent(userAgent: string): string
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
export interface SessionTokensCooldownRepositoryInterface {
|
||||||
|
setCooldown(dto: {
|
||||||
|
sessionUuid: Uuid
|
||||||
|
hashedAccessToken: string
|
||||||
|
hashedRefreshToken: string
|
||||||
|
cooldownPeriodInSeconds: number
|
||||||
|
}): Promise<void>
|
||||||
|
getHashedTokens(sessionUuid: Uuid): Promise<{
|
||||||
|
hashedAccessToken: string
|
||||||
|
hashedRefreshToken: string
|
||||||
|
} | null>
|
||||||
|
}
|
||||||
@@ -12,8 +12,6 @@ import { SettingsAssociationServiceInterface } from './SettingsAssociationServic
|
|||||||
export class SettingsAssociationService implements SettingsAssociationServiceInterface {
|
export class SettingsAssociationService implements SettingsAssociationServiceInterface {
|
||||||
private readonly UNENCRYPTED_SETTINGS = [
|
private readonly UNENCRYPTED_SETTINGS = [
|
||||||
SettingName.NAMES.EmailBackupFrequency,
|
SettingName.NAMES.EmailBackupFrequency,
|
||||||
SettingName.NAMES.MuteFailedBackupsEmails,
|
|
||||||
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
|
||||||
SettingName.NAMES.MuteSignInEmails,
|
SettingName.NAMES.MuteSignInEmails,
|
||||||
SettingName.NAMES.MuteMarketingEmails,
|
SettingName.NAMES.MuteMarketingEmails,
|
||||||
SettingName.NAMES.DropboxBackupFrequency,
|
SettingName.NAMES.DropboxBackupFrequency,
|
||||||
@@ -27,8 +25,6 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
|||||||
SettingName.NAMES.GoogleDriveBackupFrequency,
|
SettingName.NAMES.GoogleDriveBackupFrequency,
|
||||||
SettingName.NAMES.OneDriveBackupFrequency,
|
SettingName.NAMES.OneDriveBackupFrequency,
|
||||||
SettingName.NAMES.EmailBackupFrequency,
|
SettingName.NAMES.EmailBackupFrequency,
|
||||||
SettingName.NAMES.MuteFailedBackupsEmails,
|
|
||||||
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
|
||||||
SettingName.NAMES.MuteSignInEmails,
|
SettingName.NAMES.MuteSignInEmails,
|
||||||
SettingName.NAMES.MuteMarketingEmails,
|
SettingName.NAMES.MuteMarketingEmails,
|
||||||
SettingName.NAMES.ListedAuthorSecrets,
|
SettingName.NAMES.ListedAuthorSecrets,
|
||||||
|
|||||||
@@ -136,10 +136,7 @@ describe('ApplyDefaultSubscriptionSettings', () => {
|
|||||||
.fn()
|
.fn()
|
||||||
.mockReturnValue(
|
.mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[SettingName.NAMES.LogSessionUserAgent, { value: 'value1', sensitive: false, serverEncryptionVersion: 0 }],
|
||||||
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
|
||||||
{ value: 'value1', sensitive: false, serverEncryptionVersion: 0 },
|
|
||||||
],
|
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ describe('AuthenticateRequest', () => {
|
|||||||
session,
|
session,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ authorizationHeader: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeTruthy()
|
expect(response.success).toBeTruthy()
|
||||||
expect(response.responseCode).toEqual(200)
|
expect(response.responseCode).toEqual(200)
|
||||||
@@ -43,7 +46,9 @@ describe('AuthenticateRequest', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should not authorize if authorization header is missing', async () => {
|
it('should not authorize if authorization header is missing', async () => {
|
||||||
const response = await createUseCase().execute({})
|
const response = await createUseCase().execute({
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
expect(response.responseCode).toEqual(401)
|
expect(response.responseCode).toEqual(401)
|
||||||
@@ -55,7 +60,10 @@ describe('AuthenticateRequest', () => {
|
|||||||
throw new Error('something bad happened')
|
throw new Error('something bad happened')
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ authorizationHeader: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
expect(response.responseCode).toEqual(401)
|
expect(response.responseCode).toEqual(401)
|
||||||
@@ -68,7 +76,10 @@ describe('AuthenticateRequest', () => {
|
|||||||
failureType: 'INVALID_AUTH',
|
failureType: 'INVALID_AUTH',
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ authorizationHeader: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
expect(response.responseCode).toEqual(401)
|
expect(response.responseCode).toEqual(401)
|
||||||
@@ -81,7 +92,26 @@ describe('AuthenticateRequest', () => {
|
|||||||
failureType: 'EXPIRED_TOKEN',
|
failureType: 'EXPIRED_TOKEN',
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ authorizationHeader: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.success).toBeFalsy()
|
||||||
|
expect(response.responseCode).toEqual(498)
|
||||||
|
expect(response.errorTag).toEqual('expired-access-token')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not authorize user if the token is cooled down', async () => {
|
||||||
|
authenticateUser.execute = jest.fn().mockReturnValue({
|
||||||
|
success: false,
|
||||||
|
failureType: 'COOLEDDOWN_TOKEN',
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
expect(response.responseCode).toEqual(498)
|
expect(response.responseCode).toEqual(498)
|
||||||
@@ -94,7 +124,10 @@ describe('AuthenticateRequest', () => {
|
|||||||
failureType: 'REVOKED_SESSION',
|
failureType: 'REVOKED_SESSION',
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ authorizationHeader: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
expect(response.responseCode).toEqual(401)
|
expect(response.responseCode).toEqual(401)
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export class AuthenticateRequest implements UseCaseInterface {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: AuthenticateRequestDTO): Promise<AuthenticateRequestResponse> {
|
async execute(dto: AuthenticateRequestDTO): Promise<AuthenticateRequestResponse> {
|
||||||
if (!dto.authorizationHeader) {
|
if (!dto.authTokenFromHeaders) {
|
||||||
this.logger.debug('[authenticate-request] Authorization header not provided.')
|
this.logger.debug('[authenticate-request] Authorization not provided.')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -29,7 +29,9 @@ export class AuthenticateRequest implements UseCaseInterface {
|
|||||||
let authenticateResponse: AuthenticateUserResponse
|
let authenticateResponse: AuthenticateUserResponse
|
||||||
try {
|
try {
|
||||||
authenticateResponse = await this.authenticateUser.execute({
|
authenticateResponse = await this.authenticateUser.execute({
|
||||||
token: dto.authorizationHeader.replace('Bearer ', ''),
|
authTokenFromHeaders: dto.authTokenFromHeaders,
|
||||||
|
authCookies: dto.authCookies,
|
||||||
|
requestMetadata: dto.requestMetadata,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
@@ -47,6 +49,7 @@ export class AuthenticateRequest implements UseCaseInterface {
|
|||||||
if (!authenticateResponse.success) {
|
if (!authenticateResponse.success) {
|
||||||
switch (authenticateResponse.failureType) {
|
switch (authenticateResponse.failureType) {
|
||||||
case 'EXPIRED_TOKEN':
|
case 'EXPIRED_TOKEN':
|
||||||
|
case 'COOLEDDOWN_TOKEN':
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
responseCode: 498,
|
responseCode: 498,
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
export type AuthenticateRequestDTO = {
|
export type AuthenticateRequestDTO = {
|
||||||
authorizationHeader?: string
|
authTokenFromHeaders?: string
|
||||||
|
authCookies?: Map<string, string[]>
|
||||||
|
requestMetadata: {
|
||||||
|
url: string
|
||||||
|
method: string
|
||||||
|
snjs?: string
|
||||||
|
application?: string
|
||||||
|
userAgent?: string
|
||||||
|
secChUa?: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,10 @@ describe('AuthenticateUser', () => {
|
|||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ token: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeTruthy()
|
expect(response.success).toBeTruthy()
|
||||||
})
|
})
|
||||||
@@ -71,7 +74,10 @@ describe('AuthenticateUser', () => {
|
|||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ token: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
})
|
})
|
||||||
@@ -84,7 +90,10 @@ describe('AuthenticateUser', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ token: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
})
|
})
|
||||||
@@ -100,7 +109,10 @@ describe('AuthenticateUser', () => {
|
|||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ token: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
})
|
})
|
||||||
@@ -114,11 +126,33 @@ describe('AuthenticateUser', () => {
|
|||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ token: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeTruthy()
|
expect(response.success).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not authenticate a user from a session token that is in cooldown', async () => {
|
||||||
|
user.supportsSessions = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
|
authenticationMethodResolver.resolve = jest.fn().mockReturnValue({
|
||||||
|
type: 'session_token',
|
||||||
|
session,
|
||||||
|
user,
|
||||||
|
givenTokensWereInCooldown: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.success).toBeFalsy()
|
||||||
|
expect(response.failureType).toEqual('COOLEDDOWN_TOKEN')
|
||||||
|
})
|
||||||
|
|
||||||
it('should not authenticate a user from a session token if session is expired', async () => {
|
it('should not authenticate a user from a session token if session is expired', async () => {
|
||||||
timer.getUTCDate = jest.fn().mockReturnValue(new Date(200))
|
timer.getUTCDate = jest.fn().mockReturnValue(new Date(200))
|
||||||
user.supportsSessions = jest.fn().mockReturnValue(true)
|
user.supportsSessions = jest.fn().mockReturnValue(true)
|
||||||
@@ -129,7 +163,10 @@ describe('AuthenticateUser', () => {
|
|||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ token: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
})
|
})
|
||||||
@@ -144,7 +181,10 @@ describe('AuthenticateUser', () => {
|
|||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ token: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
})
|
})
|
||||||
@@ -159,7 +199,10 @@ describe('AuthenticateUser', () => {
|
|||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ token: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
})
|
})
|
||||||
@@ -172,7 +215,10 @@ describe('AuthenticateUser', () => {
|
|||||||
user,
|
user,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ token: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
})
|
})
|
||||||
@@ -183,7 +229,10 @@ describe('AuthenticateUser', () => {
|
|||||||
revokedSession,
|
revokedSession,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await createUseCase().execute({ token: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
})
|
})
|
||||||
@@ -191,7 +240,10 @@ describe('AuthenticateUser', () => {
|
|||||||
it('should not authenticate a user if authentication method could not be determined', async () => {
|
it('should not authenticate a user if authentication method could not be determined', async () => {
|
||||||
authenticationMethodResolver.resolve = jest.fn().mockReturnValue(undefined)
|
authenticationMethodResolver.resolve = jest.fn().mockReturnValue(undefined)
|
||||||
|
|
||||||
const response = await createUseCase().execute({ token: 'test' })
|
const response = await createUseCase().execute({
|
||||||
|
authTokenFromHeaders: 'test',
|
||||||
|
requestMetadata: { url: '/foobar', method: 'GET' },
|
||||||
|
})
|
||||||
|
|
||||||
expect(response.success).toBeFalsy()
|
expect(response.success).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ export class AuthenticateUser implements UseCaseInterface {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: AuthenticateUserDTO): Promise<AuthenticateUserResponse> {
|
async execute(dto: AuthenticateUserDTO): Promise<AuthenticateUserResponse> {
|
||||||
const authenticationMethod = await this.authenticationMethodResolver.resolve(dto.token)
|
const authenticationMethod = await this.authenticationMethodResolver.resolve(dto)
|
||||||
if (!authenticationMethod) {
|
if (!authenticationMethod) {
|
||||||
this.logger.debug(`[authenticate-user] No authentication method found for token: ${dto.token}`)
|
this.logger.debug(`[authenticate-user] No authentication method found for tokens: ${JSON.stringify(dto)}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -33,7 +33,7 @@ export class AuthenticateUser implements UseCaseInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authenticationMethod.type === 'revoked') {
|
if (authenticationMethod.type === 'revoked') {
|
||||||
this.logger.debug(`[authenticate-user] Session has been revoked: ${dto.token}`)
|
this.logger.debug(`[authenticate-user] Session has been revoked: ${dto.authTokenFromHeaders}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -43,7 +43,7 @@ export class AuthenticateUser implements UseCaseInterface {
|
|||||||
|
|
||||||
const user = authenticationMethod.user
|
const user = authenticationMethod.user
|
||||||
if (!user) {
|
if (!user) {
|
||||||
this.logger.debug(`[authenticate-user] No user found for authentication method. Token: ${dto.token}`)
|
this.logger.debug(`[authenticate-user] No user found for authentication method. Token: ${JSON.stringify(dto)}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -106,6 +106,25 @@ export class AuthenticateUser implements UseCaseInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authenticationMethod.givenTokensWereInCooldown) {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
this.logger.warn('Request was authenticated with tokens that were in cooldown.', {
|
||||||
|
userId: user.uuid,
|
||||||
|
sessionUuid: session.uuid,
|
||||||
|
snjs: dto.requestMetadata.snjs,
|
||||||
|
application: dto.requestMetadata.application,
|
||||||
|
url: dto.requestMetadata.url,
|
||||||
|
method: dto.requestMetadata.method,
|
||||||
|
userAgent: session.userAgent,
|
||||||
|
secChUa: session.userAgent ? dto.requestMetadata.secChUa : undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
failureType: 'COOLEDDOWN_TOKEN',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
export type AuthenticateUserDTO = {
|
export type AuthenticateUserDTO = {
|
||||||
token: string
|
authTokenFromHeaders: string
|
||||||
|
authCookies?: Map<string, string[]>
|
||||||
|
requestMetadata: {
|
||||||
|
url: string
|
||||||
|
method: string
|
||||||
|
snjs?: string
|
||||||
|
application?: string
|
||||||
|
userAgent?: string
|
||||||
|
secChUa?: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { User } from '../User/User'
|
|||||||
|
|
||||||
export type AuthenticateUserResponse = {
|
export type AuthenticateUserResponse = {
|
||||||
success: boolean
|
success: boolean
|
||||||
failureType?: 'INVALID_AUTH' | 'EXPIRED_TOKEN' | 'REVOKED_SESSION'
|
failureType?: 'INVALID_AUTH' | 'EXPIRED_TOKEN' | 'REVOKED_SESSION' | 'COOLEDDOWN_TOKEN'
|
||||||
user?: User
|
user?: User
|
||||||
session?: Session
|
session?: Session
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,56 @@ describe('ChangeCredentials', () => {
|
|||||||
it('should change password', async () => {
|
it('should change password', async () => {
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
username: Username.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
apiVersion: ApiVersion.v20200115,
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
|
currentPassword: 'qweqwe123123',
|
||||||
|
newPassword: 'test234',
|
||||||
|
pwNonce: 'asdzxc',
|
||||||
|
updatedWithUserAgent: 'Google Chrome',
|
||||||
|
kpCreated: '123',
|
||||||
|
kpOrigination: 'password-change',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBeFalsy()
|
||||||
|
|
||||||
|
expect(userRepository.save).toHaveBeenCalledWith({
|
||||||
|
encryptedPassword: expect.any(String),
|
||||||
|
pwNonce: 'asdzxc',
|
||||||
|
kpCreated: '123',
|
||||||
|
email: 'test@test.te',
|
||||||
|
uuid: '1-2-3',
|
||||||
|
kpOrigination: 'password-change',
|
||||||
|
updatedAt: new Date(1),
|
||||||
|
})
|
||||||
|
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||||
|
expect(domainEventFactory.createUserEmailChangedEvent).not.toHaveBeenCalled()
|
||||||
|
expect(deleteOtherSessionsForUser.execute).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not change password if api version is invalid', async () => {
|
||||||
|
const result = await createUseCase().execute({
|
||||||
|
username: Username.create('test@test.te').getValue(),
|
||||||
|
apiVersion: 'invalid',
|
||||||
|
currentPassword: 'qweqwe123123',
|
||||||
|
newPassword: 'test234',
|
||||||
|
pwNonce: 'asdzxc',
|
||||||
|
updatedWithUserAgent: 'Google Chrome',
|
||||||
|
kpCreated: '123',
|
||||||
|
kpOrigination: 'password-change',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should change password on legacy users', async () => {
|
||||||
|
authResponseFactory.createResponse = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue({ legacyResponse: { foo: 'bar' }, session: { uuid: '1-2-3' } as jest.Mocked<Session> })
|
||||||
|
|
||||||
|
authResponseFactoryResolver.resolveAuthResponseFactoryVersion = jest.fn().mockReturnValue(authResponseFactory)
|
||||||
|
|
||||||
|
const result = await createUseCase().execute({
|
||||||
|
username: Username.create('test@test.te').getValue(),
|
||||||
|
apiVersion: ApiVersion.VERSIONS.v20161215,
|
||||||
currentPassword: 'qweqwe123123',
|
currentPassword: 'qweqwe123123',
|
||||||
newPassword: 'test234',
|
newPassword: 'test234',
|
||||||
pwNonce: 'asdzxc',
|
pwNonce: 'asdzxc',
|
||||||
@@ -106,7 +155,7 @@ describe('ChangeCredentials', () => {
|
|||||||
|
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
username: Username.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
apiVersion: ApiVersion.v20200115,
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
currentPassword: 'qweqwe123123',
|
currentPassword: 'qweqwe123123',
|
||||||
newPassword: 'test234',
|
newPassword: 'test234',
|
||||||
newEmail: 'new@test.te',
|
newEmail: 'new@test.te',
|
||||||
@@ -139,7 +188,7 @@ describe('ChangeCredentials', () => {
|
|||||||
|
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
username: Username.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
apiVersion: ApiVersion.v20200115,
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
currentPassword: 'qweqwe123123',
|
currentPassword: 'qweqwe123123',
|
||||||
newPassword: 'test234',
|
newPassword: 'test234',
|
||||||
newEmail: 'new@test.te',
|
newEmail: 'new@test.te',
|
||||||
@@ -159,7 +208,7 @@ describe('ChangeCredentials', () => {
|
|||||||
it('should not change email if the new email is invalid', async () => {
|
it('should not change email if the new email is invalid', async () => {
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
username: Username.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
apiVersion: ApiVersion.v20200115,
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
currentPassword: 'qweqwe123123',
|
currentPassword: 'qweqwe123123',
|
||||||
newPassword: 'test234',
|
newPassword: 'test234',
|
||||||
newEmail: '',
|
newEmail: '',
|
||||||
@@ -181,7 +230,7 @@ describe('ChangeCredentials', () => {
|
|||||||
|
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
username: Username.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
apiVersion: ApiVersion.v20200115,
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
currentPassword: 'qweqwe123123',
|
currentPassword: 'qweqwe123123',
|
||||||
newPassword: 'test234',
|
newPassword: 'test234',
|
||||||
newEmail: '',
|
newEmail: '',
|
||||||
@@ -202,7 +251,7 @@ describe('ChangeCredentials', () => {
|
|||||||
it('should not change password if current password is incorrect', async () => {
|
it('should not change password if current password is incorrect', async () => {
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
username: Username.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
apiVersion: ApiVersion.v20200115,
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
currentPassword: 'test123',
|
currentPassword: 'test123',
|
||||||
newPassword: 'test234',
|
newPassword: 'test234',
|
||||||
pwNonce: 'asdzxc',
|
pwNonce: 'asdzxc',
|
||||||
@@ -217,7 +266,7 @@ describe('ChangeCredentials', () => {
|
|||||||
it('should update protocol version while changing password', async () => {
|
it('should update protocol version while changing password', async () => {
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
username: Username.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
apiVersion: ApiVersion.v20200115,
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
currentPassword: 'qweqwe123123',
|
currentPassword: 'qweqwe123123',
|
||||||
newPassword: 'test234',
|
newPassword: 'test234',
|
||||||
pwNonce: 'asdzxc',
|
pwNonce: 'asdzxc',
|
||||||
@@ -241,7 +290,7 @@ describe('ChangeCredentials', () => {
|
|||||||
|
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
username: Username.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
apiVersion: ApiVersion.v20200115,
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
currentPassword: 'qweqwe123123',
|
currentPassword: 'qweqwe123123',
|
||||||
newPassword: 'qweqwe123123',
|
newPassword: 'qweqwe123123',
|
||||||
newEmail: undefined,
|
newEmail: undefined,
|
||||||
@@ -271,7 +320,7 @@ describe('ChangeCredentials', () => {
|
|||||||
|
|
||||||
const result = await createUseCase().execute({
|
const result = await createUseCase().execute({
|
||||||
username: Username.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
apiVersion: ApiVersion.v20200115,
|
apiVersion: ApiVersion.VERSIONS.v20200115,
|
||||||
currentPassword: 'qweqwe123123',
|
currentPassword: 'qweqwe123123',
|
||||||
newPassword: 'test234',
|
newPassword: 'test234',
|
||||||
pwNonce: 'asdzxc',
|
pwNonce: 'asdzxc',
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
|||||||
import { ChangeCredentialsDTO } from './ChangeCredentialsDTO'
|
import { ChangeCredentialsDTO } from './ChangeCredentialsDTO'
|
||||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||||
import { DeleteOtherSessionsForUser } from '../DeleteOtherSessionsForUser'
|
import { DeleteOtherSessionsForUser } from '../DeleteOtherSessionsForUser'
|
||||||
import { AuthResponse20161215 } from '../../Auth/AuthResponse20161215'
|
|
||||||
import { AuthResponse20200115 } from '../../Auth/AuthResponse20200115'
|
|
||||||
import { Session } from '../../Session/Session'
|
import { Session } from '../../Session/Session'
|
||||||
import { getBody, getSubject } from '../../Email/UserEmailChanged'
|
import { getBody, getSubject } from '../../Email/UserEmailChanged'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
import { AuthResponseCreationResult } from '../../Auth/AuthResponseCreationResult'
|
||||||
|
import { ApiVersion } from '../../Api/ApiVersion'
|
||||||
|
|
||||||
export class ChangeCredentials implements UseCaseInterface<AuthResponse20161215 | AuthResponse20200115> {
|
export class ChangeCredentials implements UseCaseInterface<AuthResponseCreationResult> {
|
||||||
constructor(
|
constructor(
|
||||||
private userRepository: UserRepositoryInterface,
|
private userRepository: UserRepositoryInterface,
|
||||||
private authResponseFactoryResolver: AuthResponseFactoryResolverInterface,
|
private authResponseFactoryResolver: AuthResponseFactoryResolverInterface,
|
||||||
@@ -26,7 +26,13 @@ export class ChangeCredentials implements UseCaseInterface<AuthResponse20161215
|
|||||||
private logger: Logger,
|
private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: ChangeCredentialsDTO): Promise<Result<AuthResponse20161215 | AuthResponse20200115>> {
|
async execute(dto: ChangeCredentialsDTO): Promise<Result<AuthResponseCreationResult>> {
|
||||||
|
const apiVersionOrError = ApiVersion.create(dto.apiVersion)
|
||||||
|
if (apiVersionOrError.isFailed()) {
|
||||||
|
return Result.fail(apiVersionOrError.getError())
|
||||||
|
}
|
||||||
|
const apiVersion = apiVersionOrError.getValue()
|
||||||
|
|
||||||
const user = await this.userRepository.findOneByUsernameOrEmail(dto.username)
|
const user = await this.userRepository.findOneByUsernameOrEmail(dto.username)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return Result.fail('User not found.')
|
return Result.fail('User not found.')
|
||||||
@@ -81,21 +87,23 @@ export class ChangeCredentials implements UseCaseInterface<AuthResponse20161215
|
|||||||
await this.sendEmailChangedNotification(existingEmailAddress, updatedUser.email)
|
await this.sendEmailChangedNotification(existingEmailAddress, updatedUser.email)
|
||||||
}
|
}
|
||||||
|
|
||||||
const authResponseFactory = this.authResponseFactoryResolver.resolveAuthResponseFactoryVersion(dto.apiVersion)
|
const authResponseFactory = this.authResponseFactoryResolver.resolveAuthResponseFactoryVersion(apiVersion)
|
||||||
|
|
||||||
const authResponse = await authResponseFactory.createResponse({
|
const authResponse = await authResponseFactory.createResponse({
|
||||||
user: updatedUser,
|
user: updatedUser,
|
||||||
apiVersion: dto.apiVersion,
|
apiVersion,
|
||||||
userAgent: dto.updatedWithUserAgent,
|
userAgent: dto.updatedWithUserAgent,
|
||||||
ephemeralSession: false,
|
ephemeralSession: false,
|
||||||
readonlyAccess: false,
|
readonlyAccess: false,
|
||||||
|
snjs: dto.snjs,
|
||||||
|
application: dto.application,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (authResponse.session) {
|
if (authResponse.session) {
|
||||||
await this.deleteOtherSessionsForUserIfNeeded(user.uuid, authResponse.session, dto)
|
await this.deleteOtherSessionsForUserIfNeeded(user.uuid, authResponse.session, dto)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(authResponse.response)
|
return Result.ok(authResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async deleteOtherSessionsForUserIfNeeded(
|
private async deleteOtherSessionsForUserIfNeeded(
|
||||||
|
|||||||
@@ -11,4 +11,6 @@ export type ChangeCredentialsDTO = {
|
|||||||
protocolVersion?: string
|
protocolVersion?: string
|
||||||
kpOrigination?: string
|
kpOrigination?: string
|
||||||
kpCreated?: string
|
kpCreated?: string
|
||||||
|
snjs?: string
|
||||||
|
application?: string
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user