mirror of
https://github.com/standardnotes/server
synced 2026-04-04 21:01:15 -04:00
Compare commits
7 Commits
@standardn
...
settings_s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55f8f65c3f | ||
|
|
3953dbc6b4 | ||
|
|
0b205287d1 | ||
|
|
4f0bc57b1a | ||
|
|
7d43316597 | ||
|
|
65d31f011b | ||
|
|
80dd6efae3 |
129
.pnp.cjs
generated
129
.pnp.cjs
generated
@@ -66,7 +66,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
"reference": "workspace:packages/security"\
|
"reference": "workspace:packages/security"\
|
||||||
},\
|
},\
|
||||||
{\
|
{\
|
||||||
"name": "@standardnotes/settings",\
|
"name": "@standardnotes/settings-server",\
|
||||||
"reference": "workspace:packages/settings"\
|
"reference": "workspace:packages/settings"\
|
||||||
},\
|
},\
|
||||||
{\
|
{\
|
||||||
@@ -107,7 +107,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@standardnotes/scheduler-server", ["workspace:packages/scheduler"]],\
|
["@standardnotes/scheduler-server", ["workspace:packages/scheduler"]],\
|
||||||
["@standardnotes/security", ["workspace:packages/security"]],\
|
["@standardnotes/security", ["workspace:packages/security"]],\
|
||||||
["@standardnotes/server-monorepo", ["workspace:."]],\
|
["@standardnotes/server-monorepo", ["workspace:."]],\
|
||||||
["@standardnotes/settings", ["workspace:packages/settings"]],\
|
["@standardnotes/settings-server", ["workspace:packages/settings"]],\
|
||||||
["@standardnotes/sncrypto-node", ["workspace:packages/sncrypto-node"]],\
|
["@standardnotes/sncrypto-node", ["workspace:packages/sncrypto-node"]],\
|
||||||
["@standardnotes/syncing-server", ["workspace:packages/syncing-server"]],\
|
["@standardnotes/syncing-server", ["workspace:packages/syncing-server"]],\
|
||||||
["@standardnotes/time", ["workspace:packages/time"]],\
|
["@standardnotes/time", ["workspace:packages/time"]],\
|
||||||
@@ -2582,6 +2582,20 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.20.13", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.20.13-3efe52d749-67bdb982ec.zip/node_modules/@standardnotes/api/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/api", "npm:1.20.13"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/encryption", "npm:1.19.21"],\
|
||||||
|
["@standardnotes/models", "npm:1.38.0"],\
|
||||||
|
["@standardnotes/responses", "npm:1.12.9"],\
|
||||||
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
|
["@standardnotes/utils", "npm:1.13.0"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/api-gateway", [\
|
["@standardnotes/api-gateway", [\
|
||||||
@@ -2656,7 +2670,6 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
||||||
["@standardnotes/responses", "npm:1.11.1"],\
|
["@standardnotes/responses", "npm:1.11.1"],\
|
||||||
["@standardnotes/security", "workspace:packages/security"],\
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
|
||||||
["@standardnotes/sncrypto-common", "npm:1.13.0"],\
|
["@standardnotes/sncrypto-common", "npm:1.13.0"],\
|
||||||
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
|
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
|
||||||
["@standardnotes/time", "workspace:packages/time"],\
|
["@standardnotes/time", "workspace:packages/time"],\
|
||||||
@@ -2812,6 +2825,19 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.19.21", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.19.21-dfa10f00e6-c8c2c27bfe.zip/node_modules/@standardnotes/encryption/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/encryption", "npm:1.19.21"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/models", "npm:1.38.0"],\
|
||||||
|
["@standardnotes/responses", "npm:1.12.9"],\
|
||||||
|
["@standardnotes/sncrypto-common", "npm:1.13.3"],\
|
||||||
|
["@standardnotes/utils", "npm:1.13.0"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/event-store", [\
|
["@standardnotes/event-store", [\
|
||||||
@@ -2867,6 +2893,17 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.55.3", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.55.3-c124505183-b39fe2d49b.zip/node_modules/@standardnotes/features/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/features", "npm:1.55.3"],\
|
||||||
|
["@standardnotes/auth", "npm:3.19.4"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/files-server", [\
|
["@standardnotes/files-server", [\
|
||||||
@@ -2948,6 +2985,13 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.38.0", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.38.0-108f602f56-2dc2ac957e.zip/node_modules/@standardnotes/models/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/models", "npm:1.38.0"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/payloads", [\
|
["@standardnotes/payloads", [\
|
||||||
@@ -3001,6 +3045,17 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.12.9", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.12.9-280dc75972-353fe1ca6d.zip/node_modules/@standardnotes/responses/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/responses", "npm:1.12.9"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/features", "npm:1.55.3"],\
|
||||||
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/revisions-server", [\
|
["@standardnotes/revisions-server", [\
|
||||||
@@ -3132,15 +3187,46 @@ const RAW_RUNTIME_STATE =
|
|||||||
"linkType": "SOFT"\
|
"linkType": "SOFT"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/settings", [\
|
["@standardnotes/settings-server", [\
|
||||||
["workspace:packages/settings", {\
|
["workspace:packages/settings", {\
|
||||||
"packageLocation": "./packages/settings/",\
|
"packageLocation": "./packages/settings/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
["@standardnotes/settings-server", "workspace:packages/settings"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.30.5"],\
|
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||||
["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:4.2.1"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
|
["@standardnotes/api", "npm:1.20.13"],\
|
||||||
|
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||||
|
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||||
|
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||||
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
|
["@standardnotes/time", "workspace:packages/time"],\
|
||||||
|
["@types/cors", "npm:2.8.12"],\
|
||||||
|
["@types/dotenv", "npm:8.2.0"],\
|
||||||
|
["@types/express", "npm:4.17.14"],\
|
||||||
|
["@types/inversify-express-utils", "npm:2.0.0"],\
|
||||||
|
["@types/ioredis", "npm:5.0.0"],\
|
||||||
|
["@types/jest", "npm:29.1.1"],\
|
||||||
|
["@types/newrelic", "npm:7.0.4"],\
|
||||||
|
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
||||||
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
|
["cors", "npm:2.8.5"],\
|
||||||
|
["dotenv", "npm:16.0.1"],\
|
||||||
|
["eslint", "npm:8.25.0"],\
|
||||||
|
["eslint-plugin-prettier", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.2.1"],\
|
||||||
|
["express", "npm:4.18.2"],\
|
||||||
|
["helmet", "npm:6.0.0"],\
|
||||||
|
["inversify", "npm:6.0.1"],\
|
||||||
|
["inversify-express-utils", "npm:6.4.3"],\
|
||||||
|
["ioredis", "npm:5.2.4"],\
|
||||||
|
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.2"],\
|
||||||
|
["mysql2", "npm:2.3.3"],\
|
||||||
|
["newrelic", "npm:9.6.0"],\
|
||||||
|
["npm-check-updates", "npm:16.0.1"],\
|
||||||
["reflect-metadata", "npm:0.1.13"],\
|
["reflect-metadata", "npm:0.1.13"],\
|
||||||
["typescript", "patch:typescript@npm%3A4.8.4#optional!builtin<compat/typescript>::version=4.8.4&hash=701156"]\
|
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.0.3"],\
|
||||||
|
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.10"],\
|
||||||
|
["typescript", "patch:typescript@npm%3A4.8.4#optional!builtin<compat/typescript>::version=4.8.4&hash=701156"],\
|
||||||
|
["winston", "npm:3.8.2"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "SOFT"\
|
"linkType": "SOFT"\
|
||||||
}]\
|
}]\
|
||||||
@@ -3153,6 +3239,14 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.13.3", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.3-97ef3850ce-a73af90962.zip/node_modules/@standardnotes/sncrypto-common/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/sncrypto-common", "npm:1.13.3"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/sncrypto-node", [\
|
["@standardnotes/sncrypto-node", [\
|
||||||
@@ -3189,7 +3283,6 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@standardnotes/payloads", "npm:1.5.1"],\
|
["@standardnotes/payloads", "npm:1.5.1"],\
|
||||||
["@standardnotes/responses", "npm:1.11.1"],\
|
["@standardnotes/responses", "npm:1.11.1"],\
|
||||||
["@standardnotes/security", "workspace:packages/security"],\
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
|
||||||
["@standardnotes/time", "workspace:packages/time"],\
|
["@standardnotes/time", "workspace:packages/time"],\
|
||||||
["@types/cors", "npm:2.8.12"],\
|
["@types/cors", "npm:2.8.12"],\
|
||||||
["@types/dotenv", "npm:8.2.0"],\
|
["@types/dotenv", "npm:8.2.0"],\
|
||||||
@@ -3273,6 +3366,17 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.13.0", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.13.0-28780a59f0-1578e8adb7.zip/node_modules/@standardnotes/utils/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/utils", "npm:1.13.0"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["dompurify", "npm:2.4.1"],\
|
||||||
|
["lodash", "npm:4.17.21"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/websockets-server", [\
|
["@standardnotes/websockets-server", [\
|
||||||
@@ -6371,6 +6475,13 @@ const RAW_RUNTIME_STATE =
|
|||||||
["dompurify", "npm:2.4.0"]\
|
["dompurify", "npm:2.4.0"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:2.4.1", {\
|
||||||
|
"packageLocation": "./.yarn/cache/dompurify-npm-2.4.1-1c79f22057-ddc0633356.zip/node_modules/dompurify/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["dompurify", "npm:2.4.1"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["dot-prop", [\
|
["dot-prop", [\
|
||||||
|
|||||||
BIN
.yarn/cache/@standardnotes-api-npm-1.20.13-3efe52d749-67bdb982ec.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-api-npm-1.20.13-3efe52d749-67bdb982ec.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-encryption-npm-1.19.21-dfa10f00e6-c8c2c27bfe.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-encryption-npm-1.19.21-dfa10f00e6-c8c2c27bfe.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-features-npm-1.55.3-c124505183-b39fe2d49b.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-features-npm-1.55.3-c124505183-b39fe2d49b.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-models-npm-1.38.0-108f602f56-2dc2ac957e.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-models-npm-1.38.0-108f602f56-2dc2ac957e.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-responses-npm-1.12.9-280dc75972-353fe1ca6d.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-responses-npm-1.12.9-280dc75972-353fe1ca6d.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.3-97ef3850ce-a73af90962.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.3-97ef3850ce-a73af90962.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-utils-npm-1.13.0-28780a59f0-1578e8adb7.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-utils-npm-1.13.0-28780a59f0-1578e8adb7.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/dompurify-npm-2.4.1-1c79f22057-ddc0633356.zip
vendored
Normal file
BIN
.yarn/cache/dompurify-npm-2.4.1-1c79f22057-ddc0633356.zip
vendored
Normal file
Binary file not shown.
@@ -3,20 +3,19 @@ import 'reflect-metadata'
|
|||||||
import 'newrelic'
|
import 'newrelic'
|
||||||
|
|
||||||
import { Stream } from 'stream'
|
import { Stream } from 'stream'
|
||||||
|
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import * as dayjs from 'dayjs'
|
import * as dayjs from 'dayjs'
|
||||||
import * as utc from 'dayjs/plugin/utc'
|
import * as utc from 'dayjs/plugin/utc'
|
||||||
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
|
import { PermissionName } from '@standardnotes/features'
|
||||||
|
|
||||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||||
import TYPES from '../src/Bootstrap/Types'
|
import TYPES from '../src/Bootstrap/Types'
|
||||||
import { Env } from '../src/Bootstrap/Env'
|
import { Env } from '../src/Bootstrap/Env'
|
||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
|
||||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||||
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
||||||
import { MuteFailedBackupsEmailsOption, MuteFailedCloudBackupsEmailsOption, SettingName } from '@standardnotes/settings'
|
|
||||||
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
|
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
|
||||||
import { PermissionName } from '@standardnotes/features'
|
|
||||||
import { SettingServiceInterface } from '../src/Domain/Setting/SettingServiceInterface'
|
import { SettingServiceInterface } from '../src/Domain/Setting/SettingServiceInterface'
|
||||||
|
|
||||||
const inputArgs = process.argv.slice(2)
|
const inputArgs = process.argv.slice(2)
|
||||||
@@ -30,38 +29,38 @@ const requestBackups = async (
|
|||||||
domainEventFactory: DomainEventFactoryInterface,
|
domainEventFactory: DomainEventFactoryInterface,
|
||||||
domainEventPublisher: DomainEventPublisherInterface,
|
domainEventPublisher: DomainEventPublisherInterface,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
let settingName: SettingName,
|
let settingName: string,
|
||||||
permissionName: PermissionName,
|
permissionName: PermissionName,
|
||||||
muteEmailsSettingName: SettingName,
|
muteEmailsSettingName: string,
|
||||||
muteEmailsSettingValue: string,
|
muteEmailsSettingValue: string,
|
||||||
providerTokenSettingName: SettingName
|
providerTokenSettingName: string
|
||||||
switch (backupProvider) {
|
switch (backupProvider) {
|
||||||
case 'email':
|
case 'email':
|
||||||
settingName = SettingName.EmailBackupFrequency
|
settingName = SettingName.NAMES.EmailBackupFrequency
|
||||||
permissionName = PermissionName.DailyEmailBackup
|
permissionName = PermissionName.DailyEmailBackup
|
||||||
muteEmailsSettingName = SettingName.MuteFailedBackupsEmails
|
muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails
|
||||||
muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
|
muteEmailsSettingValue = 'muted'
|
||||||
break
|
break
|
||||||
case 'dropbox':
|
case 'dropbox':
|
||||||
settingName = SettingName.DropboxBackupFrequency
|
settingName = SettingName.NAMES.DropboxBackupFrequency
|
||||||
permissionName = PermissionName.DailyDropboxBackup
|
permissionName = PermissionName.DailyDropboxBackup
|
||||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
muteEmailsSettingValue = 'muted'
|
||||||
providerTokenSettingName = SettingName.DropboxBackupToken
|
providerTokenSettingName = SettingName.NAMES.DropboxBackupToken
|
||||||
break
|
break
|
||||||
case 'one_drive':
|
case 'one_drive':
|
||||||
settingName = SettingName.OneDriveBackupFrequency
|
settingName = SettingName.NAMES.OneDriveBackupFrequency
|
||||||
permissionName = PermissionName.DailyOneDriveBackup
|
permissionName = PermissionName.DailyOneDriveBackup
|
||||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
muteEmailsSettingValue = 'muted'
|
||||||
providerTokenSettingName = SettingName.OneDriveBackupToken
|
providerTokenSettingName = SettingName.NAMES.OneDriveBackupToken
|
||||||
break
|
break
|
||||||
case 'google_drive':
|
case 'google_drive':
|
||||||
settingName = SettingName.GoogleDriveBackupFrequency
|
settingName = SettingName.NAMES.GoogleDriveBackupFrequency
|
||||||
permissionName = PermissionName.DailyGDriveBackup
|
permissionName = PermissionName.DailyGDriveBackup
|
||||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
muteEmailsSettingValue = 'muted'
|
||||||
providerTokenSettingName = SettingName.GoogleDriveBackupToken
|
providerTokenSettingName = SettingName.NAMES.GoogleDriveBackupToken
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error(`Not handled backup provider: ${backupProvider}`)
|
throw new Error(`Not handled backup provider: ${backupProvider}`)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { Env } from '../src/Bootstrap/Env'
|
|||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||||
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
||||||
import { MuteFailedBackupsEmailsOption, SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
|
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
|
||||||
import { PermissionName } from '@standardnotes/features'
|
import { PermissionName } from '@standardnotes/features'
|
||||||
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
|
||||||
@@ -28,8 +28,8 @@ const requestBackups = async (
|
|||||||
domainEventPublisher: DomainEventPublisherInterface,
|
domainEventPublisher: DomainEventPublisherInterface,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const permissionName = PermissionName.DailyEmailBackup
|
const permissionName = PermissionName.DailyEmailBackup
|
||||||
const muteEmailsSettingName = SettingName.MuteFailedBackupsEmails
|
const muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails
|
||||||
const muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
|
const muteEmailsSettingValue = 'muted'
|
||||||
|
|
||||||
if (!backupEmail) {
|
if (!backupEmail) {
|
||||||
throw new Error('Could not trigger email backup for user - missing email parameter')
|
throw new Error('Could not trigger email backup for user - missing email parameter')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Redis, { Cluster } from 'ioredis'
|
import Redis, { Cluster } from 'ioredis'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
import { Setting } from '../src/Domain/Setting/Setting'
|
import { Setting } from '../src/Domain/Setting/Setting'
|
||||||
@@ -34,7 +34,7 @@ export class moveMfaItemsToUserSettings1627638504691 implements MigrationInterfa
|
|||||||
|
|
||||||
const setting = new Setting()
|
const setting = new Setting()
|
||||||
setting.uuid = item['uuid']
|
setting.uuid = item['uuid']
|
||||||
setting.name = SettingName.MfaSecret
|
setting.name = SettingName.NAMES.MfaSecret
|
||||||
setting.value = item['content']
|
setting.value = item['content']
|
||||||
if (item['deleted']) {
|
if (item['deleted']) {
|
||||||
setting.value = null
|
setting.value = null
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
"@standardnotes/predicates": "workspace:*",
|
"@standardnotes/predicates": "workspace:*",
|
||||||
"@standardnotes/responses": "^1.6.39",
|
"@standardnotes/responses": "^1.6.39",
|
||||||
"@standardnotes/security": "workspace:*",
|
"@standardnotes/security": "workspace:*",
|
||||||
"@standardnotes/settings": "workspace:*",
|
|
||||||
"@standardnotes/sncrypto-common": "^1.9.0",
|
"@standardnotes/sncrypto-common": "^1.9.0",
|
||||||
"@standardnotes/sncrypto-node": "workspace:*",
|
"@standardnotes/sncrypto-node": "workspace:*",
|
||||||
"@standardnotes/time": "workspace:*",
|
"@standardnotes/time": "workspace:*",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { Request } from 'express'
|
import { Request } from 'express'
|
||||||
import { inject } from 'inversify'
|
import { inject } from 'inversify'
|
||||||
import {
|
import {
|
||||||
@@ -69,7 +69,7 @@ export class AdminController extends BaseHttpController {
|
|||||||
const result = await this.doDeleteSetting.execute({
|
const result = await this.doDeleteSetting.execute({
|
||||||
uuid,
|
uuid,
|
||||||
userUuid,
|
userUuid,
|
||||||
settingName: SettingName.MfaSecret,
|
settingName: SettingName.NAMES.MfaSecret,
|
||||||
timestamp: updatedAt,
|
timestamp: updatedAt,
|
||||||
softDelete: true,
|
softDelete: true,
|
||||||
})
|
})
|
||||||
@@ -115,7 +115,7 @@ export class AdminController extends BaseHttpController {
|
|||||||
|
|
||||||
const result = await this.doDeleteSetting.execute({
|
const result = await this.doDeleteSetting.execute({
|
||||||
userUuid,
|
userUuid,
|
||||||
settingName: SettingName.EmailBackupFrequency,
|
settingName: SettingName.NAMES.EmailBackupFrequency,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
|
||||||
import { Request, Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
import { inject } from 'inversify'
|
import { inject } from 'inversify'
|
||||||
import {
|
import {
|
||||||
@@ -21,7 +20,7 @@ export class SubscriptionSettingsController extends BaseHttpController {
|
|||||||
async getSubscriptionSetting(request: Request, response: Response): Promise<results.JsonResult> {
|
async getSubscriptionSetting(request: Request, response: Response): Promise<results.JsonResult> {
|
||||||
const result = await this.doGetSubscriptionSetting.execute({
|
const result = await this.doGetSubscriptionSetting.execute({
|
||||||
userUuid: response.locals.user.uuid,
|
userUuid: response.locals.user.uuid,
|
||||||
subscriptionSettingName: request.params.subscriptionSettingName as SubscriptionSettingName,
|
subscriptionSettingName: request.params.subscriptionSettingName,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CrossServiceTokenData, TokenEncoderInterface } from '@standardnotes/security'
|
import { CrossServiceTokenData, TokenEncoderInterface } from '@standardnotes/security'
|
||||||
import { ErrorTag, RoleName } from '@standardnotes/common'
|
import { ErrorTag, RoleName } from '@standardnotes/common'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { Request, Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
import { inject } from 'inversify'
|
import { inject } from 'inversify'
|
||||||
import {
|
import {
|
||||||
@@ -77,7 +77,7 @@ export class SubscriptionTokensController extends BaseHttpController {
|
|||||||
const user = authenticateTokenResponse.user as User
|
const user = authenticateTokenResponse.user as User
|
||||||
let extensionKey = undefined
|
let extensionKey = undefined
|
||||||
const extensionKeySetting = await this.settingService.findSettingWithDecryptedValue({
|
const extensionKeySetting = await this.settingService.findSettingWithDecryptedValue({
|
||||||
settingName: SettingName.ExtensionKey,
|
settingName: SettingName.NAMES.ExtensionKey,
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
})
|
})
|
||||||
if (extensionKeySetting !== null) {
|
if (extensionKeySetting !== null) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DomainEventHandlerInterface, ExtensionKeyGrantedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, ExtensionKeyGrantedEvent } from '@standardnotes/domain-events'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { OfflineFeaturesTokenData } from '@standardnotes/security'
|
import { OfflineFeaturesTokenData } from '@standardnotes/security'
|
||||||
import { ContentDecoderInterface } from '@standardnotes/common'
|
import { ContentDecoderInterface } from '@standardnotes/common'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
@@ -54,7 +54,7 @@ export class ExtensionKeyGrantedEventHandler implements DomainEventHandlerInterf
|
|||||||
await this.settingService.createOrReplace({
|
await this.settingService.createOrReplace({
|
||||||
user,
|
user,
|
||||||
props: {
|
props: {
|
||||||
name: SettingName.ExtensionKey,
|
name: SettingName.NAMES.ExtensionKey,
|
||||||
unencryptedValue: event.payload.extensionKey,
|
unencryptedValue: event.payload.extensionKey,
|
||||||
serverEncryptionVersion: EncryptionVersion.Default,
|
serverEncryptionVersion: EncryptionVersion.Default,
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DomainEventHandlerInterface, FileRemovedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, FileRemovedEvent } from '@standardnotes/domain-events'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ export class FileRemovedEventHandler implements DomainEventHandlerInterface {
|
|||||||
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
userSubscriptionUuid: subscription.uuid,
|
userSubscriptionUuid: subscription.uuid,
|
||||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed,
|
subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed,
|
||||||
})
|
})
|
||||||
if (bytesUsedSetting === null) {
|
if (bytesUsedSetting === null) {
|
||||||
this.logger.warn(`Could not find bytes used setting for user with uuid: ${user.uuid}`)
|
this.logger.warn(`Could not find bytes used setting for user with uuid: ${user.uuid}`)
|
||||||
@@ -51,7 +51,7 @@ export class FileRemovedEventHandler implements DomainEventHandlerInterface {
|
|||||||
await this.subscriptionSettingService.createOrReplace({
|
await this.subscriptionSettingService.createOrReplace({
|
||||||
userSubscription: subscription,
|
userSubscription: subscription,
|
||||||
props: {
|
props: {
|
||||||
name: SubscriptionSettingName.FileUploadBytesUsed,
|
name: SettingName.NAMES.FileUploadBytesUsed,
|
||||||
unencryptedValue: (+bytesUsed - byteSize).toString(),
|
unencryptedValue: (+bytesUsed - byteSize).toString(),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DomainEventHandlerInterface, FileUploadedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, FileUploadedEvent } from '@standardnotes/domain-events'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ export class FileUploadedEventHandler implements DomainEventHandlerInterface {
|
|||||||
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||||
userUuid: (await subscription.user).uuid,
|
userUuid: (await subscription.user).uuid,
|
||||||
userSubscriptionUuid: subscription.uuid,
|
userSubscriptionUuid: subscription.uuid,
|
||||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed,
|
subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed,
|
||||||
})
|
})
|
||||||
if (bytesUsedSetting !== null) {
|
if (bytesUsedSetting !== null) {
|
||||||
bytesUsed = bytesUsedSetting.value as string
|
bytesUsed = bytesUsedSetting.value as string
|
||||||
@@ -56,7 +56,7 @@ export class FileUploadedEventHandler implements DomainEventHandlerInterface {
|
|||||||
await this.subscriptionSettingService.createOrReplace({
|
await this.subscriptionSettingService.createOrReplace({
|
||||||
userSubscription: subscription,
|
userSubscription: subscription,
|
||||||
props: {
|
props: {
|
||||||
name: SubscriptionSettingName.FileUploadBytesUsed,
|
name: SettingName.NAMES.FileUploadBytesUsed,
|
||||||
unencryptedValue: (+bytesUsed + byteSize).toString(),
|
unencryptedValue: (+bytesUsed + byteSize).toString(),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DomainEventHandlerInterface, ListedAccountCreatedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, ListedAccountCreatedEvent } from '@standardnotes/domain-events'
|
||||||
import { ListedAuthorSecretsData, SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@@ -25,14 +25,14 @@ export class ListedAccountCreatedEventHandler implements DomainEventHandlerInter
|
|||||||
|
|
||||||
const newSecret = { authorId: event.payload.userId, secret: event.payload.secret, hostUrl: event.payload.hostUrl }
|
const newSecret = { authorId: event.payload.userId, secret: event.payload.secret, hostUrl: event.payload.hostUrl }
|
||||||
|
|
||||||
let authSecrets: ListedAuthorSecretsData = [newSecret]
|
let authSecrets = [newSecret]
|
||||||
|
|
||||||
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||||
settingName: SettingName.ListedAuthorSecrets,
|
settingName: SettingName.NAMES.ListedAuthorSecrets,
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
})
|
})
|
||||||
if (listedAuthorSecretsSetting !== null) {
|
if (listedAuthorSecretsSetting !== null) {
|
||||||
const existingSecrets: ListedAuthorSecretsData = JSON.parse(listedAuthorSecretsSetting.value as string)
|
const existingSecrets = JSON.parse(listedAuthorSecretsSetting.value as string)
|
||||||
existingSecrets.push(newSecret)
|
existingSecrets.push(newSecret)
|
||||||
authSecrets = existingSecrets
|
authSecrets = existingSecrets
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ export class ListedAccountCreatedEventHandler implements DomainEventHandlerInter
|
|||||||
await this.settingService.createOrReplace({
|
await this.settingService.createOrReplace({
|
||||||
user,
|
user,
|
||||||
props: {
|
props: {
|
||||||
name: SettingName.ListedAuthorSecrets,
|
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||||
unencryptedValue: JSON.stringify(authSecrets),
|
unencryptedValue: JSON.stringify(authSecrets),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DomainEventHandlerInterface, ListedAccountDeletedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, ListedAccountDeletedEvent } from '@standardnotes/domain-events'
|
||||||
import { ListedAuthorSecretsData, SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter
|
|||||||
}
|
}
|
||||||
|
|
||||||
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||||
settingName: SettingName.ListedAuthorSecrets,
|
settingName: SettingName.NAMES.ListedAuthorSecrets,
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
})
|
})
|
||||||
if (listedAuthorSecretsSetting === null) {
|
if (listedAuthorSecretsSetting === null) {
|
||||||
@@ -33,9 +33,9 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingSecrets: ListedAuthorSecretsData = JSON.parse(listedAuthorSecretsSetting.value as string)
|
const existingSecrets = JSON.parse(listedAuthorSecretsSetting.value as string)
|
||||||
const filteredSecrets = existingSecrets.filter(
|
const filteredSecrets = existingSecrets.filter(
|
||||||
(secret) =>
|
(secret: Record<string, unknown>) =>
|
||||||
secret.authorId !== event.payload.userId ||
|
secret.authorId !== event.payload.userId ||
|
||||||
(secret.authorId === event.payload.userId && secret.hostUrl !== event.payload.hostUrl),
|
(secret.authorId === event.payload.userId && secret.hostUrl !== event.payload.hostUrl),
|
||||||
)
|
)
|
||||||
@@ -43,7 +43,7 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter
|
|||||||
await this.settingService.createOrReplace({
|
await this.settingService.createOrReplace({
|
||||||
user,
|
user,
|
||||||
props: {
|
props: {
|
||||||
name: SettingName.ListedAuthorSecrets,
|
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||||
unencryptedValue: JSON.stringify(filteredSecrets),
|
unencryptedValue: JSON.stringify(filteredSecrets),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
|||||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||||
@@ -48,7 +48,7 @@ export class SubscriptionReassignedEventHandler implements DomainEventHandlerInt
|
|||||||
await this.settingService.createOrReplace({
|
await this.settingService.createOrReplace({
|
||||||
user,
|
user,
|
||||||
props: {
|
props: {
|
||||||
name: SettingName.ExtensionKey,
|
name: SettingName.NAMES.ExtensionKey,
|
||||||
unencryptedValue: event.payload.extensionKey,
|
unencryptedValue: event.payload.extensionKey,
|
||||||
serverEncryptionVersion: EncryptionVersion.Default,
|
serverEncryptionVersion: EncryptionVersion.Default,
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
|||||||
import { OfflineSettingServiceInterface } from '../Setting/OfflineSettingServiceInterface'
|
import { OfflineSettingServiceInterface } from '../Setting/OfflineSettingServiceInterface'
|
||||||
import { ContentDecoderInterface } from '@standardnotes/common'
|
import { ContentDecoderInterface } from '@standardnotes/common'
|
||||||
import { OfflineSettingName } from '../Setting/OfflineSettingName'
|
import { OfflineSettingName } from '../Setting/OfflineSettingName'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||||
@@ -95,7 +95,7 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
|
|||||||
await this.settingService.createOrReplace({
|
await this.settingService.createOrReplace({
|
||||||
user,
|
user,
|
||||||
props: {
|
props: {
|
||||||
name: SettingName.ExtensionKey,
|
name: SettingName.NAMES.ExtensionKey,
|
||||||
unencryptedValue: event.payload.extensionKey,
|
unencryptedValue: event.payload.extensionKey,
|
||||||
serverEncryptionVersion: EncryptionVersion.Default,
|
serverEncryptionVersion: EncryptionVersion.Default,
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { EphemeralSession } from './EphemeralSession'
|
|||||||
import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface'
|
import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface'
|
||||||
import { RevokedSession } from './RevokedSession'
|
import { RevokedSession } from './RevokedSession'
|
||||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||||
import { LogSessionUserAgentOption } from '@standardnotes/settings'
|
|
||||||
import { Setting } from '../Setting/Setting'
|
import { Setting } from '../Setting/Setting'
|
||||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||||
|
|
||||||
@@ -171,7 +170,7 @@ describe('SessionService', () => {
|
|||||||
user.uuid = '123'
|
user.uuid = '123'
|
||||||
|
|
||||||
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue({
|
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue({
|
||||||
value: LogSessionUserAgentOption.Disabled,
|
value: 'disabled',
|
||||||
} as jest.Mocked<Setting>)
|
} as jest.Mocked<Setting>)
|
||||||
|
|
||||||
const sessionPayload = await createService().createNewSessionForUser({
|
const sessionPayload = await createService().createNewSessionForUser({
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { EphemeralSession } from './EphemeralSession'
|
|||||||
import { RevokedSession } from './RevokedSession'
|
import { RevokedSession } from './RevokedSession'
|
||||||
import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface'
|
import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface'
|
||||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||||
import { LogSessionUserAgentOption, SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { SessionBody } from '@standardnotes/responses'
|
import { SessionBody } from '@standardnotes/responses'
|
||||||
import { Uuid } from '@standardnotes/common'
|
import { Uuid } from '@standardnotes/common'
|
||||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||||
@@ -291,7 +291,7 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
|
|
||||||
private async isLoggingUserAgentEnabledOnSessions(user: User): Promise<boolean> {
|
private async isLoggingUserAgentEnabledOnSessions(user: User): Promise<boolean> {
|
||||||
const loggingSetting = await this.settingService.findSettingWithDecryptedValue({
|
const loggingSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||||
settingName: SettingName.LogSessionUserAgent,
|
settingName: SettingName.NAMES.LogSessionUserAgent,
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -299,6 +299,6 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return loggingSetting.value === LogSessionUserAgentOption.Enabled
|
return loggingSetting.value === 'enabled'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Uuid } from '@standardnotes/common'
|
import { Uuid } from '@standardnotes/common'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
|
||||||
|
|
||||||
export type FindSettingDTO = {
|
export type FindSettingDTO = {
|
||||||
userUuid: string
|
userUuid: string
|
||||||
settingName: SettingName
|
settingName: string
|
||||||
settingUuid?: Uuid
|
settingUuid?: Uuid
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Uuid } from '@standardnotes/common'
|
import { Uuid } from '@standardnotes/common'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
|
||||||
|
|
||||||
export type FindSubscriptionSettingDTO = {
|
export type FindSubscriptionSettingDTO = {
|
||||||
userUuid: Uuid
|
userUuid: Uuid
|
||||||
userSubscriptionUuid: Uuid
|
userSubscriptionUuid: Uuid
|
||||||
subscriptionSettingName: SubscriptionSettingName
|
subscriptionSettingName: string
|
||||||
settingUuid?: Uuid
|
settingUuid?: Uuid
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,8 @@ import {
|
|||||||
MuteEmailsSettingChangedEvent,
|
MuteEmailsSettingChangedEvent,
|
||||||
UserDisabledSessionUserAgentLoggingEvent,
|
UserDisabledSessionUserAgentLoggingEvent,
|
||||||
} from '@standardnotes/domain-events'
|
} from '@standardnotes/domain-events'
|
||||||
import {
|
import { MuteMarketingEmailsOption } from '@standardnotes/settings'
|
||||||
EmailBackupFrequency,
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
LogSessionUserAgentOption,
|
|
||||||
MuteMarketingEmailsOption,
|
|
||||||
OneDriveBackupFrequency,
|
|
||||||
SettingName,
|
|
||||||
} from '@standardnotes/settings'
|
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||||
@@ -71,11 +66,11 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger session cleanup if user is disabling session user agent logging', async () => {
|
it('should trigger session cleanup if user is disabling session user agent logging', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.LogSessionUserAgent,
|
name: SettingName.NAMES.LogSessionUserAgent,
|
||||||
value: LogSessionUserAgentOption.Disabled,
|
value: 'disabled',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
|
|
||||||
await createInterpreter().interpretSettingUpdated(setting, user, LogSessionUserAgentOption.Disabled)
|
await createInterpreter().interpretSettingUpdated(setting, user, 'disabled')
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent).toHaveBeenCalledWith({
|
expect(domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent).toHaveBeenCalledWith({
|
||||||
@@ -86,11 +81,11 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger backup if email backup setting is created - emails not muted', async () => {
|
it('should trigger backup if email backup setting is created - emails not muted', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.EmailBackupFrequency,
|
name: SettingName.NAMES.EmailBackupFrequency,
|
||||||
value: EmailBackupFrequency.Daily,
|
value: 'daily',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
|
|
||||||
await createInterpreter().interpretSettingUpdated(setting, user, EmailBackupFrequency.Daily)
|
await createInterpreter().interpretSettingUpdated(setting, user, 'daily')
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith('4-5-6', '', false)
|
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith('4-5-6', '', false)
|
||||||
@@ -98,16 +93,16 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger backup if email backup setting is created - emails muted', async () => {
|
it('should trigger backup if email backup setting is created - emails muted', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.EmailBackupFrequency,
|
name: SettingName.NAMES.EmailBackupFrequency,
|
||||||
value: EmailBackupFrequency.Daily,
|
value: 'daily',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({
|
||||||
name: SettingName.MuteFailedBackupsEmails,
|
name: SettingName.NAMES.MuteFailedBackupsEmails,
|
||||||
uuid: '6-7-8',
|
uuid: '6-7-8',
|
||||||
value: 'muted',
|
value: 'muted',
|
||||||
} as jest.Mocked<Setting>)
|
} as jest.Mocked<Setting>)
|
||||||
|
|
||||||
await createInterpreter().interpretSettingUpdated(setting, user, EmailBackupFrequency.Daily)
|
await createInterpreter().interpretSettingUpdated(setting, user, 'daily')
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith('4-5-6', '6-7-8', true)
|
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith('4-5-6', '6-7-8', true)
|
||||||
@@ -115,12 +110,12 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should not trigger backup if email backup setting is disabled', async () => {
|
it('should not trigger backup if email backup setting is disabled', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.EmailBackupFrequency,
|
name: SettingName.NAMES.EmailBackupFrequency,
|
||||||
value: EmailBackupFrequency.Disabled,
|
value: 'disabled',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||||
|
|
||||||
await createInterpreter().interpretSettingUpdated(setting, user, EmailBackupFrequency.Disabled)
|
await createInterpreter().interpretSettingUpdated(setting, user, 'disabled')
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createEmailBackupRequestedEvent).not.toHaveBeenCalled()
|
expect(domainEventFactory.createEmailBackupRequestedEvent).not.toHaveBeenCalled()
|
||||||
@@ -128,7 +123,7 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger cloud backup if dropbox backup setting is created', async () => {
|
it('should trigger cloud backup if dropbox backup setting is created', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.DropboxBackupToken,
|
name: SettingName.NAMES.DropboxBackupToken,
|
||||||
value: 'test-token',
|
value: 'test-token',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||||
@@ -147,11 +142,11 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger cloud backup if dropbox backup setting is created - muted emails', async () => {
|
it('should trigger cloud backup if dropbox backup setting is created - muted emails', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.DropboxBackupToken,
|
name: SettingName.NAMES.DropboxBackupToken,
|
||||||
value: 'test-token',
|
value: 'test-token',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({
|
||||||
name: SettingName.MuteFailedCloudBackupsEmails,
|
name: SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||||
uuid: '6-7-8',
|
uuid: '6-7-8',
|
||||||
value: 'muted',
|
value: 'muted',
|
||||||
} as jest.Mocked<Setting>)
|
} as jest.Mocked<Setting>)
|
||||||
@@ -170,7 +165,7 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger cloud backup if google drive backup setting is created', async () => {
|
it('should trigger cloud backup if google drive backup setting is created', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.GoogleDriveBackupToken,
|
name: SettingName.NAMES.GoogleDriveBackupToken,
|
||||||
value: 'test-token',
|
value: 'test-token',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||||
@@ -189,7 +184,7 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger cloud backup if one drive backup setting is created', async () => {
|
it('should trigger cloud backup if one drive backup setting is created', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.OneDriveBackupToken,
|
name: SettingName.NAMES.OneDriveBackupToken,
|
||||||
value: 'test-token',
|
value: 'test-token',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||||
@@ -225,13 +220,13 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger cloud backup if backup frequency setting is updated and a backup token setting is present', async () => {
|
it('should trigger cloud backup if backup frequency setting is updated and a backup token setting is present', async () => {
|
||||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({
|
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({
|
||||||
name: SettingName.OneDriveBackupToken,
|
name: SettingName.NAMES.OneDriveBackupToken,
|
||||||
serverEncryptionVersion: 1,
|
serverEncryptionVersion: 1,
|
||||||
value: 'encrypted-backup-token',
|
value: 'encrypted-backup-token',
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
} as jest.Mocked<Setting>)
|
} as jest.Mocked<Setting>)
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.OneDriveBackupFrequency,
|
name: SettingName.NAMES.OneDriveBackupFrequency,
|
||||||
serverEncryptionVersion: 0,
|
serverEncryptionVersion: 0,
|
||||||
value: 'daily',
|
value: 'daily',
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
@@ -251,19 +246,19 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should not trigger cloud backup if backup frequency setting is updated as disabled', async () => {
|
it('should not trigger cloud backup if backup frequency setting is updated as disabled', async () => {
|
||||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({
|
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({
|
||||||
name: SettingName.OneDriveBackupToken,
|
name: SettingName.NAMES.OneDriveBackupToken,
|
||||||
serverEncryptionVersion: 1,
|
serverEncryptionVersion: 1,
|
||||||
value: 'encrypted-backup-token',
|
value: 'encrypted-backup-token',
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
} as jest.Mocked<Setting>)
|
} as jest.Mocked<Setting>)
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.OneDriveBackupFrequency,
|
name: SettingName.NAMES.OneDriveBackupFrequency,
|
||||||
serverEncryptionVersion: 0,
|
serverEncryptionVersion: 0,
|
||||||
value: OneDriveBackupFrequency.Disabled,
|
value: 'disabled',
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
|
|
||||||
await createInterpreter().interpretSettingUpdated(setting, user, OneDriveBackupFrequency.Disabled)
|
await createInterpreter().interpretSettingUpdated(setting, user, 'disabled')
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createCloudBackupRequestedEvent).not.toHaveBeenCalled()
|
expect(domainEventFactory.createCloudBackupRequestedEvent).not.toHaveBeenCalled()
|
||||||
@@ -272,7 +267,7 @@ describe('SettingInterpreter', () => {
|
|||||||
it('should not trigger cloud backup if backup frequency setting is updated and a backup token setting is not present', async () => {
|
it('should not trigger cloud backup if backup frequency setting is updated and a backup token setting is not present', async () => {
|
||||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce(null)
|
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce(null)
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.OneDriveBackupFrequency,
|
name: SettingName.NAMES.OneDriveBackupFrequency,
|
||||||
serverEncryptionVersion: 0,
|
serverEncryptionVersion: 0,
|
||||||
value: 'daily',
|
value: 'daily',
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
|
|||||||
@@ -1,15 +1,5 @@
|
|||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import { EmailLevel } from '@standardnotes/domain-core'
|
import { EmailLevel, SettingName } from '@standardnotes/domain-core'
|
||||||
import {
|
|
||||||
DropboxBackupFrequency,
|
|
||||||
EmailBackupFrequency,
|
|
||||||
GoogleDriveBackupFrequency,
|
|
||||||
LogSessionUserAgentOption,
|
|
||||||
MuteFailedBackupsEmailsOption,
|
|
||||||
MuteFailedCloudBackupsEmailsOption,
|
|
||||||
OneDriveBackupFrequency,
|
|
||||||
SettingName,
|
|
||||||
} from '@standardnotes/settings'
|
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
@@ -23,22 +13,18 @@ import { SettingRepositoryInterface } from './SettingRepositoryInterface'
|
|||||||
@injectable()
|
@injectable()
|
||||||
export class SettingInterpreter implements SettingInterpreterInterface {
|
export class SettingInterpreter implements SettingInterpreterInterface {
|
||||||
private readonly cloudBackupTokenSettings = [
|
private readonly cloudBackupTokenSettings = [
|
||||||
SettingName.DropboxBackupToken,
|
SettingName.NAMES.DropboxBackupToken,
|
||||||
SettingName.GoogleDriveBackupToken,
|
SettingName.NAMES.GoogleDriveBackupToken,
|
||||||
SettingName.OneDriveBackupToken,
|
SettingName.NAMES.OneDriveBackupToken,
|
||||||
]
|
]
|
||||||
|
|
||||||
private readonly cloudBackupFrequencySettings = [
|
private readonly cloudBackupFrequencySettings = [
|
||||||
SettingName.DropboxBackupFrequency,
|
SettingName.NAMES.DropboxBackupFrequency,
|
||||||
SettingName.GoogleDriveBackupFrequency,
|
SettingName.NAMES.GoogleDriveBackupFrequency,
|
||||||
SettingName.OneDriveBackupFrequency,
|
SettingName.NAMES.OneDriveBackupFrequency,
|
||||||
]
|
]
|
||||||
|
|
||||||
private readonly cloudBackupFrequencyDisabledValues = [
|
private readonly cloudBackupFrequencyDisabledValues = ['disabled']
|
||||||
DropboxBackupFrequency.Disabled,
|
|
||||||
GoogleDriveBackupFrequency.Disabled,
|
|
||||||
OneDriveBackupFrequency.Disabled,
|
|
||||||
]
|
|
||||||
|
|
||||||
private readonly emailSettingToSubscriptionRejectionLevelMap: Map<SettingName, string> = new Map([
|
private readonly emailSettingToSubscriptionRejectionLevelMap: Map<SettingName, string> = new Map([
|
||||||
[SettingName.MuteFailedBackupsEmails, EmailLevel.LEVELS.FailedEmailBackup],
|
[SettingName.MuteFailedBackupsEmails, EmailLevel.LEVELS.FailedEmailBackup],
|
||||||
@@ -77,11 +63,11 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
|||||||
let userHasEmailsMuted = false
|
let userHasEmailsMuted = false
|
||||||
let muteEmailsSettingUuid = ''
|
let muteEmailsSettingUuid = ''
|
||||||
const muteFailedEmailsBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
|
const muteFailedEmailsBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
|
||||||
SettingName.MuteFailedBackupsEmails,
|
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||||
userUuid,
|
userUuid,
|
||||||
)
|
)
|
||||||
if (muteFailedEmailsBackupSetting !== null) {
|
if (muteFailedEmailsBackupSetting !== null) {
|
||||||
userHasEmailsMuted = muteFailedEmailsBackupSetting.value === MuteFailedBackupsEmailsOption.Muted
|
userHasEmailsMuted = muteFailedEmailsBackupSetting.value === 'muted'
|
||||||
muteEmailsSettingUuid = muteFailedEmailsBackupSetting.uuid
|
muteEmailsSettingUuid = muteFailedEmailsBackupSetting.uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,21 +86,19 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isEnablingEmailBackupSetting(setting: Setting): boolean {
|
private isEnablingEmailBackupSetting(setting: Setting): boolean {
|
||||||
return setting.name === SettingName.EmailBackupFrequency && setting.value !== EmailBackupFrequency.Disabled
|
return setting.name === SettingName.NAMES.EmailBackupFrequency && setting.value !== 'disabled'
|
||||||
}
|
}
|
||||||
|
|
||||||
private isEnablingCloudBackupSetting(setting: Setting): boolean {
|
private isEnablingCloudBackupSetting(setting: Setting): boolean {
|
||||||
return (
|
return (
|
||||||
(this.cloudBackupFrequencySettings.includes(setting.name as SettingName) ||
|
(this.cloudBackupFrequencySettings.includes(setting.name) ||
|
||||||
this.cloudBackupTokenSettings.includes(setting.name as SettingName)) &&
|
this.cloudBackupTokenSettings.includes(setting.name)) &&
|
||||||
!this.cloudBackupFrequencyDisabledValues.includes(
|
!this.cloudBackupFrequencyDisabledValues.includes(setting.value as string)
|
||||||
setting.value as DropboxBackupFrequency | OneDriveBackupFrequency | GoogleDriveBackupFrequency,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private isDisablingSessionUserAgentLogging(setting: Setting): boolean {
|
private isDisablingSessionUserAgentLogging(setting: Setting): boolean {
|
||||||
return SettingName.LogSessionUserAgent === setting.name && LogSessionUserAgentOption.Disabled === setting.value
|
return SettingName.NAMES.LogSessionUserAgent === setting.name && 'disabled' === setting.value
|
||||||
}
|
}
|
||||||
|
|
||||||
private async triggerEmailSubscriptionChange(
|
private async triggerEmailSubscriptionChange(
|
||||||
@@ -144,29 +128,26 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
|||||||
let cloudProvider
|
let cloudProvider
|
||||||
let tokenSettingName
|
let tokenSettingName
|
||||||
switch (setting.name) {
|
switch (setting.name) {
|
||||||
case SettingName.DropboxBackupToken:
|
case SettingName.NAMES.DropboxBackupToken:
|
||||||
case SettingName.DropboxBackupFrequency:
|
case SettingName.NAMES.DropboxBackupFrequency:
|
||||||
cloudProvider = 'DROPBOX'
|
cloudProvider = 'DROPBOX'
|
||||||
tokenSettingName = SettingName.DropboxBackupToken
|
tokenSettingName = SettingName.NAMES.DropboxBackupToken
|
||||||
break
|
break
|
||||||
case SettingName.GoogleDriveBackupToken:
|
case SettingName.NAMES.GoogleDriveBackupToken:
|
||||||
case SettingName.GoogleDriveBackupFrequency:
|
case SettingName.NAMES.GoogleDriveBackupFrequency:
|
||||||
cloudProvider = 'GOOGLE_DRIVE'
|
cloudProvider = 'GOOGLE_DRIVE'
|
||||||
tokenSettingName = SettingName.GoogleDriveBackupToken
|
tokenSettingName = SettingName.NAMES.GoogleDriveBackupToken
|
||||||
break
|
break
|
||||||
case SettingName.OneDriveBackupToken:
|
case SettingName.NAMES.OneDriveBackupToken:
|
||||||
case SettingName.OneDriveBackupFrequency:
|
case SettingName.NAMES.OneDriveBackupFrequency:
|
||||||
cloudProvider = 'ONE_DRIVE'
|
cloudProvider = 'ONE_DRIVE'
|
||||||
tokenSettingName = SettingName.OneDriveBackupToken
|
tokenSettingName = SettingName.NAMES.OneDriveBackupToken
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
let backupToken = null
|
let backupToken = null
|
||||||
if (this.cloudBackupFrequencySettings.includes(setting.name as SettingName)) {
|
if (this.cloudBackupFrequencySettings.includes(setting.name)) {
|
||||||
const tokenSetting = await this.settingRepository.findLastByNameAndUserUuid(
|
const tokenSetting = await this.settingRepository.findLastByNameAndUserUuid(tokenSettingName as string, userUuid)
|
||||||
tokenSettingName as SettingName,
|
|
||||||
userUuid,
|
|
||||||
)
|
|
||||||
if (tokenSetting !== null) {
|
if (tokenSetting !== null) {
|
||||||
backupToken = await this.settingDecrypter.decryptSettingValue(tokenSetting, userUuid)
|
backupToken = await this.settingDecrypter.decryptSettingValue(tokenSetting, userUuid)
|
||||||
}
|
}
|
||||||
@@ -183,11 +164,11 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
|||||||
let userHasEmailsMuted = false
|
let userHasEmailsMuted = false
|
||||||
let muteEmailsSettingUuid = ''
|
let muteEmailsSettingUuid = ''
|
||||||
const muteFailedCloudBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
|
const muteFailedCloudBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
|
||||||
SettingName.MuteFailedCloudBackupsEmails,
|
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||||
userUuid,
|
userUuid,
|
||||||
)
|
)
|
||||||
if (muteFailedCloudBackupSetting !== null) {
|
if (muteFailedCloudBackupSetting !== null) {
|
||||||
userHasEmailsMuted = muteFailedCloudBackupSetting.value === MuteFailedCloudBackupsEmailsOption.Muted
|
userHasEmailsMuted = muteFailedCloudBackupSetting.value === 'muted'
|
||||||
muteEmailsSettingUuid = muteFailedCloudBackupSetting.uuid
|
muteEmailsSettingUuid = muteFailedCloudBackupSetting.uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { ReadStream } from 'fs'
|
import { ReadStream } from 'fs'
|
||||||
|
|
||||||
import { SettingName } from '@standardnotes/settings'
|
|
||||||
import { DeleteSettingDto } from '../UseCase/DeleteSetting/DeleteSettingDto'
|
import { DeleteSettingDto } from '../UseCase/DeleteSetting/DeleteSettingDto'
|
||||||
import { Setting } from './Setting'
|
import { Setting } from './Setting'
|
||||||
|
|
||||||
export interface SettingRepositoryInterface {
|
export interface SettingRepositoryInterface {
|
||||||
findOneByUuid(uuid: string): Promise<Setting | null>
|
findOneByUuid(uuid: string): Promise<Setting | null>
|
||||||
findOneByUuidAndNames(uuid: string, names: SettingName[]): Promise<Setting | null>
|
findOneByUuidAndNames(uuid: string, names: string[]): Promise<Setting | null>
|
||||||
findOneByNameAndUserUuid(name: string, userUuid: string): Promise<Setting | null>
|
findOneByNameAndUserUuid(name: string, userUuid: string): Promise<Setting | null>
|
||||||
findLastByNameAndUserUuid(name: string, userUuid: string): Promise<Setting | null>
|
findLastByNameAndUserUuid(name: string, userUuid: string): Promise<Setting | null>
|
||||||
findAllByUserUuid(userUuid: string): Promise<Setting[]>
|
findAllByUserUuid(userUuid: string): Promise<Setting[]>
|
||||||
streamAllByNameAndValue(name: SettingName, value: string): Promise<ReadStream>
|
streamAllByNameAndValue(name: string, value: string): Promise<ReadStream>
|
||||||
deleteByUserUuid(dto: DeleteSettingDto): Promise<void>
|
deleteByUserUuid(dto: DeleteSettingDto): Promise<void>
|
||||||
save(setting: Setting): Promise<Setting>
|
save(setting: Setting): Promise<Setting>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { LogSessionUserAgentOption, MuteSignInEmailsOption, SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
import { User } from '../User/User'
|
import { User } from '../User/User'
|
||||||
@@ -54,9 +54,9 @@ describe('SettingService', () => {
|
|||||||
settingsAssociationService.getDefaultSettingsAndValuesForNewUser = jest.fn().mockReturnValue(
|
settingsAssociationService.getDefaultSettingsAndValuesForNewUser = jest.fn().mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SettingName.MuteSignInEmails,
|
SettingName.NAMES.MuteSignInEmails,
|
||||||
{
|
{
|
||||||
value: MuteSignInEmailsOption.NotMuted,
|
value: 'not_muted',
|
||||||
sensitive: 0,
|
sensitive: 0,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
},
|
},
|
||||||
@@ -67,11 +67,11 @@ describe('SettingService', () => {
|
|||||||
settingsAssociationService.getDefaultSettingsAndValuesForNewVaultAccount = jest.fn().mockReturnValue(
|
settingsAssociationService.getDefaultSettingsAndValuesForNewVaultAccount = jest.fn().mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SettingName.LogSessionUserAgent,
|
SettingName.NAMES.LogSessionUserAgent,
|
||||||
{
|
{
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
value: LogSessionUserAgentOption.Disabled,
|
value: 'disabled',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
@@ -173,9 +173,7 @@ describe('SettingService', () => {
|
|||||||
|
|
||||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
||||||
|
|
||||||
expect(
|
expect(await createService().findSettingWithDecryptedValue({ userUuid: '1-2-3', settingName: 'test' })).toEqual({
|
||||||
await createService().findSettingWithDecryptedValue({ userUuid: '1-2-3', settingName: 'test' as SettingName }),
|
|
||||||
).toEqual({
|
|
||||||
serverEncryptionVersion: 1,
|
serverEncryptionVersion: 1,
|
||||||
value: 'decrypted',
|
value: 'decrypted',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { SettingName } from '@standardnotes/settings'
|
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
@@ -74,7 +73,7 @@ export class SettingService implements SettingServiceInterface {
|
|||||||
|
|
||||||
const existing = await this.findSettingWithDecryptedValue({
|
const existing = await this.findSettingWithDecryptedValue({
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
settingName: props.name as SettingName,
|
settingName: props.name,
|
||||||
settingUuid: props.uuid,
|
settingUuid: props.uuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { PermissionName } from '@standardnotes/features'
|
import { PermissionName } from '@standardnotes/features'
|
||||||
|
|
||||||
import { SettingsAssociationService } from './SettingsAssociationService'
|
import { SettingsAssociationService } from './SettingsAssociationService'
|
||||||
@@ -11,52 +11,54 @@ describe('SettingsAssociationService', () => {
|
|||||||
const createService = () => new SettingsAssociationService()
|
const createService = () => new SettingsAssociationService()
|
||||||
|
|
||||||
it('should tell if a setting is mutable by the client', () => {
|
it('should tell if a setting is mutable by the client', () => {
|
||||||
expect(createService().isSettingMutableByClient(SettingName.DropboxBackupFrequency)).toBeTruthy()
|
expect(createService().isSettingMutableByClient(SettingName.NAMES.DropboxBackupFrequency)).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should tell if a setting is immutable by the client', () => {
|
it('should tell if a setting is immutable by the client', () => {
|
||||||
expect(createService().isSettingMutableByClient(SettingName.ListedAuthorSecrets)).toBeFalsy()
|
expect(createService().isSettingMutableByClient(SettingName.NAMES.ListedAuthorSecrets)).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return default encryption version for a setting which enecryption version is not strictly defined', () => {
|
it('should return default encryption version for a setting which enecryption version is not strictly defined', () => {
|
||||||
expect(createService().getEncryptionVersionForSetting(SettingName.MfaSecret)).toEqual(EncryptionVersion.Default)
|
expect(createService().getEncryptionVersionForSetting(SettingName.NAMES.MfaSecret)).toEqual(
|
||||||
|
EncryptionVersion.Default,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return a defined encryption version for a setting which enecryption version is strictly defined', () => {
|
it('should return a defined encryption version for a setting which enecryption version is strictly defined', () => {
|
||||||
expect(createService().getEncryptionVersionForSetting(SettingName.EmailBackupFrequency)).toEqual(
|
expect(createService().getEncryptionVersionForSetting(SettingName.NAMES.EmailBackupFrequency)).toEqual(
|
||||||
EncryptionVersion.Unencrypted,
|
EncryptionVersion.Unencrypted,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return default sensitivity for a setting which sensitivity is not strictly defined', () => {
|
it('should return default sensitivity for a setting which sensitivity is not strictly defined', () => {
|
||||||
expect(createService().getSensitivityForSetting(SettingName.DropboxBackupToken)).toBeTruthy()
|
expect(createService().getSensitivityForSetting(SettingName.NAMES.DropboxBackupToken)).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return a defined sensitivity for a setting which sensitivity is strictly defined', () => {
|
it('should return a defined sensitivity for a setting which sensitivity is strictly defined', () => {
|
||||||
expect(createService().getSensitivityForSetting(SettingName.DropboxBackupFrequency)).toBeFalsy()
|
expect(createService().getSensitivityForSetting(SettingName.NAMES.DropboxBackupFrequency)).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return the default set of settings for a newly registered user', () => {
|
it('should return the default set of settings for a newly registered user', () => {
|
||||||
const settings = createService().getDefaultSettingsAndValuesForNewUser()
|
const settings = createService().getDefaultSettingsAndValuesForNewUser()
|
||||||
const flatSettings = [...(settings as Map<SettingName, SettingDescription>).keys()]
|
const flatSettings = [...(settings as Map<string, SettingDescription>).keys()]
|
||||||
expect(flatSettings).toEqual(['MUTE_SIGN_IN_EMAILS', 'MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
expect(flatSettings).toEqual(['MUTE_SIGN_IN_EMAILS', 'MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return the default set of settings for a newly registered vault account', () => {
|
it('should return the default set of settings for a newly registered vault account', () => {
|
||||||
const settings = createService().getDefaultSettingsAndValuesForNewVaultAccount()
|
const settings = createService().getDefaultSettingsAndValuesForNewVaultAccount()
|
||||||
const flatSettings = [...(settings as Map<SettingName, SettingDescription>).keys()]
|
const flatSettings = [...(settings as Map<string, SettingDescription>).keys()]
|
||||||
expect(flatSettings).toEqual(['MUTE_SIGN_IN_EMAILS', 'MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
expect(flatSettings).toEqual(['MUTE_SIGN_IN_EMAILS', 'MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
||||||
|
|
||||||
expect(settings.get(SettingName.LogSessionUserAgent)?.value).toEqual('disabled')
|
expect(settings.get(SettingName.NAMES.LogSessionUserAgent)?.value).toEqual('disabled')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return a permission name associated to a given setting', () => {
|
it('should return a permission name associated to a given setting', () => {
|
||||||
expect(createService().getPermissionAssociatedWithSetting(SettingName.EmailBackupFrequency)).toEqual(
|
expect(createService().getPermissionAssociatedWithSetting(SettingName.NAMES.EmailBackupFrequency)).toEqual(
|
||||||
PermissionName.DailyEmailBackup,
|
PermissionName.DailyEmailBackup,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not return a permission name if not associated to a given setting', () => {
|
it('should not return a permission name if not associated to a given setting', () => {
|
||||||
expect(createService().getPermissionAssociatedWithSetting(SettingName.ExtensionKey)).toBeUndefined()
|
expect(createService().getPermissionAssociatedWithSetting(SettingName.NAMES.ExtensionKey)).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { PermissionName } from '@standardnotes/features'
|
import { PermissionName } from '@standardnotes/features'
|
||||||
import {
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
LogSessionUserAgentOption,
|
|
||||||
MuteMarketingEmailsOption,
|
|
||||||
MuteSignInEmailsOption,
|
|
||||||
SettingName,
|
|
||||||
} from '@standardnotes/settings'
|
|
||||||
import { injectable } from 'inversify'
|
import { injectable } from 'inversify'
|
||||||
|
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
@@ -15,79 +10,79 @@ import { SettingsAssociationServiceInterface } from './SettingsAssociationServic
|
|||||||
@injectable()
|
@injectable()
|
||||||
export class SettingsAssociationService implements SettingsAssociationServiceInterface {
|
export class SettingsAssociationService implements SettingsAssociationServiceInterface {
|
||||||
private readonly UNENCRYPTED_SETTINGS = [
|
private readonly UNENCRYPTED_SETTINGS = [
|
||||||
SettingName.EmailBackupFrequency,
|
SettingName.NAMES.EmailBackupFrequency,
|
||||||
SettingName.MuteFailedBackupsEmails,
|
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||||
SettingName.MuteFailedCloudBackupsEmails,
|
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||||
SettingName.MuteSignInEmails,
|
SettingName.NAMES.MuteSignInEmails,
|
||||||
SettingName.MuteMarketingEmails,
|
SettingName.NAMES.MuteMarketingEmails,
|
||||||
SettingName.DropboxBackupFrequency,
|
SettingName.NAMES.DropboxBackupFrequency,
|
||||||
SettingName.GoogleDriveBackupFrequency,
|
SettingName.NAMES.GoogleDriveBackupFrequency,
|
||||||
SettingName.OneDriveBackupFrequency,
|
SettingName.NAMES.OneDriveBackupFrequency,
|
||||||
SettingName.LogSessionUserAgent,
|
SettingName.NAMES.LogSessionUserAgent,
|
||||||
]
|
]
|
||||||
|
|
||||||
private readonly UNSENSITIVE_SETTINGS = [
|
private readonly UNSENSITIVE_SETTINGS = [
|
||||||
SettingName.DropboxBackupFrequency,
|
SettingName.NAMES.DropboxBackupFrequency,
|
||||||
SettingName.GoogleDriveBackupFrequency,
|
SettingName.NAMES.GoogleDriveBackupFrequency,
|
||||||
SettingName.OneDriveBackupFrequency,
|
SettingName.NAMES.OneDriveBackupFrequency,
|
||||||
SettingName.EmailBackupFrequency,
|
SettingName.NAMES.EmailBackupFrequency,
|
||||||
SettingName.MuteFailedBackupsEmails,
|
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||||
SettingName.MuteFailedCloudBackupsEmails,
|
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||||
SettingName.MuteSignInEmails,
|
SettingName.NAMES.MuteSignInEmails,
|
||||||
SettingName.MuteMarketingEmails,
|
SettingName.NAMES.MuteMarketingEmails,
|
||||||
SettingName.ListedAuthorSecrets,
|
SettingName.NAMES.ListedAuthorSecrets,
|
||||||
SettingName.LogSessionUserAgent,
|
SettingName.NAMES.LogSessionUserAgent,
|
||||||
]
|
]
|
||||||
|
|
||||||
private readonly CLIENT_IMMUTABLE_SETTINGS = [SettingName.ListedAuthorSecrets]
|
private readonly CLIENT_IMMUTABLE_SETTINGS = [SettingName.NAMES.ListedAuthorSecrets]
|
||||||
|
|
||||||
private readonly permissionsAssociatedWithSettings = new Map<SettingName, PermissionName>([
|
private readonly permissionsAssociatedWithSettings = new Map<string, PermissionName>([
|
||||||
[SettingName.EmailBackupFrequency, PermissionName.DailyEmailBackup],
|
[SettingName.NAMES.EmailBackupFrequency, PermissionName.DailyEmailBackup],
|
||||||
])
|
])
|
||||||
|
|
||||||
private readonly defaultSettings = new Map<SettingName, SettingDescription>([
|
private readonly defaultSettings = new Map<string, SettingDescription>([
|
||||||
[
|
[
|
||||||
SettingName.MuteSignInEmails,
|
SettingName.NAMES.MuteSignInEmails,
|
||||||
{
|
{
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
value: MuteSignInEmailsOption.NotMuted,
|
value: 'not_muted',
|
||||||
replaceable: false,
|
replaceable: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
SettingName.MuteMarketingEmails,
|
SettingName.NAMES.MuteMarketingEmails,
|
||||||
{
|
{
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
value: MuteMarketingEmailsOption.NotMuted,
|
value: 'not_muted',
|
||||||
replaceable: false,
|
replaceable: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
SettingName.LogSessionUserAgent,
|
SettingName.NAMES.LogSessionUserAgent,
|
||||||
{
|
{
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
value: LogSessionUserAgentOption.Enabled,
|
value: 'enabled',
|
||||||
replaceable: false,
|
replaceable: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
])
|
])
|
||||||
|
|
||||||
private readonly vaultAccountDefaultSettingsOverwrites = new Map<SettingName, SettingDescription>([
|
private readonly vaultAccountDefaultSettingsOverwrites = new Map<string, SettingDescription>([
|
||||||
[
|
[
|
||||||
SettingName.LogSessionUserAgent,
|
SettingName.NAMES.LogSessionUserAgent,
|
||||||
{
|
{
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
value: LogSessionUserAgentOption.Disabled,
|
value: 'disabled',
|
||||||
replaceable: false,
|
replaceable: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
])
|
])
|
||||||
|
|
||||||
isSettingMutableByClient(settingName: SettingName): boolean {
|
isSettingMutableByClient(settingName: string): boolean {
|
||||||
if (this.CLIENT_IMMUTABLE_SETTINGS.includes(settingName)) {
|
if (this.CLIENT_IMMUTABLE_SETTINGS.includes(settingName)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -95,7 +90,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
getSensitivityForSetting(settingName: SettingName): boolean {
|
getSensitivityForSetting(settingName: string): boolean {
|
||||||
if (this.UNSENSITIVE_SETTINGS.includes(settingName)) {
|
if (this.UNSENSITIVE_SETTINGS.includes(settingName)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -103,7 +98,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
getEncryptionVersionForSetting(settingName: SettingName): EncryptionVersion {
|
getEncryptionVersionForSetting(settingName: string): EncryptionVersion {
|
||||||
if (this.UNENCRYPTED_SETTINGS.includes(settingName)) {
|
if (this.UNENCRYPTED_SETTINGS.includes(settingName)) {
|
||||||
return EncryptionVersion.Unencrypted
|
return EncryptionVersion.Unencrypted
|
||||||
}
|
}
|
||||||
@@ -111,7 +106,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
|||||||
return EncryptionVersion.Default
|
return EncryptionVersion.Default
|
||||||
}
|
}
|
||||||
|
|
||||||
getPermissionAssociatedWithSetting(settingName: SettingName): PermissionName | undefined {
|
getPermissionAssociatedWithSetting(settingName: string): PermissionName | undefined {
|
||||||
if (!this.permissionsAssociatedWithSettings.has(settingName)) {
|
if (!this.permissionsAssociatedWithSettings.has(settingName)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
@@ -119,11 +114,11 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
|||||||
return this.permissionsAssociatedWithSettings.get(settingName)
|
return this.permissionsAssociatedWithSettings.get(settingName)
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultSettingsAndValuesForNewUser(): Map<SettingName, SettingDescription> {
|
getDefaultSettingsAndValuesForNewUser(): Map<string, SettingDescription> {
|
||||||
return this.defaultSettings
|
return this.defaultSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<SettingName, SettingDescription> {
|
getDefaultSettingsAndValuesForNewVaultAccount(): Map<string, SettingDescription> {
|
||||||
const defaultVaultSettings = new Map(this.defaultSettings)
|
const defaultVaultSettings = new Map(this.defaultSettings)
|
||||||
|
|
||||||
for (const vaultAccountDefaultSettingOverwriteKey of this.vaultAccountDefaultSettingsOverwrites.keys()) {
|
for (const vaultAccountDefaultSettingOverwriteKey of this.vaultAccountDefaultSettingsOverwrites.keys()) {
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { PermissionName } from '@standardnotes/features'
|
import { PermissionName } from '@standardnotes/features'
|
||||||
import { SettingName, SubscriptionSettingName } from '@standardnotes/settings'
|
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
import { SettingDescription } from './SettingDescription'
|
import { SettingDescription } from './SettingDescription'
|
||||||
|
|
||||||
export interface SettingsAssociationServiceInterface {
|
export interface SettingsAssociationServiceInterface {
|
||||||
getDefaultSettingsAndValuesForNewUser(): Map<SettingName, SettingDescription>
|
getDefaultSettingsAndValuesForNewUser(): Map<string, SettingDescription>
|
||||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<SettingName, SettingDescription>
|
getDefaultSettingsAndValuesForNewVaultAccount(): Map<string, SettingDescription>
|
||||||
getPermissionAssociatedWithSetting(settingName: SettingName): PermissionName | undefined
|
getPermissionAssociatedWithSetting(settingName: string): PermissionName | undefined
|
||||||
getEncryptionVersionForSetting(settingName: SettingName): EncryptionVersion
|
getEncryptionVersionForSetting(settingName: string): EncryptionVersion
|
||||||
getSensitivityForSetting(settingName: SettingName): boolean
|
getSensitivityForSetting(settingName: string): boolean
|
||||||
isSettingMutableByClient(settingName: SettingName | SubscriptionSettingName): boolean
|
isSettingMutableByClient(settingName: string): boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ describe('SubscriptionSettingService', () => {
|
|||||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SubscriptionSettingName.FileUploadBytesUsed,
|
SettingName.NAMES.FileUploadBytesUsed,
|
||||||
{
|
{
|
||||||
value: '0',
|
value: '0',
|
||||||
sensitive: 0,
|
sensitive: 0,
|
||||||
@@ -102,7 +102,7 @@ describe('SubscriptionSettingService', () => {
|
|||||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SubscriptionSettingName.FileUploadBytesUsed,
|
SettingName.NAMES.FileUploadBytesUsed,
|
||||||
{
|
{
|
||||||
value: '0',
|
value: '0',
|
||||||
sensitive: 0,
|
sensitive: 0,
|
||||||
@@ -127,7 +127,7 @@ describe('SubscriptionSettingService', () => {
|
|||||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SubscriptionSettingName.FileUploadBytesUsed,
|
SettingName.NAMES.FileUploadBytesUsed,
|
||||||
{
|
{
|
||||||
value: '0',
|
value: '0',
|
||||||
sensitive: 0,
|
sensitive: 0,
|
||||||
@@ -152,7 +152,7 @@ describe('SubscriptionSettingService', () => {
|
|||||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SubscriptionSettingName.FileUploadBytesUsed,
|
SettingName.NAMES.FileUploadBytesUsed,
|
||||||
{
|
{
|
||||||
value: '0',
|
value: '0',
|
||||||
sensitive: 0,
|
sensitive: 0,
|
||||||
@@ -266,7 +266,7 @@ describe('SubscriptionSettingService', () => {
|
|||||||
await createService().findSubscriptionSettingWithDecryptedValue({
|
await createService().findSubscriptionSettingWithDecryptedValue({
|
||||||
userSubscriptionUuid: '2-3-4',
|
userSubscriptionUuid: '2-3-4',
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
subscriptionSettingName: 'test' as SubscriptionSettingName,
|
subscriptionSettingName: 'test',
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
serverEncryptionVersion: 1,
|
serverEncryptionVersion: 1,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { SubscriptionName, Uuid } from '@standardnotes/common'
|
import { SubscriptionName, Uuid } from '@standardnotes/common'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@@ -98,7 +97,7 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
|||||||
const existing = await this.findSubscriptionSettingWithDecryptedValue({
|
const existing = await this.findSubscriptionSettingWithDecryptedValue({
|
||||||
userUuid: (await userSubscription.user).uuid,
|
userUuid: (await userSubscription.user).uuid,
|
||||||
userSubscriptionUuid: userSubscription.uuid,
|
userSubscriptionUuid: userSubscription.uuid,
|
||||||
subscriptionSettingName: props.name as SubscriptionSettingName,
|
subscriptionSettingName: props.name,
|
||||||
settingUuid: props.uuid,
|
settingUuid: props.uuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -128,7 +127,7 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async findPreviousSubscriptionSetting(
|
private async findPreviousSubscriptionSetting(
|
||||||
settingName: SubscriptionSettingName,
|
settingName: string,
|
||||||
currentUserSubscriptionUuid: Uuid,
|
currentUserSubscriptionUuid: Uuid,
|
||||||
userUuid: Uuid,
|
userUuid: Uuid,
|
||||||
): Promise<SubscriptionSetting | null> {
|
): Promise<SubscriptionSetting | null> {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { PermissionName } from '@standardnotes/features'
|
import { PermissionName } from '@standardnotes/features'
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
@@ -50,14 +50,11 @@ describe('SubscriptionSettingsAssociationService', () => {
|
|||||||
|
|
||||||
const flatSettings = [
|
const flatSettings = [
|
||||||
...(
|
...(
|
||||||
settings as Map<
|
settings as Map<string, { value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }>
|
||||||
SubscriptionSettingName,
|
|
||||||
{ value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }
|
|
||||||
>
|
|
||||||
).keys(),
|
).keys(),
|
||||||
]
|
]
|
||||||
expect(flatSettings).toEqual(['FILE_UPLOAD_BYTES_USED', 'FILE_UPLOAD_BYTES_LIMIT'])
|
expect(flatSettings).toEqual(['FILE_UPLOAD_BYTES_USED', 'FILE_UPLOAD_BYTES_LIMIT'])
|
||||||
expect(settings?.get(SubscriptionSettingName.FileUploadBytesLimit)).toEqual({
|
expect(settings?.get(SettingName.NAMES.FileUploadBytesLimit)).toEqual({
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: 0,
|
serverEncryptionVersion: 0,
|
||||||
value: '107374182400',
|
value: '107374182400',
|
||||||
@@ -78,14 +75,11 @@ describe('SubscriptionSettingsAssociationService', () => {
|
|||||||
|
|
||||||
const flatSettings = [
|
const flatSettings = [
|
||||||
...(
|
...(
|
||||||
settings as Map<
|
settings as Map<string, { value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }>
|
||||||
SubscriptionSettingName,
|
|
||||||
{ value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }
|
|
||||||
>
|
|
||||||
).keys(),
|
).keys(),
|
||||||
]
|
]
|
||||||
expect(flatSettings).toEqual(['FILE_UPLOAD_BYTES_USED', 'FILE_UPLOAD_BYTES_LIMIT'])
|
expect(flatSettings).toEqual(['FILE_UPLOAD_BYTES_USED', 'FILE_UPLOAD_BYTES_LIMIT'])
|
||||||
expect(settings?.get(SubscriptionSettingName.FileUploadBytesLimit)).toEqual({
|
expect(settings?.get(SettingName.NAMES.FileUploadBytesLimit)).toEqual({
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: 0,
|
serverEncryptionVersion: 0,
|
||||||
value: '104857600',
|
value: '104857600',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
||||||
import { PermissionName } from '@standardnotes/features'
|
import { PermissionName } from '@standardnotes/features'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
@@ -19,15 +19,12 @@ export class SubscriptionSettingsAssociationService implements SubscriptionSetti
|
|||||||
@inject(TYPES.RoleRepository) private roleRepository: RoleRepositoryInterface,
|
@inject(TYPES.RoleRepository) private roleRepository: RoleRepositoryInterface,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private readonly settingsToSubscriptionNameMap = new Map<
|
private readonly settingsToSubscriptionNameMap = new Map<SubscriptionName, Map<string, SettingDescription>>([
|
||||||
SubscriptionName,
|
|
||||||
Map<SubscriptionSettingName, SettingDescription>
|
|
||||||
>([
|
|
||||||
[
|
[
|
||||||
SubscriptionName.PlusPlan,
|
SubscriptionName.PlusPlan,
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SubscriptionSettingName.FileUploadBytesUsed,
|
SettingName.NAMES.FileUploadBytesUsed,
|
||||||
{ sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false },
|
{ sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false },
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
@@ -36,7 +33,7 @@ export class SubscriptionSettingsAssociationService implements SubscriptionSetti
|
|||||||
SubscriptionName.ProPlan,
|
SubscriptionName.ProPlan,
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SubscriptionSettingName.FileUploadBytesUsed,
|
SettingName.NAMES.FileUploadBytesUsed,
|
||||||
{ sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false },
|
{ sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false },
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
@@ -45,14 +42,14 @@ export class SubscriptionSettingsAssociationService implements SubscriptionSetti
|
|||||||
|
|
||||||
async getDefaultSettingsAndValuesForSubscriptionName(
|
async getDefaultSettingsAndValuesForSubscriptionName(
|
||||||
subscriptionName: SubscriptionName,
|
subscriptionName: SubscriptionName,
|
||||||
): Promise<Map<SubscriptionSettingName, SettingDescription> | undefined> {
|
): Promise<Map<string, SettingDescription> | undefined> {
|
||||||
const defaultSettings = this.settingsToSubscriptionNameMap.get(subscriptionName)
|
const defaultSettings = this.settingsToSubscriptionNameMap.get(subscriptionName)
|
||||||
|
|
||||||
if (defaultSettings === undefined) {
|
if (defaultSettings === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultSettings.set(SubscriptionSettingName.FileUploadBytesLimit, {
|
defaultSettings.set(SettingName.NAMES.FileUploadBytesLimit, {
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
value: (await this.getFileUploadLimit(subscriptionName)).toString(),
|
value: (await this.getFileUploadLimit(subscriptionName)).toString(),
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { SubscriptionName } from '@standardnotes/common'
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
|
||||||
|
|
||||||
import { SettingDescription } from './SettingDescription'
|
import { SettingDescription } from './SettingDescription'
|
||||||
|
|
||||||
export interface SubscriptionSettingsAssociationServiceInterface {
|
export interface SubscriptionSettingsAssociationServiceInterface {
|
||||||
getDefaultSettingsAndValuesForSubscriptionName(
|
getDefaultSettingsAndValuesForSubscriptionName(
|
||||||
subscriptionName: SubscriptionName,
|
subscriptionName: SubscriptionName,
|
||||||
): Promise<Map<SubscriptionSettingName, SettingDescription> | undefined>
|
): Promise<Map<string, SettingDescription> | undefined>
|
||||||
getFileUploadLimit(subscriptionName: SubscriptionName): Promise<number>
|
getFileUploadLimit(subscriptionName: SubscriptionName): Promise<number>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { SubscriptionName } from '@standardnotes/common'
|
|||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { TokenEncoderInterface, ValetTokenData } from '@standardnotes/security'
|
import { TokenEncoderInterface, ValetTokenData } from '@standardnotes/security'
|
||||||
import { CreateValetTokenPayload, CreateValetTokenResponseData } from '@standardnotes/responses'
|
import { CreateValetTokenPayload, CreateValetTokenResponseData } from '@standardnotes/responses'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
import { UseCaseInterface } from '../UseCaseInterface'
|
import { UseCaseInterface } from '../UseCaseInterface'
|
||||||
@@ -56,7 +56,7 @@ export class CreateValetToken implements UseCaseInterface {
|
|||||||
const uploadBytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
const uploadBytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||||
userUuid: regularSubscriptionUserUuid,
|
userUuid: regularSubscriptionUserUuid,
|
||||||
userSubscriptionUuid: regularSubscription.uuid,
|
userSubscriptionUuid: regularSubscription.uuid,
|
||||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed,
|
subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed,
|
||||||
})
|
})
|
||||||
if (uploadBytesUsedSetting !== null) {
|
if (uploadBytesUsedSetting !== null) {
|
||||||
uploadBytesUsed = +(uploadBytesUsedSetting.value as string)
|
uploadBytesUsed = +(uploadBytesUsedSetting.value as string)
|
||||||
@@ -70,7 +70,7 @@ export class CreateValetToken implements UseCaseInterface {
|
|||||||
await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||||
userUuid: regularSubscriptionUserUuid,
|
userUuid: regularSubscriptionUserUuid,
|
||||||
userSubscriptionUuid: regularSubscription.uuid,
|
userSubscriptionUuid: regularSubscription.uuid,
|
||||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesLimit,
|
subscriptionSettingName: SettingName.NAMES.FileUploadBytesLimit,
|
||||||
})
|
})
|
||||||
if (overwriteWithUserUploadBytesLimitSetting !== null) {
|
if (overwriteWithUserUploadBytesLimitSetting !== null) {
|
||||||
uploadBytesLimit = +(overwriteWithUserUploadBytesLimitSetting.value as string)
|
uploadBytesLimit = +(overwriteWithUserUploadBytesLimitSetting.value as string)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
import { SettingProjector } from '../../../Projection/SettingProjector'
|
import { SettingProjector } from '../../../Projection/SettingProjector'
|
||||||
import { Setting } from '../../Setting/Setting'
|
import { Setting } from '../../Setting/Setting'
|
||||||
@@ -45,12 +45,12 @@ describe('GetSetting', () => {
|
|||||||
it('should not retrieve a sensitive setting for user', async () => {
|
it('should not retrieve a sensitive setting for user', async () => {
|
||||||
setting = {
|
setting = {
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
name: SettingName.MfaSecret,
|
name: SettingName.NAMES.MfaSecret,
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
|
|
||||||
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(setting)
|
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(setting)
|
||||||
|
|
||||||
expect(await createUseCase().execute({ userUuid: '1-2-3', settingName: SettingName.MfaSecret })).toEqual({
|
expect(await createUseCase().execute({ userUuid: '1-2-3', settingName: SettingName.NAMES.MfaSecret })).toEqual({
|
||||||
success: true,
|
success: true,
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
})
|
})
|
||||||
@@ -59,7 +59,7 @@ describe('GetSetting', () => {
|
|||||||
it('should retrieve a sensitive setting for user if explicitly told to', async () => {
|
it('should retrieve a sensitive setting for user if explicitly told to', async () => {
|
||||||
setting = {
|
setting = {
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
name: SettingName.MfaSecret,
|
name: SettingName.NAMES.MfaSecret,
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
|
|
||||||
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(setting)
|
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(setting)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { SettingName } from '@standardnotes/settings'
|
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { GetSettingDto } from './GetSettingDto'
|
import { GetSettingDto } from './GetSettingDto'
|
||||||
import { GetSettingResponse } from './GetSettingResponse'
|
import { GetSettingResponse } from './GetSettingResponse'
|
||||||
@@ -19,7 +18,7 @@ export class GetSetting implements UseCaseInterface {
|
|||||||
|
|
||||||
const setting = await this.settingService.findSettingWithDecryptedValue({
|
const setting = await this.settingService.findSettingWithDecryptedValue({
|
||||||
userUuid,
|
userUuid,
|
||||||
settingName: settingName as SettingName,
|
settingName: settingName,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (setting === null) {
|
if (setting === null) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { SettingProjector } from '../../../Projection/SettingProjector'
|
import { SettingProjector } from '../../../Projection/SettingProjector'
|
||||||
import { Setting } from '../../Setting/Setting'
|
import { Setting } from '../../Setting/Setting'
|
||||||
@@ -31,7 +31,7 @@ describe('GetSettings', () => {
|
|||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
|
|
||||||
mfaSetting = {
|
mfaSetting = {
|
||||||
name: SettingName.MfaSecret,
|
name: SettingName.NAMES.MfaSecret,
|
||||||
updatedAt: 122,
|
updatedAt: 122,
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { SubscriptionSettingProjector } from '../../../Projection/SubscriptionSettingProjector'
|
import { SubscriptionSettingProjector } from '../../../Projection/SubscriptionSettingProjector'
|
||||||
import { SubscriptionSetting } from '../../Setting/SubscriptionSetting'
|
import { SubscriptionSetting } from '../../Setting/SubscriptionSetting'
|
||||||
@@ -54,7 +54,7 @@ describe('GetSubscriptionSetting', () => {
|
|||||||
expect(
|
expect(
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed,
|
subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed,
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -70,7 +70,7 @@ describe('GetSubscriptionSetting', () => {
|
|||||||
expect(
|
expect(
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesLimit,
|
subscriptionSettingName: SettingName.NAMES.FileUploadBytesLimit,
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
success: false,
|
success: false,
|
||||||
@@ -86,7 +86,7 @@ describe('GetSubscriptionSetting', () => {
|
|||||||
expect(
|
expect(
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesLimit,
|
subscriptionSettingName: SettingName.NAMES.FileUploadBytesLimit,
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
success: false,
|
success: false,
|
||||||
@@ -99,7 +99,7 @@ describe('GetSubscriptionSetting', () => {
|
|||||||
it('should not retrieve a sensitive setting for user', async () => {
|
it('should not retrieve a sensitive setting for user', async () => {
|
||||||
subscriptionSetting = {
|
subscriptionSetting = {
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
name: SubscriptionSettingName.FileUploadBytesLimit,
|
name: SettingName.NAMES.FileUploadBytesLimit,
|
||||||
} as jest.Mocked<SubscriptionSetting>
|
} as jest.Mocked<SubscriptionSetting>
|
||||||
|
|
||||||
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest
|
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest
|
||||||
@@ -109,7 +109,7 @@ describe('GetSubscriptionSetting', () => {
|
|||||||
expect(
|
expect(
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesLimit,
|
subscriptionSettingName: SettingName.NAMES.FileUploadBytesLimit,
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -120,7 +120,7 @@ describe('GetSubscriptionSetting', () => {
|
|||||||
it('should retrieve a sensitive setting for user if explicitly told to', async () => {
|
it('should retrieve a sensitive setting for user if explicitly told to', async () => {
|
||||||
subscriptionSetting = {
|
subscriptionSetting = {
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
name: SubscriptionSettingName.FileUploadBytesLimit,
|
name: SettingName.NAMES.FileUploadBytesLimit,
|
||||||
} as jest.Mocked<SubscriptionSetting>
|
} as jest.Mocked<SubscriptionSetting>
|
||||||
|
|
||||||
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest
|
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest
|
||||||
@@ -130,7 +130,7 @@ describe('GetSubscriptionSetting', () => {
|
|||||||
expect(
|
expect(
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesLimit,
|
subscriptionSettingName: SettingName.NAMES.FileUploadBytesLimit,
|
||||||
allowSensitiveRetrieval: true,
|
allowSensitiveRetrieval: true,
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Uuid } from '@standardnotes/common'
|
import { Uuid } from '@standardnotes/common'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
|
||||||
|
|
||||||
export type GetSubscriptionSettingDTO = {
|
export type GetSubscriptionSettingDTO = {
|
||||||
userUuid: Uuid
|
userUuid: Uuid
|
||||||
subscriptionSettingName: SubscriptionSettingName
|
subscriptionSettingName: string
|
||||||
allowSensitiveRetrieval?: boolean
|
allowSensitiveRetrieval?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { SimpleSetting } from '../../Setting/SimpleSetting'
|
|||||||
import { User } from '../../User/User'
|
import { User } from '../../User/User'
|
||||||
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||||
import { UpdateSetting } from './UpdateSetting'
|
import { UpdateSetting } from './UpdateSetting'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
describe('UpdateSetting', () => {
|
describe('UpdateSetting', () => {
|
||||||
let settingService: SettingServiceInterface
|
let settingService: SettingServiceInterface
|
||||||
@@ -59,7 +59,7 @@ describe('UpdateSetting', () => {
|
|||||||
|
|
||||||
it('should create a setting', async () => {
|
it('should create a setting', async () => {
|
||||||
const props = {
|
const props = {
|
||||||
name: SettingName.ExtensionKey,
|
name: SettingName.NAMES.ExtensionKey,
|
||||||
unencryptedValue: 'test-setting-value',
|
unencryptedValue: 'test-setting-value',
|
||||||
serverEncryptionVersion: EncryptionVersion.Default,
|
serverEncryptionVersion: EncryptionVersion.Default,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
@@ -88,7 +88,7 @@ describe('UpdateSetting', () => {
|
|||||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
name: SettingName.ExtensionKey,
|
name: SettingName.NAMES.ExtensionKey,
|
||||||
unencryptedValue: 'test-setting-value',
|
unencryptedValue: 'test-setting-value',
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
@@ -136,7 +136,7 @@ describe('UpdateSetting', () => {
|
|||||||
roleService.userHasPermission = jest.fn().mockReturnValue(false)
|
roleService.userHasPermission = jest.fn().mockReturnValue(false)
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
name: SettingName.ExtensionKey,
|
name: SettingName.NAMES.ExtensionKey,
|
||||||
unencryptedValue: 'test-setting-value',
|
unencryptedValue: 'test-setting-value',
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
@@ -159,7 +159,7 @@ describe('UpdateSetting', () => {
|
|||||||
settingsAssociationService.isSettingMutableByClient = jest.fn().mockReturnValue(false)
|
settingsAssociationService.isSettingMutableByClient = jest.fn().mockReturnValue(false)
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
name: SettingName.ExtensionKey,
|
name: SettingName.NAMES.ExtensionKey,
|
||||||
unencryptedValue: 'test-setting-value',
|
unencryptedValue: 'test-setting-value',
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { SettingProjector } from '../../../Projection/SettingProjector'
|
|||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { SettingServiceInterface } from '../../Setting/SettingServiceInterface'
|
import { SettingServiceInterface } from '../../Setting/SettingServiceInterface'
|
||||||
import { User } from '../../User/User'
|
import { User } from '../../User/User'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
|
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
|
||||||
import { SettingsAssociationServiceInterface } from '../../Setting/SettingsAssociationServiceInterface'
|
import { SettingsAssociationServiceInterface } from '../../Setting/SettingsAssociationServiceInterface'
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ export class UpdateSetting implements UseCaseInterface {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: UpdateSettingDto): Promise<UpdateSettingResponse> {
|
async execute(dto: UpdateSettingDto): Promise<UpdateSettingResponse> {
|
||||||
if (!Object.values(SettingName).includes(dto.props.name as SettingName)) {
|
if (!Object.values(SettingName.NAMES).includes(dto.props.name)) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: {
|
error: {
|
||||||
@@ -51,7 +51,7 @@ export class UpdateSetting implements UseCaseInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await this.userHasPermissionToUpdateSetting(user, props.name as SettingName))) {
|
if (!(await this.userHasPermissionToUpdateSetting(user, props.name))) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: {
|
error: {
|
||||||
@@ -61,10 +61,8 @@ export class UpdateSetting implements UseCaseInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
props.serverEncryptionVersion = this.settingsAssociationService.getEncryptionVersionForSetting(
|
props.serverEncryptionVersion = this.settingsAssociationService.getEncryptionVersionForSetting(props.name)
|
||||||
props.name as SettingName,
|
props.sensitive = this.settingsAssociationService.getSensitivityForSetting(props.name)
|
||||||
)
|
|
||||||
props.sensitive = this.settingsAssociationService.getSensitivityForSetting(props.name as SettingName)
|
|
||||||
|
|
||||||
const response = await this.settingService.createOrReplace({
|
const response = await this.settingService.createOrReplace({
|
||||||
user,
|
user,
|
||||||
@@ -91,8 +89,8 @@ export class UpdateSetting implements UseCaseInterface {
|
|||||||
throw new Error(`Unrecognized status: ${exhaustiveCheck}!`)
|
throw new Error(`Unrecognized status: ${exhaustiveCheck}!`)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async userHasPermissionToUpdateSetting(user: User, settingName: SettingName): Promise<boolean> {
|
private async userHasPermissionToUpdateSetting(user: User, settingName: string): Promise<boolean> {
|
||||||
const settingIsMutableByClient = await this.settingsAssociationService.isSettingMutableByClient(settingName)
|
const settingIsMutableByClient = this.settingsAssociationService.isSettingMutableByClient(settingName)
|
||||||
if (!settingIsMutableByClient) {
|
if (!settingIsMutableByClient) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
|||||||
import { VerifyMFA } from './VerifyMFA'
|
import { VerifyMFA } from './VerifyMFA'
|
||||||
import { Setting } from '../Setting/Setting'
|
import { Setting } from '../Setting/Setting'
|
||||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { SelectorInterface } from '@standardnotes/security'
|
import { SelectorInterface } from '@standardnotes/security'
|
||||||
import { LockRepositoryInterface } from '../User/LockRepositoryInterface'
|
import { LockRepositoryInterface } from '../User/LockRepositoryInterface'
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ describe('VerifyMFA', () => {
|
|||||||
lockRepository.lockSuccessfullOTP = jest.fn()
|
lockRepository.lockSuccessfullOTP = jest.fn()
|
||||||
|
|
||||||
setting = {
|
setting = {
|
||||||
name: SettingName.MfaSecret,
|
name: SettingName.NAMES.MfaSecret,
|
||||||
value: 'shhhh',
|
value: 'shhhh',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ describe('VerifyMFA', () => {
|
|||||||
|
|
||||||
it('should pass MFA verification if user has MFA deleted', async () => {
|
it('should pass MFA verification if user has MFA deleted', async () => {
|
||||||
setting = {
|
setting = {
|
||||||
name: SettingName.MfaSecret,
|
name: SettingName.NAMES.MfaSecret,
|
||||||
value: null,
|
value: null,
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ describe('VerifyMFA', () => {
|
|||||||
|
|
||||||
it('should not pass MFA verification if mfa is not correct', async () => {
|
it('should not pass MFA verification if mfa is not correct', async () => {
|
||||||
setting = {
|
setting = {
|
||||||
name: SettingName.MfaSecret,
|
name: SettingName.NAMES.MfaSecret,
|
||||||
value: 'shhhh2',
|
value: 'shhhh2',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as crypto from 'crypto'
|
import * as crypto from 'crypto'
|
||||||
import { ErrorTag } from '@standardnotes/common'
|
import { ErrorTag } from '@standardnotes/common'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { authenticator } from 'otplib'
|
import { authenticator } from 'otplib'
|
||||||
@@ -50,7 +50,7 @@ export class VerifyMFA implements UseCaseInterface {
|
|||||||
|
|
||||||
const mfaSecret = await this.settingService.findSettingWithDecryptedValue({
|
const mfaSecret = await this.settingService.findSettingWithDecryptedValue({
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
settingName: SettingName.MfaSecret,
|
settingName: SettingName.NAMES.MfaSecret,
|
||||||
})
|
})
|
||||||
if (mfaSecret === null || mfaSecret.value === null) {
|
if (mfaSecret === null || mfaSecret.value === null) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { UserSubscription } from '../../Subscription/UserSubscription'
|
|||||||
import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface'
|
import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface'
|
||||||
|
|
||||||
import { VerifyPredicate } from './VerifyPredicate'
|
import { VerifyPredicate } from './VerifyPredicate'
|
||||||
import { EmailBackupFrequency } from '@standardnotes/settings'
|
|
||||||
|
|
||||||
describe('VerifyPredicate', () => {
|
describe('VerifyPredicate', () => {
|
||||||
let settingRepository: SettingRepositoryInterface
|
let settingRepository: SettingRepositoryInterface
|
||||||
@@ -30,7 +29,7 @@ describe('VerifyPredicate', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should tell that a user has enabled email backups', async () => {
|
it('should tell that a user has enabled email backups', async () => {
|
||||||
setting = { value: EmailBackupFrequency.Weekly } as jest.Mocked<Setting>
|
setting = { value: 'weekly' } as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@@ -44,7 +43,7 @@ describe('VerifyPredicate', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should tell that a user has disabled email backups', async () => {
|
it('should tell that a user has disabled email backups', async () => {
|
||||||
setting = { value: EmailBackupFrequency.Disabled } as jest.Mocked<Setting>
|
setting = { value: 'disabled' } as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Uuid } from '@standardnotes/common'
|
import { Uuid } from '@standardnotes/common'
|
||||||
import { PredicateName, PredicateVerificationResult } from '@standardnotes/predicates'
|
import { PredicateName, PredicateVerificationResult } from '@standardnotes/predicates'
|
||||||
import { EmailBackupFrequency, SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
@@ -40,9 +40,12 @@ export class VerifyPredicate implements UseCaseInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async hasUserEnabledEmailBackups(userUuid: Uuid): Promise<boolean> {
|
private async hasUserEnabledEmailBackups(userUuid: Uuid): Promise<boolean> {
|
||||||
const setting = await this.settingRepository.findOneByNameAndUserUuid(SettingName.EmailBackupFrequency, userUuid)
|
const setting = await this.settingRepository.findOneByNameAndUserUuid(
|
||||||
|
SettingName.NAMES.EmailBackupFrequency,
|
||||||
|
userUuid,
|
||||||
|
)
|
||||||
|
|
||||||
if (setting === null || setting.value === EmailBackupFrequency.Disabled) {
|
if (setting === null || setting.value === 'disabled') {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { SettingName } from '@standardnotes/settings'
|
|
||||||
import { ReadStream } from 'fs'
|
import { ReadStream } from 'fs'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Repository } from 'typeorm'
|
import { Repository } from 'typeorm'
|
||||||
@@ -18,7 +17,7 @@ export class MySQLSettingRepository implements SettingRepositoryInterface {
|
|||||||
return this.ormRepository.save(setting)
|
return this.ormRepository.save(setting)
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByUuidAndNames(uuid: string, names: SettingName[]): Promise<Setting | null> {
|
async findOneByUuidAndNames(uuid: string, names: string[]): Promise<Setting | null> {
|
||||||
return this.ormRepository
|
return this.ormRepository
|
||||||
.createQueryBuilder('setting')
|
.createQueryBuilder('setting')
|
||||||
.where('setting.uuid = :uuid AND setting.name IN (:...names)', {
|
.where('setting.uuid = :uuid AND setting.name IN (:...names)', {
|
||||||
@@ -28,7 +27,7 @@ export class MySQLSettingRepository implements SettingRepositoryInterface {
|
|||||||
.getOne()
|
.getOne()
|
||||||
}
|
}
|
||||||
|
|
||||||
async streamAllByNameAndValue(name: SettingName, value: string): Promise<ReadStream> {
|
async streamAllByNameAndValue(name: string, value: string): Promise<ReadStream> {
|
||||||
return this.ormRepository
|
return this.ormRepository
|
||||||
.createQueryBuilder('setting')
|
.createQueryBuilder('setting')
|
||||||
.where('setting.name = :name AND setting.value = :value', {
|
.where('setting.name = :name AND setting.value = :value', {
|
||||||
|
|||||||
@@ -35,6 +35,19 @@ describe('RoleNameCollection', () => {
|
|||||||
expect(valueOrError.getValue().equals(roles2)).toBeFalsy()
|
expect(valueOrError.getValue().equals(roles2)).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should tell if collections are not equal', () => {
|
||||||
|
const roles1 = [RoleName.create('PRO_USER').getValue(), RoleName.create('PLUS_USER').getValue()]
|
||||||
|
|
||||||
|
const roles2 = RoleNameCollection.create([
|
||||||
|
RoleName.create('PRO_USER').getValue(),
|
||||||
|
RoleName.create('PLUS_USER').getValue(),
|
||||||
|
RoleName.create('CORE_USER').getValue(),
|
||||||
|
]).getValue()
|
||||||
|
|
||||||
|
const valueOrError = RoleNameCollection.create(roles1)
|
||||||
|
expect(valueOrError.getValue().equals(roles2)).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
it('should tell if collections are equal', () => {
|
it('should tell if collections are equal', () => {
|
||||||
const roles1 = [
|
const roles1 = [
|
||||||
RoleName.create(RoleName.NAMES.ProUser).getValue(),
|
RoleName.create(RoleName.NAMES.ProUser).getValue(),
|
||||||
|
|||||||
21
packages/domain-core/src/Domain/Common/Timestamps.spec.ts
Normal file
21
packages/domain-core/src/Domain/Common/Timestamps.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Timestamps } from './Timestamps'
|
||||||
|
|
||||||
|
describe('Timestamps', () => {
|
||||||
|
it('should create a value object', () => {
|
||||||
|
const valueOrError = Timestamps.create(1, 2)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().createdAt).toEqual(1)
|
||||||
|
expect(valueOrError.getValue().updatedAt).toEqual(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create an invalid value object', () => {
|
||||||
|
let valueOrError = Timestamps.create(null as unknown as number, 'b' as unknown as number)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
|
||||||
|
valueOrError = Timestamps.create(2, 'a' as unknown as number)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
30
packages/domain-core/src/Domain/Common/Timestamps.ts
Normal file
30
packages/domain-core/src/Domain/Common/Timestamps.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { ValueObject } from '../Core/ValueObject'
|
||||||
|
import { TimestampsProps } from './TimestampsProps'
|
||||||
|
|
||||||
|
export class Timestamps extends ValueObject<TimestampsProps> {
|
||||||
|
get createdAt(): number {
|
||||||
|
return this.props.createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
get updatedAt(): number {
|
||||||
|
return this.props.updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: TimestampsProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(createdAt: number, updatedAt: number): Result<Timestamps> {
|
||||||
|
if (isNaN(createdAt) || createdAt === null || createdAt === undefined) {
|
||||||
|
return Result.fail<Timestamps>(
|
||||||
|
`Could not create Timestamps. Creation date should be a number, given: ${createdAt}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isNaN(updatedAt) || updatedAt === null || updatedAt === undefined) {
|
||||||
|
return Result.fail<Timestamps>(`Could not create Timestamps. Update date should be a number, given: ${createdAt}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok<Timestamps>(new Timestamps({ createdAt, updatedAt }))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface TimestampsProps {
|
||||||
|
createdAt: number
|
||||||
|
updatedAt: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { EncryptionVersion } from './EncryptionVersion'
|
||||||
|
|
||||||
|
describe('EncryptionVersion', () => {
|
||||||
|
it('should create a value object', () => {
|
||||||
|
const valueOrError = EncryptionVersion.create(1)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().value).toEqual(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create an invalid value object', () => {
|
||||||
|
let valueOrError = EncryptionVersion.create('asd' as unknown as number)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
|
||||||
|
valueOrError = EncryptionVersion.create(null as unknown as number)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
|
||||||
|
valueOrError = EncryptionVersion.create(undefined as unknown as number)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
|
||||||
|
valueOrError = EncryptionVersion.create(754)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { ValueObject } from '../Core/ValueObject'
|
||||||
|
import { EncryptionVersionProps } from './EncryptionVersionProps'
|
||||||
|
|
||||||
|
export class EncryptionVersion extends ValueObject<EncryptionVersionProps> {
|
||||||
|
static readonly VERSIONS = {
|
||||||
|
Unencrypted: 0,
|
||||||
|
Default: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): number {
|
||||||
|
return this.props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: EncryptionVersionProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(version: number): Result<EncryptionVersion> {
|
||||||
|
if (
|
||||||
|
isNaN(version) ||
|
||||||
|
version === null ||
|
||||||
|
version === undefined ||
|
||||||
|
!Object.values(this.VERSIONS).includes(version)
|
||||||
|
) {
|
||||||
|
return Result.fail<EncryptionVersion>(
|
||||||
|
`Could not create EncryptionVersion. Version should be a number, given: ${version}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok<EncryptionVersion>(new EncryptionVersion({ value: version }))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface EncryptionVersionProps {
|
||||||
|
value: number
|
||||||
|
}
|
||||||
48
packages/domain-core/src/Domain/Setting/SettingName.ts
Normal file
48
packages/domain-core/src/Domain/Setting/SettingName.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { ValueObject } from '../Core/ValueObject'
|
||||||
|
|
||||||
|
import { SettingNameProps } from './SettingNameProps'
|
||||||
|
|
||||||
|
export class SettingName extends ValueObject<SettingNameProps> {
|
||||||
|
static readonly NAMES = {
|
||||||
|
MfaSecret: 'MFA_SECRET',
|
||||||
|
ExtensionKey: 'EXTENSION_KEY',
|
||||||
|
EmailBackupFrequency: 'EMAIL_BACKUP_FREQUENCY',
|
||||||
|
DropboxBackupFrequency: 'DROPBOX_BACKUP_FREQUENCY',
|
||||||
|
DropboxBackupToken: 'DROPBOX_BACKUP_TOKEN',
|
||||||
|
OneDriveBackupFrequency: 'ONE_DRIVE_BACKUP_FREQUENCY',
|
||||||
|
OneDriveBackupToken: 'ONE_DRIVE_BACKUP_TOKEN',
|
||||||
|
GoogleDriveBackupFrequency: 'GOOGLE_DRIVE_BACKUP_FREQUENCY',
|
||||||
|
GoogleDriveBackupToken: 'GOOGLE_DRIVE_BACKUP_TOKEN',
|
||||||
|
MuteFailedBackupsEmails: 'MUTE_FAILED_BACKUPS_EMAILS',
|
||||||
|
MuteFailedCloudBackupsEmails: 'MUTE_FAILED_CLOUD_BACKUPS_EMAILS',
|
||||||
|
MuteSignInEmails: 'MUTE_SIGN_IN_EMAILS',
|
||||||
|
MuteMarketingEmails: 'MUTE_MARKETING_EMAILS',
|
||||||
|
ListedAuthorSecrets: 'LISTED_AUTHOR_SECRETS',
|
||||||
|
LogSessionUserAgent: 'LOG_SESSION_USER_AGENT',
|
||||||
|
FileUploadBytesLimit: 'FILE_UPLOAD_BYTES_LIMIT',
|
||||||
|
FileUploadBytesUsed: 'FILE_UPLOAD_BYTES_USED',
|
||||||
|
EmailUnsubscribeToken: 'EMAIL_UNSUBSCRIBE_TOKEN',
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): string {
|
||||||
|
return this.props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
isSensitive(): boolean {
|
||||||
|
return [SettingName.NAMES.MfaSecret, SettingName.NAMES.ExtensionKey].includes(this.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: SettingNameProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(name: string): Result<SettingName> {
|
||||||
|
const isValidName = Object.values(this.NAMES).includes(name)
|
||||||
|
if (!isValidName) {
|
||||||
|
return Result.fail<SettingName>(`Invalid setting name: ${name}`)
|
||||||
|
} else {
|
||||||
|
return Result.ok<SettingName>(new SettingName({ value: name }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface SettingNameProps {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ export * from './Common/RoleNameCollection'
|
|||||||
export * from './Common/RoleNameCollectionProps'
|
export * from './Common/RoleNameCollectionProps'
|
||||||
export * from './Common/Username'
|
export * from './Common/Username'
|
||||||
export * from './Common/UsernameProps'
|
export * from './Common/UsernameProps'
|
||||||
|
export * from './Common/Timestamps'
|
||||||
|
export * from './Common/TimestampsProps'
|
||||||
export * from './Common/Uuid'
|
export * from './Common/Uuid'
|
||||||
export * from './Common/UuidProps'
|
export * from './Common/UuidProps'
|
||||||
|
|
||||||
@@ -23,8 +25,14 @@ export * from './Core/ValueObjectProps'
|
|||||||
export * from './Email/EmailLevel'
|
export * from './Email/EmailLevel'
|
||||||
export * from './Email/EmailLevelProps'
|
export * from './Email/EmailLevelProps'
|
||||||
|
|
||||||
|
export * from './Encryption/EncryptionVersion'
|
||||||
|
export * from './Encryption/EncryptionVersionProps'
|
||||||
|
|
||||||
export * from './Mapping/MapperInterface'
|
export * from './Mapping/MapperInterface'
|
||||||
|
|
||||||
|
export * from './Setting/SettingName'
|
||||||
|
export * from './Setting/SettingNameProps'
|
||||||
|
|
||||||
export * from './Subscription/SubscriptionPlanName'
|
export * from './Subscription/SubscriptionPlanName'
|
||||||
export * from './Subscription/SubscriptionPlanNameProps'
|
export * from './Subscription/SubscriptionPlanNameProps'
|
||||||
|
|
||||||
|
|||||||
70
packages/settings/bin/server.ts
Normal file
70
packages/settings/bin/server.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import 'reflect-metadata'
|
||||||
|
|
||||||
|
import 'newrelic'
|
||||||
|
|
||||||
|
import * as Sentry from '@sentry/node'
|
||||||
|
|
||||||
|
import '../src/Infra/InversifyExpress/InversifyExpressHealthCheckController'
|
||||||
|
import '../src/Infra/InversifyExpress/InversifyExpressSettingsController'
|
||||||
|
|
||||||
|
import * as cors from 'cors'
|
||||||
|
import { urlencoded, json, Request, Response, NextFunction, RequestHandler, ErrorRequestHandler } from 'express'
|
||||||
|
import * as winston from 'winston'
|
||||||
|
|
||||||
|
import { InversifyExpressServer } from 'inversify-express-utils'
|
||||||
|
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||||
|
import TYPES from '../src/Bootstrap/Types'
|
||||||
|
import { Env } from '../src/Bootstrap/Env'
|
||||||
|
|
||||||
|
const container = new ContainerConfigLoader()
|
||||||
|
void container.load().then((container) => {
|
||||||
|
const env: Env = new Env()
|
||||||
|
env.load()
|
||||||
|
|
||||||
|
const server = new InversifyExpressServer(container)
|
||||||
|
|
||||||
|
server.setConfig((app) => {
|
||||||
|
app.use((_request: Request, response: Response, next: NextFunction) => {
|
||||||
|
response.setHeader('X-Settings-Version', container.get(TYPES.VERSION))
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
app.use(json())
|
||||||
|
app.use(urlencoded({ extended: true }))
|
||||||
|
app.use(cors())
|
||||||
|
|
||||||
|
if (env.get('SENTRY_DSN', true)) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: env.get('SENTRY_DSN'),
|
||||||
|
integrations: [new Sentry.Integrations.Http({ tracing: false, breadcrumbs: true })],
|
||||||
|
tracesSampleRate: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use(Sentry.Handlers.requestHandler() as RequestHandler)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const logger: winston.Logger = container.get(TYPES.Logger)
|
||||||
|
|
||||||
|
server.setErrorConfig((app) => {
|
||||||
|
if (env.get('SENTRY_DSN', true)) {
|
||||||
|
app.use(Sentry.Handlers.errorHandler() as ErrorRequestHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use((error: Record<string, unknown>, _request: Request, response: Response, _next: NextFunction) => {
|
||||||
|
logger.error(error.stack)
|
||||||
|
|
||||||
|
response.status(500).send({
|
||||||
|
error: {
|
||||||
|
message:
|
||||||
|
"Unfortunately, we couldn't handle your request. Please try again or contact our support if the error persists.",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const serverInstance = server.build()
|
||||||
|
|
||||||
|
serverInstance.listen(env.get('PORT'))
|
||||||
|
|
||||||
|
logger.info(`Server started on port ${process.env.PORT}`)
|
||||||
|
})
|
||||||
25
packages/settings/bin/worker.ts
Normal file
25
packages/settings/bin/worker.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import 'reflect-metadata'
|
||||||
|
|
||||||
|
import 'newrelic'
|
||||||
|
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||||
|
import TYPES from '../src/Bootstrap/Types'
|
||||||
|
import { Env } from '../src/Bootstrap/Env'
|
||||||
|
import { DomainEventSubscriberFactoryInterface } from '@standardnotes/domain-events'
|
||||||
|
|
||||||
|
const container = new ContainerConfigLoader()
|
||||||
|
void container.load().then((container) => {
|
||||||
|
const env: Env = new Env()
|
||||||
|
env.load()
|
||||||
|
|
||||||
|
const logger: Logger = container.get(TYPES.Logger)
|
||||||
|
|
||||||
|
logger.info('Starting worker...')
|
||||||
|
|
||||||
|
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
|
||||||
|
subscriberFactory.create().start()
|
||||||
|
|
||||||
|
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
|
||||||
|
})
|
||||||
@@ -7,4 +7,5 @@ module.exports = {
|
|||||||
transform: {
|
transform: {
|
||||||
...tsjPreset.transform,
|
...tsjPreset.transform,
|
||||||
},
|
},
|
||||||
|
coveragePathIgnorePatterns: ['/Bootstrap/', '/Controller/', '/Infra/', '/Mapping/'],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,64 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/settings",
|
"name": "@standardnotes/settings-server",
|
||||||
"version": "1.18.4",
|
"version": "1.0.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
"description": "Settings SDK for Standard Notes projects",
|
"private": true,
|
||||||
|
"description": "Settings Server",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"author": "Standard Notes",
|
"typings": "dist/src/index.d.ts",
|
||||||
"types": "dist/src/index.d.ts",
|
"repository": "git@github.com:standardnotes/server.git",
|
||||||
"files": [
|
"author": "Karol Sójko <karolsojko@standardnotes.com>",
|
||||||
"dist/src/**/*.js",
|
|
||||||
"dist/src/**/*.d.ts"
|
|
||||||
],
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -fr dist",
|
"clean": "rm -fr dist",
|
||||||
"prestart": "yarn clean",
|
"setup:env": "cp .env.sample .env",
|
||||||
"start": "tsc -p tsconfig.json --watch",
|
|
||||||
"build": "tsc --build",
|
"build": "tsc --build",
|
||||||
"lint": "eslint . --ext .ts"
|
"lint": "eslint . --ext .ts",
|
||||||
},
|
"lint:fix": "eslint . --ext .ts --fix",
|
||||||
"devDependencies": {
|
"pretest": "yarn lint && yarn build",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50% --passWithNoTests",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"start": "yarn node dist/bin/server.js",
|
||||||
"typescript": "^4.8.4"
|
"worker": "yarn node dist/bin/worker.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"reflect-metadata": "^0.1.13"
|
"@newrelic/winston-enricher": "^4.0.0",
|
||||||
|
"@sentry/node": "^7.19.0",
|
||||||
|
"@standardnotes/api": "^1.20.13",
|
||||||
|
"@standardnotes/domain-core": "workspace:^",
|
||||||
|
"@standardnotes/domain-events": "workspace:*",
|
||||||
|
"@standardnotes/domain-events-infra": "workspace:*",
|
||||||
|
"@standardnotes/security": "workspace:^",
|
||||||
|
"@standardnotes/time": "workspace:^",
|
||||||
|
"aws-sdk": "^2.1260.0",
|
||||||
|
"cors": "2.8.5",
|
||||||
|
"dotenv": "^16.0.1",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"helmet": "^6.0.0",
|
||||||
|
"inversify": "^6.0.1",
|
||||||
|
"inversify-express-utils": "^6.4.3",
|
||||||
|
"ioredis": "^5.2.4",
|
||||||
|
"mysql2": "^2.3.3",
|
||||||
|
"newrelic": "^9.6.0",
|
||||||
|
"reflect-metadata": "0.1.13",
|
||||||
|
"typeorm": "^0.3.10",
|
||||||
|
"winston": "^3.8.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/cors": "^2.8.9",
|
||||||
|
"@types/dotenv": "^8.2.0",
|
||||||
|
"@types/express": "^4.17.14",
|
||||||
|
"@types/inversify-express-utils": "^2.0.0",
|
||||||
|
"@types/ioredis": "^5.0.0",
|
||||||
|
"@types/jest": "^29.1.1",
|
||||||
|
"@types/newrelic": "^7.0.4",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.29.0",
|
||||||
|
"eslint": "^8.14.0",
|
||||||
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
|
"jest": "^29.1.2",
|
||||||
|
"npm-check-updates": "^16.0.1",
|
||||||
|
"ts-jest": "^29.0.3",
|
||||||
|
"typescript": "^4.8.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
163
packages/settings/src/Bootstrap/Container.ts
Normal file
163
packages/settings/src/Bootstrap/Container.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import * as winston from 'winston'
|
||||||
|
import Redis from 'ioredis'
|
||||||
|
import * as AWS from 'aws-sdk'
|
||||||
|
import { Container } from 'inversify'
|
||||||
|
import {
|
||||||
|
DomainEventHandlerInterface,
|
||||||
|
DomainEventMessageHandlerInterface,
|
||||||
|
DomainEventSubscriberFactoryInterface,
|
||||||
|
} from '@standardnotes/domain-events'
|
||||||
|
import { MapperInterface } from '@standardnotes/domain-core'
|
||||||
|
import { TokenDecoderInterface, CrossServiceTokenData, TokenDecoder } from '@standardnotes/security'
|
||||||
|
import {
|
||||||
|
RedisDomainEventSubscriberFactory,
|
||||||
|
RedisEventMessageHandler,
|
||||||
|
SQSDomainEventSubscriberFactory,
|
||||||
|
SQSEventMessageHandler,
|
||||||
|
SQSNewRelicEventMessageHandler,
|
||||||
|
} from '@standardnotes/domain-events-infra'
|
||||||
|
|
||||||
|
import { Env } from './Env'
|
||||||
|
import TYPES from './Types'
|
||||||
|
import { AppDataSource } from './DataSource'
|
||||||
|
import { InversifyExpressApiGatewayAuthMiddleware } from '../Infra/InversifyExpress/InversifyExpressApiGatewayAuthMiddleware'
|
||||||
|
import { Repository } from 'typeorm'
|
||||||
|
import { TypeORMSetting } from '../Infra/TypeORM/TypeORMSetting'
|
||||||
|
import { SettingRepositoryInterface } from '../Domain/Setting/SettingRepositoryInterface'
|
||||||
|
import { MySQLSettingRepository } from '../Infra/MySQL/MySQLSettingRepository'
|
||||||
|
import { SettingsController } from '../Controller/SettingsController'
|
||||||
|
import { SettingPersistenceMapper } from '../Mapping/SettingPersistenceMapper'
|
||||||
|
import { Setting } from '../Domain/Setting/Setting'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||||
|
|
||||||
|
export class ContainerConfigLoader {
|
||||||
|
async load(): Promise<Container> {
|
||||||
|
const env: Env = new Env()
|
||||||
|
env.load()
|
||||||
|
|
||||||
|
const container = new Container()
|
||||||
|
|
||||||
|
await AppDataSource.initialize()
|
||||||
|
|
||||||
|
const redisUrl = env.get('REDIS_URL')
|
||||||
|
const isRedisInClusterMode = redisUrl.indexOf(',') > 0
|
||||||
|
let redis
|
||||||
|
if (isRedisInClusterMode) {
|
||||||
|
redis = new Redis.Cluster(redisUrl.split(','))
|
||||||
|
} else {
|
||||||
|
redis = new Redis(redisUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
container.bind(TYPES.Redis).toConstantValue(redis)
|
||||||
|
|
||||||
|
const newrelicWinstonFormatter = newrelicFormatter(winston)
|
||||||
|
const winstonFormatters = [winston.format.splat(), winston.format.json()]
|
||||||
|
if (env.get('NEW_RELIC_ENABLED', true) === 'true') {
|
||||||
|
winstonFormatters.push(newrelicWinstonFormatter())
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = winston.createLogger({
|
||||||
|
level: env.get('LOG_LEVEL') || 'info',
|
||||||
|
format: winston.format.combine(...winstonFormatters),
|
||||||
|
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL') || 'info' })],
|
||||||
|
})
|
||||||
|
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
|
||||||
|
|
||||||
|
if (env.get('SQS_QUEUE_URL', true)) {
|
||||||
|
const sqsConfig: AWS.SQS.Types.ClientConfiguration = {
|
||||||
|
apiVersion: 'latest',
|
||||||
|
region: env.get('SQS_AWS_REGION', true),
|
||||||
|
}
|
||||||
|
if (env.get('SQS_ACCESS_KEY_ID', true) && env.get('SQS_SECRET_ACCESS_KEY', true)) {
|
||||||
|
sqsConfig.credentials = {
|
||||||
|
accessKeyId: env.get('SQS_ACCESS_KEY_ID', true),
|
||||||
|
secretAccessKey: env.get('SQS_SECRET_ACCESS_KEY', true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container.bind<AWS.SQS>(TYPES.SQS).toConstantValue(new AWS.SQS(sqsConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map
|
||||||
|
container
|
||||||
|
.bind<MapperInterface<Setting, TypeORMSetting>>(TYPES.SettingPersistenceMapper)
|
||||||
|
.toConstantValue(new SettingPersistenceMapper())
|
||||||
|
|
||||||
|
// ORM
|
||||||
|
container
|
||||||
|
.bind<Repository<TypeORMSetting>>(TYPES.ORMSettingRepository)
|
||||||
|
.toConstantValue(AppDataSource.getRepository(TypeORMSetting))
|
||||||
|
|
||||||
|
// env vars
|
||||||
|
container.bind(TYPES.REDIS_URL).toConstantValue(env.get('REDIS_URL'))
|
||||||
|
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true))
|
||||||
|
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
|
||||||
|
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
|
||||||
|
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
||||||
|
container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION'))
|
||||||
|
|
||||||
|
// Repositories
|
||||||
|
container
|
||||||
|
.bind<SettingRepositoryInterface>(TYPES.SettingRepository)
|
||||||
|
.toConstantValue(
|
||||||
|
new MySQLSettingRepository(
|
||||||
|
container.get(TYPES.ORMSettingRepository),
|
||||||
|
container.get(TYPES.SettingPersistenceMapper),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// use cases
|
||||||
|
|
||||||
|
// Controller
|
||||||
|
container.bind<SettingsController>(TYPES.SettingsController).toConstantValue(new SettingsController())
|
||||||
|
|
||||||
|
// Handlers
|
||||||
|
|
||||||
|
// Services
|
||||||
|
container
|
||||||
|
.bind<TokenDecoderInterface<CrossServiceTokenData>>(TYPES.CrossServiceTokenDecoder)
|
||||||
|
.toConstantValue(new TokenDecoder<CrossServiceTokenData>(container.get(TYPES.AUTH_JWT_SECRET)))
|
||||||
|
|
||||||
|
// Middleware
|
||||||
|
container
|
||||||
|
.bind<InversifyExpressApiGatewayAuthMiddleware>(TYPES.ApiGatewayAuthMiddleware)
|
||||||
|
.to(InversifyExpressApiGatewayAuthMiddleware)
|
||||||
|
|
||||||
|
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([])
|
||||||
|
|
||||||
|
if (env.get('SQS_QUEUE_URL', true)) {
|
||||||
|
container
|
||||||
|
.bind<DomainEventMessageHandlerInterface>(TYPES.DomainEventMessageHandler)
|
||||||
|
.toConstantValue(
|
||||||
|
env.get('NEW_RELIC_ENABLED', true) === 'true'
|
||||||
|
? new SQSNewRelicEventMessageHandler(eventHandlers, container.get(TYPES.Logger))
|
||||||
|
: new SQSEventMessageHandler(eventHandlers, container.get(TYPES.Logger)),
|
||||||
|
)
|
||||||
|
container
|
||||||
|
.bind<DomainEventSubscriberFactoryInterface>(TYPES.DomainEventSubscriberFactory)
|
||||||
|
.toConstantValue(
|
||||||
|
new SQSDomainEventSubscriberFactory(
|
||||||
|
container.get(TYPES.SQS),
|
||||||
|
container.get(TYPES.SQS_QUEUE_URL),
|
||||||
|
container.get(TYPES.DomainEventMessageHandler),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
container
|
||||||
|
.bind<DomainEventMessageHandlerInterface>(TYPES.DomainEventMessageHandler)
|
||||||
|
.toConstantValue(new RedisEventMessageHandler(eventHandlers, container.get(TYPES.Logger)))
|
||||||
|
container
|
||||||
|
.bind<DomainEventSubscriberFactoryInterface>(TYPES.DomainEventSubscriberFactory)
|
||||||
|
.toConstantValue(
|
||||||
|
new RedisDomainEventSubscriberFactory(
|
||||||
|
container.get(TYPES.Redis),
|
||||||
|
container.get(TYPES.DomainEventMessageHandler),
|
||||||
|
container.get(TYPES.REDIS_EVENTS_CHANNEL),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return container
|
||||||
|
}
|
||||||
|
}
|
||||||
44
packages/settings/src/Bootstrap/DataSource.ts
Normal file
44
packages/settings/src/Bootstrap/DataSource.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { DataSource, LoggerOptions } from 'typeorm'
|
||||||
|
|
||||||
|
import { TypeORMSetting } from '../Infra/TypeORM/TypeORMSetting'
|
||||||
|
|
||||||
|
import { Env } from './Env'
|
||||||
|
|
||||||
|
const env: Env = new Env()
|
||||||
|
env.load()
|
||||||
|
|
||||||
|
const maxQueryExecutionTime = env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||||
|
? +env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||||
|
: 45_000
|
||||||
|
|
||||||
|
export const AppDataSource = new DataSource({
|
||||||
|
type: 'mysql',
|
||||||
|
charset: 'utf8mb4',
|
||||||
|
supportBigNumbers: true,
|
||||||
|
bigNumberStrings: false,
|
||||||
|
maxQueryExecutionTime,
|
||||||
|
replication: {
|
||||||
|
master: {
|
||||||
|
host: env.get('DB_HOST'),
|
||||||
|
port: parseInt(env.get('DB_PORT')),
|
||||||
|
username: env.get('DB_USERNAME'),
|
||||||
|
password: env.get('DB_PASSWORD'),
|
||||||
|
database: env.get('DB_DATABASE'),
|
||||||
|
},
|
||||||
|
slaves: [
|
||||||
|
{
|
||||||
|
host: env.get('DB_REPLICA_HOST'),
|
||||||
|
port: parseInt(env.get('DB_PORT')),
|
||||||
|
username: env.get('DB_USERNAME'),
|
||||||
|
password: env.get('DB_PASSWORD'),
|
||||||
|
database: env.get('DB_DATABASE'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
removeNodeErrorCount: 10,
|
||||||
|
restoreNodeTimeout: 5,
|
||||||
|
},
|
||||||
|
entities: [TypeORMSetting],
|
||||||
|
migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
|
||||||
|
migrationsRun: true,
|
||||||
|
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
|
||||||
|
})
|
||||||
24
packages/settings/src/Bootstrap/Env.ts
Normal file
24
packages/settings/src/Bootstrap/Env.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { config, DotenvParseOutput } from 'dotenv'
|
||||||
|
import { injectable } from 'inversify'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class Env {
|
||||||
|
private env?: DotenvParseOutput
|
||||||
|
|
||||||
|
public load(): void {
|
||||||
|
const output = config()
|
||||||
|
this.env = <DotenvParseOutput>output.parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(key: string, optional = false): string {
|
||||||
|
if (!this.env) {
|
||||||
|
this.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env[key] && !optional) {
|
||||||
|
throw new Error(`Environment variable ${key} not set`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <string>process.env[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
33
packages/settings/src/Bootstrap/Types.ts
Normal file
33
packages/settings/src/Bootstrap/Types.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
const TYPES = {
|
||||||
|
DBConnection: Symbol.for('DBConnection'),
|
||||||
|
Logger: Symbol.for('Logger'),
|
||||||
|
Redis: Symbol.for('Redis'),
|
||||||
|
SQS: Symbol.for('SQS'),
|
||||||
|
// Map
|
||||||
|
SettingPersistenceMapper: Symbol.for('SettingPersistenceMapper'),
|
||||||
|
// ORM
|
||||||
|
ORMSettingRepository: Symbol.for('ORMSettingRepository'),
|
||||||
|
// Repositories
|
||||||
|
SettingRepository: Symbol.for('SettingRepository'),
|
||||||
|
// env vars
|
||||||
|
REDIS_URL: Symbol.for('REDIS_URL'),
|
||||||
|
SQS_QUEUE_URL: Symbol.for('SQS_QUEUE_URL'),
|
||||||
|
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
|
||||||
|
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
|
||||||
|
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
|
||||||
|
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
||||||
|
VERSION: Symbol.for('VERSION'),
|
||||||
|
// use cases
|
||||||
|
// Controller
|
||||||
|
SettingsController: Symbol.for('SettingsController'),
|
||||||
|
// Handlers
|
||||||
|
// Services
|
||||||
|
CrossServiceTokenDecoder: Symbol.for('CrossServiceTokenDecoder'),
|
||||||
|
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
|
||||||
|
DomainEventMessageHandler: Symbol.for('DomainEventMessageHandler'),
|
||||||
|
Timer: Symbol.for('Timer'),
|
||||||
|
// Middleware
|
||||||
|
ApiGatewayAuthMiddleware: Symbol.for('ApiGatewayAuthMiddleware'),
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TYPES
|
||||||
3
packages/settings/src/Controller/SettingsController.ts
Normal file
3
packages/settings/src/Controller/SettingsController.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export class SettingsController {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export enum CloudProvider {
|
|
||||||
Dropbox = 'Dropbox',
|
|
||||||
Google = 'Google Drive',
|
|
||||||
OneDrive = 'OneDrive',
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export enum DropboxBackupFrequency {
|
|
||||||
Disabled = 'disabled',
|
|
||||||
Daily = 'daily',
|
|
||||||
Weekly = 'weekly',
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export enum EmailBackupFrequency {
|
|
||||||
Disabled = 'disabled',
|
|
||||||
Daily = 'daily',
|
|
||||||
Weekly = 'weekly',
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export enum GoogleDriveBackupFrequency {
|
|
||||||
Disabled = 'disabled',
|
|
||||||
Daily = 'daily',
|
|
||||||
Weekly = 'weekly',
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export type ListedAuthorSecretsData = Array<{ authorId: number; secret: string; hostUrl: string }>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum LogSessionUserAgentOption {
|
|
||||||
Disabled = 'disabled',
|
|
||||||
Enabled = 'enabled',
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum MuteFailedBackupsEmailsOption {
|
|
||||||
Muted = 'muted',
|
|
||||||
NotMuted = 'not_muted',
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum MuteFailedCloudBackupsEmailsOption {
|
|
||||||
Muted = 'muted',
|
|
||||||
NotMuted = 'not_muted',
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum MuteMarketingEmailsOption {
|
|
||||||
Muted = 'muted',
|
|
||||||
NotMuted = 'not_muted',
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum MuteSignInEmailsOption {
|
|
||||||
Muted = 'muted',
|
|
||||||
NotMuted = 'not_muted',
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export enum OneDriveBackupFrequency {
|
|
||||||
Disabled = 'disabled',
|
|
||||||
Daily = 'daily',
|
|
||||||
Weekly = 'weekly',
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { SettingName } from './SettingName'
|
|
||||||
|
|
||||||
export type SensitiveSettingName = SettingName.MfaSecret | SettingName.ExtensionKey
|
|
||||||
17
packages/settings/src/Domain/Setting/Setting.ts
Normal file
17
packages/settings/src/Domain/Setting/Setting.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { SettingProps } from './SettingProps'
|
||||||
|
|
||||||
|
export class Setting extends Entity<SettingProps> {
|
||||||
|
get id(): UniqueEntityId {
|
||||||
|
return this._id
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: SettingProps, id?: UniqueEntityId) {
|
||||||
|
super(props, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(props: SettingProps, id?: UniqueEntityId): Result<Setting> {
|
||||||
|
return Result.ok<Setting>(new Setting(props, id))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
export enum SettingName {
|
|
||||||
MfaSecret = 'MFA_SECRET',
|
|
||||||
ExtensionKey = 'EXTENSION_KEY',
|
|
||||||
EmailBackupFrequency = 'EMAIL_BACKUP_FREQUENCY',
|
|
||||||
DropboxBackupFrequency = 'DROPBOX_BACKUP_FREQUENCY',
|
|
||||||
DropboxBackupToken = 'DROPBOX_BACKUP_TOKEN',
|
|
||||||
OneDriveBackupFrequency = 'ONE_DRIVE_BACKUP_FREQUENCY',
|
|
||||||
OneDriveBackupToken = 'ONE_DRIVE_BACKUP_TOKEN',
|
|
||||||
GoogleDriveBackupFrequency = 'GOOGLE_DRIVE_BACKUP_FREQUENCY',
|
|
||||||
GoogleDriveBackupToken = 'GOOGLE_DRIVE_BACKUP_TOKEN',
|
|
||||||
MuteFailedBackupsEmails = 'MUTE_FAILED_BACKUPS_EMAILS',
|
|
||||||
MuteFailedCloudBackupsEmails = 'MUTE_FAILED_CLOUD_BACKUPS_EMAILS',
|
|
||||||
MuteSignInEmails = 'MUTE_SIGN_IN_EMAILS',
|
|
||||||
MuteMarketingEmails = 'MUTE_MARKETING_EMAILS',
|
|
||||||
ListedAuthorSecrets = 'LISTED_AUTHOR_SECRETS',
|
|
||||||
LogSessionUserAgent = 'LOG_SESSION_USER_AGENT',
|
|
||||||
}
|
|
||||||
10
packages/settings/src/Domain/Setting/SettingProps.ts
Normal file
10
packages/settings/src/Domain/Setting/SettingProps.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { EncryptionVersion, SettingName, Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
export interface SettingProps {
|
||||||
|
userUuid: Uuid
|
||||||
|
name: SettingName
|
||||||
|
value: string | null
|
||||||
|
serverEncryptionVersion: EncryptionVersion
|
||||||
|
sensitive: boolean
|
||||||
|
timestamps: Timestamps
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { SettingName, Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { Setting } from './Setting'
|
||||||
|
|
||||||
|
export interface SettingRepositoryInterface {
|
||||||
|
findOneByNameAndValue(name: SettingName, value: string): Promise<Setting | null>
|
||||||
|
setValueOnMultipleSettings(settingNames: string[], userUuid: Uuid, value: string | null): Promise<void>
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum SubscriptionSettingName {
|
|
||||||
FileUploadBytesLimit = 'FILE_UPLOAD_BYTES_LIMIT',
|
|
||||||
FileUploadBytesUsed = 'FILE_UPLOAD_BYTES_USED',
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { inject, injectable } from 'inversify'
|
||||||
|
|
||||||
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
|
import { SettingRepositoryInterface } from '../../Setting/SettingRepositoryInterface'
|
||||||
|
import { UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||||
|
import { Setting } from '../../Setting/Setting'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class GetSettings implements UseCaseInterface<Setting[]> {
|
||||||
|
constructor(
|
||||||
|
@inject(TYPES.SettingRepository) private settingRepository: SettingRepositoryInterface,
|
||||||
|
@inject(TYPES.Crypter) private crypter: CrypterInterface,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(dto: GetSettingsDto): Promise<GetSettingsResponse> {
|
||||||
|
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||||
|
if (userUuidOrError.isFailed()) {
|
||||||
|
|
||||||
|
}
|
||||||
|
let settings = await this.settingRepository.findAllByUserUuid(userUuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
export * from './CloudProvider/CloudProvider'
|
|
||||||
export * from './DropboxBackupFrequency/DropboxBackupFrequency'
|
|
||||||
export * from './EmailBackupFrequency/EmailBackupFrequency'
|
|
||||||
export * from './GoogleDriveBackupFrequency/GoogleDriveBackupFrequency'
|
|
||||||
export * from './ListedAuthorSecretsData/ListedAuthorSecretsData'
|
|
||||||
export * from './LogSessionUserAgent/LogSessionUserAgentOption'
|
|
||||||
export * from './MuteFailedBackupsEmails/MuteFailedBackupsEmailsOption'
|
|
||||||
export * from './MuteFailedCloudBackupsEmails/MuteFailedCloudBackupsEmailsOption'
|
|
||||||
export * from './MuteMarketingEmails/MuteMarketingEmailsOption'
|
|
||||||
export * from './MuteSignInEmails/MuteSignInEmailsOption'
|
|
||||||
export * from './OneDriveBackupFrequency/OneDriveBackupFrequency'
|
|
||||||
export * from './Setting/SensitiveSettingName'
|
|
||||||
export * from './Setting/SettingName'
|
|
||||||
export * from './Setting/SubscriptionSettingName'
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface MuteAllEmailsRequestParams {
|
||||||
|
unsubscribeToken: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { CrossServiceTokenData, TokenDecoderInterface } from '@standardnotes/security'
|
||||||
|
import { NextFunction, Request, Response } from 'express'
|
||||||
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { BaseMiddleware } from 'inversify-express-utils'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
import TYPES from '../../Bootstrap/Types'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class InversifyExpressApiGatewayAuthMiddleware extends BaseMiddleware {
|
||||||
|
constructor(
|
||||||
|
@inject(TYPES.CrossServiceTokenDecoder) private tokenDecoder: TokenDecoderInterface<CrossServiceTokenData>,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async handler(request: Request, response: Response, next: NextFunction): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (!request.headers['x-auth-token']) {
|
||||||
|
this.logger.debug('ApiGatewayAuthMiddleware missing x-auth-token header.')
|
||||||
|
|
||||||
|
response.status(401).send({
|
||||||
|
error: {
|
||||||
|
tag: 'invalid-auth',
|
||||||
|
message: 'Invalid login credentials.',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const token: CrossServiceTokenData | undefined = this.tokenDecoder.decodeToken(
|
||||||
|
request.headers['x-auth-token'] as string,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (token === undefined) {
|
||||||
|
this.logger.debug('ApiGatewayAuthMiddleware authentication failure.')
|
||||||
|
|
||||||
|
response.status(401).send({
|
||||||
|
error: {
|
||||||
|
tag: 'invalid-auth',
|
||||||
|
message: 'Invalid login credentials.',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.locals.user = token.user
|
||||||
|
response.locals.roles = token.roles
|
||||||
|
response.locals.session = token.session
|
||||||
|
response.locals.readOnlyAccess = token.session?.readonly_access ?? false
|
||||||
|
|
||||||
|
return next()
|
||||||
|
} catch (error) {
|
||||||
|
return next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { controller, httpGet } from 'inversify-express-utils'
|
||||||
|
|
||||||
|
@controller('/healthcheck')
|
||||||
|
export class InversifyExpressHealthCheckController {
|
||||||
|
@httpGet('/')
|
||||||
|
public async get(): Promise<string> {
|
||||||
|
return 'OK'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { BaseHttpController, controller } from 'inversify-express-utils'
|
||||||
|
import { inject } from 'inversify'
|
||||||
|
|
||||||
|
import TYPES from '../../Bootstrap/Types'
|
||||||
|
import { SettingsController } from '../../Controller/SettingsController'
|
||||||
|
|
||||||
|
@controller('/users/:userUuid/settings', TYPES.ApiGatewayAuthMiddleware)
|
||||||
|
export class InversifyExpressSettingsController extends BaseHttpController {
|
||||||
|
constructor(@inject(TYPES.SettingsController) private settingsController: SettingsController) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
}
|
||||||
39
packages/settings/src/Infra/MySQL/MySQLSettingRepository.ts
Normal file
39
packages/settings/src/Infra/MySQL/MySQLSettingRepository.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { MapperInterface, SettingName, Uuid } from '@standardnotes/domain-core'
|
||||||
|
import { Repository } from 'typeorm'
|
||||||
|
import { Setting } from '../../Domain/Setting/Setting'
|
||||||
|
|
||||||
|
import { SettingRepositoryInterface } from '../../Domain/Setting/SettingRepositoryInterface'
|
||||||
|
import { TypeORMSetting } from '../TypeORM/TypeORMSetting'
|
||||||
|
|
||||||
|
export class MySQLSettingRepository implements SettingRepositoryInterface {
|
||||||
|
constructor(
|
||||||
|
private ormRepository: Repository<TypeORMSetting>,
|
||||||
|
private settingMapper: MapperInterface<Setting, TypeORMSetting>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async findOneByNameAndValue(name: SettingName, value: string): Promise<Setting | null> {
|
||||||
|
const typeormSetting = await this.ormRepository
|
||||||
|
.createQueryBuilder()
|
||||||
|
.where('name = :name', { name: name.value })
|
||||||
|
.andWhere('value = :value', { value })
|
||||||
|
.getOne()
|
||||||
|
|
||||||
|
if (typeormSetting === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.settingMapper.toDomain(typeormSetting)
|
||||||
|
}
|
||||||
|
|
||||||
|
async setValueOnMultipleSettings(settingNames: string[], userUuid: Uuid, value: string | null): Promise<void> {
|
||||||
|
await this.ormRepository
|
||||||
|
.createQueryBuilder()
|
||||||
|
.update()
|
||||||
|
.set({
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
.where('user_uuid = :userUuid', { userUuid: userUuid.value })
|
||||||
|
.andWhere('name IN (:...settingNames)', { settingNames })
|
||||||
|
.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
55
packages/settings/src/Infra/TypeORM/TypeORMSetting.ts
Normal file
55
packages/settings/src/Infra/TypeORM/TypeORMSetting.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { EncryptionVersion } from '@standardnotes/domain-core'
|
||||||
|
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
|
||||||
|
|
||||||
|
@Entity({ name: 'settings' })
|
||||||
|
@Index('index_settings_on_name_and_user_uuid', ['name', 'userUuid'])
|
||||||
|
export class TypeORMSetting {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
declare uuid: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
length: 255,
|
||||||
|
})
|
||||||
|
declare name: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'text',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
declare value: string | null
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'server_encryption_version',
|
||||||
|
type: 'tinyint',
|
||||||
|
default: EncryptionVersion.VERSIONS.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
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'user_uuid',
|
||||||
|
length: 36,
|
||||||
|
type: 'varchar',
|
||||||
|
})
|
||||||
|
declare userUuid: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'tinyint',
|
||||||
|
width: 1,
|
||||||
|
nullable: false,
|
||||||
|
default: 0,
|
||||||
|
})
|
||||||
|
declare sensitive: boolean
|
||||||
|
}
|
||||||
72
packages/settings/src/Mapping/SettingPersistenceMapper.ts
Normal file
72
packages/settings/src/Mapping/SettingPersistenceMapper.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import {
|
||||||
|
EncryptionVersion,
|
||||||
|
MapperInterface,
|
||||||
|
SettingName,
|
||||||
|
Timestamps,
|
||||||
|
UniqueEntityId,
|
||||||
|
Uuid,
|
||||||
|
} from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { Setting } from '../Domain/Setting/Setting'
|
||||||
|
import { TypeORMSetting } from '../Infra/TypeORM/TypeORMSetting'
|
||||||
|
|
||||||
|
export class SettingPersistenceMapper implements MapperInterface<Setting, TypeORMSetting> {
|
||||||
|
toDomain(projection: TypeORMSetting): Setting {
|
||||||
|
const settingNameOrError = SettingName.create(projection.name)
|
||||||
|
if (settingNameOrError.isFailed()) {
|
||||||
|
throw new Error(`Could not create setting projection: ${settingNameOrError.getError()}`)
|
||||||
|
}
|
||||||
|
const name = settingNameOrError.getValue()
|
||||||
|
|
||||||
|
const serverEncryptionVersionOrError = EncryptionVersion.create(projection.serverEncryptionVersion)
|
||||||
|
if (serverEncryptionVersionOrError.isFailed()) {
|
||||||
|
throw new Error(`Could not create setting projection: ${serverEncryptionVersionOrError.getError()}`)
|
||||||
|
}
|
||||||
|
const serverEncryptionVersion = serverEncryptionVersionOrError.getValue()
|
||||||
|
|
||||||
|
const userUuidOrError = Uuid.create(projection.userUuid)
|
||||||
|
if (userUuidOrError.isFailed()) {
|
||||||
|
throw new Error(`Could not create setting projection: ${userUuidOrError.getError()}`)
|
||||||
|
}
|
||||||
|
const userUuid = userUuidOrError.getValue()
|
||||||
|
|
||||||
|
const timestampsOrError = Timestamps.create(projection.createdAt, projection.updatedAt)
|
||||||
|
if (timestampsOrError.isFailed()) {
|
||||||
|
throw new Error(`Could not create setting projection: ${timestampsOrError.getError()}`)
|
||||||
|
}
|
||||||
|
const timestamps = timestampsOrError.getValue()
|
||||||
|
|
||||||
|
const settingOrError = Setting.create(
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
sensitive: projection.sensitive,
|
||||||
|
serverEncryptionVersion,
|
||||||
|
timestamps,
|
||||||
|
userUuid,
|
||||||
|
value: projection.value,
|
||||||
|
},
|
||||||
|
new UniqueEntityId(projection.uuid),
|
||||||
|
)
|
||||||
|
if (settingOrError.isFailed()) {
|
||||||
|
throw new Error(`Could not create setting projection: ${settingOrError.getError()}`)
|
||||||
|
}
|
||||||
|
const setting = settingOrError.getValue()
|
||||||
|
|
||||||
|
return setting
|
||||||
|
}
|
||||||
|
|
||||||
|
toProjection(domain: Setting): TypeORMSetting {
|
||||||
|
const typeOrmSetting = new TypeORMSetting()
|
||||||
|
|
||||||
|
typeOrmSetting.name = domain.props.name.value
|
||||||
|
typeOrmSetting.sensitive = domain.props.sensitive
|
||||||
|
typeOrmSetting.serverEncryptionVersion = domain.props.serverEncryptionVersion.value
|
||||||
|
typeOrmSetting.userUuid = domain.props.userUuid.value
|
||||||
|
typeOrmSetting.uuid = domain.id.toString()
|
||||||
|
typeOrmSetting.value = domain.props.value
|
||||||
|
typeOrmSetting.createdAt = domain.props.timestamps.createdAt
|
||||||
|
typeOrmSetting.updatedAt = domain.props.timestamps.updatedAt
|
||||||
|
|
||||||
|
return typeOrmSetting
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './Domain'
|
|
||||||
@@ -5,7 +5,9 @@
|
|||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*"
|
"src/**/*",
|
||||||
|
"bin/**/*",
|
||||||
|
"migrations/**/*",
|
||||||
],
|
],
|
||||||
"references": []
|
"references": []
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user