mirror of
https://github.com/standardnotes/server
synced 2026-01-19 02:06:04 -05:00
Compare commits
53 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a0accd8ea | ||
|
|
d66ae62cf4 | ||
|
|
b01d1c659d | ||
|
|
751f3b2547 | ||
|
|
11514e3836 | ||
|
|
71689c1497 | ||
|
|
2742075edc | ||
|
|
7f16232f8b | ||
|
|
0b0703e6d1 | ||
|
|
3e376c44e3 | ||
|
|
bfe2d4bb4a | ||
|
|
7253a0a1d9 | ||
|
|
f2c5810023 | ||
|
|
2e5b9105b8 | ||
|
|
d14411d72e | ||
|
|
5226513b26 | ||
|
|
334449f8aa | ||
|
|
7f43d0c69d | ||
|
|
6f18276e7a | ||
|
|
9ff18a18a5 | ||
|
|
999e72fb1f | ||
|
|
4733e663a3 | ||
|
|
b48eeb16c3 | ||
|
|
0aa2584e82 | ||
|
|
eb8c704d84 | ||
|
|
e93fa14703 | ||
|
|
16a6815b69 | ||
|
|
b08e9731b8 | ||
|
|
9bd4fb2d79 | ||
|
|
647aeda1de | ||
|
|
78ff748d91 | ||
|
|
31f8cf1169 | ||
|
|
14bcf7b6c9 | ||
|
|
74adddd1e7 | ||
|
|
0e43bc0042 | ||
|
|
b40d539611 | ||
|
|
654663d17f | ||
|
|
75830c3a98 | ||
|
|
1b5078eb96 | ||
|
|
a5e019e290 | ||
|
|
a812f3400a | ||
|
|
15af5635f0 | ||
|
|
cee6d62791 | ||
|
|
6aee51bd45 | ||
|
|
599a84e634 | ||
|
|
1c3d19cca4 | ||
|
|
9986e8e7ce | ||
|
|
e19f7a7b7f | ||
|
|
d570146378 | ||
|
|
8a9e4370e5 | ||
|
|
ce357679e9 | ||
|
|
acab402747 | ||
|
|
e385926046 |
2
.github/ci.env
vendored
2
.github/ci.env
vendored
@@ -26,3 +26,5 @@ MYSQL_ROOT_PASSWORD=changeme123
|
||||
AUTH_JWT_SECRET=f95259c5e441f5a4646d76422cfb3df4c4488842901aa50b6c51b8be2e0040e9
|
||||
AUTH_SERVER_ENCRYPTION_SERVER_KEY=1087415dfde3093797f9a7ca93a49e7d7aa1861735eb0d32aae9c303b8c3d060
|
||||
VALET_TOKEN_SECRET=4b886819ebe1e908077c6cae96311b48a8416bd60cc91c03060e15bdf6b30d1f
|
||||
|
||||
SYNCING_SERVER_CONTENT_SIZE_TRANSFER_LIMIT=1000000
|
||||
|
||||
4
.github/workflows/common-docker-image.yml
vendored
4
.github/workflows/common-docker-image.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
run: yarn build
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- name: Login to Amazon ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@v1
|
||||
uses: aws-actions/amazon-ecr-login@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@master
|
||||
|
||||
2
.github/workflows/common-self-hosting.yml
vendored
2
.github/workflows/common-self-hosting.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
1
.github/workflows/e2e-home-server.yml
vendored
1
.github/workflows/e2e-home-server.yml
vendored
@@ -70,6 +70,7 @@ jobs:
|
||||
echo "ACCESS_TOKEN_AGE=4" >> packages/home-server/.env
|
||||
echo "REFRESH_TOKEN_AGE=10" >> packages/home-server/.env
|
||||
echo "REVISIONS_FREQUENCY=2" >> packages/home-server/.env
|
||||
echo "CONTENT_SIZE_TRANSFER_LIMIT=1000000" >> packages/home-server/.env
|
||||
echo "DB_HOST=localhost" >> packages/home-server/.env
|
||||
echo "DB_PORT=3306" >> packages/home-server/.env
|
||||
echo "DB_DATABASE=standardnotes" >> packages/home-server/.env
|
||||
|
||||
6
.github/workflows/publish.yml
vendored
6
.github/workflows/publish.yml
vendored
@@ -112,13 +112,13 @@ jobs:
|
||||
suite: 'vaults'
|
||||
|
||||
publish-self-hosting:
|
||||
needs: [ test, lint, e2e-base ]
|
||||
needs: [ test, lint, e2e-base, e2e-vaults ]
|
||||
name: Publish Self Hosting Docker Image
|
||||
uses: standardnotes/server/.github/workflows/common-self-hosting.yml@main
|
||||
secrets: inherit
|
||||
|
||||
publish-services:
|
||||
needs: [ test, lint, e2e-base ]
|
||||
needs: [ test, lint, e2e-base, e2e-vaults ]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
git config --global user.email "ci@standardnotes.com"
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v5
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.PASSPHRASE }}
|
||||
|
||||
13
.pnp.cjs
generated
13
.pnp.cjs
generated
@@ -5546,7 +5546,6 @@ const RAW_RUNTIME_STATE =
|
||||
["@types/uuid", "npm:9.0.3"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
|
||||
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
|
||||
["axios", "npm:1.4.0"],\
|
||||
["bcryptjs", "npm:2.4.3"],\
|
||||
["cors", "npm:2.8.5"],\
|
||||
["dayjs", "npm:1.11.7"],\
|
||||
@@ -5567,7 +5566,7 @@ const RAW_RUNTIME_STATE =
|
||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
|
||||
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.17"],\
|
||||
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
|
||||
["ua-parser-js", "npm:1.0.35"],\
|
||||
["ua-parser-js", "npm:1.0.37"],\
|
||||
["uuid", "npm:9.0.0"],\
|
||||
["winston", "npm:3.9.0"]\
|
||||
],\
|
||||
@@ -5786,7 +5785,6 @@ const RAW_RUNTIME_STATE =
|
||||
["@standardnotes/syncing-server", "workspace:packages/syncing-server"],\
|
||||
["@types/cors", "npm:2.8.13"],\
|
||||
["@types/express", "npm:4.17.17"],\
|
||||
["@types/prettyjson", "npm:0.0.30"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
|
||||
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
|
||||
["cors", "npm:2.8.5"],\
|
||||
@@ -5799,7 +5797,6 @@ const RAW_RUNTIME_STATE =
|
||||
["inversify", "npm:6.0.1"],\
|
||||
["inversify-express-utils", "npm:6.4.3"],\
|
||||
["prettier", "npm:3.0.3"],\
|
||||
["prettyjson", "npm:1.2.5"],\
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
|
||||
["winston", "npm:3.9.0"]\
|
||||
@@ -6081,7 +6078,7 @@ const RAW_RUNTIME_STATE =
|
||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
|
||||
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.17"],\
|
||||
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
|
||||
["ua-parser-js", "npm:1.0.35"],\
|
||||
["ua-parser-js", "npm:1.0.37"],\
|
||||
["uuid", "npm:9.0.0"],\
|
||||
["winston", "npm:3.9.0"]\
|
||||
],\
|
||||
@@ -16150,10 +16147,10 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["ua-parser-js", [\
|
||||
["npm:1.0.35", {\
|
||||
"packageLocation": "./.yarn/cache/ua-parser-js-npm-1.0.35-38ecdb7612-b69c99c20f.zip/node_modules/ua-parser-js/",\
|
||||
["npm:1.0.37", {\
|
||||
"packageLocation": "./.yarn/cache/ua-parser-js-npm-1.0.37-b79655e1b5-56508f2428.zip/node_modules/ua-parser-js/",\
|
||||
"packageDependencies": [\
|
||||
["ua-parser-js", "npm:1.0.35"]\
|
||||
["ua-parser-js", "npm:1.0.37"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
|
||||
Binary file not shown.
BIN
.yarn/cache/ua-parser-js-npm-1.0.37-b79655e1b5-56508f2428.zip
vendored
Normal file
BIN
.yarn/cache/ua-parser-js-npm-1.0.37-b79655e1b5-56508f2428.zip
vendored
Normal file
Binary file not shown.
@@ -3,6 +3,24 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.32.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.5...@standardnotes/analytics@2.32.6) (2023-11-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.32.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.4...@standardnotes/analytics@2.32.5) (2023-11-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove open telemetry from code ([#903](https://github.com/standardnotes/server/issues/903)) ([751f3b2](https://github.com/standardnotes/server/commit/751f3b25476c5be3d663ad8540c43266acd39493))
|
||||
|
||||
## [2.32.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.3...@standardnotes/analytics@2.32.4) (2023-10-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.32.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.2...@standardnotes/analytics@2.32.3) (2023-10-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.32.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.1...@standardnotes/analytics@2.32.2) (2023-10-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { OpenTelemetrySDK, OpenTelemetryTracer } from '@standardnotes/domain-events-infra'
|
||||
import { EmailLevel, ServiceIdentifier } from '@standardnotes/domain-core'
|
||||
|
||||
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AnalyticsScheduledTask })
|
||||
sdk.start()
|
||||
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
@@ -22,6 +16,7 @@ import { CalculateMonthlyRecurringRevenue } from '../src/Domain/UseCase/Calculat
|
||||
import { getBody, getSubject } from '../src/Domain/Email/DailyAnalyticsReport'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { StatisticMeasureName } from '../src/Domain/Statistics/StatisticMeasureName'
|
||||
import { EmailLevel } from '@standardnotes/domain-core'
|
||||
|
||||
const requestReport = async (
|
||||
analyticsStore: AnalyticsStoreInterface,
|
||||
@@ -275,9 +270,6 @@ void container.load().then((container) => {
|
||||
|
||||
logger.info(`Sending report to following admins: ${adminEmails}`)
|
||||
|
||||
const tracer = new OpenTelemetryTracer()
|
||||
tracer.startSpan(ServiceIdentifier.NAMES.AnalyticsScheduledTask, 'report')
|
||||
|
||||
Promise.resolve(
|
||||
requestReport(
|
||||
analyticsStore,
|
||||
@@ -293,15 +285,11 @@ void container.load().then((container) => {
|
||||
.then(() => {
|
||||
logger.info('Usage report generation complete')
|
||||
|
||||
tracer.stopSpan()
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`Could not finish usage report generation: ${error.message}`)
|
||||
|
||||
tracer.stopSpanWithError(error)
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
|
||||
import { ServiceIdentifier } from '@standardnotes/domain-core'
|
||||
|
||||
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AnalyticsWorker })
|
||||
sdk.start()
|
||||
|
||||
import { Logger } from 'winston'
|
||||
import { DomainEventSubscriberInterface } from '@standardnotes/domain-events'
|
||||
import * as dayjs from 'dayjs'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.32.2",
|
||||
"version": "2.32.6",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
DomainEventPublisherInterface,
|
||||
DomainEventSubscriberInterface,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { MapperInterface, ServiceIdentifier } from '@standardnotes/domain-core'
|
||||
import { MapperInterface } from '@standardnotes/domain-core'
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const Mixpanel = require('mixpanel')
|
||||
|
||||
@@ -16,9 +16,9 @@ import TYPES from './Types'
|
||||
import { AppDataSource } from './DataSource'
|
||||
import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
|
||||
import {
|
||||
SNSOpenTelemetryDomainEventPublisher,
|
||||
SNSDomainEventPublisher,
|
||||
SQSDomainEventSubscriber,
|
||||
SQSEventMessageHandler,
|
||||
SQSOpenTelemetryDomainEventSubscriber,
|
||||
} from '@standardnotes/domain-events-infra'
|
||||
import { Timer, TimerInterface } from '@standardnotes/time'
|
||||
import { PeriodKeyGeneratorInterface } from '../Domain/Time/PeriodKeyGeneratorInterface'
|
||||
@@ -139,9 +139,7 @@ export class ContainerConfigLoader {
|
||||
|
||||
container
|
||||
.bind<DomainEventPublisherInterface>(TYPES.DomainEventPublisher)
|
||||
.toConstantValue(
|
||||
new SNSOpenTelemetryDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN)),
|
||||
)
|
||||
.toConstantValue(new SNSDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN)))
|
||||
if (env.get('MIXPANEL_TOKEN', true)) {
|
||||
container.bind<Mixpanel>(TYPES.MixpanelClient).toConstantValue(Mixpanel.init(env.get('MIXPANEL_TOKEN', true)))
|
||||
}
|
||||
@@ -242,8 +240,7 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<DomainEventSubscriberInterface>(TYPES.DomainEventSubscriber)
|
||||
.toConstantValue(
|
||||
new SQSOpenTelemetryDomainEventSubscriber(
|
||||
ServiceIdentifier.NAMES.AnalyticsWorker,
|
||||
new SQSDomainEventSubscriber(
|
||||
container.get<SQSClient>(TYPES.SQS),
|
||||
container.get<string>(TYPES.SQS_QUEUE_URL),
|
||||
container.get<DomainEventMessageHandlerInterface>(TYPES.DomainEventMessageHandler),
|
||||
|
||||
@@ -3,6 +3,72 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.81.10](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.9...@standardnotes/api-gateway@1.81.10) (2023-11-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.81.9](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.8...@standardnotes/api-gateway@1.81.9) (2023-11-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove open telemetry from code ([#903](https://github.com/standardnotes/api-gateway/issues/903)) ([751f3b2](https://github.com/standardnotes/api-gateway/commit/751f3b25476c5be3d663ad8540c43266acd39493))
|
||||
|
||||
## [1.81.8](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.7...@standardnotes/api-gateway@1.81.8) (2023-11-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* retry attempts on session validation and more verbose logs ([#898](https://github.com/standardnotes/api-gateway/issues/898)) ([3e376c4](https://github.com/standardnotes/api-gateway/commit/3e376c44e3a6c336dcff3d8ef5eb3ab040d9a561))
|
||||
|
||||
## [1.81.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.6...@standardnotes/api-gateway@1.81.7) (2023-10-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add fallback methods for 404 requests ([#893](https://github.com/standardnotes/api-gateway/issues/893)) ([16a6815](https://github.com/standardnotes/api-gateway/commit/16a6815b69e344573ae07682f3bac1d44d715d79))
|
||||
|
||||
## [1.81.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.5...@standardnotes/api-gateway@1.81.6) (2023-10-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** logs for errors reaching service ([14bcf7b](https://github.com/standardnotes/api-gateway/commit/14bcf7b6c9403c3413e7579f58ea17168d14dce7))
|
||||
|
||||
## [1.81.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.4...@standardnotes/api-gateway@1.81.5) (2023-10-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.81.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.3...@standardnotes/api-gateway@1.81.4) (2023-10-26)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** retry attempts and logs ([654663d](https://github.com/standardnotes/api-gateway/commit/654663d17f6eee15f7bf2bc7f40e6c37a3d8e53c))
|
||||
|
||||
## [1.81.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.2...@standardnotes/api-gateway@1.81.3) (2023-10-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.81.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.1...@standardnotes/api-gateway@1.81.2) (2023-10-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** add session validation retry attempts on timedout requests ([6aee51b](https://github.com/standardnotes/api-gateway/commit/6aee51bd45c25e85d01075a9c8d2854b32dd6e3c))
|
||||
|
||||
## [1.81.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.0...@standardnotes/api-gateway@1.81.1) (2023-10-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** logs severity on retry attempts ([1c3d19c](https://github.com/standardnotes/api-gateway/commit/1c3d19cca43a7a3eba2b0d05c820de5112edf89e))
|
||||
|
||||
# [1.81.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.80.1...@standardnotes/api-gateway@1.81.0) (2023-10-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **api-gateway:** add retry attempts on timedout requests ([#885](https://github.com/standardnotes/api-gateway/issues/885)) ([ce35767](https://github.com/standardnotes/api-gateway/commit/ce357679e9bc704ab562e9d6ca192f49a794a664))
|
||||
|
||||
## [1.80.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.80.0...@standardnotes/api-gateway@1.80.1) (2023-10-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** stringify error in service proxy ([e385926](https://github.com/standardnotes/api-gateway/commit/e38592604644e0f52df0865ffae5b7e79d1d3d07))
|
||||
|
||||
# [1.80.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.79.14...@standardnotes/api-gateway@1.80.0) (2023-10-19)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
|
||||
import { ServiceIdentifier } from '@standardnotes/domain-core'
|
||||
|
||||
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.ApiGateway })
|
||||
sdk.start()
|
||||
|
||||
import '../src/Controller/LegacyController'
|
||||
import '../src/Controller/HealthCheckController'
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.80.0",
|
||||
"version": "1.81.10",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -103,6 +103,8 @@ export class ContainerConfigLoader {
|
||||
.to(SubscriptionTokenAuthMiddleware)
|
||||
|
||||
// Services
|
||||
container.bind<TimerInterface>(TYPES.ApiGateway_Timer).toConstantValue(new Timer())
|
||||
|
||||
if (isConfiguredForHomeServer) {
|
||||
if (!configuration?.serviceContainer) {
|
||||
throw new Error('Service container is required when configured for home server')
|
||||
@@ -115,7 +117,6 @@ export class ContainerConfigLoader {
|
||||
} else {
|
||||
container.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy).to(HttpServiceProxy)
|
||||
}
|
||||
container.bind<TimerInterface>(TYPES.ApiGateway_Timer).toConstantValue(new Timer())
|
||||
|
||||
if (isConfiguredForHomeServer) {
|
||||
container
|
||||
|
||||
@@ -74,13 +74,16 @@ export abstract class AuthMiddleware extends BaseMiddleware {
|
||||
response.locals.sharedVaultOwnerContext = decodedToken.shared_vault_owner_context
|
||||
response.locals.belongsToSharedVaults = decodedToken.belongs_to_shared_vaults ?? []
|
||||
} catch (error) {
|
||||
const errorMessage = (error as AxiosError).isAxiosError
|
||||
? JSON.stringify((error as AxiosError).response?.data)
|
||||
: (error as Error).message
|
||||
let detailedErrorMessage = (error as Error).message
|
||||
if (error instanceof AxiosError) {
|
||||
detailedErrorMessage = `Status: ${error.status}, code: ${error.code}, message: ${error.message}`
|
||||
}
|
||||
|
||||
this.logger.error(`Could not pass the request to sessions/validate on underlying service: ${errorMessage}`)
|
||||
this.logger.error(
|
||||
`Could not pass the request to sessions/validate on underlying service: ${detailedErrorMessage}`,
|
||||
)
|
||||
|
||||
this.logger.debug('Response error: %O', (error as AxiosError).response ?? error)
|
||||
this.logger.debug(`Response error: ${JSON.stringify(error)}`)
|
||||
|
||||
if ((error as AxiosError).response?.headers['content-type']) {
|
||||
response.setHeader('content-type', (error as AxiosError).response?.headers['content-type'] as string)
|
||||
@@ -91,7 +94,14 @@ export abstract class AuthMiddleware extends BaseMiddleware {
|
||||
? +((error as AxiosError).code as string)
|
||||
: 500
|
||||
|
||||
response.status(errorCode).send(errorMessage)
|
||||
const responseErrorMessage = (error as AxiosError).response?.data
|
||||
|
||||
response
|
||||
.status(errorCode)
|
||||
.send(
|
||||
responseErrorMessage ??
|
||||
"Unfortunately, we couldn't handle your request. Please try again or contact our support if the error persists.",
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { BaseHttpController, all, controller, results } from 'inversify-express-utils'
|
||||
|
||||
@controller('')
|
||||
export class FallbackController extends BaseHttpController {
|
||||
@all('*')
|
||||
public async fallback(): Promise<results.NotFoundResult> {
|
||||
return this.notFound()
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './AuthMiddleware'
|
||||
export * from './FallbackController'
|
||||
export * from './HealthCheckController'
|
||||
export * from './SubscriptionTokenAuthMiddleware'
|
||||
export * from './TokenAuthenticationMethod'
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Logger } from 'winston'
|
||||
import { TYPES } from '../../Bootstrap/Types'
|
||||
import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface'
|
||||
import { ServiceProxyInterface } from './ServiceProxyInterface'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
@injectable()
|
||||
export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
@@ -22,31 +23,49 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
@inject(TYPES.ApiGateway_HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
|
||||
@inject(TYPES.ApiGateway_CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
|
||||
@inject(TYPES.ApiGateway_Logger) private logger: Logger,
|
||||
@inject(TYPES.ApiGateway_Timer) private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async validateSession(headers: {
|
||||
authorization: string
|
||||
sharedVaultOwnerContext?: string
|
||||
}): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
|
||||
const authResponse = await this.httpClient.request({
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: headers.authorization,
|
||||
Accept: 'application/json',
|
||||
'x-shared-vault-owner-context': headers.sharedVaultOwnerContext,
|
||||
},
|
||||
validateStatus: (status: number) => {
|
||||
return status >= 200 && status < 500
|
||||
},
|
||||
url: `${this.authServerUrl}/sessions/validate`,
|
||||
})
|
||||
async validateSession(
|
||||
headers: {
|
||||
authorization: string
|
||||
sharedVaultOwnerContext?: string
|
||||
},
|
||||
retryAttempt?: number,
|
||||
): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
|
||||
try {
|
||||
const authResponse = await this.httpClient.request({
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: headers.authorization,
|
||||
Accept: 'application/json',
|
||||
'x-shared-vault-owner-context': headers.sharedVaultOwnerContext,
|
||||
},
|
||||
validateStatus: (status: number) => {
|
||||
return status >= 200 && status < 500
|
||||
},
|
||||
url: `${this.authServerUrl}/sessions/validate`,
|
||||
})
|
||||
|
||||
return {
|
||||
status: authResponse.status,
|
||||
data: authResponse.data,
|
||||
headers: {
|
||||
contentType: authResponse.headers['content-type'] as string,
|
||||
},
|
||||
return {
|
||||
status: authResponse.status,
|
||||
data: authResponse.data,
|
||||
headers: {
|
||||
contentType: authResponse.headers['content-type'] as string,
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
const requestDidNotMakeIt = this.requestTimedOutOrDidNotReachDestination(error as Record<string, unknown>)
|
||||
const tooManyRetryAttempts = retryAttempt && retryAttempt > 2
|
||||
if (!tooManyRetryAttempts && requestDidNotMakeIt) {
|
||||
await this.timer.sleep(50)
|
||||
|
||||
const nextRetryAttempt = retryAttempt ? retryAttempt + 1 : 1
|
||||
|
||||
return this.validateSession(headers, nextRetryAttempt)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +188,7 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
retryAttempt?: number,
|
||||
): Promise<AxiosResponse | undefined> {
|
||||
try {
|
||||
const headers: Record<string, string> = {}
|
||||
@@ -211,17 +231,47 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
await this.crossServiceTokenCache.invalidate(userUuid)
|
||||
}
|
||||
|
||||
if (retryAttempt) {
|
||||
this.logger.debug(
|
||||
`Request to ${serverUrl}/${endpointOrMethodIdentifier} succeeded after ${retryAttempt} retries`,
|
||||
)
|
||||
}
|
||||
|
||||
return serviceResponse
|
||||
} catch (error) {
|
||||
const errorMessage = (error as AxiosError).isAxiosError
|
||||
? JSON.stringify((error as AxiosError).response?.data)
|
||||
: (error as Error).message
|
||||
const requestDidNotMakeIt = this.requestTimedOutOrDidNotReachDestination(error as Record<string, unknown>)
|
||||
const tooManyRetryAttempts = retryAttempt && retryAttempt > 2
|
||||
if (!tooManyRetryAttempts && requestDidNotMakeIt) {
|
||||
await this.timer.sleep(50)
|
||||
|
||||
const nextRetryAttempt = retryAttempt ? retryAttempt + 1 : 1
|
||||
|
||||
this.logger.debug(
|
||||
`Retrying request to ${serverUrl}/${endpointOrMethodIdentifier} for the ${nextRetryAttempt} time`,
|
||||
)
|
||||
|
||||
return this.getServerResponse(
|
||||
serverUrl,
|
||||
request,
|
||||
response,
|
||||
endpointOrMethodIdentifier,
|
||||
payload,
|
||||
nextRetryAttempt,
|
||||
)
|
||||
}
|
||||
|
||||
let detailedErrorMessage = (error as Error).message
|
||||
if (error instanceof AxiosError) {
|
||||
detailedErrorMessage = `Status: ${error.status}, code: ${error.code}, message: ${error.message}`
|
||||
}
|
||||
|
||||
this.logger.error(
|
||||
`Could not pass the request to ${serverUrl}/${endpointOrMethodIdentifier} on underlying service: ${errorMessage}`,
|
||||
tooManyRetryAttempts
|
||||
? `Request to ${serverUrl}/${endpointOrMethodIdentifier} timed out after ${retryAttempt} retries`
|
||||
: `Could not pass the request to ${serverUrl}/${endpointOrMethodIdentifier} on underlying service: ${detailedErrorMessage}`,
|
||||
)
|
||||
|
||||
this.logger.debug('Response error: %O', (error as AxiosError).response ?? error)
|
||||
this.logger.debug(`Response error: ${JSON.stringify(error)}`)
|
||||
|
||||
if ((error as AxiosError).response?.headers['content-type']) {
|
||||
response.setHeader('content-type', (error as AxiosError).response?.headers['content-type'] as string)
|
||||
@@ -232,7 +282,14 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
? +((error as AxiosError).code as string)
|
||||
: 500
|
||||
|
||||
response.status(errorCode).send(errorMessage)
|
||||
const responseErrorMessage = (error as AxiosError).response?.data
|
||||
|
||||
response
|
||||
.status(errorCode)
|
||||
.send(
|
||||
responseErrorMessage ??
|
||||
"Unfortunately, we couldn't handle your request. Please try again or contact our support if the error persists.",
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
@@ -363,4 +420,13 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private requestTimedOutOrDidNotReachDestination(error: Record<string, unknown>): boolean {
|
||||
return (
|
||||
('code' in error && error.code === 'ETIMEDOUT') ||
|
||||
('response' in error &&
|
||||
'status' in (error.response as Record<string, unknown>) &&
|
||||
[503, 504].includes((error.response as Record<string, unknown>).status as number))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,13 @@ export interface ServiceProxyInterface {
|
||||
endpointOrMethodIdentifier: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void>
|
||||
validateSession(headers: { authorization: string; sharedVaultOwnerContext?: string }): Promise<{
|
||||
validateSession(
|
||||
headers: {
|
||||
authorization: string
|
||||
sharedVaultOwnerContext?: string
|
||||
},
|
||||
retryAttempt?: number,
|
||||
): Promise<{
|
||||
status: number
|
||||
data: unknown
|
||||
headers: {
|
||||
|
||||
@@ -9,10 +9,13 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
|
||||
private filesServerUrl: string,
|
||||
) {}
|
||||
|
||||
async validateSession(headers: {
|
||||
authorization: string
|
||||
sharedVaultOwnerContext?: string
|
||||
}): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
|
||||
async validateSession(
|
||||
headers: {
|
||||
authorization: string
|
||||
sharedVaultOwnerContext?: string
|
||||
},
|
||||
_retryAttempt?: number,
|
||||
): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
|
||||
const authService = this.serviceContainer.get(ServiceIdentifier.create(ServiceIdentifier.NAMES.Auth).getValue())
|
||||
if (!authService) {
|
||||
throw new Error('Auth service not found')
|
||||
|
||||
@@ -43,13 +43,6 @@ SNS_AWS_REGION=
|
||||
SQS_QUEUE_URL=
|
||||
SQS_AWS_REGION=
|
||||
|
||||
SYNCING_SERVER_URL=http://syncing-server-js:3000
|
||||
|
||||
# (Optional) User Server
|
||||
USER_SERVER_REGISTRATION_URL=
|
||||
USER_SERVER_CHANGE_EMAIL_URL=
|
||||
USER_SERVER_AUTH_KEY=
|
||||
|
||||
VALET_TOKEN_SECRET=
|
||||
VALET_TOKEN_TTL=
|
||||
|
||||
|
||||
@@ -3,6 +3,86 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.165.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.165.3...@standardnotes/auth-server@1.165.4) (2023-11-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* account deletion event ([#904](https://github.com/standardnotes/server/issues/904)) ([d66ae62](https://github.com/standardnotes/server/commit/d66ae62cf4f413cac5f6f4eac45dc0f1ddbc9e32))
|
||||
|
||||
## [1.165.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.165.2...@standardnotes/auth-server@1.165.3) (2023-11-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove open telemetry from code ([#903](https://github.com/standardnotes/server/issues/903)) ([751f3b2](https://github.com/standardnotes/server/commit/751f3b25476c5be3d663ad8540c43266acd39493))
|
||||
|
||||
## [1.165.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.165.1...@standardnotes/auth-server@1.165.2) (2023-11-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** change log severity on user authentication ([7f16232](https://github.com/standardnotes/server/commit/7f16232f8b13e3736801b6dc0af799e0559a3cfa))
|
||||
|
||||
## [1.165.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.165.0...@standardnotes/auth-server@1.165.1) (2023-11-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* retry attempts on session validation and more verbose logs ([#898](https://github.com/standardnotes/server/issues/898)) ([3e376c4](https://github.com/standardnotes/server/commit/3e376c44e3a6c336dcff3d8ef5eb3ab040d9a561))
|
||||
|
||||
# [1.165.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.164.2...@standardnotes/auth-server@1.165.0) (2023-11-02)
|
||||
|
||||
### Features
|
||||
|
||||
* add shared vault invitation email notifications ([#897](https://github.com/standardnotes/server/issues/897)) ([7253a0a](https://github.com/standardnotes/server/commit/7253a0a1d92099df844c9baf6541b440bbcb0a68))
|
||||
|
||||
## [1.164.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.164.1...@standardnotes/auth-server@1.164.2) (2023-11-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.164.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.164.0...@standardnotes/auth-server@1.164.1) (2023-11-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** creating valet tokens for shared subscription users ([#895](https://github.com/standardnotes/server/issues/895)) ([b48eeb1](https://github.com/standardnotes/server/commit/b48eeb16c32031e73e9757e34c4b50ca0a3a773d))
|
||||
|
||||
# [1.164.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.163.2...@standardnotes/auth-server@1.164.0) (2023-11-01)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add sending email to old email address when the address is changed ([#894](https://github.com/standardnotes/server/issues/894)) ([eb8c704](https://github.com/standardnotes/server/commit/eb8c704d84277130dc0dc51c1fe475a7220612cd))
|
||||
|
||||
## [1.163.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.163.1...@standardnotes/auth-server@1.163.2) (2023-10-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** checking permissions to update setting only when directly performed by user ([#892](https://github.com/standardnotes/server/issues/892)) ([9bd4fb2](https://github.com/standardnotes/server/commit/9bd4fb2d794dae032286c68f23d3896b68735bdd))
|
||||
|
||||
## [1.163.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.163.0...@standardnotes/auth-server@1.163.1) (2023-10-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** add more information on the listed creation error ([78ff748](https://github.com/standardnotes/server/commit/78ff748d911a5a4063903847ef761822bbb8f4e2))
|
||||
|
||||
# [1.163.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.162.0...@standardnotes/auth-server@1.163.0) (2023-10-26)
|
||||
|
||||
### Features
|
||||
|
||||
* extract setting name to domain-core package ([0e43bc0](https://github.com/standardnotes/server/commit/0e43bc00427113f421b0c4b67c067f0de96caf52))
|
||||
|
||||
# [1.162.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.161.0...@standardnotes/auth-server@1.162.0) (2023-10-26)
|
||||
|
||||
### Features
|
||||
|
||||
* refactor settings ([#890](https://github.com/standardnotes/server/issues/890)) ([1b5078e](https://github.com/standardnotes/server/commit/1b5078eb9629397822f5403643c60fbf4182df92))
|
||||
|
||||
# [1.161.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.160.0...@standardnotes/auth-server@1.161.0) (2023-10-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow to cancel previous subscription when activating premium features in e2e tests ([15af563](https://github.com/standardnotes/server/commit/15af5635f05a8363336aa33830e0157f519eee83))
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** remove axios http calls to payments server ([#889](https://github.com/standardnotes/server/issues/889)) ([a812f34](https://github.com/standardnotes/server/commit/a812f3400af3712fd5481b0c38c8805bb9c79e2c))
|
||||
|
||||
# [1.160.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.159.2...@standardnotes/auth-server@1.160.0) (2023-10-19)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { OpenTelemetrySDK, OpenTelemetryTracer } from '@standardnotes/domain-events-infra'
|
||||
import { ServiceIdentifier } from '@standardnotes/domain-core'
|
||||
|
||||
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AuthScheduledTask })
|
||||
sdk.start()
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
|
||||
import { Stream } from 'stream'
|
||||
|
||||
@@ -18,7 +14,7 @@ import { Env } from '../src/Bootstrap/Env'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
||||
import { MuteFailedBackupsEmailsOption, SettingName } from '@standardnotes/settings'
|
||||
import { MuteFailedBackupsEmailsOption } from '@standardnotes/settings'
|
||||
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
import { GetUserKeyParams } from '../src/Domain/UseCase/GetUserKeyParams/GetUserKeyParams'
|
||||
@@ -62,8 +58,8 @@ const requestBackups = async (
|
||||
muteEmailsSettingName,
|
||||
setting.setting_user_uuid,
|
||||
)
|
||||
if (emailsMutedSetting !== null && emailsMutedSetting.value !== null) {
|
||||
userHasEmailsMuted = emailsMutedSetting.value === muteEmailsSettingValue
|
||||
if (emailsMutedSetting !== null && emailsMutedSetting.props.value !== null) {
|
||||
userHasEmailsMuted = emailsMutedSetting.props.value === muteEmailsSettingValue
|
||||
}
|
||||
|
||||
const keyParamsResponse = await getUserKeyParamsUseCase.execute({
|
||||
@@ -74,7 +70,7 @@ const requestBackups = async (
|
||||
await domainEventPublisher.publish(
|
||||
domainEventFactory.createEmailBackupRequestedEvent(
|
||||
setting.setting_user_uuid,
|
||||
emailsMutedSetting?.uuid as string,
|
||||
emailsMutedSetting?.id.toString() as string,
|
||||
userHasEmailsMuted,
|
||||
keyParamsResponse.keyParams,
|
||||
),
|
||||
@@ -106,24 +102,17 @@ void container.load().then((container) => {
|
||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.Auth_DomainEventPublisher)
|
||||
const getUserKeyParamsUseCase: GetUserKeyParams = container.get(TYPES.Auth_GetUserKeyParams)
|
||||
|
||||
const tracer = new OpenTelemetryTracer()
|
||||
tracer.startSpan(ServiceIdentifier.NAMES.AuthScheduledTask, 'backup')
|
||||
|
||||
Promise.resolve(
|
||||
requestBackups(settingRepository, roleService, domainEventFactory, domainEventPublisher, getUserKeyParamsUseCase),
|
||||
)
|
||||
.then(() => {
|
||||
logger.info(`${backupFrequency} ${backupProvider} backup requesting complete`)
|
||||
|
||||
tracer.stopSpan()
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`Could not finish ${backupFrequency} ${backupProvider} backup requesting: ${error.message}`)
|
||||
|
||||
tracer.stopSpanWithError(error)
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { OpenTelemetrySDK, OpenTelemetryTracer } from '@standardnotes/domain-events-infra'
|
||||
import { ServiceIdentifier } from '@standardnotes/domain-core'
|
||||
|
||||
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AuthScheduledTask })
|
||||
sdk.start()
|
||||
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
@@ -36,22 +30,15 @@ void container.load().then((container) => {
|
||||
const cleanupSessionTraces: CleanupSessionTraces = container.get(TYPES.Auth_CleanupSessionTraces)
|
||||
const cleanupExpiredSessions: CleanupExpiredSessions = container.get(TYPES.Auth_CleanupExpiredSessions)
|
||||
|
||||
const tracer = new OpenTelemetryTracer()
|
||||
tracer.startSpan(ServiceIdentifier.NAMES.AuthScheduledTask, 'cleanup')
|
||||
|
||||
Promise.resolve(cleanup(cleanupSessionTraces, cleanupExpiredSessions))
|
||||
.then(() => {
|
||||
logger.info('Expired sessions and session traces cleaned.')
|
||||
|
||||
tracer.stopSpan()
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`Could not clean sessions and session traces: ${error.message}`)
|
||||
|
||||
tracer.stopSpanWithError(error)
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
|
||||
import { ServiceIdentifier } from '@standardnotes/domain-core'
|
||||
|
||||
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.Auth })
|
||||
sdk.start()
|
||||
|
||||
import '../src/Infra/InversifyExpressUtils/AnnotatedAuthController'
|
||||
import '../src/Infra/InversifyExpressUtils/AnnotatedAuthenticatorsController'
|
||||
import '../src/Infra/InversifyExpressUtils/AnnotatedSessionsController'
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { OpenTelemetrySDK, OpenTelemetryTracer } from '@standardnotes/domain-events-infra'
|
||||
import { ServiceIdentifier } from '@standardnotes/domain-core'
|
||||
|
||||
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AuthScheduledTask })
|
||||
sdk.start()
|
||||
|
||||
import { Logger } from 'winston'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
@@ -26,9 +20,6 @@ void container.load().then((container) => {
|
||||
const persistStats: PersistStatistics = container.get(TYPES.Auth_PersistStatistics)
|
||||
const timer: TimerInterface = container.get(TYPES.Auth_Timer)
|
||||
|
||||
const tracer = new OpenTelemetryTracer()
|
||||
tracer.startSpan(ServiceIdentifier.NAMES.AuthScheduledTask, 'stats')
|
||||
|
||||
Promise.resolve(
|
||||
persistStats.execute({
|
||||
sessionsInADay: timer.getUTCDateNDaysAgo(1),
|
||||
@@ -37,15 +28,11 @@ void container.load().then((container) => {
|
||||
.then(() => {
|
||||
logger.info('Stats persisted.')
|
||||
|
||||
tracer.stopSpan()
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`Could not persist stats: ${error.message}`)
|
||||
|
||||
tracer.stopSpanWithError(error)
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { OpenTelemetrySDK, OpenTelemetryTracer } from '@standardnotes/domain-events-infra'
|
||||
import { Email, ServiceIdentifier } from '@standardnotes/domain-core'
|
||||
|
||||
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AuthScheduledTask })
|
||||
sdk.start()
|
||||
|
||||
import { Logger } from 'winston'
|
||||
import * as dayjs from 'dayjs'
|
||||
import * as utc from 'dayjs/plugin/utc'
|
||||
@@ -16,11 +10,12 @@ import { Env } from '../src/Bootstrap/Env'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
||||
import { MuteFailedBackupsEmailsOption, SettingName } from '@standardnotes/settings'
|
||||
import { MuteFailedBackupsEmailsOption } from '@standardnotes/settings'
|
||||
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
|
||||
import { GetUserKeyParams } from '../src/Domain/UseCase/GetUserKeyParams/GetUserKeyParams'
|
||||
import { Email, SettingName } from '@standardnotes/domain-core'
|
||||
|
||||
const inputArgs = process.argv.slice(2)
|
||||
const backupEmail = inputArgs[0]
|
||||
@@ -55,8 +50,8 @@ const requestBackups = async (
|
||||
|
||||
let userHasEmailsMuted = false
|
||||
const emailsMutedSetting = await settingRepository.findOneByNameAndUserUuid(muteEmailsSettingName, user.uuid)
|
||||
if (emailsMutedSetting !== null && emailsMutedSetting.value !== null) {
|
||||
userHasEmailsMuted = emailsMutedSetting.value === muteEmailsSettingValue
|
||||
if (emailsMutedSetting !== null && emailsMutedSetting.props.value !== null) {
|
||||
userHasEmailsMuted = emailsMutedSetting.props.value === muteEmailsSettingValue
|
||||
}
|
||||
|
||||
const keyParamsResponse = await getUserKeyParamsUseCase.execute({
|
||||
@@ -67,7 +62,7 @@ const requestBackups = async (
|
||||
await domainEventPublisher.publish(
|
||||
domainEventFactory.createEmailBackupRequestedEvent(
|
||||
user.uuid,
|
||||
emailsMutedSetting?.uuid as string,
|
||||
emailsMutedSetting?.id.toString() as string,
|
||||
userHasEmailsMuted,
|
||||
keyParamsResponse.keyParams,
|
||||
),
|
||||
@@ -94,9 +89,6 @@ void container.load().then((container) => {
|
||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.Auth_DomainEventPublisher)
|
||||
const getUserKeyParamsUseCase: GetUserKeyParams = container.get(TYPES.Auth_GetUserKeyParams)
|
||||
|
||||
const tracer = new OpenTelemetryTracer()
|
||||
tracer.startSpan(ServiceIdentifier.NAMES.AuthScheduledTask, 'user_email_backup')
|
||||
|
||||
Promise.resolve(
|
||||
requestBackups(
|
||||
userRepository,
|
||||
@@ -110,15 +102,11 @@ void container.load().then((container) => {
|
||||
.then(() => {
|
||||
logger.info(`Email backup requesting complete for ${backupEmail}`)
|
||||
|
||||
tracer.stopSpan()
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`Could not finish email backup requesting for ${backupEmail}: ${error.message}`)
|
||||
|
||||
tracer.stopSpanWithError(error)
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
|
||||
import { ServiceIdentifier } from '@standardnotes/domain-core'
|
||||
|
||||
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AuthWorker })
|
||||
sdk.start()
|
||||
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Redis, { Cluster } from 'ioredis'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
import { Setting } from '../../src/Domain/Setting/Setting'
|
||||
import { User } from '../../src/Domain/User/User'
|
||||
import { EncryptionVersion } from '../../src/Domain/Encryption/EncryptionVersion'
|
||||
import { SettingName, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
export class moveMfaItemsToUserSettings1627638504691 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
@@ -32,19 +32,21 @@ export class moveMfaItemsToUserSettings1627638504691 implements MigrationInterfa
|
||||
usersMFAStatus.set(item['user_uuid'], 1)
|
||||
usersMFAUpdatedAt.set(item['user_uuid'], item['updated_at_timestamp'])
|
||||
|
||||
const setting = new Setting()
|
||||
setting.uuid = item['uuid']
|
||||
setting.name = SettingName.NAMES.MfaSecret
|
||||
setting.value = item['content']
|
||||
const settingOrError = Setting.create(
|
||||
{
|
||||
name: SettingName.NAMES.MfaSecret,
|
||||
value: item['deleted'] ? null : item['content'],
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
timestamps: Timestamps.create(item['created_at_timestamp'], item['updated_at_timestamp']).getValue(),
|
||||
userUuid: Uuid.create(user.uuid).getValue(),
|
||||
sensitive: true,
|
||||
},
|
||||
new UniqueEntityId(item['uuid']),
|
||||
)
|
||||
if (item['deleted']) {
|
||||
setting.value = null
|
||||
usersMFAStatus.set(item['user_uuid'], 0)
|
||||
}
|
||||
setting.serverEncryptionVersion = EncryptionVersion.Unencrypted
|
||||
setting.createdAt = item['created_at_timestamp']
|
||||
setting.updatedAt = item['updated_at_timestamp']
|
||||
setting.user = Promise.resolve(user)
|
||||
await queryRunner.manager.save(setting)
|
||||
await queryRunner.manager.save(settingOrError.getValue())
|
||||
}
|
||||
|
||||
const redisClient = this.getRedisClient()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.160.0",
|
||||
"version": "1.165.4",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -54,7 +54,6 @@
|
||||
"@standardnotes/sncrypto-common": "^1.13.4",
|
||||
"@standardnotes/sncrypto-node": "workspace:*",
|
||||
"@standardnotes/time": "workspace:*",
|
||||
"axios": "^1.1.3",
|
||||
"bcryptjs": "2.4.3",
|
||||
"cors": "2.8.5",
|
||||
"dayjs": "^1.11.6",
|
||||
|
||||
@@ -3,7 +3,9 @@ import { Result, ServiceInterface } from '@standardnotes/domain-core'
|
||||
export interface AuthServiceInterface extends ServiceInterface {
|
||||
activatePremiumFeatures(dto: {
|
||||
username: string
|
||||
subscriptionId: number
|
||||
subscriptionPlanName?: string
|
||||
endsAt?: Date
|
||||
cancelPreviousSubscription?: boolean
|
||||
}): Promise<Result<string>>
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
DomainEventSubscriberInterface,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { TimerInterface, Timer } from '@standardnotes/time'
|
||||
import { UAParser } from 'ua-parser-js'
|
||||
import { UAParser, UAParserInstance } from 'ua-parser-js'
|
||||
|
||||
import { Env } from './Env'
|
||||
import TYPES from './Types'
|
||||
@@ -45,7 +45,6 @@ import { LockRepository } from '../Infra/Redis/LockRepository'
|
||||
import { TypeORMRevokedSessionRepository } from '../Infra/TypeORM/TypeORMRevokedSessionRepository'
|
||||
import { AuthenticationMethodResolver } from '../Domain/Auth/AuthenticationMethodResolver'
|
||||
import { RevokedSession } from '../Domain/Session/RevokedSession'
|
||||
import { UserRegisteredEventHandler } from '../Domain/Handler/UserRegisteredEventHandler'
|
||||
import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
|
||||
import { AuthenticateRequest } from '../Domain/UseCase/AuthenticateRequest'
|
||||
import { Role } from '../Domain/Role/Role'
|
||||
@@ -57,10 +56,7 @@ import { TypeORMSettingRepository } from '../Infra/TypeORM/TypeORMSettingReposit
|
||||
import { CrypterInterface } from '../Domain/Encryption/CrypterInterface'
|
||||
import { CrypterNode } from '../Domain/Encryption/CrypterNode'
|
||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||
import { GetSettings } from '../Domain/UseCase/GetSettings/GetSettings'
|
||||
import { SettingProjector } from '../Projection/SettingProjector'
|
||||
import { GetSetting } from '../Domain/UseCase/GetSetting/GetSetting'
|
||||
import { UpdateSetting } from '../Domain/UseCase/UpdateSetting/UpdateSetting'
|
||||
import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountDeletionRequestedEventHandler'
|
||||
import { SubscriptionPurchasedEventHandler } from '../Domain/Handler/SubscriptionPurchasedEventHandler'
|
||||
import { SubscriptionRenewedEventHandler } from '../Domain/Handler/SubscriptionRenewedEventHandler'
|
||||
@@ -68,11 +64,6 @@ import { SubscriptionRefundedEventHandler } from '../Domain/Handler/Subscription
|
||||
import { SubscriptionExpiredEventHandler } from '../Domain/Handler/SubscriptionExpiredEventHandler'
|
||||
import { DeleteAccount } from '../Domain/UseCase/DeleteAccount/DeleteAccount'
|
||||
import { DeleteSetting } from '../Domain/UseCase/DeleteSetting/DeleteSetting'
|
||||
import { SettingFactory } from '../Domain/Setting/SettingFactory'
|
||||
import { SettingService } from '../Domain/Setting/SettingService'
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const axios = require('axios')
|
||||
import { AxiosInstance } from 'axios'
|
||||
import { UserSubscription } from '../Domain/Subscription/UserSubscription'
|
||||
import { TypeORMUserSubscriptionRepository } from '../Infra/TypeORM/TypeORMUserSubscriptionRepository'
|
||||
import { WebSocketsClientService } from '../Infra/WebSockets/WebSocketsClientService'
|
||||
@@ -84,14 +75,13 @@ import { RoleToSubscriptionMapInterface } from '../Domain/Role/RoleToSubscriptio
|
||||
import { RoleToSubscriptionMap } from '../Domain/Role/RoleToSubscriptionMap'
|
||||
import { FeatureServiceInterface } from '../Domain/Feature/FeatureServiceInterface'
|
||||
import { FeatureService } from '../Domain/Feature/FeatureService'
|
||||
import { SettingServiceInterface } from '../Domain/Setting/SettingServiceInterface'
|
||||
import { ExtensionKeyGrantedEventHandler } from '../Domain/Handler/ExtensionKeyGrantedEventHandler'
|
||||
import {
|
||||
DirectCallDomainEventPublisher,
|
||||
DirectCallEventMessageHandler,
|
||||
SNSOpenTelemetryDomainEventPublisher,
|
||||
SNSDomainEventPublisher,
|
||||
SQSDomainEventSubscriber,
|
||||
SQSEventMessageHandler,
|
||||
SQSOpenTelemetryDomainEventSubscriber,
|
||||
} from '@standardnotes/domain-events-infra'
|
||||
import { GetUserSubscription } from '../Domain/UseCase/GetUserSubscription/GetUserSubscription'
|
||||
import { ChangeCredentials } from '../Domain/UseCase/ChangeCredentials/ChangeCredentials'
|
||||
@@ -117,7 +107,6 @@ import { AuthenticateOfflineSubscriptionToken } from '../Domain/UseCase/Authenti
|
||||
import { SubscriptionCancelledEventHandler } from '../Domain/Handler/SubscriptionCancelledEventHandler'
|
||||
import { ContentDecoder, ContentDecoderInterface, ProtocolVersion } from '@standardnotes/common'
|
||||
import { GetUserOfflineSubscription } from '../Domain/UseCase/GetUserOfflineSubscription/GetUserOfflineSubscription'
|
||||
import { UserEmailChangedEventHandler } from '../Domain/Handler/UserEmailChangedEventHandler'
|
||||
import { SettingsAssociationServiceInterface } from '../Domain/Setting/SettingsAssociationServiceInterface'
|
||||
import { SettingsAssociationService } from '../Domain/Setting/SettingsAssociationService'
|
||||
import { SubscriptionSyncRequestedEventHandler } from '../Domain/Handler/SubscriptionSyncRequestedEventHandler'
|
||||
@@ -143,8 +132,8 @@ import { FileRemovedEventHandler } from '../Domain/Handler/FileRemovedEventHandl
|
||||
import { UserDisabledSessionUserAgentLoggingEventHandler } from '../Domain/Handler/UserDisabledSessionUserAgentLoggingEventHandler'
|
||||
import { SettingInterpreterInterface } from '../Domain/Setting/SettingInterpreterInterface'
|
||||
import { SettingInterpreter } from '../Domain/Setting/SettingInterpreter'
|
||||
import { SettingDecrypterInterface } from '../Domain/Setting/SettingDecrypterInterface'
|
||||
import { SettingDecrypter } from '../Domain/Setting/SettingDecrypter'
|
||||
import { SettingCrypterInterface } from '../Domain/Setting/SettingCrypterInterface'
|
||||
import { SettingCrypter } from '../Domain/Setting/SettingCrypter'
|
||||
import { SharedSubscriptionInvitationRepositoryInterface } from '../Domain/SharedSubscription/SharedSubscriptionInvitationRepositoryInterface'
|
||||
import { TypeORMSharedSubscriptionInvitationRepository } from '../Infra/TypeORM/TypeORMSharedSubscriptionInvitationRepository'
|
||||
import { InviteToSharedSubscription } from '../Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription'
|
||||
@@ -153,16 +142,9 @@ import { AcceptSharedSubscriptionInvitation } from '../Domain/UseCase/AcceptShar
|
||||
import { DeclineSharedSubscriptionInvitation } from '../Domain/UseCase/DeclineSharedSubscriptionInvitation/DeclineSharedSubscriptionInvitation'
|
||||
import { CancelSharedSubscriptionInvitation } from '../Domain/UseCase/CancelSharedSubscriptionInvitation/CancelSharedSubscriptionInvitation'
|
||||
import { SharedSubscriptionInvitationCreatedEventHandler } from '../Domain/Handler/SharedSubscriptionInvitationCreatedEventHandler'
|
||||
import { SubscriptionSetting } from '../Domain/Setting/SubscriptionSetting'
|
||||
import { SubscriptionSettingServiceInterface } from '../Domain/Setting/SubscriptionSettingServiceInterface'
|
||||
import { SubscriptionSettingService } from '../Domain/Setting/SubscriptionSettingService'
|
||||
import { SubscriptionSettingRepositoryInterface } from '../Domain/Setting/SubscriptionSettingRepositoryInterface'
|
||||
import { TypeORMSubscriptionSettingRepository } from '../Infra/TypeORM/TypeORMSubscriptionSettingRepository'
|
||||
import { SettingFactoryInterface } from '../Domain/Setting/SettingFactoryInterface'
|
||||
import { ListSharedSubscriptionInvitations } from '../Domain/UseCase/ListSharedSubscriptionInvitations/ListSharedSubscriptionInvitations'
|
||||
import { UserSubscriptionServiceInterface } from '../Domain/Subscription/UserSubscriptionServiceInterface'
|
||||
import { UserSubscriptionService } from '../Domain/Subscription/UserSubscriptionService'
|
||||
import { SubscriptionSettingProjector } from '../Projection/SubscriptionSettingProjector'
|
||||
import { SubscriptionSettingsAssociationService } from '../Domain/Setting/SubscriptionSettingsAssociationService'
|
||||
import { SubscriptionSettingsAssociationServiceInterface } from '../Domain/Setting/SubscriptionSettingsAssociationServiceInterface'
|
||||
import { PKCERepositoryInterface } from '../Domain/User/PKCERepositoryInterface'
|
||||
@@ -188,7 +170,6 @@ import {
|
||||
ControllerContainer,
|
||||
ControllerContainerInterface,
|
||||
MapperInterface,
|
||||
ServiceIdentifier,
|
||||
SharedVaultUser,
|
||||
} from '@standardnotes/domain-core'
|
||||
import { SessionTracePersistenceMapper } from '../Mapping/SessionTracePersistenceMapper'
|
||||
@@ -271,6 +252,29 @@ import { UserDesignatedAsSurvivorInSharedVaultEventHandler } from '../Domain/Han
|
||||
import { DisableEmailSettingBasedOnEmailSubscription } from '../Domain/UseCase/DisableEmailSettingBasedOnEmailSubscription/DisableEmailSettingBasedOnEmailSubscription'
|
||||
import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
|
||||
import { KeyParamsFactoryInterface } from '../Domain/User/KeyParamsFactoryInterface'
|
||||
import { TypeORMSubscriptionSetting } from '../Infra/TypeORM/TypeORMSubscriptionSetting'
|
||||
import { SetSettingValue } from '../Domain/UseCase/SetSettingValue/SetSettingValue'
|
||||
import { ApplyDefaultSubscriptionSettings } from '../Domain/UseCase/ApplyDefaultSubscriptionSettings/ApplyDefaultSubscriptionSettings'
|
||||
import { GetSubscriptionSetting } from '../Domain/UseCase/GetSubscriptionSetting/GetSubscriptionSetting'
|
||||
import { SetSubscriptionSettingValue } from '../Domain/UseCase/SetSubscriptionSettingValue/SetSubscriptionSettingValue'
|
||||
import { GetSettings } from '../Domain/UseCase/GetSettings/GetSettings'
|
||||
import { GetSubscriptionSettings } from '../Domain/UseCase/GetSubscriptionSettings/GetSubscriptionSettings'
|
||||
import { GetAllSettingsForUser } from '../Domain/UseCase/GetAllSettingsForUser/GetAllSettingsForUser'
|
||||
import { GetRegularSubscriptionForUser } from '../Domain/UseCase/GetRegularSubscriptionForUser/GetRegularSubscriptionForUser'
|
||||
import { GetSharedSubscriptionForUser } from '../Domain/UseCase/GetSharedSubscriptionForUser/GetSharedSubscriptionForUser'
|
||||
import { GetSharedOrRegularSubscriptionForUser } from '../Domain/UseCase/GetSharedOrRegularSubscriptionForUser/GetSharedOrRegularSubscriptionForUser'
|
||||
import { ProjectorInterface } from '../Projection/ProjectorInterface'
|
||||
import { SettingHttpRepresentation } from '../Mapping/Http/SettingHttpRepresentation'
|
||||
import { SubscriptionSetting } from '../Domain/Setting/SubscriptionSetting'
|
||||
import { SubscriptionSettingHttpRepresentation } from '../Mapping/Http/SubscriptionSettingHttpRepresentation'
|
||||
import { SettingHttpMapper } from '../Mapping/Http/SettingHttpMapper'
|
||||
import { SubscriptionSettingHttpMapper } from '../Mapping/Http/SubscriptionSettingHttpMapper'
|
||||
import { TypeORMSetting } from '../Infra/TypeORM/TypeORMSetting'
|
||||
import { SettingPersistenceMapper } from '../Mapping/Persistence/SettingPersistenceMapper'
|
||||
import { SubscriptionSettingPersistenceMapper } from '../Mapping/Persistence/SubscriptionSettingPersistenceMapper'
|
||||
import { ApplyDefaultSettings } from '../Domain/UseCase/ApplyDefaultSettings/ApplyDefaultSettings'
|
||||
import { AuthResponseFactoryResolverInterface } from '../Domain/Auth/AuthResponseFactoryResolverInterface'
|
||||
import { UserInvitedToSharedVaultEventHandler } from '../Domain/Handler/UserInvitedToSharedVaultEventHandler'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
constructor(private mode: 'server' | 'worker' = 'server') {}
|
||||
@@ -374,10 +378,7 @@ export class ContainerConfigLoader {
|
||||
.toConstantValue(
|
||||
isConfiguredForHomeServer
|
||||
? directCallDomainEventPublisher
|
||||
: new SNSOpenTelemetryDomainEventPublisher(
|
||||
container.get(TYPES.Auth_SNS),
|
||||
container.get(TYPES.Auth_SNS_TOPIC_ARN),
|
||||
),
|
||||
: new SNSDomainEventPublisher(container.get(TYPES.Auth_SNS), container.get(TYPES.Auth_SNS_TOPIC_ARN)),
|
||||
)
|
||||
|
||||
// Mapping
|
||||
@@ -401,6 +402,22 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<MapperInterface<SharedVaultUser, TypeORMSharedVaultUser>>(TYPES.Auth_SharedVaultUserPersistenceMapper)
|
||||
.toConstantValue(new SharedVaultUserPersistenceMapper())
|
||||
container
|
||||
.bind<MapperInterface<Setting, SettingHttpRepresentation>>(TYPES.Auth_SettingHttpMapper)
|
||||
.toConstantValue(new SettingHttpMapper())
|
||||
container
|
||||
.bind<MapperInterface<SubscriptionSetting, SubscriptionSettingHttpRepresentation>>(
|
||||
TYPES.Auth_SubscriptionSettingHttpMapper,
|
||||
)
|
||||
.toConstantValue(new SubscriptionSettingHttpMapper())
|
||||
container
|
||||
.bind<MapperInterface<Setting, TypeORMSetting>>(TYPES.Auth_SettingPersistenceMapper)
|
||||
.toConstantValue(new SettingPersistenceMapper())
|
||||
container
|
||||
.bind<MapperInterface<SubscriptionSetting, TypeORMSubscriptionSetting>>(
|
||||
TYPES.Auth_SubscriptionSettingPersistenceMapper,
|
||||
)
|
||||
.toConstantValue(new SubscriptionSettingPersistenceMapper())
|
||||
|
||||
// ORM
|
||||
container
|
||||
@@ -417,14 +434,14 @@ export class ContainerConfigLoader {
|
||||
.bind<Repository<Session>>(TYPES.Auth_ORMSessionRepository)
|
||||
.toConstantValue(appDataSource.getRepository(Session))
|
||||
container
|
||||
.bind<Repository<Setting>>(TYPES.Auth_ORMSettingRepository)
|
||||
.toConstantValue(appDataSource.getRepository(Setting))
|
||||
.bind<Repository<TypeORMSetting>>(TYPES.Auth_ORMSettingRepository)
|
||||
.toConstantValue(appDataSource.getRepository(TypeORMSetting))
|
||||
container
|
||||
.bind<Repository<SharedSubscriptionInvitation>>(TYPES.Auth_ORMSharedSubscriptionInvitationRepository)
|
||||
.toConstantValue(appDataSource.getRepository(SharedSubscriptionInvitation))
|
||||
container
|
||||
.bind<Repository<SubscriptionSetting>>(TYPES.Auth_ORMSubscriptionSettingRepository)
|
||||
.toConstantValue(appDataSource.getRepository(SubscriptionSetting))
|
||||
.bind<Repository<TypeORMSubscriptionSetting>>(TYPES.Auth_ORMSubscriptionSettingRepository)
|
||||
.toConstantValue(appDataSource.getRepository(TypeORMSubscriptionSetting))
|
||||
container.bind<Repository<User>>(TYPES.Auth_ORMUserRepository).toConstantValue(appDataSource.getRepository(User))
|
||||
container
|
||||
.bind<Repository<UserSubscription>>(TYPES.Auth_ORMUserSubscriptionRepository)
|
||||
@@ -451,10 +468,24 @@ export class ContainerConfigLoader {
|
||||
.bind<RevokedSessionRepositoryInterface>(TYPES.Auth_RevokedSessionRepository)
|
||||
.to(TypeORMRevokedSessionRepository)
|
||||
container.bind<UserRepositoryInterface>(TYPES.Auth_UserRepository).to(TypeORMUserRepository)
|
||||
container.bind<SettingRepositoryInterface>(TYPES.Auth_SettingRepository).to(TypeORMSettingRepository)
|
||||
container
|
||||
.bind<SettingRepositoryInterface>(TYPES.Auth_SettingRepository)
|
||||
.toConstantValue(
|
||||
new TypeORMSettingRepository(
|
||||
container.get<Repository<TypeORMSetting>>(TYPES.Auth_ORMSettingRepository),
|
||||
container.get<MapperInterface<Setting, TypeORMSetting>>(TYPES.Auth_SettingPersistenceMapper),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SubscriptionSettingRepositoryInterface>(TYPES.Auth_SubscriptionSettingRepository)
|
||||
.to(TypeORMSubscriptionSettingRepository)
|
||||
.toConstantValue(
|
||||
new TypeORMSubscriptionSettingRepository(
|
||||
container.get<Repository<TypeORMSubscriptionSetting>>(TYPES.Auth_ORMSubscriptionSettingRepository),
|
||||
container.get<MapperInterface<SubscriptionSetting, TypeORMSubscriptionSetting>>(
|
||||
TYPES.Auth_SubscriptionSettingPersistenceMapper,
|
||||
),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<OfflineSettingRepositoryInterface>(TYPES.Auth_OfflineSettingRepository)
|
||||
.to(TypeORMOfflineSettingRepository)
|
||||
@@ -517,13 +548,6 @@ export class ContainerConfigLoader {
|
||||
container.bind<UserProjector>(TYPES.Auth_UserProjector).to(UserProjector)
|
||||
container.bind<RoleProjector>(TYPES.Auth_RoleProjector).to(RoleProjector)
|
||||
container.bind<PermissionProjector>(TYPES.Auth_PermissionProjector).to(PermissionProjector)
|
||||
container.bind<SettingProjector>(TYPES.Auth_SettingProjector).to(SettingProjector)
|
||||
container
|
||||
.bind<SubscriptionSettingProjector>(TYPES.Auth_SubscriptionSettingProjector)
|
||||
.to(SubscriptionSettingProjector)
|
||||
|
||||
// Factories
|
||||
container.bind<SettingFactoryInterface>(TYPES.Auth_SettingFactory).to(SettingFactory)
|
||||
|
||||
// env vars
|
||||
container.bind(TYPES.Auth_JWT_SECRET).toConstantValue(env.get('JWT_SECRET'))
|
||||
@@ -562,16 +586,7 @@ export class ContainerConfigLoader {
|
||||
.toConstantValue(env.get('DISABLE_USER_REGISTRATION', true) === 'true')
|
||||
container.bind(TYPES.Auth_SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true))
|
||||
container.bind(TYPES.Auth_SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true))
|
||||
container
|
||||
.bind(TYPES.Auth_USER_SERVER_REGISTRATION_URL)
|
||||
.toConstantValue(env.get('USER_SERVER_REGISTRATION_URL', true))
|
||||
container.bind(TYPES.Auth_USER_SERVER_AUTH_KEY).toConstantValue(env.get('USER_SERVER_AUTH_KEY', true))
|
||||
container
|
||||
.bind(TYPES.Auth_USER_SERVER_CHANGE_EMAIL_URL)
|
||||
.toConstantValue(env.get('USER_SERVER_CHANGE_EMAIL_URL', true))
|
||||
container.bind(TYPES.Auth_SYNCING_SERVER_URL).toConstantValue(env.get('SYNCING_SERVER_URL', true))
|
||||
container.bind(TYPES.Auth_VERSION).toConstantValue(env.get('VERSION', true) ?? 'development')
|
||||
container.bind(TYPES.Auth_PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
|
||||
container
|
||||
.bind(TYPES.Auth_SESSION_TRACE_DAYS_TTL)
|
||||
.toConstantValue(env.get('SESSION_TRACE_DAYS_TTL', true) ? +env.get('SESSION_TRACE_DAYS_TTL', true) : 90)
|
||||
@@ -654,16 +669,61 @@ export class ContainerConfigLoader {
|
||||
.to(RedisSubscriptionTokenRepository)
|
||||
}
|
||||
|
||||
// Services
|
||||
container
|
||||
.bind<TraceSession>(TYPES.Auth_TraceSession)
|
||||
.toConstantValue(
|
||||
new TraceSession(
|
||||
container.get(TYPES.Auth_SessionTraceRepository),
|
||||
container.get(TYPES.Auth_Timer),
|
||||
container.get(TYPES.Auth_SESSION_TRACE_DAYS_TTL),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SelectorInterface<ProtocolVersion>>(TYPES.Auth_ProtocolVersionSelector)
|
||||
.toConstantValue(new DeterministicSelector<ProtocolVersion>())
|
||||
container.bind<UAParser>(TYPES.Auth_DeviceDetector).toConstantValue(new UAParser())
|
||||
container.bind<SessionService>(TYPES.Auth_SessionService).to(SessionService)
|
||||
container.bind<UAParserInstance>(TYPES.Auth_DeviceDetector).toConstantValue(new UAParser())
|
||||
container.bind<CrypterInterface>(TYPES.Auth_Crypter).to(CrypterNode)
|
||||
container
|
||||
.bind<SettingCrypterInterface>(TYPES.Auth_SettingCrypter)
|
||||
.toConstantValue(
|
||||
new SettingCrypter(
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<CrypterInterface>(TYPES.Auth_Crypter),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GetSetting>(TYPES.Auth_GetSetting)
|
||||
.toConstantValue(
|
||||
new GetSetting(
|
||||
container.get<SettingRepositoryInterface>(TYPES.Auth_SettingRepository),
|
||||
container.get<SettingCrypterInterface>(TYPES.Auth_SettingCrypter),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SessionService>(TYPES.Auth_SessionService)
|
||||
.toConstantValue(
|
||||
new SessionService(
|
||||
container.get<SessionRepositoryInterface>(TYPES.Auth_SessionRepository),
|
||||
container.get<EphemeralSessionRepositoryInterface>(TYPES.Auth_EphemeralSessionRepository),
|
||||
container.get<RevokedSessionRepositoryInterface>(TYPES.Auth_RevokedSessionRepository),
|
||||
container.get<UAParserInstance>(TYPES.Auth_DeviceDetector),
|
||||
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
container.get<number>(TYPES.Auth_ACCESS_TOKEN_AGE),
|
||||
container.get<number>(TYPES.Auth_REFRESH_TOKEN_AGE),
|
||||
container.get<CryptoNode>(TYPES.Auth_CryptoNode),
|
||||
container.get<TraceSession>(TYPES.Auth_TraceSession),
|
||||
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
|
||||
container.get<string[]>(TYPES.Auth_READONLY_USERS),
|
||||
container.get<GetSetting>(TYPES.Auth_GetSetting),
|
||||
),
|
||||
)
|
||||
container.bind<AuthResponseFactory20161215>(TYPES.Auth_AuthResponseFactory20161215).to(AuthResponseFactory20161215)
|
||||
container.bind<AuthResponseFactory20190520>(TYPES.Auth_AuthResponseFactory20190520).to(AuthResponseFactory20190520)
|
||||
container.bind<AuthResponseFactory20200115>(TYPES.Auth_AuthResponseFactory20200115).to(AuthResponseFactory20200115)
|
||||
container.bind<AuthResponseFactoryResolver>(TYPES.Auth_AuthResponseFactoryResolver).to(AuthResponseFactoryResolver)
|
||||
container
|
||||
.bind<AuthResponseFactoryResolverInterface>(TYPES.Auth_AuthResponseFactoryResolver)
|
||||
.to(AuthResponseFactoryResolver)
|
||||
container.bind<KeyParamsFactory>(TYPES.Auth_KeyParamsFactory).to(KeyParamsFactory)
|
||||
container
|
||||
.bind<TokenDecoderInterface<SessionTokenData>>(TYPES.Auth_SessionTokenDecoder)
|
||||
@@ -698,12 +758,9 @@ export class ContainerConfigLoader {
|
||||
.bind<AuthenticationMethodResolver>(TYPES.Auth_AuthenticationMethodResolver)
|
||||
.to(AuthenticationMethodResolver)
|
||||
container.bind<DomainEventFactory>(TYPES.Auth_DomainEventFactory).to(DomainEventFactory)
|
||||
container.bind<AxiosInstance>(TYPES.Auth_HTTPClient).toConstantValue(axios.create())
|
||||
container.bind<CrypterInterface>(TYPES.Auth_Crypter).to(CrypterNode)
|
||||
container
|
||||
.bind<SettingsAssociationServiceInterface>(TYPES.Auth_SettingsAssociationService)
|
||||
.to(SettingsAssociationService)
|
||||
container.bind<SettingDecrypterInterface>(TYPES.Auth_SettingDecrypter).to(SettingDecrypter)
|
||||
|
||||
container
|
||||
.bind<GetUserKeyParams>(TYPES.Auth_GetUserKeyParams)
|
||||
@@ -726,21 +783,6 @@ export class ContainerConfigLoader {
|
||||
),
|
||||
)
|
||||
|
||||
container
|
||||
.bind<SettingServiceInterface>(TYPES.Auth_SettingService)
|
||||
.toConstantValue(
|
||||
new SettingService(
|
||||
container.get<SettingFactoryInterface>(TYPES.Auth_SettingFactory),
|
||||
container.get<SettingRepositoryInterface>(TYPES.Auth_SettingRepository),
|
||||
container.get<SettingsAssociationServiceInterface>(TYPES.Auth_SettingsAssociationService),
|
||||
container.get<SettingInterpreterInterface>(TYPES.Auth_SettingInterpreter),
|
||||
container.get<SettingDecrypterInterface>(TYPES.Auth_SettingDecrypter),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SubscriptionSettingServiceInterface>(TYPES.Auth_SubscriptionSettingService)
|
||||
.to(SubscriptionSettingService)
|
||||
container.bind<OfflineSettingServiceInterface>(TYPES.Auth_OfflineSettingService).to(OfflineSettingService)
|
||||
container.bind<ContentDecoderInterface>(TYPES.Auth_ContenDecoder).toConstantValue(new ContentDecoder())
|
||||
container.bind<ClientServiceInterface>(TYPES.Auth_WebSocketsClientService).to(WebSocketsClientService)
|
||||
@@ -749,11 +791,19 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<SubscriptionSettingsAssociationServiceInterface>(TYPES.Auth_SubscriptionSettingsAssociationService)
|
||||
.to(SubscriptionSettingsAssociationService)
|
||||
container.bind<FeatureServiceInterface>(TYPES.Auth_FeatureService).to(FeatureService)
|
||||
container
|
||||
.bind<FeatureServiceInterface>(TYPES.Auth_FeatureService)
|
||||
.toConstantValue(
|
||||
new FeatureService(
|
||||
container.get<RoleToSubscriptionMapInterface>(TYPES.Auth_RoleToSubscriptionMap),
|
||||
container.get<OfflineUserSubscriptionRepositoryInterface>(TYPES.Auth_OfflineUserSubscriptionRepository),
|
||||
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SelectorInterface<boolean>>(TYPES.Auth_BooleanSelector)
|
||||
.toConstantValue(new DeterministicSelector<boolean>())
|
||||
container.bind<UserSubscriptionServiceInterface>(TYPES.Auth_UserSubscriptionService).to(UserSubscriptionService)
|
||||
|
||||
// Middleware
|
||||
container.bind<SessionMiddleware>(TYPES.Auth_SessionMiddleware).to(SessionMiddleware)
|
||||
@@ -780,15 +830,6 @@ export class ContainerConfigLoader {
|
||||
container.bind<OfflineUserAuthMiddleware>(TYPES.Auth_OfflineUserAuthMiddleware).to(OfflineUserAuthMiddleware)
|
||||
|
||||
// use cases
|
||||
container
|
||||
.bind<TraceSession>(TYPES.Auth_TraceSession)
|
||||
.toConstantValue(
|
||||
new TraceSession(
|
||||
container.get(TYPES.Auth_SessionTraceRepository),
|
||||
container.get(TYPES.Auth_Timer),
|
||||
container.get(TYPES.Auth_SESSION_TRACE_DAYS_TTL),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<PersistStatistics>(TYPES.Auth_PersistStatistics)
|
||||
.toConstantValue(
|
||||
@@ -863,24 +904,65 @@ export class ContainerConfigLoader {
|
||||
container.get(TYPES.Auth_FeatureService),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SetSettingValue>(TYPES.Auth_SetSettingValue)
|
||||
.toConstantValue(
|
||||
new SetSettingValue(
|
||||
container.get<GetSetting>(TYPES.Auth_GetSetting),
|
||||
container.get<SettingRepositoryInterface>(TYPES.Auth_SettingRepository),
|
||||
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||
container.get<SettingsAssociationServiceInterface>(TYPES.Auth_SettingsAssociationService),
|
||||
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
|
||||
container.get<SettingCrypterInterface>(TYPES.Auth_SettingCrypter),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GenerateRecoveryCodes>(TYPES.Auth_GenerateRecoveryCodes)
|
||||
.toConstantValue(
|
||||
new GenerateRecoveryCodes(
|
||||
container.get(TYPES.Auth_UserRepository),
|
||||
container.get(TYPES.Auth_SettingService),
|
||||
container.get(TYPES.Auth_SetSettingValue),
|
||||
container.get(TYPES.Auth_CryptoNode),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting)
|
||||
.toConstantValue(
|
||||
new GetSubscriptionSetting(
|
||||
container.get<SubscriptionSettingRepositoryInterface>(TYPES.Auth_SubscriptionSettingRepository),
|
||||
container.get<SettingCrypterInterface>(TYPES.Auth_SettingCrypter),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SetSubscriptionSettingValue>(TYPES.Auth_SetSubscriptionSettingValue)
|
||||
.toConstantValue(
|
||||
new SetSubscriptionSettingValue(
|
||||
container.get<SubscriptionSettingRepositoryInterface>(TYPES.Auth_SubscriptionSettingRepository),
|
||||
container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting),
|
||||
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<ApplyDefaultSubscriptionSettings>(TYPES.Auth_ApplyDefaultSubscriptionSettings)
|
||||
.toConstantValue(
|
||||
new ApplyDefaultSubscriptionSettings(
|
||||
container.get<SubscriptionSettingsAssociationServiceInterface>(
|
||||
TYPES.Auth_SubscriptionSettingsAssociationService,
|
||||
),
|
||||
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
|
||||
container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting),
|
||||
container.get<SetSubscriptionSettingValue>(TYPES.Auth_SetSubscriptionSettingValue),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<ActivatePremiumFeatures>(TYPES.Auth_ActivatePremiumFeatures)
|
||||
.toConstantValue(
|
||||
new ActivatePremiumFeatures(
|
||||
container.get(TYPES.Auth_UserRepository),
|
||||
container.get(TYPES.Auth_UserSubscriptionRepository),
|
||||
container.get(TYPES.Auth_SubscriptionSettingService),
|
||||
container.get(TYPES.Auth_RoleService),
|
||||
container.get(TYPES.Auth_Timer),
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
|
||||
container.get<ApplyDefaultSubscriptionSettings>(TYPES.Auth_ApplyDefaultSubscriptionSettings),
|
||||
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
|
||||
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -894,47 +976,149 @@ export class ContainerConfigLoader {
|
||||
container.bind<AuthenticateRequest>(TYPES.Auth_AuthenticateRequest).to(AuthenticateRequest)
|
||||
container.bind<RefreshSessionToken>(TYPES.Auth_RefreshSessionToken).to(RefreshSessionToken)
|
||||
container.bind<SignIn>(TYPES.Auth_SignIn).to(SignIn)
|
||||
container.bind<VerifyMFA>(TYPES.Auth_VerifyMFA).to(VerifyMFA)
|
||||
container
|
||||
.bind<VerifyMFA>(TYPES.Auth_VerifyMFA)
|
||||
.toConstantValue(
|
||||
new VerifyMFA(
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<SelectorInterface<boolean>>(TYPES.Auth_BooleanSelector),
|
||||
container.get<LockRepositoryInterface>(TYPES.Auth_LockRepository),
|
||||
container.get<string>(TYPES.Auth_PSEUDO_KEY_PARAMS_KEY),
|
||||
container.get<AuthenticatorRepositoryInterface>(TYPES.Auth_AuthenticatorRepository),
|
||||
container.get<VerifyAuthenticatorAuthenticationResponse>(
|
||||
TYPES.Auth_VerifyAuthenticatorAuthenticationResponse,
|
||||
),
|
||||
container.get<GetSetting>(TYPES.Auth_GetSetting),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container.bind<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts).to(ClearLoginAttempts)
|
||||
container.bind<IncreaseLoginAttempts>(TYPES.Auth_IncreaseLoginAttempts).to(IncreaseLoginAttempts)
|
||||
container
|
||||
.bind<GetUserKeyParamsRecovery>(TYPES.Auth_GetUserKeyParamsRecovery)
|
||||
.toConstantValue(
|
||||
new GetUserKeyParamsRecovery(
|
||||
container.get(TYPES.Auth_KeyParamsFactory),
|
||||
container.get(TYPES.Auth_UserRepository),
|
||||
container.get(TYPES.Auth_PKCERepository),
|
||||
container.get(TYPES.Auth_SettingService),
|
||||
container.get<KeyParamsFactoryInterface>(TYPES.Auth_KeyParamsFactory),
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<PKCERepositoryInterface>(TYPES.Auth_PKCERepository),
|
||||
container.get<GetSetting>(TYPES.Auth_GetSetting),
|
||||
),
|
||||
)
|
||||
container.bind<UpdateUser>(TYPES.Auth_UpdateUser).to(UpdateUser)
|
||||
container.bind<Register>(TYPES.Auth_Register).to(Register)
|
||||
container
|
||||
.bind<ApplyDefaultSettings>(TYPES.Auth_ApplyDefaultSettings)
|
||||
.toConstantValue(
|
||||
new ApplyDefaultSettings(
|
||||
container.get<SettingsAssociationServiceInterface>(TYPES.Auth_SettingsAssociationService),
|
||||
container.get<SetSettingValue>(TYPES.Auth_SetSettingValue),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<Register>(TYPES.Auth_Register)
|
||||
.toConstantValue(
|
||||
new Register(
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<RoleRepositoryInterface>(TYPES.Auth_RoleRepository),
|
||||
container.get<AuthResponseFactory20200115>(TYPES.Auth_AuthResponseFactory20200115),
|
||||
container.get<CrypterInterface>(TYPES.Auth_Crypter),
|
||||
container.get<boolean>(TYPES.Auth_DISABLE_USER_REGISTRATION),
|
||||
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||
container.get<ApplyDefaultSettings>(TYPES.Auth_ApplyDefaultSettings),
|
||||
),
|
||||
)
|
||||
container.bind<GetActiveSessionsForUser>(TYPES.Auth_GetActiveSessionsForUser).to(GetActiveSessionsForUser)
|
||||
container.bind<DeleteOtherSessionsForUser>(TYPES.Auth_DeleteOtherSessionsForUser).to(DeleteOtherSessionsForUser)
|
||||
container.bind<DeleteSessionForUser>(TYPES.Auth_DeleteSessionForUser).to(DeleteSessionForUser)
|
||||
container.bind<ChangeCredentials>(TYPES.Auth_ChangeCredentials).to(ChangeCredentials)
|
||||
container.bind<GetSettings>(TYPES.Auth_GetSettings).to(GetSettings)
|
||||
container.bind<GetSetting>(TYPES.Auth_GetSetting).to(GetSetting)
|
||||
container
|
||||
.bind<ChangeCredentials>(TYPES.Auth_ChangeCredentials)
|
||||
.toConstantValue(
|
||||
new ChangeCredentials(
|
||||
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<TimerInterface>(TYPES.Auth_Timer),
|
||||
container.get<DeleteOtherSessionsForUser>(TYPES.Auth_DeleteOtherSessionsForUser),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GetSettings>(TYPES.Auth_GetSettings)
|
||||
.toConstantValue(
|
||||
new GetSettings(
|
||||
container.get<SettingRepositoryInterface>(TYPES.Auth_SettingRepository),
|
||||
container.get<SettingCrypterInterface>(TYPES.Auth_SettingCrypter),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GetSubscriptionSettings>(TYPES.Auth_GetSubscriptionSettings)
|
||||
.toConstantValue(
|
||||
new GetSubscriptionSettings(
|
||||
container.get<SubscriptionSettingRepositoryInterface>(TYPES.Auth_SubscriptionSettingRepository),
|
||||
container.get<SettingCrypterInterface>(TYPES.Auth_SettingCrypter),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GetRegularSubscriptionForUser>(TYPES.Auth_GetRegularSubscriptionForUser)
|
||||
.toConstantValue(
|
||||
new GetRegularSubscriptionForUser(
|
||||
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GetSharedSubscriptionForUser>(TYPES.Auth_GetSharedSubscriptionForUser)
|
||||
.toConstantValue(
|
||||
new GetSharedSubscriptionForUser(
|
||||
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GetSharedOrRegularSubscriptionForUser>(TYPES.Auth_GetSharedOrRegularSubscriptionForUser)
|
||||
.toConstantValue(
|
||||
new GetSharedOrRegularSubscriptionForUser(
|
||||
container.get<GetRegularSubscriptionForUser>(TYPES.Auth_GetRegularSubscriptionForUser),
|
||||
container.get<GetSharedSubscriptionForUser>(TYPES.Auth_GetSharedSubscriptionForUser),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GetAllSettingsForUser>(TYPES.Auth_GetAllSettingsForUser)
|
||||
.toConstantValue(
|
||||
new GetAllSettingsForUser(
|
||||
container.get<GetSettings>(TYPES.Auth_GetSettings),
|
||||
container.get<GetSharedOrRegularSubscriptionForUser>(TYPES.Auth_GetSharedOrRegularSubscriptionForUser),
|
||||
container.get<GetSubscriptionSettings>(TYPES.Auth_GetSubscriptionSettings),
|
||||
),
|
||||
)
|
||||
container.bind<GetUserFeatures>(TYPES.Auth_GetUserFeatures).to(GetUserFeatures)
|
||||
container.bind<UpdateSetting>(TYPES.Auth_UpdateSetting).to(UpdateSetting)
|
||||
container.bind<DeleteSetting>(TYPES.Auth_DeleteSetting).to(DeleteSetting)
|
||||
container
|
||||
.bind<SignInWithRecoveryCodes>(TYPES.Auth_SignInWithRecoveryCodes)
|
||||
.toConstantValue(
|
||||
new SignInWithRecoveryCodes(
|
||||
container.get(TYPES.Auth_UserRepository),
|
||||
container.get(TYPES.Auth_AuthResponseFactory20200115),
|
||||
container.get(TYPES.Auth_PKCERepository),
|
||||
container.get(TYPES.Auth_Crypter),
|
||||
container.get(TYPES.Auth_SettingService),
|
||||
container.get(TYPES.Auth_GenerateRecoveryCodes),
|
||||
container.get(TYPES.Auth_IncreaseLoginAttempts),
|
||||
container.get(TYPES.Auth_ClearLoginAttempts),
|
||||
container.get(TYPES.Auth_DeleteSetting),
|
||||
container.get(TYPES.Auth_AuthenticatorRepository),
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<AuthResponseFactory20200115>(TYPES.Auth_AuthResponseFactory20200115),
|
||||
container.get<PKCERepositoryInterface>(TYPES.Auth_PKCERepository),
|
||||
container.get<CrypterInterface>(TYPES.Auth_Crypter),
|
||||
container.get<GetSetting>(TYPES.Auth_GetSetting),
|
||||
container.get<GenerateRecoveryCodes>(TYPES.Auth_GenerateRecoveryCodes),
|
||||
container.get<IncreaseLoginAttempts>(TYPES.Auth_IncreaseLoginAttempts),
|
||||
container.get<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts),
|
||||
container.get<DeleteSetting>(TYPES.Auth_DeleteSetting),
|
||||
container.get<AuthenticatorRepositoryInterface>(TYPES.Auth_AuthenticatorRepository),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<DeleteAccount>(TYPES.Auth_DeleteAccount)
|
||||
.toConstantValue(
|
||||
new DeleteAccount(
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<GetRegularSubscriptionForUser>(TYPES.Auth_GetRegularSubscriptionForUser),
|
||||
container.get<GetSharedSubscriptionForUser>(TYPES.Auth_GetSharedSubscriptionForUser),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
|
||||
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||
),
|
||||
)
|
||||
container.bind<DeleteAccount>(TYPES.Auth_DeleteAccount).to(DeleteAccount)
|
||||
container.bind<GetUserSubscription>(TYPES.Auth_GetUserSubscription).to(GetUserSubscription)
|
||||
container.bind<GetUserOfflineSubscription>(TYPES.Auth_GetUserOfflineSubscription).to(GetUserOfflineSubscription)
|
||||
container.bind<CreateSubscriptionToken>(TYPES.Auth_CreateSubscriptionToken).to(CreateSubscriptionToken)
|
||||
@@ -947,12 +1131,38 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<CreateOfflineSubscriptionToken>(TYPES.Auth_CreateOfflineSubscriptionToken)
|
||||
.to(CreateOfflineSubscriptionToken)
|
||||
container.bind<CreateValetToken>(TYPES.Auth_CreateValetToken).to(CreateValetToken)
|
||||
container
|
||||
.bind<CreateValetToken>(TYPES.Auth_CreateValetToken)
|
||||
.toConstantValue(
|
||||
new CreateValetToken(
|
||||
container.get<TokenEncoderInterface<ValetTokenData>>(TYPES.Auth_ValetTokenEncoder),
|
||||
container.get<SubscriptionSettingsAssociationServiceInterface>(
|
||||
TYPES.Auth_SubscriptionSettingsAssociationService,
|
||||
),
|
||||
container.get<GetRegularSubscriptionForUser>(TYPES.Auth_GetRegularSubscriptionForUser),
|
||||
container.get<GetSharedSubscriptionForUser>(TYPES.Auth_GetSharedSubscriptionForUser),
|
||||
container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting),
|
||||
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||
container.get<number>(TYPES.Auth_VALET_TOKEN_TTL),
|
||||
),
|
||||
)
|
||||
container.bind<CreateListedAccount>(TYPES.Auth_CreateListedAccount).to(CreateListedAccount)
|
||||
container.bind<InviteToSharedSubscription>(TYPES.Auth_InviteToSharedSubscription).to(InviteToSharedSubscription)
|
||||
container
|
||||
.bind<AcceptSharedSubscriptionInvitation>(TYPES.Auth_AcceptSharedSubscriptionInvitation)
|
||||
.to(AcceptSharedSubscriptionInvitation)
|
||||
.toConstantValue(
|
||||
new AcceptSharedSubscriptionInvitation(
|
||||
container.get<SharedSubscriptionInvitationRepositoryInterface>(
|
||||
TYPES.Auth_SharedSubscriptionInvitationRepository,
|
||||
),
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
|
||||
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
|
||||
container.get<ApplyDefaultSubscriptionSettings>(TYPES.Auth_ApplyDefaultSubscriptionSettings),
|
||||
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<DeclineSharedSubscriptionInvitation>(TYPES.Auth_DeclineSharedSubscriptionInvitation)
|
||||
.to(DeclineSharedSubscriptionInvitation)
|
||||
@@ -963,15 +1173,32 @@ export class ContainerConfigLoader {
|
||||
.bind<ListSharedSubscriptionInvitations>(TYPES.Auth_ListSharedSubscriptionInvitations)
|
||||
.to(ListSharedSubscriptionInvitations)
|
||||
container.bind<VerifyPredicate>(TYPES.Auth_VerifyPredicate).to(VerifyPredicate)
|
||||
container.bind<CreateCrossServiceToken>(TYPES.Auth_CreateCrossServiceToken).to(CreateCrossServiceToken)
|
||||
container
|
||||
.bind<CreateCrossServiceToken>(TYPES.Auth_CreateCrossServiceToken)
|
||||
.toConstantValue(
|
||||
new CreateCrossServiceToken(
|
||||
container.get<ProjectorInterface<User>>(TYPES.Auth_UserProjector),
|
||||
container.get<ProjectorInterface<Session>>(TYPES.Auth_SessionProjector),
|
||||
container.get<ProjectorInterface<Role>>(TYPES.Auth_RoleProjector),
|
||||
container.get<TokenEncoderInterface<CrossServiceTokenData>>(TYPES.Auth_CrossServiceTokenEncoder),
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<number>(TYPES.Auth_AUTH_JWT_TTL),
|
||||
container.get<GetRegularSubscriptionForUser>(TYPES.Auth_GetRegularSubscriptionForUser),
|
||||
container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting),
|
||||
container.get<SharedVaultUserRepositoryInterface>(TYPES.Auth_SharedVaultUserRepository),
|
||||
),
|
||||
)
|
||||
container.bind<ProcessUserRequest>(TYPES.Auth_ProcessUserRequest).to(ProcessUserRequest)
|
||||
container
|
||||
.bind<UpdateStorageQuotaUsedForUser>(TYPES.Auth_UpdateStorageQuotaUsedForUser)
|
||||
.toConstantValue(
|
||||
new UpdateStorageQuotaUsedForUser(
|
||||
container.get(TYPES.Auth_UserRepository),
|
||||
container.get(TYPES.Auth_UserSubscriptionService),
|
||||
container.get(TYPES.Auth_SubscriptionSettingService),
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<GetRegularSubscriptionForUser>(TYPES.Auth_GetRegularSubscriptionForUser),
|
||||
container.get<GetSharedSubscriptionForUser>(TYPES.Auth_GetSharedSubscriptionForUser),
|
||||
container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting),
|
||||
container.get<SetSubscriptionSettingValue>(TYPES.Auth_SetSubscriptionSettingValue),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
@@ -999,8 +1226,9 @@ export class ContainerConfigLoader {
|
||||
.toConstantValue(
|
||||
new DisableEmailSettingBasedOnEmailSubscription(
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<SettingRepositoryInterface>(TYPES.Auth_SettingRepository),
|
||||
container.get<SettingFactoryInterface>(TYPES.Auth_SettingFactory),
|
||||
container.get<SetSettingValue>(TYPES.Auth_SetSettingValue),
|
||||
container.get<SetSubscriptionSettingValue>(TYPES.Auth_SetSubscriptionSettingValue),
|
||||
container.get<GetSharedOrRegularSubscriptionForUser>(TYPES.Auth_GetSharedOrRegularSubscriptionForUser),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1041,7 +1269,6 @@ export class ContainerConfigLoader {
|
||||
container.bind<UserRequestsController>(TYPES.Auth_UserRequestsController).to(UserRequestsController)
|
||||
|
||||
// Handlers
|
||||
container.bind<UserRegisteredEventHandler>(TYPES.Auth_UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
||||
container
|
||||
.bind<AccountDeletionRequestedEventHandler>(TYPES.Auth_AccountDeletionRequestedEventHandler)
|
||||
.toConstantValue(
|
||||
@@ -1055,7 +1282,16 @@ export class ContainerConfigLoader {
|
||||
)
|
||||
container
|
||||
.bind<SubscriptionPurchasedEventHandler>(TYPES.Auth_SubscriptionPurchasedEventHandler)
|
||||
.to(SubscriptionPurchasedEventHandler)
|
||||
.toConstantValue(
|
||||
new SubscriptionPurchasedEventHandler(
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
|
||||
container.get<ApplyDefaultSubscriptionSettings>(TYPES.Auth_ApplyDefaultSubscriptionSettings),
|
||||
container.get<OfflineUserSubscriptionRepositoryInterface>(TYPES.Auth_OfflineUserSubscriptionRepository),
|
||||
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SubscriptionCancelledEventHandler>(TYPES.Auth_SubscriptionCancelledEventHandler)
|
||||
.to(SubscriptionCancelledEventHandler)
|
||||
@@ -1070,16 +1306,42 @@ export class ContainerConfigLoader {
|
||||
.to(SubscriptionExpiredEventHandler)
|
||||
container
|
||||
.bind<SubscriptionSyncRequestedEventHandler>(TYPES.Auth_SubscriptionSyncRequestedEventHandler)
|
||||
.to(SubscriptionSyncRequestedEventHandler)
|
||||
.toConstantValue(
|
||||
new SubscriptionSyncRequestedEventHandler(
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
|
||||
container.get<OfflineUserSubscriptionRepositoryInterface>(TYPES.Auth_OfflineUserSubscriptionRepository),
|
||||
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
|
||||
container.get<ApplyDefaultSubscriptionSettings>(TYPES.Auth_ApplyDefaultSubscriptionSettings),
|
||||
container.get<SetSettingValue>(TYPES.Auth_SetSettingValue),
|
||||
container.get<OfflineSettingServiceInterface>(TYPES.Auth_OfflineSettingService),
|
||||
container.get<ContentDecoderInterface>(TYPES.Auth_ContenDecoder),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<ExtensionKeyGrantedEventHandler>(TYPES.Auth_ExtensionKeyGrantedEventHandler)
|
||||
.to(ExtensionKeyGrantedEventHandler)
|
||||
.toConstantValue(
|
||||
new ExtensionKeyGrantedEventHandler(
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<SetSettingValue>(TYPES.Auth_SetSettingValue),
|
||||
container.get<OfflineSettingServiceInterface>(TYPES.Auth_OfflineSettingService),
|
||||
container.get<ContentDecoderInterface>(TYPES.Auth_ContenDecoder),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SubscriptionReassignedEventHandler>(TYPES.Auth_SubscriptionReassignedEventHandler)
|
||||
.to(SubscriptionReassignedEventHandler)
|
||||
container
|
||||
.bind<UserEmailChangedEventHandler>(TYPES.Auth_UserEmailChangedEventHandler)
|
||||
.to(UserEmailChangedEventHandler)
|
||||
.toConstantValue(
|
||||
new SubscriptionReassignedEventHandler(
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
|
||||
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
container.get<ApplyDefaultSubscriptionSettings>(TYPES.Auth_ApplyDefaultSubscriptionSettings),
|
||||
container.get<SetSettingValue>(TYPES.Auth_SetSettingValue),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<FileUploadedEventHandler>(TYPES.Auth_FileUploadedEventHandler)
|
||||
.toConstantValue(
|
||||
@@ -1122,10 +1384,24 @@ export class ContainerConfigLoader {
|
||||
)
|
||||
container
|
||||
.bind<ListedAccountCreatedEventHandler>(TYPES.Auth_ListedAccountCreatedEventHandler)
|
||||
.to(ListedAccountCreatedEventHandler)
|
||||
.toConstantValue(
|
||||
new ListedAccountCreatedEventHandler(
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<GetSetting>(TYPES.Auth_GetSetting),
|
||||
container.get<SetSettingValue>(TYPES.Auth_SetSettingValue),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<ListedAccountDeletedEventHandler>(TYPES.Auth_ListedAccountDeletedEventHandler)
|
||||
.to(ListedAccountDeletedEventHandler)
|
||||
.toConstantValue(
|
||||
new ListedAccountDeletedEventHandler(
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<GetSetting>(TYPES.Auth_GetSetting),
|
||||
container.get<SetSettingValue>(TYPES.Auth_SetSettingValue),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<UserDisabledSessionUserAgentLoggingEventHandler>(TYPES.Auth_UserDisabledSessionUserAgentLoggingEventHandler)
|
||||
.to(UserDisabledSessionUserAgentLoggingEventHandler)
|
||||
@@ -1180,9 +1456,17 @@ export class ContainerConfigLoader {
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<UserInvitedToSharedVaultEventHandler>(TYPES.Auth_UserInvitedToSharedVaultEventHandler)
|
||||
.toConstantValue(
|
||||
new UserInvitedToSharedVaultEventHandler(
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
|
||||
),
|
||||
)
|
||||
|
||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||
['USER_REGISTERED', container.get(TYPES.Auth_UserRegisteredEventHandler)],
|
||||
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Auth_AccountDeletionRequestedEventHandler)],
|
||||
['SUBSCRIPTION_PURCHASED', container.get(TYPES.Auth_SubscriptionPurchasedEventHandler)],
|
||||
['SUBSCRIPTION_CANCELLED', container.get(TYPES.Auth_SubscriptionCancelledEventHandler)],
|
||||
@@ -1192,7 +1476,6 @@ export class ContainerConfigLoader {
|
||||
['SUBSCRIPTION_SYNC_REQUESTED', container.get(TYPES.Auth_SubscriptionSyncRequestedEventHandler)],
|
||||
['EXTENSION_KEY_GRANTED', container.get(TYPES.Auth_ExtensionKeyGrantedEventHandler)],
|
||||
['SUBSCRIPTION_REASSIGNED', container.get(TYPES.Auth_SubscriptionReassignedEventHandler)],
|
||||
['USER_EMAIL_CHANGED', container.get(TYPES.Auth_UserEmailChangedEventHandler)],
|
||||
['FILE_UPLOADED', container.get(TYPES.Auth_FileUploadedEventHandler)],
|
||||
['SHARED_VAULT_FILE_UPLOADED', container.get(TYPES.Auth_SharedVaultFileUploadedEventHandler)],
|
||||
['SHARED_VAULT_FILE_MOVED', container.get(TYPES.Auth_SharedVaultFileMovedEventHandler)],
|
||||
@@ -1217,6 +1500,7 @@ export class ContainerConfigLoader {
|
||||
'USER_DESIGNATED_AS_SURVIVOR_IN_SHARED_VAULT',
|
||||
container.get(TYPES.Auth_UserDesignatedAsSurvivorInSharedVaultEventHandler),
|
||||
],
|
||||
['USER_INVITED_TO_SHARED_VAULT', container.get(TYPES.Auth_UserInvitedToSharedVaultEventHandler)],
|
||||
])
|
||||
|
||||
if (isConfiguredForHomeServer) {
|
||||
@@ -1236,8 +1520,7 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<DomainEventSubscriberInterface>(TYPES.Auth_DomainEventSubscriber)
|
||||
.toConstantValue(
|
||||
new SQSOpenTelemetryDomainEventSubscriber(
|
||||
ServiceIdentifier.NAMES.AuthWorker,
|
||||
new SQSDomainEventSubscriber(
|
||||
container.get<SQSClient>(TYPES.Auth_SQS),
|
||||
container.get<string>(TYPES.Auth_SQS_QUEUE_URL),
|
||||
container.get<DomainEventMessageHandlerInterface>(TYPES.Auth_DomainEventMessageHandler),
|
||||
@@ -1343,33 +1626,41 @@ export class ContainerConfigLoader {
|
||||
.bind<BaseSubscriptionTokensController>(TYPES.Auth_BaseSubscriptionTokensController)
|
||||
.toConstantValue(
|
||||
new BaseSubscriptionTokensController(
|
||||
container.get(TYPES.Auth_CreateSubscriptionToken),
|
||||
container.get(TYPES.Auth_AuthenticateSubscriptionToken),
|
||||
container.get(TYPES.Auth_SettingService),
|
||||
container.get(TYPES.Auth_UserProjector),
|
||||
container.get(TYPES.Auth_RoleProjector),
|
||||
container.get(TYPES.Auth_CrossServiceTokenEncoder),
|
||||
container.get(TYPES.Auth_AUTH_JWT_TTL),
|
||||
container.get(TYPES.Auth_ControllerContainer),
|
||||
container.get<CreateSubscriptionToken>(TYPES.Auth_CreateSubscriptionToken),
|
||||
container.get<AuthenticateSubscriptionToken>(TYPES.Auth_AuthenticateSubscriptionToken),
|
||||
container.get<GetSetting>(TYPES.Auth_GetSetting),
|
||||
container.get<ProjectorInterface<User>>(TYPES.Auth_UserProjector),
|
||||
container.get<ProjectorInterface<Role>>(TYPES.Auth_RoleProjector),
|
||||
container.get<TokenEncoderInterface<CrossServiceTokenData>>(TYPES.Auth_CrossServiceTokenEncoder),
|
||||
container.get<number>(TYPES.Auth_AUTH_JWT_TTL),
|
||||
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<BaseSubscriptionSettingsController>(TYPES.Auth_BaseSubscriptionSettingsController)
|
||||
.toConstantValue(
|
||||
new BaseSubscriptionSettingsController(
|
||||
container.get(TYPES.Auth_GetSetting),
|
||||
container.get(TYPES.Auth_ControllerContainer),
|
||||
container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting),
|
||||
container.get<GetSharedOrRegularSubscriptionForUser>(TYPES.Auth_GetSharedOrRegularSubscriptionForUser),
|
||||
container.get<MapperInterface<SubscriptionSetting, SubscriptionSettingHttpRepresentation>>(
|
||||
TYPES.Auth_SubscriptionSettingHttpMapper,
|
||||
),
|
||||
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<BaseSettingsController>(TYPES.Auth_BaseSettingsController)
|
||||
.toConstantValue(
|
||||
new BaseSettingsController(
|
||||
container.get(TYPES.Auth_GetSettings),
|
||||
container.get(TYPES.Auth_GetSetting),
|
||||
container.get(TYPES.Auth_UpdateSetting),
|
||||
container.get(TYPES.Auth_DeleteSetting),
|
||||
container.get(TYPES.Auth_ControllerContainer),
|
||||
container.get<GetAllSettingsForUser>(TYPES.Auth_GetAllSettingsForUser),
|
||||
container.get<GetSetting>(TYPES.Auth_GetSetting),
|
||||
container.get<SetSettingValue>(TYPES.Auth_SetSettingValue),
|
||||
container.get<DeleteSetting>(TYPES.Auth_DeleteSetting),
|
||||
container.get<MapperInterface<Setting, SettingHttpRepresentation>>(TYPES.Auth_SettingHttpMapper),
|
||||
container.get<MapperInterface<SubscriptionSetting, SubscriptionSettingHttpRepresentation>>(
|
||||
TYPES.Auth_SubscriptionSettingHttpMapper,
|
||||
),
|
||||
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
|
||||
),
|
||||
)
|
||||
container
|
||||
|
||||
@@ -5,8 +5,6 @@ import { Role } from '../Domain/Role/Role'
|
||||
import { RevokedSession } from '../Domain/Session/RevokedSession'
|
||||
import { Session } from '../Domain/Session/Session'
|
||||
import { OfflineSetting } from '../Domain/Setting/OfflineSetting'
|
||||
import { Setting } from '../Domain/Setting/Setting'
|
||||
import { SubscriptionSetting } from '../Domain/Setting/SubscriptionSetting'
|
||||
import { SharedSubscriptionInvitation } from '../Domain/SharedSubscription/SharedSubscriptionInvitation'
|
||||
import { OfflineUserSubscription } from '../Domain/Subscription/OfflineUserSubscription'
|
||||
import { UserSubscription } from '../Domain/Subscription/UserSubscription'
|
||||
@@ -19,6 +17,8 @@ import { TypeORMSessionTrace } from '../Infra/TypeORM/TypeORMSessionTrace'
|
||||
import { Env } from './Env'
|
||||
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
|
||||
import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
|
||||
import { TypeORMSubscriptionSetting } from '../Infra/TypeORM/TypeORMSubscriptionSetting'
|
||||
import { TypeORMSetting } from '../Infra/TypeORM/TypeORMSetting'
|
||||
|
||||
export class AppDataSource {
|
||||
private _dataSource: DataSource | undefined
|
||||
@@ -61,10 +61,10 @@ export class AppDataSource {
|
||||
RevokedSession,
|
||||
Role,
|
||||
Permission,
|
||||
Setting,
|
||||
TypeORMSetting,
|
||||
OfflineSetting,
|
||||
SharedSubscriptionInvitation,
|
||||
SubscriptionSetting,
|
||||
TypeORMSubscriptionSetting,
|
||||
TypeORMSessionTrace,
|
||||
TypeORMAuthenticator,
|
||||
TypeORMAuthenticatorChallenge,
|
||||
|
||||
@@ -26,9 +26,11 @@ export class Service implements AuthServiceInterface {
|
||||
|
||||
async activatePremiumFeatures(dto: {
|
||||
username: string
|
||||
subscriptionId: number
|
||||
subscriptionPlanName?: string
|
||||
uploadBytesLimit?: number
|
||||
endsAt?: Date
|
||||
cancelPreviousSubscription?: boolean
|
||||
}): Promise<Result<string>> {
|
||||
if (!this.container) {
|
||||
return Result.fail('Container not initialized')
|
||||
|
||||
@@ -10,6 +10,10 @@ const TYPES = {
|
||||
Auth_AuthenticatorHttpMapper: Symbol.for('Auth_AuthenticatorHttpMapper'),
|
||||
Auth_CacheEntryPersistenceMapper: Symbol.for('Auth_CacheEntryPersistenceMapper'),
|
||||
Auth_SharedVaultUserPersistenceMapper: Symbol.for('Auth_SharedVaultUserPersistenceMapper'),
|
||||
Auth_SettingHttpMapper: Symbol.for('Auth_SettingHttpMapper'),
|
||||
Auth_SubscriptionSettingHttpMapper: Symbol.for('Auth_SubscriptionSettingHttpMapper'),
|
||||
Auth_SubscriptionSettingPersistenceMapper: Symbol.for('Auth_SubscriptionSettingPersistenceMapper'),
|
||||
Auth_SettingPersistenceMapper: Symbol.for('Auth_SettingPersistenceMapper'),
|
||||
// Controller
|
||||
Auth_ControllerContainer: Symbol.for('Auth_ControllerContainer'),
|
||||
Auth_AuthController: Symbol.for('Auth_AuthController'),
|
||||
@@ -65,10 +69,6 @@ const TYPES = {
|
||||
Auth_UserProjector: Symbol.for('Auth_UserProjector'),
|
||||
Auth_RoleProjector: Symbol.for('Auth_RoleProjector'),
|
||||
Auth_PermissionProjector: Symbol.for('Auth_PermissionProjector'),
|
||||
Auth_SettingProjector: Symbol.for('Auth_SettingProjector'),
|
||||
Auth_SubscriptionSettingProjector: Symbol.for('Auth_SubscriptionSettingProjector'),
|
||||
// Factories
|
||||
Auth_SettingFactory: Symbol.for('Auth_SettingFactory'),
|
||||
// env vars
|
||||
Auth_JWT_SECRET: Symbol.for('Auth_JWT_SECRET'),
|
||||
Auth_LEGACY_JWT_SECRET: Symbol.for('Auth_LEGACY_JWT_SECRET'),
|
||||
@@ -91,12 +91,7 @@ const TYPES = {
|
||||
Auth_SNS_AWS_REGION: Symbol.for('Auth_SNS_AWS_REGION'),
|
||||
Auth_SQS_QUEUE_URL: Symbol.for('Auth_SQS_QUEUE_URL'),
|
||||
Auth_SQS_AWS_REGION: Symbol.for('Auth_SQS_AWS_REGION'),
|
||||
Auth_USER_SERVER_REGISTRATION_URL: Symbol.for('Auth_USER_SERVER_REGISTRATION_URL'),
|
||||
Auth_USER_SERVER_AUTH_KEY: Symbol.for('Auth_USER_SERVER_AUTH_KEY'),
|
||||
Auth_USER_SERVER_CHANGE_EMAIL_URL: Symbol.for('Auth_USER_SERVER_CHANGE_EMAIL_URL'),
|
||||
Auth_SYNCING_SERVER_URL: Symbol.for('Auth_SYNCING_SERVER_URL'),
|
||||
Auth_VERSION: Symbol.for('Auth_VERSION'),
|
||||
Auth_PAYMENTS_SERVER_URL: Symbol.for('Auth_PAYMENTS_SERVER_URL'),
|
||||
Auth_SESSION_TRACE_DAYS_TTL: Symbol.for('Auth_SESSION_TRACE_DAYS_TTL'),
|
||||
Auth_U2F_RELYING_PARTY_ID: Symbol.for('Auth_U2F_RELYING_PARTY_ID'),
|
||||
Auth_U2F_RELYING_PARTY_NAME: Symbol.for('Auth_U2F_RELYING_PARTY_NAME'),
|
||||
@@ -120,9 +115,12 @@ const TYPES = {
|
||||
Auth_DeleteSessionForUser: Symbol.for('Auth_DeleteSessionForUser'),
|
||||
Auth_ChangeCredentials: Symbol.for('Auth_ChangePassword'),
|
||||
Auth_GetSettings: Symbol.for('Auth_GetSettings'),
|
||||
Auth_GetSubscriptionSettings: Symbol.for('Auth_GetSubscriptionSettings'),
|
||||
Auth_GetRegularSubscriptionForUser: Symbol.for('Auth_GetRegularSubscriptionForUser'),
|
||||
Auth_GetSharedSubscriptionForUser: Symbol.for('Auth_GetSharedSubscriptionForUser'),
|
||||
Auth_GetAllSettingsForUser: Symbol.for('Auth_GetAllSettingsForUser'),
|
||||
Auth_GetSetting: Symbol.for('Auth_GetSetting'),
|
||||
Auth_GetUserFeatures: Symbol.for('Auth_GetUserFeatures'),
|
||||
Auth_UpdateSetting: Symbol.for('Auth_UpdateSetting'),
|
||||
Auth_DeleteSetting: Symbol.for('Auth_DeleteSetting'),
|
||||
Auth_DeleteAccount: Symbol.for('Auth_DeleteAccount'),
|
||||
Auth_GetUserSubscription: Symbol.for('Auth_GetUserSubscription'),
|
||||
@@ -151,7 +149,12 @@ const TYPES = {
|
||||
Auth_VerifyAuthenticatorAuthenticationResponse: Symbol.for('Auth_VerifyAuthenticatorAuthenticationResponse'),
|
||||
Auth_ListAuthenticators: Symbol.for('Auth_ListAuthenticators'),
|
||||
Auth_DeleteAuthenticator: Symbol.for('Auth_DeleteAuthenticator'),
|
||||
Auth_SetSettingValue: Symbol.for('Auth_SetSettingValue'),
|
||||
Auth_GenerateRecoveryCodes: Symbol.for('Auth_GenerateRecoveryCodes'),
|
||||
Auth_GetSubscriptionSetting: Symbol.for('Auth_GetSubscriptionSetting'),
|
||||
Auth_SetSubscriptionSettingValue: Symbol.for('Auth_SetSubscriptionSettingValue'),
|
||||
Auth_ApplyDefaultSubscriptionSettings: Symbol.for('Auth_ApplyDefaultSubscriptionSettings'),
|
||||
Auth_ApplyDefaultSettings: Symbol.for('Auth_ApplyDefaultSettings'),
|
||||
Auth_ActivatePremiumFeatures: Symbol.for('Auth_ActivatePremiumFeatures'),
|
||||
Auth_SignInWithRecoveryCodes: Symbol.for('Auth_SignInWithRecoveryCodes'),
|
||||
Auth_GetUserKeyParamsRecovery: Symbol.for('Auth_GetUserKeyParamsRecovery'),
|
||||
@@ -159,9 +162,9 @@ const TYPES = {
|
||||
Auth_AddSharedVaultUser: Symbol.for('Auth_AddSharedVaultUser'),
|
||||
Auth_RemoveSharedVaultUser: Symbol.for('Auth_RemoveSharedVaultUser'),
|
||||
Auth_DesignateSurvivor: Symbol.for('Auth_DesignateSurvivor'),
|
||||
Auth_GetSharedOrRegularSubscriptionForUser: Symbol.for('Auth_GetSharedOrRegularSubscriptionForUser'),
|
||||
Auth_DisableEmailSettingBasedOnEmailSubscription: Symbol.for('Auth_DisableEmailSettingBasedOnEmailSubscription'),
|
||||
// Handlers
|
||||
Auth_UserRegisteredEventHandler: Symbol.for('Auth_UserRegisteredEventHandler'),
|
||||
Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'),
|
||||
Auth_SubscriptionPurchasedEventHandler: Symbol.for('Auth_SubscriptionPurchasedEventHandler'),
|
||||
Auth_SubscriptionCancelledEventHandler: Symbol.for('Auth_SubscriptionCancelledEventHandler'),
|
||||
@@ -171,7 +174,6 @@ const TYPES = {
|
||||
Auth_SubscriptionExpiredEventHandler: Symbol.for('Auth_SubscriptionExpiredEventHandler'),
|
||||
Auth_SubscriptionSyncRequestedEventHandler: Symbol.for('Auth_SubscriptionSyncRequestedEventHandler'),
|
||||
Auth_ExtensionKeyGrantedEventHandler: Symbol.for('Auth_ExtensionKeyGrantedEventHandler'),
|
||||
Auth_UserEmailChangedEventHandler: Symbol.for('Auth_UserEmailChangedEventHandler'),
|
||||
Auth_FileUploadedEventHandler: Symbol.for('Auth_FileUploadedEventHandler'),
|
||||
Auth_SharedVaultFileUploadedEventHandler: Symbol.for('Auth_SharedVaultFileUploadedEventHandler'),
|
||||
Auth_SharedVaultFileMovedEventHandler: Symbol.for('Auth_SharedVaultFileMovedEventHandler'),
|
||||
@@ -193,11 +195,10 @@ const TYPES = {
|
||||
Auth_UserDesignatedAsSurvivorInSharedVaultEventHandler: Symbol.for(
|
||||
'Auth_UserDesignatedAsSurvivorInSharedVaultEventHandler',
|
||||
),
|
||||
Auth_UserInvitedToSharedVaultEventHandler: Symbol.for('Auth_UserInvitedToSharedVaultEventHandler'),
|
||||
// Services
|
||||
Auth_DeviceDetector: Symbol.for('Auth_DeviceDetector'),
|
||||
Auth_SessionService: Symbol.for('Auth_SessionService'),
|
||||
Auth_SettingService: Symbol.for('Auth_SettingService'),
|
||||
Auth_SubscriptionSettingService: Symbol.for('Auth_SubscriptionSettingService'),
|
||||
Auth_OfflineSettingService: Symbol.for('Auth_OfflineSettingService'),
|
||||
Auth_AuthResponseFactory20161215: Symbol.for('Auth_AuthResponseFactory20161215'),
|
||||
Auth_AuthResponseFactory20190520: Symbol.for('Auth_AuthResponseFactory20190520'),
|
||||
@@ -218,7 +219,6 @@ const TYPES = {
|
||||
Auth_DomainEventSubscriber: Symbol.for('Auth_DomainEventSubscriber'),
|
||||
Auth_DomainEventFactory: Symbol.for('Auth_DomainEventFactory'),
|
||||
Auth_DomainEventMessageHandler: Symbol.for('Auth_DomainEventMessageHandler'),
|
||||
Auth_HTTPClient: Symbol.for('Auth_HTTPClient'),
|
||||
Auth_Crypter: Symbol.for('Auth_Crypter'),
|
||||
Auth_CryptoNode: Symbol.for('Auth_CryptoNode'),
|
||||
Auth_Timer: Symbol.for('Auth_Timer'),
|
||||
@@ -229,11 +229,10 @@ const TYPES = {
|
||||
Auth_SettingsAssociationService: Symbol.for('Auth_SettingsAssociationService'),
|
||||
Auth_SubscriptionSettingsAssociationService: Symbol.for('Auth_SubscriptionSettingsAssociationService'),
|
||||
Auth_FeatureService: Symbol.for('Auth_FeatureService'),
|
||||
Auth_SettingDecrypter: Symbol.for('Auth_SettingDecrypter'),
|
||||
Auth_SettingCrypter: Symbol.for('Auth_SettingCrypter'),
|
||||
Auth_SettingInterpreter: Symbol.for('Auth_SettingInterpreter'),
|
||||
Auth_ProtocolVersionSelector: Symbol.for('Auth_ProtocolVersionSelector'),
|
||||
Auth_BooleanSelector: Symbol.for('Auth_BooleanSelector'),
|
||||
Auth_UserSubscriptionService: Symbol.for('Auth_UserSubscriptionService'),
|
||||
Auth_BaseAuthController: Symbol.for('Auth_BaseAuthController'),
|
||||
Auth_BaseAuthenticatorsController: Symbol.for('Auth_BaseAuthenticatorsController'),
|
||||
Auth_BaseSubscriptionInvitesController: Symbol.for('Auth_BaseSubscriptionInvitesController'),
|
||||
|
||||
9
packages/auth/src/Domain/Email/UserEmailChanged.ts
Normal file
9
packages/auth/src/Domain/Email/UserEmailChanged.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { html } from './user-email-changed.html'
|
||||
|
||||
export function getSubject(): string {
|
||||
return 'Confirmation: Your Email Address Has Been Successfully Updated'
|
||||
}
|
||||
|
||||
export function getBody(newEmail: string): string {
|
||||
return html(newEmail)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { html } from './user-invited-to-shared-vault.html'
|
||||
|
||||
export function getSubject(): string {
|
||||
return "You're Invited to a Shared Vault!"
|
||||
}
|
||||
|
||||
export function getBody(): string {
|
||||
return html()
|
||||
}
|
||||
14
packages/auth/src/Domain/Email/user-email-changed.html.ts
Normal file
14
packages/auth/src/Domain/Email/user-email-changed.html.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export const html = (newEmail: string) => `
|
||||
<p>Hello,</p>
|
||||
|
||||
<p>We are writing to inform you that your request to update your email address has been successfully processed. The email address associated with your Standard Notes account has now been changed to the following:</p>
|
||||
<p>New Email Address: ${newEmail}</p>
|
||||
|
||||
<p>From now on, you can log in to your account using your new email address. Your password and all other account details remain the same. If you did not initiate this change or have any concerns about this update, please contact our support team by visiting our <a href="https://standardnotes.com/help">help page</a>
|
||||
or by replying directly to this email.</p>
|
||||
|
||||
<p>Best regards,</p>
|
||||
<p>
|
||||
Standard Notes
|
||||
</p>
|
||||
`
|
||||
@@ -0,0 +1,21 @@
|
||||
export const html = () => `
|
||||
<p>Hello,</p>
|
||||
|
||||
<p>You've been invited to join a shared vault. This shared workspace will help you collaborate and securely manage notes and files.</p>
|
||||
|
||||
<p>To accept this invitation and access the shared vault, please follow these steps:</p>
|
||||
|
||||
<ol>
|
||||
<li>Go to your account settings.</li>
|
||||
<li>Navigate to the "Vaults" section.</li>
|
||||
<li>You will find the invitation there — simply click to accept.</li>
|
||||
</ol>
|
||||
|
||||
<p>If you have any questions, please contact our support team by visiting our <a href="https://standardnotes.com/help">help page</a>
|
||||
or by replying directly to this email.</p>
|
||||
|
||||
<p>Best regards,</p>
|
||||
<p>
|
||||
Standard Notes
|
||||
</p>
|
||||
`
|
||||
@@ -281,9 +281,16 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
|
||||
createAccountDeletionRequestedEvent(dto: {
|
||||
userUuid: string
|
||||
email: string
|
||||
userCreatedAtTimestamp: number
|
||||
regularSubscriptionUuid: string | undefined
|
||||
roleNames: string[]
|
||||
regularSubscription?: {
|
||||
uuid: string
|
||||
ownerUuid: string
|
||||
}
|
||||
sharedSubscription?: {
|
||||
uuid: string
|
||||
ownerUuid: string
|
||||
}
|
||||
}): AccountDeletionRequestedEvent {
|
||||
return {
|
||||
type: 'ACCOUNT_DELETION_REQUESTED',
|
||||
|
||||
@@ -45,9 +45,16 @@ export interface DomainEventFactoryInterface {
|
||||
): EmailBackupRequestedEvent
|
||||
createAccountDeletionRequestedEvent(dto: {
|
||||
userUuid: string
|
||||
email: string
|
||||
userCreatedAtTimestamp: number
|
||||
regularSubscriptionUuid: string | undefined
|
||||
roleNames: string[]
|
||||
regularSubscription?: {
|
||||
uuid: string
|
||||
ownerUuid: string
|
||||
}
|
||||
sharedSubscription?: {
|
||||
uuid: string
|
||||
ownerUuid: string
|
||||
}
|
||||
}): AccountDeletionRequestedEvent
|
||||
createUserRolesChangedEvent(userUuid: string, email: string, currentRoles: string[]): UserRolesChangedEvent
|
||||
createUserEmailChangedEvent(userUuid: string, fromEmail: string, toEmail: string): UserEmailChangedEvent
|
||||
|
||||
@@ -35,6 +35,7 @@ import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/Offl
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
|
||||
describe('FeatureService', () => {
|
||||
let roleToSubscriptionMap: RoleToSubscriptionMapInterface
|
||||
@@ -52,8 +53,10 @@ describe('FeatureService', () => {
|
||||
let offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
let offlineUserSubscription: OfflineUserSubscription
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
|
||||
const createService = () => new FeatureService(roleToSubscriptionMap, offlineUserSubscriptionRepository, timer)
|
||||
const createService = () =>
|
||||
new FeatureService(roleToSubscriptionMap, offlineUserSubscriptionRepository, timer, userSubscriptionRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
roleToSubscriptionMap = {} as jest.Mocked<RoleToSubscriptionMapInterface>
|
||||
@@ -107,11 +110,10 @@ describe('FeatureService', () => {
|
||||
renewedAt: null,
|
||||
planName: SubscriptionName.PlusPlan,
|
||||
endsAt: 555,
|
||||
user: Promise.resolve(user),
|
||||
userUuid: 'user-1-1-1',
|
||||
cancelled: false,
|
||||
subscriptionId: 1,
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
subscriptionSettings: Promise.resolve([]),
|
||||
}
|
||||
|
||||
subscription2 = {
|
||||
@@ -121,11 +123,10 @@ describe('FeatureService', () => {
|
||||
renewedAt: null,
|
||||
planName: SubscriptionName.ProPlan,
|
||||
endsAt: 777,
|
||||
user: Promise.resolve(user),
|
||||
userUuid: 'user-1-1-1',
|
||||
cancelled: false,
|
||||
subscriptionId: 2,
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
subscriptionSettings: Promise.resolve([]),
|
||||
}
|
||||
|
||||
subscription3 = {
|
||||
@@ -135,11 +136,10 @@ describe('FeatureService', () => {
|
||||
renewedAt: null,
|
||||
planName: SubscriptionName.PlusPlan,
|
||||
endsAt: 333,
|
||||
user: Promise.resolve(user),
|
||||
userUuid: 'user-1-1-1',
|
||||
cancelled: true,
|
||||
subscriptionId: 3,
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
subscriptionSettings: Promise.resolve([]),
|
||||
}
|
||||
|
||||
subscription4 = {
|
||||
@@ -149,19 +149,20 @@ describe('FeatureService', () => {
|
||||
renewedAt: null,
|
||||
planName: SubscriptionName.PlusPlan,
|
||||
endsAt: 333,
|
||||
user: Promise.resolve(user),
|
||||
userUuid: 'user-1-1-1',
|
||||
cancelled: true,
|
||||
subscriptionId: 4,
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
subscriptionSettings: Promise.resolve([]),
|
||||
}
|
||||
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1]),
|
||||
subscriptions: Promise.resolve([subscription1]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1])
|
||||
|
||||
offlineUserSubscription = {
|
||||
roles: Promise.resolve([role1]),
|
||||
uuid: 'subscription-1-1-1',
|
||||
@@ -251,9 +252,10 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1, role2, nonSubscriptionRole]),
|
||||
subscriptions: Promise.resolve([subscription1, subscription2]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
|
||||
|
||||
expect(await createService().userIsEntitledToFeature(user, 'files-beta')).toBe(true)
|
||||
})
|
||||
|
||||
@@ -273,9 +275,12 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1]),
|
||||
subscriptions: Promise.resolve([subscription3, subscription1, subscription4]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest
|
||||
.fn()
|
||||
.mockReturnValue([subscription3, subscription1, subscription4])
|
||||
|
||||
const features = await createService().getFeaturesForUser(user)
|
||||
expect(features).toEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -288,14 +293,13 @@ describe('FeatureService', () => {
|
||||
})
|
||||
|
||||
it('should not return user features if a subscription could not be found', async () => {
|
||||
const subscriptions: Array<UserSubscription> = []
|
||||
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1]),
|
||||
subscriptions: Promise.resolve(subscriptions),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([])
|
||||
|
||||
expect(await createService().getFeaturesForUser(user)).toEqual([])
|
||||
})
|
||||
|
||||
@@ -311,9 +315,12 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1]),
|
||||
subscriptions: Promise.resolve([subscription3, subscription1, subscription4]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest
|
||||
.fn()
|
||||
.mockReturnValue([subscription3, subscription1, subscription4])
|
||||
|
||||
expect(await createService().getFeaturesForUser(user)).toEqual([])
|
||||
})
|
||||
|
||||
@@ -325,19 +332,19 @@ describe('FeatureService', () => {
|
||||
renewedAt: null,
|
||||
planName: 'non existing plan name' as SubscriptionName,
|
||||
endsAt: 555,
|
||||
user: Promise.resolve(user),
|
||||
userUuid: 'user-1-1-1',
|
||||
cancelled: false,
|
||||
subscriptionId: 1,
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
subscriptionSettings: Promise.resolve([]),
|
||||
}
|
||||
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1]),
|
||||
subscriptions: Promise.resolve([subscription1]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1])
|
||||
|
||||
expect(await createService().getFeaturesForUser(user)).toEqual([])
|
||||
})
|
||||
|
||||
@@ -356,9 +363,10 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1, role2]),
|
||||
subscriptions: Promise.resolve([subscription1, subscription2]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
|
||||
|
||||
const features = await createService().getFeaturesForUser(user)
|
||||
expect(features).toEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -414,9 +422,10 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1, role2, nonSubscriptionRole]),
|
||||
subscriptions: Promise.resolve([subscription1, subscription2]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
|
||||
|
||||
const features = await createService().getFeaturesForUser(user)
|
||||
expect(features).toEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -450,9 +459,10 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1, role2]),
|
||||
subscriptions: Promise.resolve([subscription1, subscription2]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
|
||||
|
||||
const longestExpireAt = 777
|
||||
|
||||
const features = await createService().getFeaturesForUser(user)
|
||||
@@ -487,9 +497,10 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1, role2]),
|
||||
subscriptions: Promise.resolve([subscription1, subscription2]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
|
||||
|
||||
const features = await createService().getFeaturesForUser(user)
|
||||
expect(features).toEqual(
|
||||
expect.arrayContaining([
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { FeatureDescription, GetFeatures } from '@standardnotes/features'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { RoleToSubscriptionMapInterface } from '../Role/RoleToSubscriptionMapInterface'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { RoleToSubscriptionMapInterface } from '../Role/RoleToSubscriptionMapInterface'
|
||||
import { User } from '../User/User'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { FeatureServiceInterface } from './FeatureServiceInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { Role } from '../Role/Role'
|
||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
|
||||
@injectable()
|
||||
export class FeatureService implements FeatureServiceInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_RoleToSubscriptionMap) private roleToSubscriptionMap: RoleToSubscriptionMapInterface,
|
||||
@inject(TYPES.Auth_OfflineUserSubscriptionRepository)
|
||||
private roleToSubscriptionMap: RoleToSubscriptionMapInterface,
|
||||
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
|
||||
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
|
||||
private timer: TimerInterface,
|
||||
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
||||
) {}
|
||||
|
||||
async userIsEntitledToFeature(user: User, featureIdentifier: string): Promise<boolean> {
|
||||
@@ -61,7 +59,7 @@ export class FeatureService implements FeatureServiceInterface {
|
||||
}
|
||||
|
||||
async getFeaturesForUser(user: User): Promise<Array<FeatureDescription>> {
|
||||
const userSubscriptions = await user.subscriptions
|
||||
const userSubscriptions = await this.userSubscriptionRepository.findByUserUuid(user.uuid)
|
||||
|
||||
return this.getFeaturesForSubscriptions(userSubscriptions, await user.roles)
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { ExtensionKeyGrantedEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import * as dayjs from 'dayjs'
|
||||
|
||||
import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { ExtensionKeyGrantedEventHandler } from './ExtensionKeyGrantedEventHandler'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { OfflineSettingServiceInterface } from '../Setting/OfflineSettingServiceInterface'
|
||||
import { ContentDecoderInterface, SubscriptionName } from '@standardnotes/common'
|
||||
|
||||
describe('ExtensionKeyGrantedEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
let logger: Logger
|
||||
let user: User
|
||||
let event: ExtensionKeyGrantedEvent
|
||||
let settingService: SettingServiceInterface
|
||||
let offlineSettingService: OfflineSettingServiceInterface
|
||||
let contentDecoder: ContentDecoderInterface
|
||||
let timestamp: number
|
||||
|
||||
const createHandler = () =>
|
||||
new ExtensionKeyGrantedEventHandler(userRepository, settingService, offlineSettingService, contentDecoder, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {
|
||||
uuid: '123',
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(user)
|
||||
|
||||
settingService = {} as jest.Mocked<SettingServiceInterface>
|
||||
settingService.createOrReplace = jest.fn()
|
||||
|
||||
offlineSettingService = {} as jest.Mocked<OfflineSettingServiceInterface>
|
||||
offlineSettingService.createOrUpdate = jest.fn()
|
||||
|
||||
timestamp = dayjs.utc().valueOf()
|
||||
|
||||
event = {} as jest.Mocked<ExtensionKeyGrantedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
userEmail: 'test@test.com',
|
||||
extensionKey: 'abc123',
|
||||
offline: false,
|
||||
offlineFeaturesToken: 'test',
|
||||
subscriptionName: SubscriptionName.ProPlan,
|
||||
origin: 'update-subscription',
|
||||
timestamp,
|
||||
payAmount: 1000,
|
||||
billingEveryNMonths: 1,
|
||||
activeUntil: new Date(10).toString(),
|
||||
}
|
||||
|
||||
contentDecoder = {} as jest.Mocked<ContentDecoderInterface>
|
||||
contentDecoder.decode = jest.fn().mockReturnValue({
|
||||
featuresUrl: 'http://features-url',
|
||||
extensionKey: 'key',
|
||||
})
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = jest.fn()
|
||||
logger.warn = jest.fn()
|
||||
})
|
||||
|
||||
it('should add extension key as an user offline features token for offline user setting', async () => {
|
||||
event.payload.offline = true
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(offlineSettingService.createOrUpdate).toHaveBeenCalledWith({
|
||||
email: 'test@test.com',
|
||||
name: 'FEATURES_TOKEN',
|
||||
value: 'key',
|
||||
})
|
||||
})
|
||||
|
||||
it('should add extension key as an user offline features token if not possible to decode', async () => {
|
||||
event.payload.offline = true
|
||||
|
||||
contentDecoder.decode = jest.fn().mockReturnValue({})
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(offlineSettingService.createOrUpdate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should add extension key as user setting', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).toHaveBeenCalledWith({
|
||||
props: {
|
||||
name: 'EXTENSION_KEY',
|
||||
serverEncryptionVersion: 1,
|
||||
unencryptedValue: 'abc123',
|
||||
sensitive: true,
|
||||
},
|
||||
user: {
|
||||
uuid: '123',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should not do anything if no user is found for specified email', async () => {
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not do anything if user email is invalid', async () => {
|
||||
event.payload.userEmail = ''
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,26 +1,21 @@
|
||||
import { DomainEventHandlerInterface, ExtensionKeyGrantedEvent } from '@standardnotes/domain-events'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { SettingName, Username } from '@standardnotes/domain-core'
|
||||
import { OfflineFeaturesTokenData } from '@standardnotes/security'
|
||||
import { ContentDecoderInterface } from '@standardnotes/common'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { OfflineSettingServiceInterface } from '../Setting/OfflineSettingServiceInterface'
|
||||
import { OfflineSettingName } from '../Setting/OfflineSettingName'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { SetSettingValue } from '../UseCase/SetSettingValue/SetSettingValue'
|
||||
|
||||
@injectable()
|
||||
export class ExtensionKeyGrantedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_UserRepository) private userRepository: UserRepositoryInterface,
|
||||
@inject(TYPES.Auth_SettingService) private settingService: SettingServiceInterface,
|
||||
@inject(TYPES.Auth_OfflineSettingService) private offlineSettingService: OfflineSettingServiceInterface,
|
||||
@inject(TYPES.Auth_ContenDecoder) private contentDecoder: ContentDecoderInterface,
|
||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||
private userRepository: UserRepositoryInterface,
|
||||
private setSettingValue: SetSettingValue,
|
||||
private offlineSettingService: OfflineSettingServiceInterface,
|
||||
private contentDecoder: ContentDecoderInterface,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: ExtensionKeyGrantedEvent): Promise<void> {
|
||||
@@ -58,14 +53,14 @@ export class ExtensionKeyGrantedEventHandler implements DomainEventHandlerInterf
|
||||
return
|
||||
}
|
||||
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
unencryptedValue: event.payload.extensionKey,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
sensitive: true,
|
||||
},
|
||||
const result = await this.setSettingValue.execute({
|
||||
userUuid: user.uuid,
|
||||
settingName: SettingName.NAMES.ExtensionKey,
|
||||
value: event.payload.extensionKey,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Could not set extension key for user ${user.uuid}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
import { ListedAccountCreatedEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { ListedAccountCreatedEventHandler } from './ListedAccountCreatedEventHandler'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { User } from '../User/User'
|
||||
import { Setting } from '../Setting/Setting'
|
||||
|
||||
describe('ListedAccountCreatedEventHandler', () => {
|
||||
let settingService: SettingServiceInterface
|
||||
let userRepository: UserRepositoryInterface
|
||||
let event: ListedAccountCreatedEvent
|
||||
let user: User
|
||||
let logger: Logger
|
||||
|
||||
const createHandler = () => new ListedAccountCreatedEventHandler(userRepository, settingService, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {} as jest.Mocked<User>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(user)
|
||||
|
||||
settingService = {} as jest.Mocked<SettingServiceInterface>
|
||||
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
|
||||
settingService.createOrReplace = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<ListedAccountCreatedEvent>
|
||||
event.payload = {
|
||||
userEmail: 'test@test.com',
|
||||
userId: 1,
|
||||
userName: 'testuser',
|
||||
secret: 'new-secret',
|
||||
hostUrl: 'https://dev.listed.to',
|
||||
}
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.warn = jest.fn()
|
||||
})
|
||||
|
||||
it('should not save the listed secret if username is invalid', async () => {
|
||||
event.payload.userEmail = ''
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not save the listed secret if user is not found', async () => {
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should save the listed secret as a user setting', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).toHaveBeenCalledWith({
|
||||
user,
|
||||
props: {
|
||||
name: 'LISTED_AUTHOR_SECRETS',
|
||||
sensitive: false,
|
||||
unencryptedValue: '[{"authorId":1,"secret":"new-secret","hostUrl":"https://dev.listed.to"}]',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should add the listed secret as a user setting to an existing list of secrets', async () => {
|
||||
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue({
|
||||
value: '[{"authorId":2,"secret":"old-secret","hostUrl":"https://dev.listed.to"}]',
|
||||
} as jest.Mocked<Setting>)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).toHaveBeenCalledWith({
|
||||
user,
|
||||
props: {
|
||||
name: 'LISTED_AUTHOR_SECRETS',
|
||||
sensitive: false,
|
||||
unencryptedValue:
|
||||
'[{"authorId":2,"secret":"old-secret","hostUrl":"https://dev.listed.to"},{"authorId":1,"secret":"new-secret","hostUrl":"https://dev.listed.to"}]',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,19 +1,18 @@
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { SettingName, Username } from '@standardnotes/domain-core'
|
||||
import { DomainEventHandlerInterface, ListedAccountCreatedEvent } from '@standardnotes/domain-events'
|
||||
import { ListedAuthorSecretsData, SettingName } from '@standardnotes/settings'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { ListedAuthorSecretsData } from '@standardnotes/settings'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { GetSetting } from '../UseCase/GetSetting/GetSetting'
|
||||
import { SetSettingValue } from '../UseCase/SetSettingValue/SetSettingValue'
|
||||
|
||||
@injectable()
|
||||
export class ListedAccountCreatedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_UserRepository) private userRepository: UserRepositoryInterface,
|
||||
@inject(TYPES.Auth_SettingService) private settingService: SettingServiceInterface,
|
||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||
private userRepository: UserRepositoryInterface,
|
||||
private getSetting: GetSetting,
|
||||
private setSettingValue: SetSettingValue,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: ListedAccountCreatedEvent): Promise<void> {
|
||||
@@ -34,23 +33,27 @@ export class ListedAccountCreatedEventHandler implements DomainEventHandlerInter
|
||||
|
||||
let authSecrets: ListedAuthorSecretsData = [newSecret]
|
||||
|
||||
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||
settingName: SettingName.create(SettingName.NAMES.ListedAuthorSecrets).getValue(),
|
||||
const listedAuthorSecretsSettingOrError = await this.getSetting.execute({
|
||||
settingName: SettingName.NAMES.ListedAuthorSecrets,
|
||||
userUuid: user.uuid,
|
||||
decrypted: true,
|
||||
allowSensitiveRetrieval: false,
|
||||
})
|
||||
if (listedAuthorSecretsSetting !== null) {
|
||||
const existingSecrets: ListedAuthorSecretsData = JSON.parse(listedAuthorSecretsSetting.value as string)
|
||||
if (!listedAuthorSecretsSettingOrError.isFailed()) {
|
||||
const listedAuthorSecretsSetting = listedAuthorSecretsSettingOrError.getValue()
|
||||
const existingSecrets: ListedAuthorSecretsData = JSON.parse(listedAuthorSecretsSetting.decryptedValue as string)
|
||||
existingSecrets.push(newSecret)
|
||||
authSecrets = existingSecrets
|
||||
}
|
||||
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||
unencryptedValue: JSON.stringify(authSecrets),
|
||||
sensitive: false,
|
||||
},
|
||||
const result = await this.setSettingValue.execute({
|
||||
userUuid: user.uuid,
|
||||
settingName: SettingName.NAMES.ListedAuthorSecrets,
|
||||
value: JSON.stringify(authSecrets),
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Could not update listed author secrets for user with uuid ${user.uuid}: ${result.getError()}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
import { ListedAccountDeletedEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { ListedAccountDeletedEventHandler } from './ListedAccountDeletedEventHandler'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { User } from '../User/User'
|
||||
import { Setting } from '../Setting/Setting'
|
||||
|
||||
describe('ListedAccountDeletedEventHandler', () => {
|
||||
let settingService: SettingServiceInterface
|
||||
let userRepository: UserRepositoryInterface
|
||||
let event: ListedAccountDeletedEvent
|
||||
let user: User
|
||||
let logger: Logger
|
||||
|
||||
const createHandler = () => new ListedAccountDeletedEventHandler(userRepository, settingService, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {} as jest.Mocked<User>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(user)
|
||||
|
||||
settingService = {} as jest.Mocked<SettingServiceInterface>
|
||||
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue({
|
||||
value: '[{"authorId":1,"secret":"my-secret","hostUrl":"https://dev.listed.to"}]',
|
||||
} as jest.Mocked<Setting>)
|
||||
settingService.createOrReplace = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<ListedAccountDeletedEvent>
|
||||
event.payload = {
|
||||
userEmail: 'test@test.com',
|
||||
userId: 1,
|
||||
userName: 'testuser',
|
||||
secret: 'my-secret',
|
||||
hostUrl: 'https://dev.listed.to',
|
||||
}
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.warn = jest.fn()
|
||||
})
|
||||
|
||||
it('should not remove the listed secret if username is invalid', async () => {
|
||||
event.payload.userEmail = ''
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not remove the listed secret if user is not found', async () => {
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not remove the listed secret if setting is not found', async () => {
|
||||
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should remove the listed secret from the user setting', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).toHaveBeenCalledWith({
|
||||
user,
|
||||
props: {
|
||||
name: 'LISTED_AUTHOR_SECRETS',
|
||||
sensitive: false,
|
||||
unencryptedValue: '[]',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove the listed secret from an existing list of secrets', async () => {
|
||||
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue({
|
||||
value:
|
||||
'[{"authorId":2,"secret":"old-secret","hostUrl":"https://dev.listed.to"},{"authorId":1,"secret":"my-secret","hostUrl":"https://dev.listed.to"},{"authorId":1,"secret":"my-secret","hostUrl":"https://local.listed.to"}]',
|
||||
} as jest.Mocked<Setting>)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).toHaveBeenCalledWith({
|
||||
user,
|
||||
props: {
|
||||
name: 'LISTED_AUTHOR_SECRETS',
|
||||
sensitive: false,
|
||||
unencryptedValue:
|
||||
'[{"authorId":2,"secret":"old-secret","hostUrl":"https://dev.listed.to"},{"authorId":1,"secret":"my-secret","hostUrl":"https://local.listed.to"}]',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,19 +1,18 @@
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { SettingName, Username } from '@standardnotes/domain-core'
|
||||
import { DomainEventHandlerInterface, ListedAccountDeletedEvent } from '@standardnotes/domain-events'
|
||||
import { ListedAuthorSecretsData, SettingName } from '@standardnotes/settings'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { ListedAuthorSecretsData } from '@standardnotes/settings'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { GetSetting } from '../UseCase/GetSetting/GetSetting'
|
||||
import { SetSettingValue } from '../UseCase/SetSettingValue/SetSettingValue'
|
||||
|
||||
@injectable()
|
||||
export class ListedAccountDeletedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_UserRepository) private userRepository: UserRepositoryInterface,
|
||||
@inject(TYPES.Auth_SettingService) private settingService: SettingServiceInterface,
|
||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||
private userRepository: UserRepositoryInterface,
|
||||
private getSetting: GetSetting,
|
||||
private setSettingValue: SetSettingValue,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: ListedAccountDeletedEvent): Promise<void> {
|
||||
@@ -31,30 +30,35 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter
|
||||
return
|
||||
}
|
||||
|
||||
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||
settingName: SettingName.create(SettingName.NAMES.ListedAuthorSecrets).getValue(),
|
||||
const listedAuthorSecretsSettingOrError = await this.getSetting.execute({
|
||||
settingName: SettingName.NAMES.ListedAuthorSecrets,
|
||||
decrypted: true,
|
||||
userUuid: user.uuid,
|
||||
allowSensitiveRetrieval: false,
|
||||
})
|
||||
if (listedAuthorSecretsSetting === null) {
|
||||
this.logger.warn(`Could not find listed secrets setting for user ${user.uuid}`)
|
||||
if (listedAuthorSecretsSettingOrError.isFailed()) {
|
||||
this.logger.error(`Could not find listed secrets setting for user ${user.uuid}`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const existingSecrets: ListedAuthorSecretsData = JSON.parse(listedAuthorSecretsSetting.value as string)
|
||||
const listedAuthorSecretsSetting = listedAuthorSecretsSettingOrError.getValue()
|
||||
|
||||
const existingSecrets: ListedAuthorSecretsData = JSON.parse(listedAuthorSecretsSetting.decryptedValue as string)
|
||||
const filteredSecrets = existingSecrets.filter(
|
||||
(secret) =>
|
||||
secret.authorId !== event.payload.userId ||
|
||||
(secret.authorId === event.payload.userId && secret.hostUrl !== event.payload.hostUrl),
|
||||
)
|
||||
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||
unencryptedValue: JSON.stringify(filteredSecrets),
|
||||
sensitive: false,
|
||||
},
|
||||
const result = await this.setSettingValue.execute({
|
||||
settingName: SettingName.NAMES.ListedAuthorSecrets,
|
||||
value: JSON.stringify(filteredSecrets),
|
||||
userUuid: user.uuid,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Could not update listed author secrets for user with uuid ${user.uuid}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Logger } from 'winston'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { PaymentsAccountDeletedEvent } from '@standardnotes/domain-events'
|
||||
|
||||
import { DeleteAccount } from '../UseCase/DeleteAccount/DeleteAccount'
|
||||
import { PaymentsAccountDeletedEventHandler } from './PaymentsAccountDeletedEventHandler'
|
||||
|
||||
describe('PaymentsAccountDeletedEventHandler', () => {
|
||||
let deleteAccountUseCase: DeleteAccount
|
||||
let logger: Logger
|
||||
let event: PaymentsAccountDeletedEvent
|
||||
|
||||
const createHandler = () => new PaymentsAccountDeletedEventHandler(deleteAccountUseCase, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
deleteAccountUseCase = {} as jest.Mocked<DeleteAccount>
|
||||
deleteAccountUseCase.execute = jest.fn().mockResolvedValue(Result.ok('success'))
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
|
||||
event = {
|
||||
payload: {
|
||||
username: 'username',
|
||||
},
|
||||
} as jest.Mocked<PaymentsAccountDeletedEvent>
|
||||
})
|
||||
|
||||
it('should delete account', async () => {
|
||||
const handler = createHandler()
|
||||
|
||||
await handler.handle(event)
|
||||
|
||||
expect(deleteAccountUseCase.execute).toHaveBeenCalledWith({
|
||||
username: 'username',
|
||||
})
|
||||
})
|
||||
|
||||
it('should log error if delete account fails', async () => {
|
||||
const handler = createHandler()
|
||||
|
||||
deleteAccountUseCase.execute = jest.fn().mockResolvedValue(Result.fail('error'))
|
||||
|
||||
await handler.handle(event)
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith('Failed to delete account for user username: error')
|
||||
})
|
||||
})
|
||||
@@ -1,131 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import {
|
||||
DomainEventPublisherInterface,
|
||||
DomainEventService,
|
||||
PredicateVerificationRequestedEvent,
|
||||
PredicateVerificationRequestedEventPayload,
|
||||
PredicateVerifiedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||
import { VerifyPredicate } from '../UseCase/VerifyPredicate/VerifyPredicate'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
|
||||
import { PredicateVerificationRequestedEventHandler } from './PredicateVerificationRequestedEventHandler'
|
||||
import { User } from '../User/User'
|
||||
|
||||
describe('PredicateVerificationRequestedEventHandler', () => {
|
||||
let verifyPredicate: VerifyPredicate
|
||||
let userRepository: UserRepositoryInterface
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
let logger: Logger
|
||||
let event: PredicateVerificationRequestedEvent
|
||||
|
||||
const createHandler = () =>
|
||||
new PredicateVerificationRequestedEventHandler(
|
||||
verifyPredicate,
|
||||
userRepository,
|
||||
domainEventFactory,
|
||||
domainEventPublisher,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
verifyPredicate = {} as jest.Mocked<VerifyPredicate>
|
||||
verifyPredicate.execute = jest
|
||||
.fn()
|
||||
.mockReturnValue({ predicateVerificationResult: PredicateVerificationResult.Affirmed })
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue({ uuid: '1-2-3' } as jest.Mocked<User>)
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createPredicateVerifiedEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<PredicateVerifiedEvent>)
|
||||
|
||||
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
|
||||
domainEventPublisher.publish = jest.fn()
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.warn = jest.fn()
|
||||
logger.info = jest.fn()
|
||||
logger.debug = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<PredicateVerificationRequestedEvent>
|
||||
event.meta = {
|
||||
correlation: {
|
||||
userIdentifier: '2-3-4',
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: DomainEventService.Auth,
|
||||
}
|
||||
event.payload = {
|
||||
predicate: {} as jest.Mocked<Predicate>,
|
||||
} as jest.Mocked<PredicateVerificationRequestedEventPayload>
|
||||
})
|
||||
|
||||
it('should verify a predicate by user uuid', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(verifyPredicate.execute).toHaveBeenCalledWith({
|
||||
predicate: event.payload.predicate,
|
||||
userUuid: '2-3-4',
|
||||
})
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should verify a predicate by user email', async () => {
|
||||
event.meta = {
|
||||
correlation: {
|
||||
userIdentifier: 'test@test.te',
|
||||
userIdentifierType: 'email',
|
||||
},
|
||||
origin: DomainEventService.Auth,
|
||||
}
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(verifyPredicate.execute).toHaveBeenCalledWith({
|
||||
predicate: event.payload.predicate,
|
||||
userUuid: '1-2-3',
|
||||
})
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should do nothing if username is invalid', async () => {
|
||||
event.meta = {
|
||||
correlation: {
|
||||
userIdentifier: ' ',
|
||||
userIdentifierType: 'email',
|
||||
},
|
||||
origin: DomainEventService.Auth,
|
||||
}
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(verifyPredicate.execute).not.toHaveBeenCalled()
|
||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should mark a predicate verification with undetermined result if user is missing', async () => {
|
||||
event.meta = {
|
||||
correlation: {
|
||||
userIdentifier: 'test@test.te',
|
||||
userIdentifierType: 'email',
|
||||
},
|
||||
origin: DomainEventService.Auth,
|
||||
}
|
||||
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(verifyPredicate.execute).not.toHaveBeenCalled()
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,45 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SharedSubscriptionInvitationCreatedEvent } from '@standardnotes/domain-events'
|
||||
|
||||
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
|
||||
import { AcceptSharedSubscriptionInvitation } from '../UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation'
|
||||
|
||||
import { SharedSubscriptionInvitationCreatedEventHandler } from './SharedSubscriptionInvitationCreatedEventHandler'
|
||||
|
||||
describe('SharedSubscriptionInvitationCreatedEventHandler', () => {
|
||||
let acceptSharedSubscriptionInvitation: AcceptSharedSubscriptionInvitation
|
||||
|
||||
const createHandler = () => new SharedSubscriptionInvitationCreatedEventHandler(acceptSharedSubscriptionInvitation)
|
||||
|
||||
beforeEach(() => {
|
||||
acceptSharedSubscriptionInvitation = {} as jest.Mocked<AcceptSharedSubscriptionInvitation>
|
||||
acceptSharedSubscriptionInvitation.execute = jest.fn()
|
||||
})
|
||||
|
||||
it('should accept automatically invitation for hash invitees', async () => {
|
||||
const event = {
|
||||
payload: {
|
||||
inviteeIdentifierType: InviteeIdentifierType.Hash,
|
||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||
},
|
||||
} as jest.Mocked<SharedSubscriptionInvitationCreatedEvent>
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(acceptSharedSubscriptionInvitation.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not accept automatically invitation for email invitees', async () => {
|
||||
const event = {
|
||||
payload: {
|
||||
inviteeIdentifierType: InviteeIdentifierType.Email,
|
||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||
},
|
||||
} as jest.Mocked<SharedSubscriptionInvitationCreatedEvent>
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(acceptSharedSubscriptionInvitation.execute).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,63 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
||||
|
||||
import * as dayjs from 'dayjs'
|
||||
|
||||
import { SubscriptionCancelledEventHandler } from './SubscriptionCancelledEventHandler'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
|
||||
describe('SubscriptionCancelledEventHandler', () => {
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
let offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface
|
||||
let event: SubscriptionCancelledEvent
|
||||
let timestamp: number
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionCancelledEventHandler(userSubscriptionRepository, offlineUserSubscriptionRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||
userSubscriptionRepository.updateCancelled = jest.fn()
|
||||
|
||||
offlineUserSubscriptionRepository = {} as jest.Mocked<OfflineUserSubscriptionRepositoryInterface>
|
||||
offlineUserSubscriptionRepository.updateCancelled = jest.fn()
|
||||
|
||||
timestamp = dayjs.utc().valueOf()
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionCancelledEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
subscriptionName: SubscriptionName.ProPlan,
|
||||
timestamp,
|
||||
offline: false,
|
||||
replaced: false,
|
||||
subscriptionCreatedAt: 1,
|
||||
subscriptionEndsAt: 2,
|
||||
subscriptionUpdatedAt: 2,
|
||||
lastPayedAt: 1,
|
||||
userExistingSubscriptionsCount: 1,
|
||||
billingFrequency: 1,
|
||||
payAmount: 12.99,
|
||||
}
|
||||
})
|
||||
|
||||
it('should update subscription cancelled', async () => {
|
||||
event.payload.timestamp = 1642395451516000
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(userSubscriptionRepository.updateCancelled).toHaveBeenCalledWith(1, true, 1642395451516000)
|
||||
})
|
||||
|
||||
it('should update offline subscription cancelled', async () => {
|
||||
event.payload.offline = true
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(offlineUserSubscriptionRepository.updateCancelled).toHaveBeenCalledWith(1, true, timestamp)
|
||||
})
|
||||
})
|
||||
@@ -1,123 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
import { SubscriptionExpiredEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import * as dayjs from 'dayjs'
|
||||
|
||||
import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { SubscriptionExpiredEventHandler } from './SubscriptionExpiredEventHandler'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
|
||||
describe('SubscriptionExpiredEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
let offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface
|
||||
let roleService: RoleServiceInterface
|
||||
let logger: Logger
|
||||
let user: User
|
||||
let event: SubscriptionExpiredEvent
|
||||
let timestamp: number
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionExpiredEventHandler(
|
||||
userRepository,
|
||||
userSubscriptionRepository,
|
||||
offlineUserSubscriptionRepository,
|
||||
roleService,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {
|
||||
uuid: '123',
|
||||
email: 'test@test.com',
|
||||
roles: Promise.resolve([
|
||||
{
|
||||
name: RoleName.NAMES.ProUser,
|
||||
},
|
||||
]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(user)
|
||||
userRepository.save = jest.fn().mockReturnValue(user)
|
||||
|
||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||
userSubscriptionRepository.updateEndsAt = jest.fn()
|
||||
userSubscriptionRepository.countActiveSubscriptions = jest.fn().mockReturnValue(13)
|
||||
userSubscriptionRepository.findBySubscriptionId = jest
|
||||
.fn()
|
||||
.mockReturnValue([{ user: Promise.resolve(user) } as jest.Mocked<UserSubscription>])
|
||||
|
||||
offlineUserSubscriptionRepository = {} as jest.Mocked<OfflineUserSubscriptionRepositoryInterface>
|
||||
offlineUserSubscriptionRepository.updateEndsAt = jest.fn()
|
||||
|
||||
roleService = {} as jest.Mocked<RoleServiceInterface>
|
||||
roleService.removeUserRoleBasedOnSubscription = jest.fn()
|
||||
|
||||
timestamp = dayjs.utc().valueOf()
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionExpiredEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
subscriptionName: SubscriptionName.PlusPlan,
|
||||
timestamp,
|
||||
offline: false,
|
||||
totalActiveSubscriptionsCount: 123,
|
||||
userExistingSubscriptionsCount: 2,
|
||||
billingFrequency: 1,
|
||||
payAmount: 12.99,
|
||||
}
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = jest.fn()
|
||||
logger.warn = jest.fn()
|
||||
})
|
||||
|
||||
it('should update the user role', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.removeUserRoleBasedOnSubscription).toHaveBeenCalledWith(user, SubscriptionName.PlusPlan)
|
||||
})
|
||||
|
||||
it('should update subscription ends at', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(userSubscriptionRepository.updateEndsAt).toHaveBeenCalledWith(1, timestamp, timestamp)
|
||||
})
|
||||
|
||||
it('should update offline subscription ends at', async () => {
|
||||
event.payload.offline = true
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(offlineUserSubscriptionRepository.updateEndsAt).toHaveBeenCalledWith(1, timestamp, timestamp)
|
||||
})
|
||||
|
||||
it('should not do anything if no user is found for specified email', async () => {
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.removeUserRoleBasedOnSubscription).not.toHaveBeenCalled()
|
||||
expect(userSubscriptionRepository.updateEndsAt).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not do anything if username is invalid', async () => {
|
||||
event.payload.userEmail = ' '
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.removeUserRoleBasedOnSubscription).not.toHaveBeenCalled()
|
||||
expect(userSubscriptionRepository.updateEndsAt).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -7,7 +7,7 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -48,7 +48,22 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
||||
private async removeRoleFromSubscriptionUsers(subscriptionId: number, subscriptionName: string): Promise<void> {
|
||||
const userSubscriptions = await this.userSubscriptionRepository.findBySubscriptionId(subscriptionId)
|
||||
for (const userSubscription of userSubscriptions) {
|
||||
await this.roleService.removeUserRoleBasedOnSubscription(await userSubscription.user, subscriptionName)
|
||||
const userUuidOrError = Uuid.create(userSubscription.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
this.logger.warn(`Could not remove role from user with uuid: ${userUuidOrError.getError()}`)
|
||||
|
||||
continue
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const user = await this.userRepository.findOneByUuid(userUuid)
|
||||
if (user === null) {
|
||||
this.logger.warn(`Could not find user with uuid: ${userUuid.value}`)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
await this.roleService.removeUserRoleBasedOnSubscription(user, subscriptionName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
import { SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import * as dayjs from 'dayjs'
|
||||
|
||||
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { SubscriptionPurchasedEventHandler } from './SubscriptionPurchasedEventHandler'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
|
||||
describe('SubscriptionPurchasedEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
let offlineUserSubscription: OfflineUserSubscription
|
||||
let offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface
|
||||
let roleService: RoleServiceInterface
|
||||
let logger: Logger
|
||||
let user: User
|
||||
let subscription: UserSubscription
|
||||
let event: SubscriptionPurchasedEvent
|
||||
let subscriptionExpiresAt: number
|
||||
let subscriptionSettingService: SubscriptionSettingServiceInterface
|
||||
let timestamp: number
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionPurchasedEventHandler(
|
||||
userRepository,
|
||||
userSubscriptionRepository,
|
||||
offlineUserSubscriptionRepository,
|
||||
roleService,
|
||||
subscriptionSettingService,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {
|
||||
uuid: '123',
|
||||
email: 'test@test.com',
|
||||
roles: Promise.resolve([
|
||||
{
|
||||
name: RoleName.NAMES.CoreUser,
|
||||
},
|
||||
]),
|
||||
} as jest.Mocked<User>
|
||||
subscription = {
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
} as jest.Mocked<UserSubscription>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(user)
|
||||
userRepository.save = jest.fn().mockReturnValue(user)
|
||||
|
||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||
userSubscriptionRepository.countByUserUuid = jest.fn().mockReturnValue(0)
|
||||
userSubscriptionRepository.countActiveSubscriptions = jest.fn().mockReturnValue(13)
|
||||
userSubscriptionRepository.save = jest.fn().mockReturnValue(subscription)
|
||||
|
||||
offlineUserSubscription = {} as jest.Mocked<OfflineUserSubscription>
|
||||
|
||||
offlineUserSubscriptionRepository = {} as jest.Mocked<OfflineUserSubscriptionRepositoryInterface>
|
||||
offlineUserSubscriptionRepository.findOneBySubscriptionId = jest.fn().mockReturnValue(offlineUserSubscription)
|
||||
offlineUserSubscriptionRepository.save = jest.fn().mockReturnValue(offlineUserSubscription)
|
||||
|
||||
roleService = {} as jest.Mocked<RoleServiceInterface>
|
||||
roleService.addUserRoleBasedOnSubscription = jest.fn()
|
||||
roleService.setOfflineUserRole = jest.fn()
|
||||
|
||||
subscriptionExpiresAt = timestamp + 365 * 1000
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionPurchasedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
subscriptionName: SubscriptionName.ProPlan,
|
||||
subscriptionExpiresAt,
|
||||
timestamp: dayjs.utc().valueOf(),
|
||||
offline: false,
|
||||
discountCode: null,
|
||||
limitedDiscountPurchased: false,
|
||||
newSubscriber: true,
|
||||
totalActiveSubscriptionsCount: 123,
|
||||
userRegisteredAt: dayjs.utc().valueOf() - 23,
|
||||
billingFrequency: 12,
|
||||
payAmount: 29.99,
|
||||
}
|
||||
|
||||
subscriptionSettingService = {} as jest.Mocked<SubscriptionSettingServiceInterface>
|
||||
subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription = jest.fn()
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = jest.fn()
|
||||
logger.warn = jest.fn()
|
||||
})
|
||||
|
||||
it('should update the user role', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.addUserRoleBasedOnSubscription).toHaveBeenCalledWith(user, SubscriptionName.ProPlan)
|
||||
})
|
||||
|
||||
it('should update user default settings', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription).toHaveBeenCalledWith(
|
||||
subscription,
|
||||
)
|
||||
})
|
||||
|
||||
it('should update the offline user role', async () => {
|
||||
event.payload.offline = true
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.setOfflineUserRole).toHaveBeenCalledWith(offlineUserSubscription)
|
||||
})
|
||||
|
||||
it('should create subscription', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
subscription.planName = SubscriptionName.ProPlan
|
||||
subscription.endsAt = subscriptionExpiresAt
|
||||
subscription.subscriptionId = 1
|
||||
subscription.user = Promise.resolve(user)
|
||||
|
||||
expect(userSubscriptionRepository.save).toHaveBeenCalledWith({
|
||||
...subscription,
|
||||
createdAt: expect.any(Number),
|
||||
updatedAt: expect.any(Number),
|
||||
cancelled: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should create an offline subscription', async () => {
|
||||
event.payload.offline = true
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(offlineUserSubscriptionRepository.save).toHaveBeenCalledWith({
|
||||
endsAt: subscriptionExpiresAt,
|
||||
subscriptionId: 1,
|
||||
planName: 'PRO_PLAN',
|
||||
email: 'test@test.com',
|
||||
createdAt: expect.any(Number),
|
||||
updatedAt: expect.any(Number),
|
||||
cancelled: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should not do anything if no user is found for specified email', async () => {
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.addUserRoleBasedOnSubscription).not.toHaveBeenCalled()
|
||||
expect(userSubscriptionRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not do anything if username is invalid', async () => {
|
||||
event.payload.userEmail = ' '
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.addUserRoleBasedOnSubscription).not.toHaveBeenCalled()
|
||||
expect(userSubscriptionRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,7 @@
|
||||
import { DomainEventHandlerInterface, SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
@@ -11,21 +10,16 @@ import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscri
|
||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { ApplyDefaultSubscriptionSettings } from '../UseCase/ApplyDefaultSubscriptionSettings/ApplyDefaultSubscriptionSettings'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_UserRepository) private userRepository: UserRepositoryInterface,
|
||||
@inject(TYPES.Auth_UserSubscriptionRepository)
|
||||
private userRepository: UserRepositoryInterface,
|
||||
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
||||
@inject(TYPES.Auth_OfflineUserSubscriptionRepository)
|
||||
private applyDefaultSubscriptionSettings: ApplyDefaultSubscriptionSettings,
|
||||
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
|
||||
@inject(TYPES.Auth_RoleService) private roleService: RoleServiceInterface,
|
||||
@inject(TYPES.Auth_SubscriptionSettingService)
|
||||
private subscriptionSettingService: SubscriptionSettingServiceInterface,
|
||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||
private roleService: RoleServiceInterface,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionPurchasedEvent): Promise<void> {
|
||||
@@ -66,7 +60,15 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
|
||||
await this.addUserRole(user, event.payload.subscriptionName)
|
||||
|
||||
await this.subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription(userSubscription)
|
||||
const result = await this.applyDefaultSubscriptionSettings.execute({
|
||||
userSubscriptionUuid: userSubscription.uuid,
|
||||
userUuid: user.uuid,
|
||||
subscriptionPlanName: event.payload.subscriptionName,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Could not apply default subscription settings for user ${user.uuid}: ${result.getError()}`)
|
||||
}
|
||||
}
|
||||
|
||||
private async addUserRole(user: User, subscriptionName: string): Promise<void> {
|
||||
@@ -82,7 +84,7 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
): Promise<UserSubscription> {
|
||||
const subscription = new UserSubscription()
|
||||
subscription.planName = subscriptionName
|
||||
subscription.user = Promise.resolve(user)
|
||||
subscription.userUuid = user.uuid
|
||||
subscription.createdAt = timestamp
|
||||
subscription.updatedAt = timestamp
|
||||
subscription.endsAt = subscriptionExpiresAt
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
import { SubscriptionReassignedEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import * as dayjs from 'dayjs'
|
||||
|
||||
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { SubscriptionReassignedEventHandler } from './SubscriptionReassignedEventHandler'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||
|
||||
describe('SubscriptionReassignedEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
let roleService: RoleServiceInterface
|
||||
let logger: Logger
|
||||
let user: User
|
||||
let subscription: UserSubscription
|
||||
let event: SubscriptionReassignedEvent
|
||||
let subscriptionExpiresAt: number
|
||||
let timestamp: number
|
||||
let settingService: SettingServiceInterface
|
||||
let subscriptionSettingService: SubscriptionSettingServiceInterface
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionReassignedEventHandler(
|
||||
userRepository,
|
||||
userSubscriptionRepository,
|
||||
roleService,
|
||||
settingService,
|
||||
subscriptionSettingService,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {
|
||||
uuid: '123',
|
||||
email: 'test@test.com',
|
||||
roles: Promise.resolve([
|
||||
{
|
||||
name: RoleName.NAMES.CoreUser,
|
||||
},
|
||||
]),
|
||||
} as jest.Mocked<User>
|
||||
subscription = {
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
} as jest.Mocked<UserSubscription>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(user)
|
||||
userRepository.save = jest.fn().mockReturnValue(user)
|
||||
|
||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||
userSubscriptionRepository.save = jest.fn().mockReturnValue(subscription)
|
||||
|
||||
roleService = {} as jest.Mocked<RoleServiceInterface>
|
||||
roleService.addUserRoleBasedOnSubscription = jest.fn()
|
||||
|
||||
subscriptionExpiresAt = timestamp + 365 * 1000
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionReassignedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
offline: false,
|
||||
extensionKey: 'abc123',
|
||||
userEmail: 'test@test.com',
|
||||
subscriptionName: SubscriptionName.ProPlan,
|
||||
subscriptionExpiresAt,
|
||||
timestamp: dayjs.utc().valueOf(),
|
||||
}
|
||||
|
||||
settingService = {} as jest.Mocked<SettingServiceInterface>
|
||||
settingService.createOrReplace = jest.fn()
|
||||
|
||||
subscriptionSettingService = {} as jest.Mocked<SubscriptionSettingServiceInterface>
|
||||
subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription = jest.fn()
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = jest.fn()
|
||||
logger.warn = jest.fn()
|
||||
})
|
||||
|
||||
it('should update user default settings', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription).toHaveBeenCalledWith(
|
||||
subscription,
|
||||
)
|
||||
})
|
||||
|
||||
it('should update the user role', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.addUserRoleBasedOnSubscription).toHaveBeenCalledWith(user, SubscriptionName.ProPlan)
|
||||
})
|
||||
|
||||
it('should create subscription', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
subscription.planName = SubscriptionName.ProPlan
|
||||
subscription.endsAt = subscriptionExpiresAt
|
||||
subscription.subscriptionId = 1
|
||||
subscription.user = Promise.resolve(user)
|
||||
|
||||
expect(userSubscriptionRepository.save).toHaveBeenCalledWith({
|
||||
...subscription,
|
||||
createdAt: expect.any(Number),
|
||||
updatedAt: expect.any(Number),
|
||||
cancelled: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should create an extension key setting for the user', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).toHaveBeenCalledWith({
|
||||
props: {
|
||||
name: 'EXTENSION_KEY',
|
||||
serverEncryptionVersion: 1,
|
||||
unencryptedValue: 'abc123',
|
||||
sensitive: true,
|
||||
},
|
||||
user: {
|
||||
uuid: '123',
|
||||
email: 'test@test.com',
|
||||
roles: Promise.resolve([
|
||||
{
|
||||
name: RoleName.NAMES.CoreUser,
|
||||
},
|
||||
]),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should not do anything if no user is found for specified email', async () => {
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.addUserRoleBasedOnSubscription).not.toHaveBeenCalled()
|
||||
expect(userSubscriptionRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not do anything if username is invalid', async () => {
|
||||
event.payload.userEmail = ' '
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.addUserRoleBasedOnSubscription).not.toHaveBeenCalled()
|
||||
expect(userSubscriptionRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,31 +1,24 @@
|
||||
import { DomainEventHandlerInterface, SubscriptionReassignedEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { SettingName, Username } from '@standardnotes/domain-core'
|
||||
import { ApplyDefaultSubscriptionSettings } from '../UseCase/ApplyDefaultSubscriptionSettings/ApplyDefaultSubscriptionSettings'
|
||||
import { SetSettingValue } from '../UseCase/SetSettingValue/SetSettingValue'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionReassignedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_UserRepository) private userRepository: UserRepositoryInterface,
|
||||
@inject(TYPES.Auth_UserSubscriptionRepository)
|
||||
private userRepository: UserRepositoryInterface,
|
||||
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
||||
@inject(TYPES.Auth_RoleService) private roleService: RoleServiceInterface,
|
||||
@inject(TYPES.Auth_SettingService) private settingService: SettingServiceInterface,
|
||||
@inject(TYPES.Auth_SubscriptionSettingService)
|
||||
private subscriptionSettingService: SubscriptionSettingServiceInterface,
|
||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||
private roleService: RoleServiceInterface,
|
||||
private logger: Logger,
|
||||
private applyDefaultSubscriptionSettings: ApplyDefaultSubscriptionSettings,
|
||||
private setSettingValue: SetSettingValue,
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionReassignedEvent): Promise<void> {
|
||||
@@ -53,17 +46,25 @@ export class SubscriptionReassignedEventHandler implements DomainEventHandlerInt
|
||||
|
||||
await this.addUserRole(user, event.payload.subscriptionName)
|
||||
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
unencryptedValue: event.payload.extensionKey,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
sensitive: true,
|
||||
},
|
||||
const result = await this.setSettingValue.execute({
|
||||
userUuid: user.uuid,
|
||||
settingName: SettingName.NAMES.ExtensionKey,
|
||||
value: event.payload.extensionKey,
|
||||
})
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Could not set extension key for user ${user.uuid}`)
|
||||
}
|
||||
|
||||
await this.subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription(userSubscription)
|
||||
const applyingSettingsResult = await this.applyDefaultSubscriptionSettings.execute({
|
||||
subscriptionPlanName: event.payload.subscriptionName,
|
||||
userUuid: user.uuid,
|
||||
userSubscriptionUuid: userSubscription.uuid,
|
||||
})
|
||||
if (applyingSettingsResult.isFailed()) {
|
||||
this.logger.error(
|
||||
`Could not apply default subscription settings for user ${user.uuid}: ${applyingSettingsResult.getError()}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private async addUserRole(user: User, subscriptionName: string): Promise<void> {
|
||||
@@ -79,7 +80,7 @@ export class SubscriptionReassignedEventHandler implements DomainEventHandlerInt
|
||||
): Promise<UserSubscription> {
|
||||
const subscription = new UserSubscription()
|
||||
subscription.planName = subscriptionName
|
||||
subscription.user = Promise.resolve(user)
|
||||
subscription.userUuid = user.uuid
|
||||
subscription.createdAt = timestamp
|
||||
subscription.updatedAt = timestamp
|
||||
subscription.endsAt = subscriptionExpiresAt
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
import { SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import * as dayjs from 'dayjs'
|
||||
|
||||
import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { SubscriptionRefundedEventHandler } from './SubscriptionRefundedEventHandler'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
|
||||
describe('SubscriptionRefundedEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
let offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface
|
||||
let roleService: RoleServiceInterface
|
||||
let logger: Logger
|
||||
let user: User
|
||||
let event: SubscriptionRefundedEvent
|
||||
let timestamp: number
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionRefundedEventHandler(
|
||||
userRepository,
|
||||
userSubscriptionRepository,
|
||||
offlineUserSubscriptionRepository,
|
||||
roleService,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {
|
||||
uuid: '123',
|
||||
email: 'test@test.com',
|
||||
roles: Promise.resolve([
|
||||
{
|
||||
name: RoleName.NAMES.ProUser,
|
||||
},
|
||||
]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(user)
|
||||
userRepository.save = jest.fn().mockReturnValue(user)
|
||||
|
||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||
userSubscriptionRepository.updateEndsAt = jest.fn()
|
||||
userSubscriptionRepository.countByUserUuid = jest.fn().mockReturnValue(1)
|
||||
userSubscriptionRepository.countActiveSubscriptions = jest.fn().mockReturnValue(13)
|
||||
userSubscriptionRepository.findBySubscriptionId = jest
|
||||
.fn()
|
||||
.mockReturnValue([{ user: Promise.resolve(user) } as jest.Mocked<UserSubscription>])
|
||||
|
||||
offlineUserSubscriptionRepository = {} as jest.Mocked<OfflineUserSubscriptionRepositoryInterface>
|
||||
offlineUserSubscriptionRepository.updateEndsAt = jest.fn()
|
||||
|
||||
roleService = {} as jest.Mocked<RoleServiceInterface>
|
||||
roleService.removeUserRoleBasedOnSubscription = jest.fn()
|
||||
|
||||
timestamp = dayjs.utc().valueOf()
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionRefundedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
subscriptionName: SubscriptionName.PlusPlan,
|
||||
timestamp,
|
||||
offline: false,
|
||||
userExistingSubscriptionsCount: 3,
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
billingFrequency: 1,
|
||||
payAmount: 12.99,
|
||||
}
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = jest.fn()
|
||||
logger.warn = jest.fn()
|
||||
})
|
||||
|
||||
it('should update the user role', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.removeUserRoleBasedOnSubscription).toHaveBeenCalledWith(user, SubscriptionName.PlusPlan)
|
||||
})
|
||||
|
||||
it('should update subscription ends at', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(userSubscriptionRepository.updateEndsAt).toHaveBeenCalledWith(1, timestamp, timestamp)
|
||||
})
|
||||
|
||||
it('should update offline subscription ends at', async () => {
|
||||
event.payload.offline = true
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(offlineUserSubscriptionRepository.updateEndsAt).toHaveBeenCalledWith(1, timestamp, timestamp)
|
||||
})
|
||||
|
||||
it('should not do anything if no user is found for specified email', async () => {
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.removeUserRoleBasedOnSubscription).not.toHaveBeenCalled()
|
||||
expect(userSubscriptionRepository.updateEndsAt).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not do anything if username is invalid', async () => {
|
||||
event.payload.userEmail = ' '
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.removeUserRoleBasedOnSubscription).not.toHaveBeenCalled()
|
||||
expect(userSubscriptionRepository.updateEndsAt).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -7,7 +7,7 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionRefundedEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -48,7 +48,22 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
||||
private async removeRoleFromSubscriptionUsers(subscriptionId: number, subscriptionName: string): Promise<void> {
|
||||
const userSubscriptions = await this.userSubscriptionRepository.findBySubscriptionId(subscriptionId)
|
||||
for (const userSubscription of userSubscriptions) {
|
||||
await this.roleService.removeUserRoleBasedOnSubscription(await userSubscription.user, subscriptionName)
|
||||
const userUuidOrError = Uuid.create(userSubscription.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
this.logger.warn(`Could not remove role from user with uuid: ${userUuidOrError.getError()}`)
|
||||
|
||||
continue
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const user = await this.userRepository.findOneByUuid(userUuid)
|
||||
if (user === null) {
|
||||
this.logger.warn(`Could not find user with uuid: ${userUuid.value}`)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
await this.roleService.removeUserRoleBasedOnSubscription(user, subscriptionName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
import { SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
||||
import * as dayjs from 'dayjs'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { SubscriptionRenewedEventHandler } from './SubscriptionRenewedEventHandler'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { User } from '../User/User'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
|
||||
describe('SubscriptionRenewedEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
let offlineUserSubscription: OfflineUserSubscription
|
||||
let offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface
|
||||
let roleService: RoleServiceInterface
|
||||
let logger: Logger
|
||||
let user: User
|
||||
let subscription: UserSubscription
|
||||
let event: SubscriptionRenewedEvent
|
||||
let subscriptionExpiresAt: number
|
||||
let timestamp: number
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionRenewedEventHandler(
|
||||
userRepository,
|
||||
userSubscriptionRepository,
|
||||
offlineUserSubscriptionRepository,
|
||||
roleService,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {
|
||||
uuid: '123',
|
||||
email: 'test@test.com',
|
||||
roles: Promise.resolve([
|
||||
{
|
||||
name: RoleName.NAMES.CoreUser,
|
||||
},
|
||||
]),
|
||||
} as jest.Mocked<User>
|
||||
subscription = {} as jest.Mocked<UserSubscription>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(user)
|
||||
userRepository.save = jest.fn().mockReturnValue(user)
|
||||
|
||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||
userSubscriptionRepository.updateEndsAt = jest.fn()
|
||||
userSubscriptionRepository.save = jest.fn().mockReturnValue(subscription)
|
||||
userSubscriptionRepository.findBySubscriptionId = jest
|
||||
.fn()
|
||||
.mockReturnValue([{ user: Promise.resolve(user) } as jest.Mocked<UserSubscription>])
|
||||
|
||||
offlineUserSubscription = {} as jest.Mocked<OfflineUserSubscription>
|
||||
|
||||
offlineUserSubscriptionRepository = {} as jest.Mocked<OfflineUserSubscriptionRepositoryInterface>
|
||||
offlineUserSubscriptionRepository.findOneBySubscriptionId = jest.fn().mockReturnValue(offlineUserSubscription)
|
||||
offlineUserSubscriptionRepository.save = jest.fn().mockReturnValue(offlineUserSubscription)
|
||||
|
||||
roleService = {} as jest.Mocked<RoleServiceInterface>
|
||||
roleService.addUserRoleBasedOnSubscription = jest.fn()
|
||||
roleService.setOfflineUserRole = jest.fn()
|
||||
|
||||
timestamp = dayjs.utc().valueOf()
|
||||
subscriptionExpiresAt = dayjs.utc().valueOf() + 365 * 1000
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionRenewedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
subscriptionName: SubscriptionName.ProPlan,
|
||||
subscriptionExpiresAt,
|
||||
timestamp,
|
||||
offline: false,
|
||||
billingFrequency: 1,
|
||||
payAmount: 12.99,
|
||||
}
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.warn = jest.fn()
|
||||
})
|
||||
|
||||
it('should update subscription ends at', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(userSubscriptionRepository.updateEndsAt).toHaveBeenCalledWith(1, subscriptionExpiresAt, timestamp)
|
||||
})
|
||||
|
||||
it('should update offline subscription ends at', async () => {
|
||||
event.payload.offline = true
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(offlineUserSubscriptionRepository.save).toHaveBeenCalledWith(offlineUserSubscription)
|
||||
})
|
||||
|
||||
it('should update the user role', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.addUserRoleBasedOnSubscription).toHaveBeenCalledWith(user, SubscriptionName.ProPlan)
|
||||
})
|
||||
|
||||
it('should update the offline user role', async () => {
|
||||
event.payload.offline = true
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.setOfflineUserRole).toHaveBeenCalledWith(offlineUserSubscription)
|
||||
})
|
||||
|
||||
it('should not do anything if no user is found for specified email', async () => {
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.addUserRoleBasedOnSubscription).not.toHaveBeenCalled()
|
||||
expect(userSubscriptionRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not do anything if username is invalid', async () => {
|
||||
event.payload.userEmail = ' '
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.addUserRoleBasedOnSubscription).not.toHaveBeenCalled()
|
||||
expect(userSubscriptionRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not do anything if no offline subscription is found for specified id', async () => {
|
||||
event.payload.offline = true
|
||||
|
||||
offlineUserSubscriptionRepository.findOneBySubscriptionId = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.addUserRoleBasedOnSubscription).not.toHaveBeenCalled()
|
||||
expect(userSubscriptionRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -8,7 +8,7 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { Logger } from 'winston'
|
||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -71,7 +71,20 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
||||
private async addRoleToSubscriptionUsers(subscriptionId: number, subscriptionName: string): Promise<void> {
|
||||
const userSubscriptions = await this.userSubscriptionRepository.findBySubscriptionId(subscriptionId)
|
||||
for (const userSubscription of userSubscriptions) {
|
||||
const user = await userSubscription.user
|
||||
const userUuidOrError = Uuid.create(userSubscription.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
this.logger.warn(`Could not add role to user with uuid: ${userUuidOrError.getError()}`)
|
||||
|
||||
continue
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const user = await this.userRepository.findOneByUuid(userUuid)
|
||||
if (user === null) {
|
||||
this.logger.warn(`Could not find user with uuid: ${userUuid.value}`)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
await this.roleService.addUserRoleBasedOnSubscription(user, subscriptionName)
|
||||
}
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { ContentDecoderInterface, SubscriptionName } from '@standardnotes/common'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
import { SubscriptionSyncRequestedEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import * as dayjs from 'dayjs'
|
||||
|
||||
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { SubscriptionSyncRequestedEventHandler } from './SubscriptionSyncRequestedEventHandler'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { OfflineSettingServiceInterface } from '../Setting/OfflineSettingServiceInterface'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||
|
||||
describe('SubscriptionSyncRequestedEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
let offlineUserSubscription: OfflineUserSubscription
|
||||
let offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface
|
||||
let roleService: RoleServiceInterface
|
||||
let logger: Logger
|
||||
let user: User
|
||||
let subscription: UserSubscription
|
||||
let event: SubscriptionSyncRequestedEvent
|
||||
let subscriptionExpiresAt: number
|
||||
let settingService: SettingServiceInterface
|
||||
let subscriptionSettingService: SubscriptionSettingServiceInterface
|
||||
let timestamp: number
|
||||
let offlineSettingService: OfflineSettingServiceInterface
|
||||
let contentDecoder: ContentDecoderInterface
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionSyncRequestedEventHandler(
|
||||
userRepository,
|
||||
userSubscriptionRepository,
|
||||
offlineUserSubscriptionRepository,
|
||||
roleService,
|
||||
settingService,
|
||||
subscriptionSettingService,
|
||||
offlineSettingService,
|
||||
contentDecoder,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {
|
||||
uuid: '123',
|
||||
email: 'test@test.com',
|
||||
roles: Promise.resolve([
|
||||
{
|
||||
name: RoleName.NAMES.CoreUser,
|
||||
},
|
||||
]),
|
||||
} as jest.Mocked<User>
|
||||
subscription = {
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
} as jest.Mocked<UserSubscription>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(user)
|
||||
userRepository.save = jest.fn().mockReturnValue(user)
|
||||
|
||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||
userSubscriptionRepository.save = jest.fn().mockReturnValue(subscription)
|
||||
userSubscriptionRepository.findBySubscriptionIdAndType = jest.fn().mockReturnValue([])
|
||||
|
||||
offlineUserSubscription = {} as jest.Mocked<OfflineUserSubscription>
|
||||
|
||||
offlineUserSubscriptionRepository = {} as jest.Mocked<OfflineUserSubscriptionRepositoryInterface>
|
||||
offlineUserSubscriptionRepository.findOneBySubscriptionId = jest.fn().mockReturnValue(null)
|
||||
offlineUserSubscriptionRepository.save = jest.fn().mockReturnValue(offlineUserSubscription)
|
||||
|
||||
offlineSettingService = {} as jest.Mocked<OfflineSettingServiceInterface>
|
||||
offlineSettingService.createOrUpdate = jest.fn()
|
||||
|
||||
contentDecoder = {} as jest.Mocked<ContentDecoderInterface>
|
||||
contentDecoder.decode = jest.fn().mockReturnValue({
|
||||
featuresUrl: 'http://features-url',
|
||||
extensionKey: 'key',
|
||||
})
|
||||
|
||||
roleService = {} as jest.Mocked<RoleServiceInterface>
|
||||
roleService.addUserRoleBasedOnSubscription = jest.fn()
|
||||
roleService.setOfflineUserRole = jest.fn()
|
||||
|
||||
subscriptionExpiresAt = timestamp + 365 * 1000
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionSyncRequestedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
subscriptionName: SubscriptionName.ProPlan,
|
||||
subscriptionExpiresAt,
|
||||
timestamp: dayjs.utc().valueOf(),
|
||||
offline: false,
|
||||
extensionKey: 'abc123',
|
||||
offlineFeaturesToken: 'test',
|
||||
canceled: false,
|
||||
}
|
||||
|
||||
settingService = {} as jest.Mocked<SettingServiceInterface>
|
||||
settingService.createOrReplace = jest.fn()
|
||||
|
||||
subscriptionSettingService = {} as jest.Mocked<SubscriptionSettingServiceInterface>
|
||||
subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription = jest.fn()
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = jest.fn()
|
||||
logger.warn = jest.fn()
|
||||
})
|
||||
|
||||
it('should update the user role', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.addUserRoleBasedOnSubscription).toHaveBeenCalledWith(user, SubscriptionName.ProPlan)
|
||||
})
|
||||
|
||||
it('should update user default settings', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription).toHaveBeenCalledWith(
|
||||
subscription,
|
||||
)
|
||||
|
||||
expect(settingService.createOrReplace).toHaveBeenCalledWith({
|
||||
props: {
|
||||
name: 'EXTENSION_KEY',
|
||||
serverEncryptionVersion: 1,
|
||||
unencryptedValue: 'abc123',
|
||||
sensitive: true,
|
||||
},
|
||||
user: {
|
||||
email: 'test@test.com',
|
||||
roles: Promise.resolve([
|
||||
{
|
||||
name: RoleName.NAMES.CoreUser,
|
||||
},
|
||||
]),
|
||||
uuid: '123',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should update the offline user role', async () => {
|
||||
event.payload.offline = true
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.setOfflineUserRole).toHaveBeenCalledWith(offlineUserSubscription)
|
||||
})
|
||||
|
||||
it('should not update the offline user features token if it is not possible to decode the extension key', async () => {
|
||||
event.payload.offline = true
|
||||
|
||||
contentDecoder.decode = jest.fn().mockReturnValue({})
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(settingService.createOrReplace).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should create subscription', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
subscription.planName = SubscriptionName.ProPlan
|
||||
subscription.endsAt = subscriptionExpiresAt
|
||||
subscription.subscriptionId = 1
|
||||
subscription.user = Promise.resolve(user)
|
||||
|
||||
expect(userSubscriptionRepository.save).toHaveBeenCalledWith({
|
||||
...subscription,
|
||||
createdAt: expect.any(Number),
|
||||
updatedAt: expect.any(Number),
|
||||
cancelled: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should update an existing subscription', async () => {
|
||||
userSubscriptionRepository.findBySubscriptionIdAndType = jest
|
||||
.fn()
|
||||
.mockReturnValue([{} as jest.Mocked<UserSubscription>])
|
||||
await createHandler().handle(event)
|
||||
|
||||
subscription.planName = SubscriptionName.ProPlan
|
||||
subscription.endsAt = subscriptionExpiresAt
|
||||
subscription.subscriptionId = 1
|
||||
subscription.user = Promise.resolve(user)
|
||||
|
||||
expect(userSubscriptionRepository.save).toHaveBeenCalledWith({
|
||||
...subscription,
|
||||
createdAt: expect.any(Number),
|
||||
updatedAt: expect.any(Number),
|
||||
cancelled: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should create an offline subscription', async () => {
|
||||
event.payload.offline = true
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(offlineUserSubscriptionRepository.save).toHaveBeenCalledWith({
|
||||
endsAt: subscriptionExpiresAt,
|
||||
subscriptionId: 1,
|
||||
planName: 'PRO_PLAN',
|
||||
email: 'test@test.com',
|
||||
createdAt: expect.any(Number),
|
||||
updatedAt: expect.any(Number),
|
||||
cancelled: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should update an offline subscription', async () => {
|
||||
offlineUserSubscriptionRepository.findOneBySubscriptionId = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<OfflineUserSubscription>)
|
||||
event.payload.offline = true
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(offlineUserSubscriptionRepository.save).toHaveBeenCalledWith({
|
||||
endsAt: subscriptionExpiresAt,
|
||||
subscriptionId: 1,
|
||||
planName: 'PRO_PLAN',
|
||||
email: 'test@test.com',
|
||||
createdAt: expect.any(Number),
|
||||
updatedAt: expect.any(Number),
|
||||
cancelled: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should not do anything if no user is found for specified email', async () => {
|
||||
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.addUserRoleBasedOnSubscription).not.toHaveBeenCalled()
|
||||
expect(userSubscriptionRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not do anything if username is invalid', async () => {
|
||||
event.payload.userEmail = ' '
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(roleService.addUserRoleBasedOnSubscription).not.toHaveBeenCalled()
|
||||
expect(userSubscriptionRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,9 @@
|
||||
import { OfflineFeaturesTokenData } from '@standardnotes/security'
|
||||
import { SettingName, Username } from '@standardnotes/domain-core'
|
||||
import { ContentDecoderInterface } from '@standardnotes/common'
|
||||
import { DomainEventHandlerInterface, SubscriptionSyncRequestedEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
@@ -11,31 +11,23 @@ import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { OfflineSettingServiceInterface } from '../Setting/OfflineSettingServiceInterface'
|
||||
import { ContentDecoderInterface } from '@standardnotes/common'
|
||||
import { OfflineSettingName } from '../Setting/OfflineSettingName'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { ApplyDefaultSubscriptionSettings } from '../UseCase/ApplyDefaultSubscriptionSettings/ApplyDefaultSubscriptionSettings'
|
||||
import { SetSettingValue } from '../UseCase/SetSettingValue/SetSettingValue'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionSyncRequestedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_UserRepository) private userRepository: UserRepositoryInterface,
|
||||
@inject(TYPES.Auth_UserSubscriptionRepository)
|
||||
private userRepository: UserRepositoryInterface,
|
||||
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
||||
@inject(TYPES.Auth_OfflineUserSubscriptionRepository)
|
||||
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
|
||||
@inject(TYPES.Auth_RoleService) private roleService: RoleServiceInterface,
|
||||
@inject(TYPES.Auth_SettingService) private settingService: SettingServiceInterface,
|
||||
@inject(TYPES.Auth_SubscriptionSettingService)
|
||||
private subscriptionSettingService: SubscriptionSettingServiceInterface,
|
||||
@inject(TYPES.Auth_OfflineSettingService) private offlineSettingService: OfflineSettingServiceInterface,
|
||||
@inject(TYPES.Auth_ContenDecoder) private contentDecoder: ContentDecoderInterface,
|
||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||
private roleService: RoleServiceInterface,
|
||||
private applyDefaultSubscriptionSettings: ApplyDefaultSubscriptionSettings,
|
||||
private setSettingValue: SetSettingValue,
|
||||
private offlineSettingService: OfflineSettingServiceInterface,
|
||||
private contentDecoder: ContentDecoderInterface,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionSyncRequestedEvent): Promise<void> {
|
||||
@@ -95,17 +87,26 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
|
||||
|
||||
await this.roleService.addUserRoleBasedOnSubscription(user, event.payload.subscriptionName)
|
||||
|
||||
await this.subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription(userSubscription)
|
||||
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
unencryptedValue: event.payload.extensionKey,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
sensitive: true,
|
||||
},
|
||||
const applyingSettingsResult = await this.applyDefaultSubscriptionSettings.execute({
|
||||
userSubscriptionUuid: userSubscription.uuid,
|
||||
userUuid: user.uuid,
|
||||
subscriptionPlanName: event.payload.subscriptionName,
|
||||
})
|
||||
if (applyingSettingsResult.isFailed()) {
|
||||
this.logger.error(
|
||||
`Could not apply default subscription settings for user ${user.uuid}: ${applyingSettingsResult.getError()}`,
|
||||
)
|
||||
}
|
||||
|
||||
const result = await this.setSettingValue.execute({
|
||||
userUuid: user.uuid,
|
||||
settingName: SettingName.NAMES.ExtensionKey,
|
||||
value: event.payload.subscriptionName,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Could not set extension key for user ${user.uuid}`)
|
||||
}
|
||||
}
|
||||
|
||||
private async createOrUpdateSubscription(
|
||||
@@ -127,7 +128,7 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
|
||||
}
|
||||
|
||||
subscription.planName = subscriptionName
|
||||
subscription.user = Promise.resolve(user)
|
||||
subscription.userUuid = user.uuid
|
||||
subscription.createdAt = timestamp
|
||||
subscription.updatedAt = timestamp
|
||||
subscription.endsAt = subscriptionExpiresAt
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { UserDisabledSessionUserAgentLoggingEvent } from '@standardnotes/domain-events'
|
||||
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
|
||||
|
||||
import { UserDisabledSessionUserAgentLoggingEventHandler } from './UserDisabledSessionUserAgentLoggingEventHandler'
|
||||
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
|
||||
|
||||
describe('UserDisabledSessionUserAgentLoggingEventHandler', () => {
|
||||
let sessionRepository: SessionRepositoryInterface
|
||||
let revokedSessionRepository: RevokedSessionRepositoryInterface
|
||||
let event: UserDisabledSessionUserAgentLoggingEvent
|
||||
|
||||
const createHandler = () =>
|
||||
new UserDisabledSessionUserAgentLoggingEventHandler(sessionRepository, revokedSessionRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
sessionRepository = {} as jest.Mocked<SessionRepositoryInterface>
|
||||
sessionRepository.clearUserAgentByUserUuid = jest.fn()
|
||||
|
||||
revokedSessionRepository = {} as jest.Mocked<RevokedSessionRepositoryInterface>
|
||||
revokedSessionRepository.clearUserAgentByUserUuid = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<UserDisabledSessionUserAgentLoggingEvent>
|
||||
event.payload = {
|
||||
userUuid: '1-2-3',
|
||||
email: 'test@test.te',
|
||||
}
|
||||
})
|
||||
|
||||
it('should clear all user agent info from all user sessions', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(sessionRepository.clearUserAgentByUserUuid).toHaveBeenCalledWith('1-2-3')
|
||||
expect(revokedSessionRepository.clearUserAgentByUserUuid).toHaveBeenCalledWith('1-2-3')
|
||||
})
|
||||
})
|
||||
@@ -1,63 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { UserEmailChangedEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
import { AxiosInstance } from 'axios'
|
||||
|
||||
import { UserEmailChangedEventHandler } from './UserEmailChangedEventHandler'
|
||||
|
||||
describe('UserEmailChangedEventHandler', () => {
|
||||
let httpClient: AxiosInstance
|
||||
const userServerChangeEmailUrl = 'https://user-server/change-email'
|
||||
const userServerAuthKey = 'auth-key'
|
||||
let event: UserEmailChangedEvent
|
||||
let logger: Logger
|
||||
|
||||
const createHandler = () =>
|
||||
new UserEmailChangedEventHandler(httpClient, userServerChangeEmailUrl, userServerAuthKey, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
httpClient = {} as jest.Mocked<AxiosInstance>
|
||||
httpClient.request = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<UserEmailChangedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
userUuid: '1-2-3',
|
||||
fromEmail: 'test@test.te',
|
||||
toEmail: 'test2@test.te',
|
||||
}
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
})
|
||||
|
||||
it('should send a request to the user management server about an email change', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(httpClient.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
url: 'https://user-server/change-email',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
key: 'auth-key',
|
||||
user: {
|
||||
uuid: '1-2-3',
|
||||
from_email: 'test@test.te',
|
||||
to_email: 'test2@test.te',
|
||||
},
|
||||
},
|
||||
validateStatus: expect.any(Function),
|
||||
})
|
||||
})
|
||||
|
||||
it('should not send a request to the user management server about an email change if url is not defined', async () => {
|
||||
const handler = new UserEmailChangedEventHandler(httpClient, '', userServerAuthKey, logger)
|
||||
await handler.handle(event)
|
||||
|
||||
expect(httpClient.request).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,48 +0,0 @@
|
||||
import { DomainEventHandlerInterface, UserEmailChangedEvent } from '@standardnotes/domain-events'
|
||||
import { AxiosInstance } from 'axios'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
|
||||
@injectable()
|
||||
export class UserEmailChangedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_HTTPClient) private httpClient: AxiosInstance,
|
||||
@inject(TYPES.Auth_USER_SERVER_CHANGE_EMAIL_URL) private userServerChangeEmailUrl: string,
|
||||
@inject(TYPES.Auth_USER_SERVER_AUTH_KEY) private userServerAuthKey: string,
|
||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: UserEmailChangedEvent): Promise<void> {
|
||||
if (!this.userServerChangeEmailUrl) {
|
||||
this.logger.debug('User server change email url not defined. Skipped post email change actions.')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.logger.debug(`Changing user email from ${event.payload.fromEmail} to ${event.payload.toEmail}`)
|
||||
|
||||
await this.httpClient.request({
|
||||
method: 'POST',
|
||||
url: this.userServerChangeEmailUrl,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
key: this.userServerAuthKey,
|
||||
user: {
|
||||
uuid: event.payload.userUuid,
|
||||
from_email: event.payload.fromEmail,
|
||||
to_email: event.payload.toEmail,
|
||||
},
|
||||
},
|
||||
validateStatus:
|
||||
/* istanbul ignore next */
|
||||
(status: number) => status >= 200 && status < 500,
|
||||
})
|
||||
|
||||
this.logger.debug(`Successfully changed user email to ${event.payload.toEmail}`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
DomainEventHandlerInterface,
|
||||
DomainEventPublisherInterface,
|
||||
UserInvitedToSharedVaultEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { EmailLevel, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||
import { getBody, getSubject } from '../Email/UserInvitedToSharedVault'
|
||||
|
||||
export class UserInvitedToSharedVaultEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
private userRepository: UserRepositoryInterface,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
) {}
|
||||
|
||||
async handle(event: UserInvitedToSharedVaultEvent): Promise<void> {
|
||||
const userUuidOrError = Uuid.create(event.payload.invite.user_uuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const user = await this.userRepository.findOneByUuid(userUuid)
|
||||
if (!user) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createEmailRequestedEvent({
|
||||
body: getBody(),
|
||||
level: EmailLevel.LEVELS.System,
|
||||
subject: getSubject(),
|
||||
messageIdentifier: 'USER_INVITED_TO_SHARED_VAULT',
|
||||
userEmail: user.email,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
import { UserRegisteredEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { UserRegisteredEventHandler } from './UserRegisteredEventHandler'
|
||||
import { AxiosInstance } from 'axios'
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
|
||||
describe('UserRegisteredEventHandler', () => {
|
||||
let httpClient: AxiosInstance
|
||||
const userServerRegistrationUrl = 'https://user-server/registration'
|
||||
const userServerAuthKey = 'auth-key'
|
||||
let event: UserRegisteredEvent
|
||||
let logger: Logger
|
||||
|
||||
const createHandler = () =>
|
||||
new UserRegisteredEventHandler(httpClient, userServerRegistrationUrl, userServerAuthKey, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
httpClient = {} as jest.Mocked<AxiosInstance>
|
||||
httpClient.request = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<UserRegisteredEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
userUuid: '1-2-3',
|
||||
email: 'test@test.te',
|
||||
protocolVersion: ProtocolVersion.V004,
|
||||
}
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
})
|
||||
|
||||
it('should send a request to the user management server about a registration', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(httpClient.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
url: 'https://user-server/registration',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
key: 'auth-key',
|
||||
user: {
|
||||
created_at: new Date(1),
|
||||
email: 'test@test.te',
|
||||
},
|
||||
},
|
||||
validateStatus: expect.any(Function),
|
||||
})
|
||||
})
|
||||
|
||||
it('should not send a request to the user management server about a registration if url is not defined', async () => {
|
||||
const handler = new UserRegisteredEventHandler(httpClient, '', userServerAuthKey, logger)
|
||||
await handler.handle(event)
|
||||
|
||||
expect(httpClient.request).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
import { DomainEventHandlerInterface, UserRegisteredEvent } from '@standardnotes/domain-events'
|
||||
import { AxiosInstance } from 'axios'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
|
||||
@injectable()
|
||||
export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_HTTPClient) private httpClient: AxiosInstance,
|
||||
@inject(TYPES.Auth_USER_SERVER_REGISTRATION_URL) private userServerRegistrationUrl: string,
|
||||
@inject(TYPES.Auth_USER_SERVER_AUTH_KEY) private userServerAuthKey: string,
|
||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: UserRegisteredEvent): Promise<void> {
|
||||
if (!this.userServerRegistrationUrl) {
|
||||
this.logger.debug('User server registration url not defined. Skipped post-registration actions.')
|
||||
return
|
||||
}
|
||||
|
||||
await this.httpClient.request({
|
||||
method: 'POST',
|
||||
url: this.userServerRegistrationUrl,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
key: this.userServerAuthKey,
|
||||
user: {
|
||||
email: event.payload.email,
|
||||
created_at: event.createdAt,
|
||||
},
|
||||
},
|
||||
validateStatus:
|
||||
/* istanbul ignore next */
|
||||
(status: number) => status >= 200 && status < 500,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import { EphemeralSessionRepositoryInterface } from './EphemeralSessionRepositor
|
||||
import { EphemeralSession } from './EphemeralSession'
|
||||
import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface'
|
||||
import { RevokedSession } from './RevokedSession'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { LogSessionUserAgentOption } from '@standardnotes/settings'
|
||||
import { Setting } from '../Setting/Setting'
|
||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||
@@ -19,6 +18,7 @@ import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscri
|
||||
import { TraceSession } from '../UseCase/TraceSession/TraceSession'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { GetSetting } from '../UseCase/GetSetting/GetSetting'
|
||||
|
||||
describe('SessionService', () => {
|
||||
let sessionRepository: SessionRepositoryInterface
|
||||
@@ -27,7 +27,7 @@ describe('SessionService', () => {
|
||||
let existingSession: Session
|
||||
let existingEphemeralSession: EphemeralSession
|
||||
let revokedSession: RevokedSession
|
||||
let settingService: SettingServiceInterface
|
||||
let getSetting: GetSetting
|
||||
let deviceDetector: UAParser
|
||||
let timer: TimerInterface
|
||||
let logger: winston.Logger
|
||||
@@ -46,11 +46,11 @@ describe('SessionService', () => {
|
||||
logger,
|
||||
123,
|
||||
234,
|
||||
settingService,
|
||||
cryptoNode,
|
||||
traceSession,
|
||||
userSubscriptionRepository,
|
||||
readonlyUsers,
|
||||
getSetting,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -72,8 +72,8 @@ describe('SessionService', () => {
|
||||
sessionRepository.insert = jest.fn()
|
||||
sessionRepository.update = jest.fn()
|
||||
|
||||
settingService = {} as jest.Mocked<SettingServiceInterface>
|
||||
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
|
||||
getSetting = {} as jest.Mocked<GetSetting>
|
||||
getSetting.execute = jest.fn().mockReturnValue(Result.fail('not found'))
|
||||
|
||||
ephemeralSessionRepository = {} as jest.Mocked<EphemeralSessionRepositoryInterface>
|
||||
ephemeralSessionRepository.insert = jest.fn()
|
||||
@@ -240,9 +240,12 @@ describe('SessionService', () => {
|
||||
const user = {} as jest.Mocked<User>
|
||||
user.uuid = '123'
|
||||
|
||||
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue({
|
||||
value: LogSessionUserAgentOption.Disabled,
|
||||
} as jest.Mocked<Setting>)
|
||||
getSetting.execute = jest.fn().mockReturnValue(
|
||||
Result.ok({
|
||||
setting: {} as jest.Mocked<Setting>,
|
||||
decryptedValue: LogSessionUserAgentOption.Disabled,
|
||||
}),
|
||||
)
|
||||
|
||||
const result = await createService().createNewSessionForUser({
|
||||
user,
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import * as crypto from 'crypto'
|
||||
import * as dayjs from 'dayjs'
|
||||
import { UAParser } from 'ua-parser-js'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { UAParserInstance } from 'ua-parser-js'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { Logger } from 'winston'
|
||||
import { LogSessionUserAgentOption, SettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { LogSessionUserAgentOption } from '@standardnotes/settings'
|
||||
import { SessionBody } from '@standardnotes/responses'
|
||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { Session } from './Session'
|
||||
import { SessionRepositoryInterface } from './SessionRepositoryInterface'
|
||||
import { SessionServiceInterface } from './SessionServiceInterface'
|
||||
@@ -18,30 +17,27 @@ import { EphemeralSessionRepositoryInterface } from './EphemeralSessionRepositor
|
||||
import { EphemeralSession } from './EphemeralSession'
|
||||
import { RevokedSession } from './RevokedSession'
|
||||
import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { TraceSession } from '../UseCase/TraceSession/TraceSession'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { GetSetting } from '../UseCase/GetSetting/GetSetting'
|
||||
|
||||
@injectable()
|
||||
export class SessionService implements SessionServiceInterface {
|
||||
static readonly SESSION_TOKEN_VERSION = 1
|
||||
|
||||
constructor(
|
||||
@inject(TYPES.Auth_SessionRepository) private sessionRepository: SessionRepositoryInterface,
|
||||
@inject(TYPES.Auth_EphemeralSessionRepository)
|
||||
private sessionRepository: SessionRepositoryInterface,
|
||||
private ephemeralSessionRepository: EphemeralSessionRepositoryInterface,
|
||||
@inject(TYPES.Auth_RevokedSessionRepository) private revokedSessionRepository: RevokedSessionRepositoryInterface,
|
||||
@inject(TYPES.Auth_DeviceDetector) private deviceDetector: UAParser,
|
||||
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
|
||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||
@inject(TYPES.Auth_ACCESS_TOKEN_AGE) private accessTokenAge: number,
|
||||
@inject(TYPES.Auth_REFRESH_TOKEN_AGE) private refreshTokenAge: number,
|
||||
@inject(TYPES.Auth_SettingService) private settingService: SettingServiceInterface,
|
||||
@inject(TYPES.Auth_CryptoNode) private cryptoNode: CryptoNode,
|
||||
@inject(TYPES.Auth_TraceSession) private traceSession: TraceSession,
|
||||
@inject(TYPES.Auth_UserSubscriptionRepository)
|
||||
private revokedSessionRepository: RevokedSessionRepositoryInterface,
|
||||
private deviceDetector: UAParserInstance,
|
||||
private timer: TimerInterface,
|
||||
private logger: Logger,
|
||||
private accessTokenAge: number,
|
||||
private refreshTokenAge: number,
|
||||
private cryptoNode: CryptoNode,
|
||||
private traceSession: TraceSession,
|
||||
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
||||
@inject(TYPES.Auth_READONLY_USERS) private readonlyUsers: string[],
|
||||
private readonlyUsers: string[],
|
||||
private getSetting: GetSetting,
|
||||
) {}
|
||||
|
||||
async createNewSessionForUser(dto: {
|
||||
@@ -320,15 +316,17 @@ export class SessionService implements SessionServiceInterface {
|
||||
}
|
||||
|
||||
private async isLoggingUserAgentEnabledOnSessions(user: User): Promise<boolean> {
|
||||
const loggingSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||
settingName: SettingName.create(SettingName.NAMES.LogSessionUserAgent).getValue(),
|
||||
const loggingSettingOrError = await this.getSetting.execute({
|
||||
settingName: SettingName.NAMES.LogSessionUserAgent,
|
||||
decrypted: true,
|
||||
userUuid: user.uuid,
|
||||
allowSensitiveRetrieval: true,
|
||||
})
|
||||
|
||||
if (loggingSetting === null) {
|
||||
if (loggingSettingOrError.isFailed()) {
|
||||
return true
|
||||
}
|
||||
const loggingSetting = loggingSettingOrError.getValue()
|
||||
|
||||
return loggingSetting.value === LogSessionUserAgentOption.Enabled
|
||||
return loggingSetting.decryptedValue === LogSessionUserAgentOption.Enabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { User } from '../User/User'
|
||||
import { SettingProps } from './SettingProps'
|
||||
|
||||
export type CreateOrReplaceSettingDto = {
|
||||
user: User
|
||||
props: SettingProps
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { Setting } from './Setting'
|
||||
|
||||
export type CreateOrReplaceSettingResponse = {
|
||||
status: 'created' | 'replaced'
|
||||
setting: Setting
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { User } from '../User/User'
|
||||
import { SubscriptionSettingProps } from './SubscriptionSettingProps'
|
||||
|
||||
export type CreateOrReplaceSubscriptionSettingDTO = {
|
||||
userSubscription: UserSubscription
|
||||
user: User
|
||||
props: SubscriptionSettingProps
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { SubscriptionSetting } from './SubscriptionSetting'
|
||||
|
||||
export type CreateOrReplaceSubscriptionSettingResponse = {
|
||||
status: 'created' | 'replaced'
|
||||
subscriptionSetting: SubscriptionSetting
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
|
||||
export type FindSettingDTO = {
|
||||
userUuid: string
|
||||
settingName: SettingName
|
||||
settingUuid?: string
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
|
||||
export type FindSubscriptionSettingDTO = {
|
||||
userUuid: string
|
||||
userSubscriptionUuid: string
|
||||
subscriptionSettingName: SettingName
|
||||
settingUuid?: string
|
||||
}
|
||||
@@ -1,60 +1,13 @@
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { User } from '../User/User'
|
||||
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
|
||||
@Entity({ name: 'settings' })
|
||||
@Index('index_settings_on_name_and_user_uuid', ['name', 'user'])
|
||||
export class Setting {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
import { SettingProps } from './SettingProps'
|
||||
|
||||
@Column({
|
||||
length: 255,
|
||||
})
|
||||
declare name: string
|
||||
export class Setting extends Entity<SettingProps> {
|
||||
private constructor(props: SettingProps, id?: UniqueEntityId) {
|
||||
super(props, id)
|
||||
}
|
||||
|
||||
@Column({
|
||||
type: 'text',
|
||||
nullable: true,
|
||||
})
|
||||
declare value: string | null
|
||||
|
||||
@Column({
|
||||
name: 'server_encryption_version',
|
||||
type: 'tinyint',
|
||||
default: EncryptionVersion.Unencrypted,
|
||||
})
|
||||
declare serverEncryptionVersion: number
|
||||
|
||||
@Column({
|
||||
name: 'created_at',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare createdAt: number
|
||||
|
||||
@Column({
|
||||
name: 'updated_at',
|
||||
type: 'bigint',
|
||||
})
|
||||
@Index('index_settings_on_updated_at')
|
||||
declare updatedAt: number
|
||||
|
||||
@ManyToOne(
|
||||
/* istanbul ignore next */
|
||||
() => User,
|
||||
/* istanbul ignore next */
|
||||
(user) => user.settings,
|
||||
/* istanbul ignore next */
|
||||
{ onDelete: 'CASCADE', nullable: false, lazy: true, eager: false },
|
||||
)
|
||||
@JoinColumn({ name: 'user_uuid', referencedColumnName: 'uuid' })
|
||||
declare user: Promise<User>
|
||||
|
||||
@Column({
|
||||
type: 'tinyint',
|
||||
width: 1,
|
||||
nullable: false,
|
||||
default: 0,
|
||||
})
|
||||
declare sensitive: boolean
|
||||
static create(props: SettingProps, id?: UniqueEntityId): Result<Setting> {
|
||||
return Result.ok<Setting>(new Setting(props, id))
|
||||
}
|
||||
}
|
||||
|
||||
239
packages/auth/src/Domain/Setting/SettingCrypter.spec.ts
Normal file
239
packages/auth/src/Domain/Setting/SettingCrypter.spec.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
import 'reflect-metadata'
|
||||
import { CrypterInterface } from '../Encryption/CrypterInterface'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { Setting } from './Setting'
|
||||
|
||||
import { SettingCrypter } from './SettingCrypter'
|
||||
import { SubscriptionSetting } from './SubscriptionSetting'
|
||||
import { SettingName, Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
describe('SettingCrypter', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
let crypter: CrypterInterface
|
||||
let user: User
|
||||
|
||||
const createDecrypter = () => new SettingCrypter(userRepository, crypter)
|
||||
|
||||
beforeEach(() => {
|
||||
crypter = {} as jest.Mocked<CrypterInterface>
|
||||
crypter.decryptForUser = jest.fn().mockReturnValue('decrypted')
|
||||
|
||||
user = {
|
||||
uuid: '4-5-6',
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
|
||||
})
|
||||
|
||||
describe('setting', () => {
|
||||
it('should encrypt a string value', async () => {
|
||||
const string = 'decrypted'
|
||||
|
||||
crypter.encryptForUser = jest.fn().mockReturnValue('encrypted')
|
||||
|
||||
const encrypted = await createDecrypter().encryptValue(
|
||||
string,
|
||||
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
)
|
||||
|
||||
expect(encrypted).toEqual('encrypted')
|
||||
})
|
||||
|
||||
it('should return null when trying to encrypt a null value', async () => {
|
||||
const encrypted = await createDecrypter().encryptValue(
|
||||
null,
|
||||
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
)
|
||||
|
||||
expect(encrypted).toBeNull()
|
||||
})
|
||||
|
||||
it('should throw error when encrypting and user is not found', async () => {
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
let caughtError = null
|
||||
try {
|
||||
await createDecrypter().encryptValue('test', Uuid.create('00000000-0000-0000-0000-000000000000').getValue())
|
||||
} catch (error) {
|
||||
caughtError = error
|
||||
}
|
||||
|
||||
expect(caughtError).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should decrypt an encrypted value of a setting', async () => {
|
||||
const setting = Setting.create({
|
||||
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||
value: 'encrypted',
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sensitive: false,
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
expect(await createDecrypter().decryptSettingValue(setting, '00000000-0000-0000-0000-000000000000')).toEqual(
|
||||
'decrypted',
|
||||
)
|
||||
})
|
||||
|
||||
it('should return null if the setting value is null', async () => {
|
||||
const setting = Setting.create({
|
||||
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||
value: null,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sensitive: false,
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
expect(await createDecrypter().decryptSettingValue(setting, '00000000-0000-0000-0000-000000000000')).toBeNull()
|
||||
})
|
||||
|
||||
it('should return unencrypted value if the setting value is unencrypted', async () => {
|
||||
const setting = Setting.create({
|
||||
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||
value: 'test',
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sensitive: false,
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
expect(await createDecrypter().decryptSettingValue(setting, '00000000-0000-0000-0000-000000000000')).toEqual(
|
||||
'test',
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw if the user could not be found', async () => {
|
||||
const setting = Setting.create({
|
||||
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||
value: 'encrypted',
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sensitive: false,
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
let caughtError = null
|
||||
try {
|
||||
await createDecrypter().decryptSettingValue(setting, '00000000-0000-0000-0000-000000000000')
|
||||
} catch (error) {
|
||||
caughtError = error
|
||||
}
|
||||
|
||||
expect(caughtError).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should throw if the user uuid is invalid', async () => {
|
||||
const setting = Setting.create({
|
||||
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||
value: 'encrypted',
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sensitive: false,
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
let caughtError = null
|
||||
try {
|
||||
await createDecrypter().decryptSettingValue(setting, 'invalid')
|
||||
} catch (error) {
|
||||
caughtError = error
|
||||
}
|
||||
|
||||
expect(caughtError).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('subscription setting', () => {
|
||||
it('should decrypt an encrypted value of a setting', async () => {
|
||||
const setting = SubscriptionSetting.create({
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
value: 'encrypted',
|
||||
sensitive: true,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
userSubscriptionUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
expect(
|
||||
await createDecrypter().decryptSubscriptionSettingValue(setting, '00000000-0000-0000-0000-000000000000'),
|
||||
).toEqual('decrypted')
|
||||
})
|
||||
|
||||
it('should return null if the setting value is null', async () => {
|
||||
const setting = SubscriptionSetting.create({
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
value: null,
|
||||
sensitive: true,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
userSubscriptionUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
expect(
|
||||
await createDecrypter().decryptSubscriptionSettingValue(setting, '00000000-0000-0000-0000-000000000000'),
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('should return unencrypted value if the setting value is unencrypted', async () => {
|
||||
const setting = SubscriptionSetting.create({
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
value: 'test',
|
||||
sensitive: true,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
userSubscriptionUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
expect(
|
||||
await createDecrypter().decryptSubscriptionSettingValue(setting, '00000000-0000-0000-0000-000000000000'),
|
||||
).toEqual('test')
|
||||
})
|
||||
|
||||
it('should throw if the user could not be found', async () => {
|
||||
const setting = SubscriptionSetting.create({
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
value: 'encrypted',
|
||||
sensitive: true,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
userSubscriptionUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
let caughtError = null
|
||||
try {
|
||||
await createDecrypter().decryptSubscriptionSettingValue(setting, '00000000-0000-0000-0000-000000000000')
|
||||
} catch (error) {
|
||||
caughtError = error
|
||||
}
|
||||
|
||||
expect(caughtError).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should throw if the user uuid is invalid', async () => {
|
||||
const setting = SubscriptionSetting.create({
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
value: 'encrypted',
|
||||
sensitive: true,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
userSubscriptionUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
let caughtError = null
|
||||
try {
|
||||
await createDecrypter().decryptSubscriptionSettingValue(setting, 'invalid')
|
||||
} catch (error) {
|
||||
caughtError = error
|
||||
}
|
||||
|
||||
expect(caughtError).not.toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
60
packages/auth/src/Domain/Setting/SettingCrypter.ts
Normal file
60
packages/auth/src/Domain/Setting/SettingCrypter.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { CrypterInterface } from '../Encryption/CrypterInterface'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { Setting } from './Setting'
|
||||
import { SettingCrypterInterface } from './SettingCrypterInterface'
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
import { SubscriptionSetting } from './SubscriptionSetting'
|
||||
|
||||
export class SettingCrypter implements SettingCrypterInterface {
|
||||
constructor(
|
||||
private userRepository: UserRepositoryInterface,
|
||||
private crypter: CrypterInterface,
|
||||
) {}
|
||||
|
||||
async encryptValue(value: string | null, userUuid: Uuid): Promise<string | null> {
|
||||
if (value === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const user = await this.userRepository.findOneByUuid(userUuid)
|
||||
|
||||
if (user === null) {
|
||||
throw new Error(`Could not find user with uuid: ${userUuid.value}`)
|
||||
}
|
||||
|
||||
return this.crypter.encryptForUser(value, user)
|
||||
}
|
||||
|
||||
async decryptSettingValue(setting: Setting, userUuidString: string): Promise<string | null> {
|
||||
return this.decrypt(setting.props.value, setting.props.serverEncryptionVersion, userUuidString)
|
||||
}
|
||||
|
||||
async decryptSubscriptionSettingValue(setting: SubscriptionSetting, userUuidString: string): Promise<string | null> {
|
||||
return this.decrypt(setting.props.value, setting.props.serverEncryptionVersion, userUuidString)
|
||||
}
|
||||
|
||||
private async decrypt(
|
||||
value: string | null,
|
||||
serverEncryptionVersion: number,
|
||||
userUuidString: string,
|
||||
): Promise<string | null> {
|
||||
if (value !== null && serverEncryptionVersion === EncryptionVersion.Default) {
|
||||
const userUuidOrError = Uuid.create(userUuidString)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
throw new Error(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const user = await this.userRepository.findOneByUuid(userUuid)
|
||||
|
||||
if (user === null) {
|
||||
throw new Error(`Could not find user with uuid: ${userUuid.value}`)
|
||||
}
|
||||
|
||||
return this.crypter.decryptForUser(value, user)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
}
|
||||
10
packages/auth/src/Domain/Setting/SettingCrypterInterface.ts
Normal file
10
packages/auth/src/Domain/Setting/SettingCrypterInterface.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { Setting } from './Setting'
|
||||
import { SubscriptionSetting } from './SubscriptionSetting'
|
||||
|
||||
export interface SettingCrypterInterface {
|
||||
encryptValue(value: string | null, userUuid: Uuid): Promise<string | null>
|
||||
decryptSettingValue(value: Setting, userUuid: string): Promise<string | null>
|
||||
decryptSubscriptionSettingValue(setting: SubscriptionSetting, userUuid: string): Promise<string | null>
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
import { CrypterInterface } from '../Encryption/CrypterInterface'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { Setting } from './Setting'
|
||||
|
||||
import { SettingDecrypter } from './SettingDecrypter'
|
||||
|
||||
describe('SettingDecrypter', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
let crypter: CrypterInterface
|
||||
let user: User
|
||||
|
||||
const createDecrypter = () => new SettingDecrypter(userRepository, crypter)
|
||||
|
||||
beforeEach(() => {
|
||||
crypter = {} as jest.Mocked<CrypterInterface>
|
||||
crypter.decryptForUser = jest.fn().mockReturnValue('decrypted')
|
||||
|
||||
user = {
|
||||
uuid: '4-5-6',
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
|
||||
})
|
||||
|
||||
it('should decrypt an encrypted value of a setting', async () => {
|
||||
const setting = {
|
||||
value: 'encrypted',
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
expect(await createDecrypter().decryptSettingValue(setting, '00000000-0000-0000-0000-000000000000')).toEqual(
|
||||
'decrypted',
|
||||
)
|
||||
})
|
||||
|
||||
it('should return null if the setting value is null', async () => {
|
||||
const setting = {
|
||||
value: null,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
expect(await createDecrypter().decryptSettingValue(setting, '00000000-0000-0000-0000-000000000000')).toBeNull()
|
||||
})
|
||||
|
||||
it('should return unencrypted value if the setting value is unencrypted', async () => {
|
||||
const setting = {
|
||||
value: 'test',
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
expect(await createDecrypter().decryptSettingValue(setting, '00000000-0000-0000-0000-000000000000')).toEqual('test')
|
||||
})
|
||||
|
||||
it('should throw if the user could not be found', async () => {
|
||||
const setting = {
|
||||
value: 'encrypted',
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
} as jest.Mocked<Setting>
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
let caughtError = null
|
||||
try {
|
||||
await createDecrypter().decryptSettingValue(setting, '00000000-0000-0000-0000-000000000000')
|
||||
} catch (error) {
|
||||
caughtError = error
|
||||
}
|
||||
|
||||
expect(caughtError).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should throw if the user uuid is invalid', async () => {
|
||||
const setting = {
|
||||
value: 'encrypted',
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
let caughtError = null
|
||||
try {
|
||||
await createDecrypter().decryptSettingValue(setting, 'invalid')
|
||||
} catch (error) {
|
||||
caughtError = error
|
||||
}
|
||||
|
||||
expect(caughtError).not.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,37 +0,0 @@
|
||||
import { inject, injectable } from 'inversify'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { CrypterInterface } from '../Encryption/CrypterInterface'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { Setting } from './Setting'
|
||||
import { SettingDecrypterInterface } from './SettingDecrypterInterface'
|
||||
import { SubscriptionSetting } from './SubscriptionSetting'
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
@injectable()
|
||||
export class SettingDecrypter implements SettingDecrypterInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_UserRepository) private userRepository: UserRepositoryInterface,
|
||||
@inject(TYPES.Auth_Crypter) private crypter: CrypterInterface,
|
||||
) {}
|
||||
|
||||
async decryptSettingValue(setting: Setting | SubscriptionSetting, userUuidString: string): Promise<string | null> {
|
||||
if (setting.value !== null && setting.serverEncryptionVersion === EncryptionVersion.Default) {
|
||||
const userUuidOrError = Uuid.create(userUuidString)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
throw new Error(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const user = await this.userRepository.findOneByUuid(userUuid)
|
||||
|
||||
if (user === null) {
|
||||
throw new Error(`Could not find user with uuid: ${userUuid.value}`)
|
||||
}
|
||||
|
||||
return this.crypter.decryptForUser(setting.value, user)
|
||||
}
|
||||
|
||||
return setting.value
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { Setting } from './Setting'
|
||||
import { SubscriptionSetting } from './SubscriptionSetting'
|
||||
|
||||
export interface SettingDecrypterInterface {
|
||||
decryptSettingValue(setting: Setting | SubscriptionSetting, userUuid: string): Promise<string | null>
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
|
||||
export type SettingDescription = {
|
||||
value: string
|
||||
sensitive: boolean
|
||||
serverEncryptionVersion: EncryptionVersion
|
||||
replaceable: boolean
|
||||
}
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { CrypterInterface } from '../Encryption/CrypterInterface'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { User } from '../User/User'
|
||||
import { Setting } from './Setting'
|
||||
import { SettingFactory } from './SettingFactory'
|
||||
import { SettingProps } from './SettingProps'
|
||||
import { SubscriptionSettingProps } from './SubscriptionSettingProps'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { SubscriptionSetting } from './SubscriptionSetting'
|
||||
|
||||
describe('SettingFactory', () => {
|
||||
let crypter: CrypterInterface
|
||||
let timer: TimerInterface
|
||||
let user: User
|
||||
let userSubscription: UserSubscription
|
||||
|
||||
const createFactory = () => new SettingFactory(crypter, timer)
|
||||
|
||||
beforeEach(() => {
|
||||
crypter = {} as jest.Mocked<CrypterInterface>
|
||||
crypter.encryptForUser = jest.fn().mockReturnValue('encrypted')
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
|
||||
|
||||
user = {} as jest.Mocked<User>
|
||||
|
||||
userSubscription = {
|
||||
user: Promise.resolve(user),
|
||||
} as jest.Mocked<UserSubscription>
|
||||
})
|
||||
|
||||
it('should create a Setting', async () => {
|
||||
const props: SettingProps = {
|
||||
name: 'name',
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
sensitive: false,
|
||||
}
|
||||
const actual = await createFactory().create(props, user)
|
||||
|
||||
expect(actual).toEqual({
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
name: 'name',
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: 0,
|
||||
user: Promise.resolve(user),
|
||||
uuid: expect.any(String),
|
||||
value: 'value',
|
||||
})
|
||||
})
|
||||
|
||||
it('should create a SubscriptionSetting', async () => {
|
||||
const props: SubscriptionSettingProps = {
|
||||
name: 'name',
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
sensitive: false,
|
||||
}
|
||||
const actual = await createFactory().createSubscriptionSetting(props, userSubscription)
|
||||
|
||||
expect(actual).toEqual({
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
name: 'name',
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: 0,
|
||||
userSubscription: Promise.resolve(userSubscription),
|
||||
uuid: expect.any(String),
|
||||
value: 'value',
|
||||
})
|
||||
})
|
||||
|
||||
it('should create an encrypted SubscriptionSetting', async () => {
|
||||
const value = 'value'
|
||||
const props: SettingProps = {
|
||||
name: 'name',
|
||||
unencryptedValue: value,
|
||||
sensitive: false,
|
||||
}
|
||||
|
||||
const actual = await createFactory().createSubscriptionSetting(props, userSubscription)
|
||||
|
||||
expect(actual).toEqual({
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
name: 'name',
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: 1,
|
||||
userSubscription: Promise.resolve(userSubscription),
|
||||
uuid: expect.any(String),
|
||||
value: 'encrypted',
|
||||
})
|
||||
})
|
||||
|
||||
it('should create a SubscriptionSetting replacement', async () => {
|
||||
const original = {
|
||||
userSubscription: Promise.resolve(userSubscription),
|
||||
} as jest.Mocked<SubscriptionSetting>
|
||||
original.uuid = '2-3-4'
|
||||
|
||||
const props: SettingProps = {
|
||||
name: 'name',
|
||||
unencryptedValue: 'value2',
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
sensitive: true,
|
||||
}
|
||||
|
||||
const actual = await createFactory().createSubscriptionSettingReplacement(original, props)
|
||||
|
||||
expect(actual).toEqual({
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
name: 'name',
|
||||
sensitive: true,
|
||||
serverEncryptionVersion: 0,
|
||||
userSubscription: Promise.resolve(userSubscription),
|
||||
uuid: '2-3-4',
|
||||
value: 'value2',
|
||||
})
|
||||
})
|
||||
|
||||
it('should create a Setting replacement', async () => {
|
||||
const original = {} as jest.Mocked<Setting>
|
||||
original.uuid = '2-3-4'
|
||||
|
||||
const props: SettingProps = {
|
||||
name: 'name',
|
||||
unencryptedValue: 'value2',
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
sensitive: true,
|
||||
}
|
||||
|
||||
const actual = await createFactory().createReplacement(original, props)
|
||||
|
||||
expect(actual).toEqual({
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
name: 'name',
|
||||
sensitive: true,
|
||||
serverEncryptionVersion: 0,
|
||||
user: Promise.resolve(user),
|
||||
uuid: '2-3-4',
|
||||
value: 'value2',
|
||||
})
|
||||
})
|
||||
|
||||
it('should create an encrypted Setting', async () => {
|
||||
const value = 'value'
|
||||
const props: SettingProps = {
|
||||
name: 'name',
|
||||
unencryptedValue: value,
|
||||
sensitive: false,
|
||||
}
|
||||
|
||||
const actual = await createFactory().create(props, user)
|
||||
|
||||
expect(actual).toEqual({
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
name: 'name',
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: 1,
|
||||
user: Promise.resolve(user),
|
||||
uuid: expect.any(String),
|
||||
value: 'encrypted',
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw for unrecognized encryption version', async () => {
|
||||
const value = 'value'
|
||||
const props: SettingProps = {
|
||||
name: 'name',
|
||||
unencryptedValue: value,
|
||||
serverEncryptionVersion: 99999999999,
|
||||
sensitive: false,
|
||||
}
|
||||
|
||||
await expect(async () => await createFactory().create(props, user)).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
@@ -1,114 +0,0 @@
|
||||
import { inject, injectable } from 'inversify'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { User } from '../User/User'
|
||||
import { Setting } from './Setting'
|
||||
import { SettingProps } from './SettingProps'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { CrypterInterface } from '../Encryption/CrypterInterface'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { SettingFactoryInterface } from './SettingFactoryInterface'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { SubscriptionSetting } from './SubscriptionSetting'
|
||||
import { SubscriptionSettingProps } from './SubscriptionSettingProps'
|
||||
|
||||
@injectable()
|
||||
export class SettingFactory implements SettingFactoryInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_Crypter) private crypter: CrypterInterface,
|
||||
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async createSubscriptionSetting(
|
||||
props: SubscriptionSettingProps,
|
||||
userSubscription: UserSubscription,
|
||||
): Promise<SubscriptionSetting> {
|
||||
const uuid = props.uuid ?? uuidv4()
|
||||
const now = this.timer.getTimestampInMicroseconds()
|
||||
const createdAt = props.createdAt ?? now
|
||||
const updatedAt = props.updatedAt ?? now
|
||||
|
||||
const { name, unencryptedValue, serverEncryptionVersion = EncryptionVersion.Default, sensitive } = props
|
||||
|
||||
const subscriptionSetting = {
|
||||
uuid,
|
||||
userSubscription: Promise.resolve(userSubscription),
|
||||
name,
|
||||
value: await this.createValue({
|
||||
unencryptedValue,
|
||||
serverEncryptionVersion,
|
||||
user: await userSubscription.user,
|
||||
}),
|
||||
serverEncryptionVersion,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
sensitive,
|
||||
}
|
||||
|
||||
return Object.assign(new SubscriptionSetting(), subscriptionSetting)
|
||||
}
|
||||
|
||||
async createSubscriptionSettingReplacement(
|
||||
original: SubscriptionSetting,
|
||||
props: SubscriptionSettingProps,
|
||||
): Promise<SubscriptionSetting> {
|
||||
const { uuid, userSubscription } = original
|
||||
|
||||
return Object.assign(await this.createSubscriptionSetting(props, await userSubscription), {
|
||||
uuid,
|
||||
})
|
||||
}
|
||||
|
||||
async create(props: SettingProps, user: User): Promise<Setting> {
|
||||
const uuid = props.uuid ?? uuidv4()
|
||||
const now = this.timer.getTimestampInMicroseconds()
|
||||
const createdAt = props.createdAt ?? now
|
||||
const updatedAt = props.updatedAt ?? now
|
||||
|
||||
const { name, unencryptedValue, serverEncryptionVersion = EncryptionVersion.Default, sensitive } = props
|
||||
|
||||
const setting = {
|
||||
uuid,
|
||||
user: Promise.resolve(user),
|
||||
name,
|
||||
value: await this.createValue({
|
||||
unencryptedValue,
|
||||
serverEncryptionVersion,
|
||||
user,
|
||||
}),
|
||||
serverEncryptionVersion,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
sensitive,
|
||||
}
|
||||
|
||||
return Object.assign(new Setting(), setting)
|
||||
}
|
||||
|
||||
async createReplacement(original: Setting, props: SettingProps): Promise<Setting> {
|
||||
const { uuid, user } = original
|
||||
|
||||
return Object.assign(await this.create(props, await user), {
|
||||
uuid,
|
||||
})
|
||||
}
|
||||
|
||||
async createValue({
|
||||
unencryptedValue,
|
||||
serverEncryptionVersion,
|
||||
user,
|
||||
}: {
|
||||
unencryptedValue: string | null
|
||||
serverEncryptionVersion: number
|
||||
user: User
|
||||
}): Promise<string | null> {
|
||||
switch (serverEncryptionVersion) {
|
||||
case EncryptionVersion.Unencrypted:
|
||||
return unencryptedValue
|
||||
case EncryptionVersion.Default:
|
||||
return this.crypter.encryptForUser(unencryptedValue as string, user)
|
||||
default:
|
||||
throw Error(`Unrecognized encryption version: ${serverEncryptionVersion}!`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { User } from '../User/User'
|
||||
import { Setting } from './Setting'
|
||||
import { SettingProps } from './SettingProps'
|
||||
import { SubscriptionSetting } from './SubscriptionSetting'
|
||||
import { SubscriptionSettingProps } from './SubscriptionSettingProps'
|
||||
|
||||
export interface SettingFactoryInterface {
|
||||
create(props: SettingProps, user: User): Promise<Setting>
|
||||
createSubscriptionSetting(
|
||||
props: SubscriptionSettingProps,
|
||||
userSubscription: UserSubscription,
|
||||
): Promise<SubscriptionSetting>
|
||||
createReplacement(original: Setting, props: SettingProps): Promise<Setting>
|
||||
createSubscriptionSettingReplacement(
|
||||
original: SubscriptionSetting,
|
||||
props: SubscriptionSettingProps,
|
||||
): Promise<SubscriptionSetting>
|
||||
}
|
||||
@@ -4,30 +4,26 @@ import {
|
||||
MuteEmailsSettingChangedEvent,
|
||||
UserDisabledSessionUserAgentLoggingEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import {
|
||||
EmailBackupFrequency,
|
||||
LogSessionUserAgentOption,
|
||||
MuteMarketingEmailsOption,
|
||||
SettingName,
|
||||
} from '@standardnotes/settings'
|
||||
import { EmailBackupFrequency, LogSessionUserAgentOption, MuteMarketingEmailsOption } from '@standardnotes/settings'
|
||||
import 'reflect-metadata'
|
||||
import { Logger } from 'winston'
|
||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||
import { User } from '../User/User'
|
||||
import { Setting } from './Setting'
|
||||
import { SettingDecrypterInterface } from './SettingDecrypterInterface'
|
||||
import { SettingCrypterInterface } from './SettingCrypterInterface'
|
||||
|
||||
import { SettingInterpreter } from './SettingInterpreter'
|
||||
import { SettingRepositoryInterface } from './SettingRepositoryInterface'
|
||||
import { GetUserKeyParams } from '../UseCase/GetUserKeyParams/GetUserKeyParams'
|
||||
import { KeyParamsData } from '@standardnotes/responses'
|
||||
import { Uuid, Timestamps, UniqueEntityId, SettingName } from '@standardnotes/domain-core'
|
||||
|
||||
describe('SettingInterpreter', () => {
|
||||
let user: User
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let settingRepository: SettingRepositoryInterface
|
||||
let settingDecrypter: SettingDecrypterInterface
|
||||
let settingCrypter: SettingCrypterInterface
|
||||
let logger: Logger
|
||||
let getUserKeyParams: GetUserKeyParams
|
||||
|
||||
@@ -44,8 +40,8 @@ describe('SettingInterpreter', () => {
|
||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
settingDecrypter = {} as jest.Mocked<SettingDecrypterInterface>
|
||||
settingDecrypter.decryptSettingValue = jest.fn().mockReturnValue('decrypted')
|
||||
settingCrypter = {} as jest.Mocked<SettingCrypterInterface>
|
||||
settingCrypter.decryptSettingValue = jest.fn().mockReturnValue('decrypted')
|
||||
|
||||
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
|
||||
domainEventPublisher.publish = jest.fn()
|
||||
@@ -96,11 +92,19 @@ describe('SettingInterpreter', () => {
|
||||
})
|
||||
|
||||
it('should trigger backup if email backup setting is created - emails muted', async () => {
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({
|
||||
name: SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
uuid: '6-7-8',
|
||||
value: 'muted',
|
||||
} as jest.Mocked<Setting>)
|
||||
const setting = Setting.create(
|
||||
{
|
||||
name: SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
value: 'muted',
|
||||
serverEncryptionVersion: 0,
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sensitive: false,
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
},
|
||||
new UniqueEntityId('7fb54003-1dd2-40bd-8900-2bacd6cf629c'),
|
||||
).getValue()
|
||||
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(
|
||||
SettingName.NAMES.EmailBackupFrequency,
|
||||
@@ -109,7 +113,12 @@ describe('SettingInterpreter', () => {
|
||||
)
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith('4-5-6', '6-7-8', true, {})
|
||||
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith(
|
||||
'4-5-6',
|
||||
'7fb54003-1dd2-40bd-8900-2bacd6cf629c',
|
||||
true,
|
||||
{},
|
||||
)
|
||||
})
|
||||
|
||||
it('should not trigger backup if email backup setting is disabled', async () => {
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { EmailLevel } from '@standardnotes/domain-core'
|
||||
import {
|
||||
EmailBackupFrequency,
|
||||
LogSessionUserAgentOption,
|
||||
MuteFailedBackupsEmailsOption,
|
||||
SettingName,
|
||||
} from '@standardnotes/settings'
|
||||
import { EmailLevel, SettingName } from '@standardnotes/domain-core'
|
||||
import { EmailBackupFrequency, LogSessionUserAgentOption, MuteFailedBackupsEmailsOption } from '@standardnotes/settings'
|
||||
|
||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||
import { User } from '../User/User'
|
||||
@@ -54,8 +49,8 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
||||
userUuid,
|
||||
)
|
||||
if (muteFailedEmailsBackupSetting !== null) {
|
||||
userHasEmailsMuted = muteFailedEmailsBackupSetting.value === MuteFailedBackupsEmailsOption.Muted
|
||||
muteEmailsSettingUuid = muteFailedEmailsBackupSetting.uuid
|
||||
userHasEmailsMuted = muteFailedEmailsBackupSetting.props.value === MuteFailedBackupsEmailsOption.Muted
|
||||
muteEmailsSettingUuid = muteFailedEmailsBackupSetting.id.toString()
|
||||
}
|
||||
|
||||
const keyParamsResponse = await this.getUserKeyParams.execute({
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Setting } from './Setting'
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
export type SettingProps = Omit<
|
||||
Setting,
|
||||
'uuid' | 'user' | 'createdAt' | 'updatedAt' | 'serverEncryptionVersion' | 'value'
|
||||
> & {
|
||||
uuid?: string
|
||||
createdAt?: number
|
||||
updatedAt?: number
|
||||
unencryptedValue: string | null
|
||||
serverEncryptionVersion?: number
|
||||
export interface SettingProps {
|
||||
name: string
|
||||
value: string | null
|
||||
serverEncryptionVersion: number
|
||||
timestamps: Timestamps
|
||||
sensitive: boolean
|
||||
userUuid: Uuid
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReadStream } from 'fs'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { DeleteSettingDto } from '../UseCase/DeleteSetting/DeleteSettingDto'
|
||||
import { Setting } from './Setting'
|
||||
|
||||
@@ -13,5 +13,6 @@ export interface SettingRepositoryInterface {
|
||||
streamAllByNameAndValue(name: SettingName, value: string): Promise<ReadStream>
|
||||
streamAllByName(name: SettingName): Promise<ReadStream>
|
||||
deleteByUserUuid(dto: DeleteSettingDto): Promise<void>
|
||||
save(setting: Setting): Promise<Setting>
|
||||
insert(setting: Setting): Promise<void>
|
||||
update(setting: Setting): Promise<void>
|
||||
}
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { LogSessionUserAgentOption, MuteSignInEmailsOption, SettingName } from '@standardnotes/settings'
|
||||
import { Logger } from 'winston'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { User } from '../User/User'
|
||||
import { Setting } from './Setting'
|
||||
import { SettingRepositoryInterface } from './SettingRepositoryInterface'
|
||||
|
||||
import { SettingService } from './SettingService'
|
||||
import { SettingsAssociationServiceInterface } from './SettingsAssociationServiceInterface'
|
||||
import { SettingInterpreterInterface } from './SettingInterpreterInterface'
|
||||
import { SettingDecrypterInterface } from './SettingDecrypterInterface'
|
||||
import { SettingFactoryInterface } from './SettingFactoryInterface'
|
||||
|
||||
describe('SettingService', () => {
|
||||
let setting: Setting
|
||||
let user: User
|
||||
let factory: SettingFactoryInterface
|
||||
let settingRepository: SettingRepositoryInterface
|
||||
let settingsAssociationService: SettingsAssociationServiceInterface
|
||||
let settingInterpreter: SettingInterpreterInterface
|
||||
let settingDecrypter: SettingDecrypterInterface
|
||||
let logger: Logger
|
||||
|
||||
const createService = () =>
|
||||
new SettingService(
|
||||
factory,
|
||||
settingRepository,
|
||||
settingsAssociationService,
|
||||
settingInterpreter,
|
||||
settingDecrypter,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {
|
||||
uuid: '4-5-6',
|
||||
} as jest.Mocked<User>
|
||||
user.isPotentiallyAPrivateUsernameAccount = jest.fn().mockReturnValue(false)
|
||||
|
||||
setting = {
|
||||
name: SettingName.NAMES.DropboxBackupToken,
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
factory = {} as jest.Mocked<SettingFactoryInterface>
|
||||
factory.create = jest.fn().mockReturnValue(setting)
|
||||
factory.createReplacement = jest.fn().mockReturnValue(setting)
|
||||
|
||||
settingRepository = {} as jest.Mocked<SettingRepositoryInterface>
|
||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
settingRepository.save = jest.fn().mockImplementation((setting) => setting)
|
||||
|
||||
settingsAssociationService = {} as jest.Mocked<SettingsAssociationServiceInterface>
|
||||
settingsAssociationService.getDefaultSettingsAndValuesForNewUser = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SettingName.NAMES.MuteSignInEmails,
|
||||
{
|
||||
value: MuteSignInEmailsOption.NotMuted,
|
||||
sensitive: 0,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
},
|
||||
],
|
||||
]),
|
||||
)
|
||||
|
||||
settingsAssociationService.getDefaultSettingsAndValuesForNewPrivateUsernameAccount = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SettingName.NAMES.LogSessionUserAgent,
|
||||
{
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
value: LogSessionUserAgentOption.Disabled,
|
||||
},
|
||||
],
|
||||
]),
|
||||
)
|
||||
|
||||
settingInterpreter = {} as jest.Mocked<SettingInterpreterInterface>
|
||||
settingInterpreter.interpretSettingUpdated = jest.fn()
|
||||
|
||||
settingDecrypter = {} as jest.Mocked<SettingDecrypterInterface>
|
||||
settingDecrypter.decryptSettingValue = jest.fn().mockReturnValue('decrypted')
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
logger.warn = jest.fn()
|
||||
logger.error = jest.fn()
|
||||
})
|
||||
|
||||
it('should create default settings for a newly registered user', async () => {
|
||||
await createService().applyDefaultSettingsUponRegistration(user)
|
||||
|
||||
expect(settingRepository.save).toHaveBeenCalledWith(setting)
|
||||
})
|
||||
|
||||
it('should create default settings for a newly registered vault account', async () => {
|
||||
user.isPotentiallyAPrivateUsernameAccount = jest.fn().mockReturnValue(true)
|
||||
|
||||
await createService().applyDefaultSettingsUponRegistration(user)
|
||||
|
||||
expect(settingRepository.save).toHaveBeenCalledWith(setting)
|
||||
})
|
||||
|
||||
it("should create setting if it doesn't exist", async () => {
|
||||
const result = await createService().createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: 1,
|
||||
sensitive: false,
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.status).toEqual('created')
|
||||
})
|
||||
|
||||
it('should throw error if setting name is not valid', async () => {
|
||||
await expect(
|
||||
createService().createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: 'invalid',
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: 1,
|
||||
sensitive: false,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrowError('Invalid setting name: invalid')
|
||||
})
|
||||
|
||||
it('should create setting with a given uuid if it does not exist', async () => {
|
||||
settingRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
const result = await createService().createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
uuid: '1-2-3',
|
||||
name: SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: 1,
|
||||
sensitive: false,
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.status).toEqual('created')
|
||||
})
|
||||
|
||||
it('should replace setting if it does exist', async () => {
|
||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
||||
|
||||
const result = await createService().createOrReplace({
|
||||
user: user,
|
||||
props: {
|
||||
...setting,
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: 1,
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.status).toEqual('replaced')
|
||||
})
|
||||
|
||||
it('should replace setting with a given uuid if it does exist', async () => {
|
||||
settingRepository.findOneByUuid = jest.fn().mockReturnValue(setting)
|
||||
|
||||
const result = await createService().createOrReplace({
|
||||
user: user,
|
||||
props: {
|
||||
...setting,
|
||||
uuid: '1-2-3',
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: 1,
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.status).toEqual('replaced')
|
||||
})
|
||||
|
||||
it('should find and decrypt the value of a setting for user', async () => {
|
||||
setting = {
|
||||
value: 'encrypted',
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
||||
|
||||
expect(
|
||||
await createService().findSettingWithDecryptedValue({
|
||||
userUuid: '1-2-3',
|
||||
settingName: SettingName.create(SettingName.NAMES.LogSessionUserAgent).getValue(),
|
||||
}),
|
||||
).toEqual({
|
||||
serverEncryptionVersion: 1,
|
||||
value: 'decrypted',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,110 +0,0 @@
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { User } from '../User/User'
|
||||
import { CreateOrReplaceSettingDto } from './CreateOrReplaceSettingDto'
|
||||
import { CreateOrReplaceSettingResponse } from './CreateOrReplaceSettingResponse'
|
||||
import { FindSettingDTO } from './FindSettingDTO'
|
||||
import { Setting } from './Setting'
|
||||
import { SettingRepositoryInterface } from './SettingRepositoryInterface'
|
||||
import { SettingServiceInterface } from './SettingServiceInterface'
|
||||
import { SettingsAssociationServiceInterface } from './SettingsAssociationServiceInterface'
|
||||
import { SettingInterpreterInterface } from './SettingInterpreterInterface'
|
||||
import { SettingDecrypterInterface } from './SettingDecrypterInterface'
|
||||
import { SettingFactoryInterface } from './SettingFactoryInterface'
|
||||
|
||||
export class SettingService implements SettingServiceInterface {
|
||||
constructor(
|
||||
private factory: SettingFactoryInterface,
|
||||
private settingRepository: SettingRepositoryInterface,
|
||||
private settingsAssociationService: SettingsAssociationServiceInterface,
|
||||
private settingInterpreter: SettingInterpreterInterface,
|
||||
private settingDecrypter: SettingDecrypterInterface,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async applyDefaultSettingsUponRegistration(user: User): Promise<void> {
|
||||
let defaultSettingsWithValues = this.settingsAssociationService.getDefaultSettingsAndValuesForNewUser()
|
||||
if (user.isPotentiallyAPrivateUsernameAccount()) {
|
||||
defaultSettingsWithValues =
|
||||
this.settingsAssociationService.getDefaultSettingsAndValuesForNewPrivateUsernameAccount()
|
||||
}
|
||||
|
||||
for (const settingName of defaultSettingsWithValues.keys()) {
|
||||
this.logger.debug(`Creating setting ${settingName} for user ${user.uuid}`)
|
||||
|
||||
const setting = defaultSettingsWithValues.get(settingName) as {
|
||||
value: string
|
||||
sensitive: boolean
|
||||
serverEncryptionVersion: number
|
||||
}
|
||||
|
||||
await this.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: settingName,
|
||||
unencryptedValue: setting.value,
|
||||
serverEncryptionVersion: setting.serverEncryptionVersion,
|
||||
sensitive: setting.sensitive,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async findSettingWithDecryptedValue(dto: FindSettingDTO): Promise<Setting | null> {
|
||||
let setting: Setting | null
|
||||
if (dto.settingUuid !== undefined) {
|
||||
setting = await this.settingRepository.findOneByUuid(dto.settingUuid)
|
||||
} else {
|
||||
setting = await this.settingRepository.findLastByNameAndUserUuid(dto.settingName.value, dto.userUuid)
|
||||
}
|
||||
|
||||
if (setting === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
setting.value = await this.settingDecrypter.decryptSettingValue(setting, dto.userUuid)
|
||||
|
||||
return setting
|
||||
}
|
||||
|
||||
async createOrReplace(dto: CreateOrReplaceSettingDto): Promise<CreateOrReplaceSettingResponse> {
|
||||
const { user, props } = dto
|
||||
|
||||
const settingNameOrError = SettingName.create(props.name)
|
||||
if (settingNameOrError.isFailed()) {
|
||||
throw new Error(settingNameOrError.getError())
|
||||
}
|
||||
const settingName = settingNameOrError.getValue()
|
||||
|
||||
const existing = await this.findSettingWithDecryptedValue({
|
||||
userUuid: user.uuid,
|
||||
settingName,
|
||||
settingUuid: props.uuid,
|
||||
})
|
||||
|
||||
if (existing === null) {
|
||||
const setting = await this.settingRepository.save(await this.factory.create(props, user))
|
||||
|
||||
this.logger.debug('[%s] Created setting %s: %O', user.uuid, props.name, setting)
|
||||
|
||||
await this.settingInterpreter.interpretSettingUpdated(setting.name, user, props.unencryptedValue)
|
||||
|
||||
return {
|
||||
status: 'created',
|
||||
setting,
|
||||
}
|
||||
}
|
||||
|
||||
const setting = await this.settingRepository.save(await this.factory.createReplacement(existing, props))
|
||||
|
||||
this.logger.debug('[%s] Replaced existing setting %s with: %O', user.uuid, props.name, setting)
|
||||
|
||||
await this.settingInterpreter.interpretSettingUpdated(setting.name, user, props.unencryptedValue)
|
||||
|
||||
return {
|
||||
status: 'replaced',
|
||||
setting,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { User } from '../User/User'
|
||||
import { CreateOrReplaceSettingDto } from './CreateOrReplaceSettingDto'
|
||||
import { CreateOrReplaceSettingResponse } from './CreateOrReplaceSettingResponse'
|
||||
import { FindSettingDTO } from './FindSettingDTO'
|
||||
import { Setting } from './Setting'
|
||||
|
||||
export interface SettingServiceInterface {
|
||||
applyDefaultSettingsUponRegistration(user: User): Promise<void>
|
||||
createOrReplace(dto: CreateOrReplaceSettingDto): Promise<CreateOrReplaceSettingResponse>
|
||||
findSettingWithDecryptedValue(dto: FindSettingDTO): Promise<Setting | null>
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user