diff --git a/.pnp.cjs b/.pnp.cjs index 6e143b2ed..06cd648bd 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -66,7 +66,7 @@ const RAW_RUNTIME_STATE = "reference": "workspace:packages/security"\ },\ {\ - "name": "@standardnotes/settings",\ + "name": "@standardnotes/settings-server",\ "reference": "workspace:packages/settings"\ },\ {\ @@ -107,7 +107,7 @@ const RAW_RUNTIME_STATE = ["@standardnotes/scheduler-server", ["workspace:packages/scheduler"]],\ ["@standardnotes/security", ["workspace:packages/security"]],\ ["@standardnotes/server-monorepo", ["workspace:."]],\ - ["@standardnotes/settings", ["workspace:packages/settings"]],\ + ["@standardnotes/settings-server", ["workspace:packages/settings"]],\ ["@standardnotes/sncrypto-node", ["workspace:packages/sncrypto-node"]],\ ["@standardnotes/syncing-server", ["workspace:packages/syncing-server"]],\ ["@standardnotes/time", ["workspace:packages/time"]],\ @@ -2582,6 +2582,20 @@ const RAW_RUNTIME_STATE = ["reflect-metadata", "npm:0.1.13"]\ ],\ "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", [\ @@ -2656,7 +2670,6 @@ const RAW_RUNTIME_STATE = ["@standardnotes/predicates", "workspace:packages/predicates"],\ ["@standardnotes/responses", "npm:1.11.1"],\ ["@standardnotes/security", "workspace:packages/security"],\ - ["@standardnotes/settings", "workspace:packages/settings"],\ ["@standardnotes/sncrypto-common", "npm:1.13.0"],\ ["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\ ["@standardnotes/time", "workspace:packages/time"],\ @@ -2812,6 +2825,19 @@ const RAW_RUNTIME_STATE = ["reflect-metadata", "npm:0.1.13"]\ ],\ "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", [\ @@ -2867,6 +2893,17 @@ const RAW_RUNTIME_STATE = ["reflect-metadata", "npm:0.1.13"]\ ],\ "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", [\ @@ -2948,6 +2985,13 @@ const RAW_RUNTIME_STATE = ["reflect-metadata", "npm:0.1.13"]\ ],\ "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", [\ @@ -3001,6 +3045,17 @@ const RAW_RUNTIME_STATE = ["reflect-metadata", "npm:0.1.13"]\ ],\ "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", [\ @@ -3132,15 +3187,46 @@ const RAW_RUNTIME_STATE = "linkType": "SOFT"\ }]\ ]],\ - ["@standardnotes/settings", [\ + ["@standardnotes/settings-server", [\ ["workspace:packages/settings", {\ "packageLocation": "./packages/settings/",\ "packageDependencies": [\ - ["@standardnotes/settings", "workspace:packages/settings"],\ - ["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.30.5"],\ - ["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:4.2.1"],\ + ["@standardnotes/settings-server", "workspace:packages/settings"],\ + ["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\ + ["@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"],\ - ["typescript", "patch:typescript@npm%3A4.8.4#optional!builtin::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::version=4.8.4&hash=701156"],\ + ["winston", "npm:3.8.2"]\ ],\ "linkType": "SOFT"\ }]\ @@ -3153,6 +3239,14 @@ const RAW_RUNTIME_STATE = ["reflect-metadata", "npm:0.1.13"]\ ],\ "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", [\ @@ -3189,7 +3283,6 @@ const RAW_RUNTIME_STATE = ["@standardnotes/payloads", "npm:1.5.1"],\ ["@standardnotes/responses", "npm:1.11.1"],\ ["@standardnotes/security", "workspace:packages/security"],\ - ["@standardnotes/settings", "workspace:packages/settings"],\ ["@standardnotes/time", "workspace:packages/time"],\ ["@types/cors", "npm:2.8.12"],\ ["@types/dotenv", "npm:8.2.0"],\ @@ -3273,6 +3366,17 @@ const RAW_RUNTIME_STATE = ["reflect-metadata", "npm:0.1.13"]\ ],\ "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", [\ @@ -6371,6 +6475,13 @@ const RAW_RUNTIME_STATE = ["dompurify", "npm:2.4.0"]\ ],\ "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", [\ diff --git a/.yarn/cache/@standardnotes-api-npm-1.20.13-3efe52d749-67bdb982ec.zip b/.yarn/cache/@standardnotes-api-npm-1.20.13-3efe52d749-67bdb982ec.zip new file mode 100644 index 000000000..03b92ff67 Binary files /dev/null and b/.yarn/cache/@standardnotes-api-npm-1.20.13-3efe52d749-67bdb982ec.zip differ diff --git a/.yarn/cache/@standardnotes-encryption-npm-1.19.21-dfa10f00e6-c8c2c27bfe.zip b/.yarn/cache/@standardnotes-encryption-npm-1.19.21-dfa10f00e6-c8c2c27bfe.zip new file mode 100644 index 000000000..5d7c1fb2b Binary files /dev/null and b/.yarn/cache/@standardnotes-encryption-npm-1.19.21-dfa10f00e6-c8c2c27bfe.zip differ diff --git a/.yarn/cache/@standardnotes-features-npm-1.55.3-c124505183-b39fe2d49b.zip b/.yarn/cache/@standardnotes-features-npm-1.55.3-c124505183-b39fe2d49b.zip new file mode 100644 index 000000000..1447fcdc0 Binary files /dev/null and b/.yarn/cache/@standardnotes-features-npm-1.55.3-c124505183-b39fe2d49b.zip differ diff --git a/.yarn/cache/@standardnotes-models-npm-1.38.0-108f602f56-2dc2ac957e.zip b/.yarn/cache/@standardnotes-models-npm-1.38.0-108f602f56-2dc2ac957e.zip new file mode 100644 index 000000000..79d3cd135 Binary files /dev/null and b/.yarn/cache/@standardnotes-models-npm-1.38.0-108f602f56-2dc2ac957e.zip differ diff --git a/.yarn/cache/@standardnotes-responses-npm-1.12.9-280dc75972-353fe1ca6d.zip b/.yarn/cache/@standardnotes-responses-npm-1.12.9-280dc75972-353fe1ca6d.zip new file mode 100644 index 000000000..7d1b94a83 Binary files /dev/null and b/.yarn/cache/@standardnotes-responses-npm-1.12.9-280dc75972-353fe1ca6d.zip differ diff --git a/.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.3-97ef3850ce-a73af90962.zip b/.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.3-97ef3850ce-a73af90962.zip new file mode 100644 index 000000000..524f39f7b Binary files /dev/null and b/.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.3-97ef3850ce-a73af90962.zip differ diff --git a/.yarn/cache/@standardnotes-utils-npm-1.13.0-28780a59f0-1578e8adb7.zip b/.yarn/cache/@standardnotes-utils-npm-1.13.0-28780a59f0-1578e8adb7.zip new file mode 100644 index 000000000..d5b19947b Binary files /dev/null and b/.yarn/cache/@standardnotes-utils-npm-1.13.0-28780a59f0-1578e8adb7.zip differ diff --git a/.yarn/cache/dompurify-npm-2.4.1-1c79f22057-ddc0633356.zip b/.yarn/cache/dompurify-npm-2.4.1-1c79f22057-ddc0633356.zip new file mode 100644 index 000000000..70bc9d428 Binary files /dev/null and b/.yarn/cache/dompurify-npm-2.4.1-1c79f22057-ddc0633356.zip differ diff --git a/packages/auth/bin/backup.ts b/packages/auth/bin/backup.ts index e0f6e524f..2b61dab4a 100644 --- a/packages/auth/bin/backup.ts +++ b/packages/auth/bin/backup.ts @@ -3,20 +3,19 @@ import 'reflect-metadata' import 'newrelic' import { Stream } from 'stream' - import { Logger } from 'winston' import * as dayjs from 'dayjs' 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 TYPES from '../src/Bootstrap/Types' import { Env } from '../src/Bootstrap/Env' -import { DomainEventPublisherInterface } from '@standardnotes/domain-events' import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface' import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface' -import { MuteFailedBackupsEmailsOption, MuteFailedCloudBackupsEmailsOption, SettingName } from '@standardnotes/settings' import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface' -import { PermissionName } from '@standardnotes/features' import { SettingServiceInterface } from '../src/Domain/Setting/SettingServiceInterface' const inputArgs = process.argv.slice(2) @@ -30,38 +29,38 @@ const requestBackups = async ( domainEventFactory: DomainEventFactoryInterface, domainEventPublisher: DomainEventPublisherInterface, ): Promise => { - let settingName: SettingName, + let settingName: string, permissionName: PermissionName, - muteEmailsSettingName: SettingName, + muteEmailsSettingName: string, muteEmailsSettingValue: string, - providerTokenSettingName: SettingName + providerTokenSettingName: string switch (backupProvider) { case 'email': - settingName = SettingName.EmailBackupFrequency + settingName = SettingName.NAMES.EmailBackupFrequency permissionName = PermissionName.DailyEmailBackup - muteEmailsSettingName = SettingName.MuteFailedBackupsEmails - muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted + muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails + muteEmailsSettingValue = 'muted' break case 'dropbox': - settingName = SettingName.DropboxBackupFrequency + settingName = SettingName.NAMES.DropboxBackupFrequency permissionName = PermissionName.DailyDropboxBackup - muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails - muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted - providerTokenSettingName = SettingName.DropboxBackupToken + muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails + muteEmailsSettingValue = 'muted' + providerTokenSettingName = SettingName.NAMES.DropboxBackupToken break case 'one_drive': - settingName = SettingName.OneDriveBackupFrequency + settingName = SettingName.NAMES.OneDriveBackupFrequency permissionName = PermissionName.DailyOneDriveBackup - muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails - muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted - providerTokenSettingName = SettingName.OneDriveBackupToken + muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails + muteEmailsSettingValue = 'muted' + providerTokenSettingName = SettingName.NAMES.OneDriveBackupToken break case 'google_drive': - settingName = SettingName.GoogleDriveBackupFrequency + settingName = SettingName.NAMES.GoogleDriveBackupFrequency permissionName = PermissionName.DailyGDriveBackup - muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails - muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted - providerTokenSettingName = SettingName.GoogleDriveBackupToken + muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails + muteEmailsSettingValue = 'muted' + providerTokenSettingName = SettingName.NAMES.GoogleDriveBackupToken break default: throw new Error(`Not handled backup provider: ${backupProvider}`) diff --git a/packages/auth/bin/user_email_backup.ts b/packages/auth/bin/user_email_backup.ts index ce3288c14..b6fb261f9 100644 --- a/packages/auth/bin/user_email_backup.ts +++ b/packages/auth/bin/user_email_backup.ts @@ -12,7 +12,7 @@ import { Env } from '../src/Bootstrap/Env' import { DomainEventPublisherInterface } from '@standardnotes/domain-events' import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface' import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface' -import { MuteFailedBackupsEmailsOption, SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface' import { PermissionName } from '@standardnotes/features' import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface' @@ -28,8 +28,8 @@ const requestBackups = async ( domainEventPublisher: DomainEventPublisherInterface, ): Promise => { const permissionName = PermissionName.DailyEmailBackup - const muteEmailsSettingName = SettingName.MuteFailedBackupsEmails - const muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted + const muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails + const muteEmailsSettingValue = 'muted' if (!backupEmail) { throw new Error('Could not trigger email backup for user - missing email parameter') diff --git a/packages/auth/migrations/1627638504691-move_mfa_items_to_user_settings.ts b/packages/auth/migrations/1627638504691-move_mfa_items_to_user_settings.ts index ae4f64b2a..539550756 100644 --- a/packages/auth/migrations/1627638504691-move_mfa_items_to_user_settings.ts +++ b/packages/auth/migrations/1627638504691-move_mfa_items_to_user_settings.ts @@ -1,5 +1,5 @@ import Redis, { Cluster } from 'ioredis' -import { SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { MigrationInterface, QueryRunner } from 'typeorm' import { Setting } from '../src/Domain/Setting/Setting' @@ -34,7 +34,7 @@ export class moveMfaItemsToUserSettings1627638504691 implements MigrationInterfa const setting = new Setting() setting.uuid = item['uuid'] - setting.name = SettingName.MfaSecret + setting.name = SettingName.NAMES.MfaSecret setting.value = item['content'] if (item['deleted']) { setting.value = null diff --git a/packages/auth/package.json b/packages/auth/package.json index 03a6268f4..6b2005a80 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -41,7 +41,6 @@ "@standardnotes/predicates": "workspace:*", "@standardnotes/responses": "^1.6.39", "@standardnotes/security": "workspace:*", - "@standardnotes/settings": "workspace:*", "@standardnotes/sncrypto-common": "^1.9.0", "@standardnotes/sncrypto-node": "workspace:*", "@standardnotes/time": "workspace:*", diff --git a/packages/auth/src/Controller/AdminController.ts b/packages/auth/src/Controller/AdminController.ts index 765ae1502..5071e34ac 100644 --- a/packages/auth/src/Controller/AdminController.ts +++ b/packages/auth/src/Controller/AdminController.ts @@ -1,4 +1,4 @@ -import { SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { Request } from 'express' import { inject } from 'inversify' import { @@ -69,7 +69,7 @@ export class AdminController extends BaseHttpController { const result = await this.doDeleteSetting.execute({ uuid, userUuid, - settingName: SettingName.MfaSecret, + settingName: SettingName.NAMES.MfaSecret, timestamp: updatedAt, softDelete: true, }) @@ -115,7 +115,7 @@ export class AdminController extends BaseHttpController { const result = await this.doDeleteSetting.execute({ userUuid, - settingName: SettingName.EmailBackupFrequency, + settingName: SettingName.NAMES.EmailBackupFrequency, }) if (result.success) { diff --git a/packages/auth/src/Controller/SubscriptionSettingsController.ts b/packages/auth/src/Controller/SubscriptionSettingsController.ts index 4b5eecbec..d8c4c6e88 100644 --- a/packages/auth/src/Controller/SubscriptionSettingsController.ts +++ b/packages/auth/src/Controller/SubscriptionSettingsController.ts @@ -1,4 +1,3 @@ -import { SubscriptionSettingName } from '@standardnotes/settings' import { Request, Response } from 'express' import { inject } from 'inversify' import { @@ -21,7 +20,7 @@ export class SubscriptionSettingsController extends BaseHttpController { async getSubscriptionSetting(request: Request, response: Response): Promise { const result = await this.doGetSubscriptionSetting.execute({ userUuid: response.locals.user.uuid, - subscriptionSettingName: request.params.subscriptionSettingName as SubscriptionSettingName, + subscriptionSettingName: request.params.subscriptionSettingName, }) if (result.success) { diff --git a/packages/auth/src/Controller/SubscriptionTokensController.ts b/packages/auth/src/Controller/SubscriptionTokensController.ts index 106773db6..623c9c767 100644 --- a/packages/auth/src/Controller/SubscriptionTokensController.ts +++ b/packages/auth/src/Controller/SubscriptionTokensController.ts @@ -1,6 +1,6 @@ import { CrossServiceTokenData, TokenEncoderInterface } from '@standardnotes/security' import { ErrorTag, RoleName } from '@standardnotes/common' -import { SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { Request, Response } from 'express' import { inject } from 'inversify' import { @@ -77,7 +77,7 @@ export class SubscriptionTokensController extends BaseHttpController { const user = authenticateTokenResponse.user as User let extensionKey = undefined const extensionKeySetting = await this.settingService.findSettingWithDecryptedValue({ - settingName: SettingName.ExtensionKey, + settingName: SettingName.NAMES.ExtensionKey, userUuid: user.uuid, }) if (extensionKeySetting !== null) { diff --git a/packages/auth/src/Domain/Handler/ExtensionKeyGrantedEventHandler.ts b/packages/auth/src/Domain/Handler/ExtensionKeyGrantedEventHandler.ts index 07c94d729..b4a5766b2 100644 --- a/packages/auth/src/Domain/Handler/ExtensionKeyGrantedEventHandler.ts +++ b/packages/auth/src/Domain/Handler/ExtensionKeyGrantedEventHandler.ts @@ -1,5 +1,5 @@ import { DomainEventHandlerInterface, ExtensionKeyGrantedEvent } from '@standardnotes/domain-events' -import { SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { OfflineFeaturesTokenData } from '@standardnotes/security' import { ContentDecoderInterface } from '@standardnotes/common' import { inject, injectable } from 'inversify' @@ -54,7 +54,7 @@ export class ExtensionKeyGrantedEventHandler implements DomainEventHandlerInterf await this.settingService.createOrReplace({ user, props: { - name: SettingName.ExtensionKey, + name: SettingName.NAMES.ExtensionKey, unencryptedValue: event.payload.extensionKey, serverEncryptionVersion: EncryptionVersion.Default, sensitive: true, diff --git a/packages/auth/src/Domain/Handler/FileRemovedEventHandler.ts b/packages/auth/src/Domain/Handler/FileRemovedEventHandler.ts index e03a9040c..297415f16 100644 --- a/packages/auth/src/Domain/Handler/FileRemovedEventHandler.ts +++ b/packages/auth/src/Domain/Handler/FileRemovedEventHandler.ts @@ -1,5 +1,5 @@ import { DomainEventHandlerInterface, FileRemovedEvent } from '@standardnotes/domain-events' -import { SubscriptionSettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { inject, injectable } from 'inversify' import { Logger } from 'winston' @@ -38,7 +38,7 @@ export class FileRemovedEventHandler implements DomainEventHandlerInterface { const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({ userUuid: user.uuid, userSubscriptionUuid: subscription.uuid, - subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed, + subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed, }) if (bytesUsedSetting === null) { 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({ userSubscription: subscription, props: { - name: SubscriptionSettingName.FileUploadBytesUsed, + name: SettingName.NAMES.FileUploadBytesUsed, unencryptedValue: (+bytesUsed - byteSize).toString(), sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, diff --git a/packages/auth/src/Domain/Handler/FileUploadedEventHandler.ts b/packages/auth/src/Domain/Handler/FileUploadedEventHandler.ts index 714a8dc04..b87a401ca 100644 --- a/packages/auth/src/Domain/Handler/FileUploadedEventHandler.ts +++ b/packages/auth/src/Domain/Handler/FileUploadedEventHandler.ts @@ -1,5 +1,5 @@ import { DomainEventHandlerInterface, FileUploadedEvent } from '@standardnotes/domain-events' -import { SubscriptionSettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { inject, injectable } from 'inversify' import { Logger } from 'winston' @@ -47,7 +47,7 @@ export class FileUploadedEventHandler implements DomainEventHandlerInterface { const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({ userUuid: (await subscription.user).uuid, userSubscriptionUuid: subscription.uuid, - subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed, + subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed, }) if (bytesUsedSetting !== null) { bytesUsed = bytesUsedSetting.value as string @@ -56,7 +56,7 @@ export class FileUploadedEventHandler implements DomainEventHandlerInterface { await this.subscriptionSettingService.createOrReplace({ userSubscription: subscription, props: { - name: SubscriptionSettingName.FileUploadBytesUsed, + name: SettingName.NAMES.FileUploadBytesUsed, unencryptedValue: (+bytesUsed + byteSize).toString(), sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, diff --git a/packages/auth/src/Domain/Handler/ListedAccountCreatedEventHandler.ts b/packages/auth/src/Domain/Handler/ListedAccountCreatedEventHandler.ts index 1ab394497..2ef292686 100644 --- a/packages/auth/src/Domain/Handler/ListedAccountCreatedEventHandler.ts +++ b/packages/auth/src/Domain/Handler/ListedAccountCreatedEventHandler.ts @@ -1,5 +1,5 @@ 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 { 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 } - let authSecrets: ListedAuthorSecretsData = [newSecret] + let authSecrets = [newSecret] const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({ - settingName: SettingName.ListedAuthorSecrets, + settingName: SettingName.NAMES.ListedAuthorSecrets, userUuid: user.uuid, }) if (listedAuthorSecretsSetting !== null) { - const existingSecrets: ListedAuthorSecretsData = JSON.parse(listedAuthorSecretsSetting.value as string) + const existingSecrets = JSON.parse(listedAuthorSecretsSetting.value as string) existingSecrets.push(newSecret) authSecrets = existingSecrets } @@ -40,7 +40,7 @@ export class ListedAccountCreatedEventHandler implements DomainEventHandlerInter await this.settingService.createOrReplace({ user, props: { - name: SettingName.ListedAuthorSecrets, + name: SettingName.NAMES.ListedAuthorSecrets, unencryptedValue: JSON.stringify(authSecrets), sensitive: false, }, diff --git a/packages/auth/src/Domain/Handler/ListedAccountDeletedEventHandler.ts b/packages/auth/src/Domain/Handler/ListedAccountDeletedEventHandler.ts index 945a73728..0fc4d1668 100644 --- a/packages/auth/src/Domain/Handler/ListedAccountDeletedEventHandler.ts +++ b/packages/auth/src/Domain/Handler/ListedAccountDeletedEventHandler.ts @@ -1,5 +1,5 @@ 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 { Logger } from 'winston' @@ -24,7 +24,7 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter } const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({ - settingName: SettingName.ListedAuthorSecrets, + settingName: SettingName.NAMES.ListedAuthorSecrets, userUuid: user.uuid, }) if (listedAuthorSecretsSetting === null) { @@ -33,9 +33,9 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter return } - const existingSecrets: ListedAuthorSecretsData = JSON.parse(listedAuthorSecretsSetting.value as string) + const existingSecrets = JSON.parse(listedAuthorSecretsSetting.value as string) const filteredSecrets = existingSecrets.filter( - (secret) => + (secret: Record) => secret.authorId !== event.payload.userId || (secret.authorId === event.payload.userId && secret.hostUrl !== event.payload.hostUrl), ) @@ -43,7 +43,7 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter await this.settingService.createOrReplace({ user, props: { - name: SettingName.ListedAuthorSecrets, + name: SettingName.NAMES.ListedAuthorSecrets, unencryptedValue: JSON.stringify(filteredSecrets), sensitive: false, }, diff --git a/packages/auth/src/Domain/Handler/SubscriptionReassignedEventHandler.ts b/packages/auth/src/Domain/Handler/SubscriptionReassignedEventHandler.ts index cb5abd69a..f2e59fd25 100644 --- a/packages/auth/src/Domain/Handler/SubscriptionReassignedEventHandler.ts +++ b/packages/auth/src/Domain/Handler/SubscriptionReassignedEventHandler.ts @@ -10,7 +10,7 @@ import { UserRepositoryInterface } from '../User/UserRepositoryInterface' import { UserSubscription } from '../Subscription/UserSubscription' import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface' import { SettingServiceInterface } from '../Setting/SettingServiceInterface' -import { SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { EncryptionVersion } from '../Encryption/EncryptionVersion' import { UserSubscriptionType } from '../Subscription/UserSubscriptionType' import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface' @@ -48,7 +48,7 @@ export class SubscriptionReassignedEventHandler implements DomainEventHandlerInt await this.settingService.createOrReplace({ user, props: { - name: SettingName.ExtensionKey, + name: SettingName.NAMES.ExtensionKey, unencryptedValue: event.payload.extensionKey, serverEncryptionVersion: EncryptionVersion.Default, sensitive: true, diff --git a/packages/auth/src/Domain/Handler/SubscriptionSyncRequestedEventHandler.ts b/packages/auth/src/Domain/Handler/SubscriptionSyncRequestedEventHandler.ts index fb0cfbbd4..99a3d1061 100644 --- a/packages/auth/src/Domain/Handler/SubscriptionSyncRequestedEventHandler.ts +++ b/packages/auth/src/Domain/Handler/SubscriptionSyncRequestedEventHandler.ts @@ -15,7 +15,7 @@ import { SettingServiceInterface } from '../Setting/SettingServiceInterface' import { OfflineSettingServiceInterface } from '../Setting/OfflineSettingServiceInterface' import { ContentDecoderInterface } from '@standardnotes/common' import { OfflineSettingName } from '../Setting/OfflineSettingName' -import { SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { EncryptionVersion } from '../Encryption/EncryptionVersion' import { UserSubscriptionType } from '../Subscription/UserSubscriptionType' import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface' @@ -95,7 +95,7 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler await this.settingService.createOrReplace({ user, props: { - name: SettingName.ExtensionKey, + name: SettingName.NAMES.ExtensionKey, unencryptedValue: event.payload.extensionKey, serverEncryptionVersion: EncryptionVersion.Default, sensitive: true, diff --git a/packages/auth/src/Domain/Session/SessionService.spec.ts b/packages/auth/src/Domain/Session/SessionService.spec.ts index 4140af480..55807adb5 100644 --- a/packages/auth/src/Domain/Session/SessionService.spec.ts +++ b/packages/auth/src/Domain/Session/SessionService.spec.ts @@ -12,7 +12,6 @@ import { EphemeralSession } from './EphemeralSession' import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface' import { RevokedSession } from './RevokedSession' import { SettingServiceInterface } from '../Setting/SettingServiceInterface' -import { LogSessionUserAgentOption } from '@standardnotes/settings' import { Setting } from '../Setting/Setting' import { CryptoNode } from '@standardnotes/sncrypto-node' @@ -171,7 +170,7 @@ describe('SessionService', () => { user.uuid = '123' settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue({ - value: LogSessionUserAgentOption.Disabled, + value: 'disabled', } as jest.Mocked) const sessionPayload = await createService().createNewSessionForUser({ diff --git a/packages/auth/src/Domain/Session/SessionService.ts b/packages/auth/src/Domain/Session/SessionService.ts index a392846c2..9af02e1c4 100644 --- a/packages/auth/src/Domain/Session/SessionService.ts +++ b/packages/auth/src/Domain/Session/SessionService.ts @@ -16,7 +16,7 @@ import { EphemeralSession } from './EphemeralSession' import { RevokedSession } from './RevokedSession' import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface' import { SettingServiceInterface } from '../Setting/SettingServiceInterface' -import { LogSessionUserAgentOption, SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { SessionBody } from '@standardnotes/responses' import { Uuid } from '@standardnotes/common' import { CryptoNode } from '@standardnotes/sncrypto-node' @@ -291,7 +291,7 @@ export class SessionService implements SessionServiceInterface { private async isLoggingUserAgentEnabledOnSessions(user: User): Promise { const loggingSetting = await this.settingService.findSettingWithDecryptedValue({ - settingName: SettingName.LogSessionUserAgent, + settingName: SettingName.NAMES.LogSessionUserAgent, userUuid: user.uuid, }) @@ -299,6 +299,6 @@ export class SessionService implements SessionServiceInterface { return true } - return loggingSetting.value === LogSessionUserAgentOption.Enabled + return loggingSetting.value === 'enabled' } } diff --git a/packages/auth/src/Domain/Setting/FindSettingDTO.ts b/packages/auth/src/Domain/Setting/FindSettingDTO.ts index d341870bb..82f65b727 100644 --- a/packages/auth/src/Domain/Setting/FindSettingDTO.ts +++ b/packages/auth/src/Domain/Setting/FindSettingDTO.ts @@ -1,8 +1,7 @@ import { Uuid } from '@standardnotes/common' -import { SettingName } from '@standardnotes/settings' export type FindSettingDTO = { userUuid: string - settingName: SettingName + settingName: string settingUuid?: Uuid } diff --git a/packages/auth/src/Domain/Setting/FindSubscriptionSettingDTO.ts b/packages/auth/src/Domain/Setting/FindSubscriptionSettingDTO.ts index 549137efc..6fefbd14d 100644 --- a/packages/auth/src/Domain/Setting/FindSubscriptionSettingDTO.ts +++ b/packages/auth/src/Domain/Setting/FindSubscriptionSettingDTO.ts @@ -1,9 +1,8 @@ import { Uuid } from '@standardnotes/common' -import { SubscriptionSettingName } from '@standardnotes/settings' export type FindSubscriptionSettingDTO = { userUuid: Uuid userSubscriptionUuid: Uuid - subscriptionSettingName: SubscriptionSettingName + subscriptionSettingName: string settingUuid?: Uuid } diff --git a/packages/auth/src/Domain/Setting/SettingInterpreter.spec.ts b/packages/auth/src/Domain/Setting/SettingInterpreter.spec.ts index c3b0af950..d93eb048b 100644 --- a/packages/auth/src/Domain/Setting/SettingInterpreter.spec.ts +++ b/packages/auth/src/Domain/Setting/SettingInterpreter.spec.ts @@ -5,13 +5,8 @@ import { MuteEmailsSettingChangedEvent, UserDisabledSessionUserAgentLoggingEvent, } from '@standardnotes/domain-events' -import { - EmailBackupFrequency, - LogSessionUserAgentOption, - MuteMarketingEmailsOption, - OneDriveBackupFrequency, - SettingName, -} from '@standardnotes/settings' +import { MuteMarketingEmailsOption } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import 'reflect-metadata' import { Logger } from 'winston' 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 () => { const setting = { - name: SettingName.LogSessionUserAgent, - value: LogSessionUserAgentOption.Disabled, + name: SettingName.NAMES.LogSessionUserAgent, + value: 'disabled', } as jest.Mocked - await createInterpreter().interpretSettingUpdated(setting, user, LogSessionUserAgentOption.Disabled) + await createInterpreter().interpretSettingUpdated(setting, user, 'disabled') expect(domainEventPublisher.publish).toHaveBeenCalled() expect(domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent).toHaveBeenCalledWith({ @@ -86,11 +81,11 @@ describe('SettingInterpreter', () => { it('should trigger backup if email backup setting is created - emails not muted', async () => { const setting = { - name: SettingName.EmailBackupFrequency, - value: EmailBackupFrequency.Daily, + name: SettingName.NAMES.EmailBackupFrequency, + value: 'daily', } as jest.Mocked - await createInterpreter().interpretSettingUpdated(setting, user, EmailBackupFrequency.Daily) + await createInterpreter().interpretSettingUpdated(setting, user, 'daily') expect(domainEventPublisher.publish).toHaveBeenCalled() 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 () => { const setting = { - name: SettingName.EmailBackupFrequency, - value: EmailBackupFrequency.Daily, + name: SettingName.NAMES.EmailBackupFrequency, + value: 'daily', } as jest.Mocked settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({ - name: SettingName.MuteFailedBackupsEmails, + name: SettingName.NAMES.MuteFailedBackupsEmails, uuid: '6-7-8', value: 'muted', } as jest.Mocked) - await createInterpreter().interpretSettingUpdated(setting, user, EmailBackupFrequency.Daily) + await createInterpreter().interpretSettingUpdated(setting, user, 'daily') expect(domainEventPublisher.publish).toHaveBeenCalled() 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 () => { const setting = { - name: SettingName.EmailBackupFrequency, - value: EmailBackupFrequency.Disabled, + name: SettingName.NAMES.EmailBackupFrequency, + value: 'disabled', } as jest.Mocked 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(domainEventFactory.createEmailBackupRequestedEvent).not.toHaveBeenCalled() @@ -128,7 +123,7 @@ describe('SettingInterpreter', () => { it('should trigger cloud backup if dropbox backup setting is created', async () => { const setting = { - name: SettingName.DropboxBackupToken, + name: SettingName.NAMES.DropboxBackupToken, value: 'test-token', } as jest.Mocked 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 () => { const setting = { - name: SettingName.DropboxBackupToken, + name: SettingName.NAMES.DropboxBackupToken, value: 'test-token', } as jest.Mocked settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({ - name: SettingName.MuteFailedCloudBackupsEmails, + name: SettingName.NAMES.MuteFailedCloudBackupsEmails, uuid: '6-7-8', value: 'muted', } as jest.Mocked) @@ -170,7 +165,7 @@ describe('SettingInterpreter', () => { it('should trigger cloud backup if google drive backup setting is created', async () => { const setting = { - name: SettingName.GoogleDriveBackupToken, + name: SettingName.NAMES.GoogleDriveBackupToken, value: 'test-token', } as jest.Mocked 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 () => { const setting = { - name: SettingName.OneDriveBackupToken, + name: SettingName.NAMES.OneDriveBackupToken, value: 'test-token', } as jest.Mocked 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 () => { settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({ - name: SettingName.OneDriveBackupToken, + name: SettingName.NAMES.OneDriveBackupToken, serverEncryptionVersion: 1, value: 'encrypted-backup-token', sensitive: true, } as jest.Mocked) const setting = { - name: SettingName.OneDriveBackupFrequency, + name: SettingName.NAMES.OneDriveBackupFrequency, serverEncryptionVersion: 0, value: 'daily', sensitive: false, @@ -251,19 +246,19 @@ describe('SettingInterpreter', () => { it('should not trigger cloud backup if backup frequency setting is updated as disabled', async () => { settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({ - name: SettingName.OneDriveBackupToken, + name: SettingName.NAMES.OneDriveBackupToken, serverEncryptionVersion: 1, value: 'encrypted-backup-token', sensitive: true, } as jest.Mocked) const setting = { - name: SettingName.OneDriveBackupFrequency, + name: SettingName.NAMES.OneDriveBackupFrequency, serverEncryptionVersion: 0, - value: OneDriveBackupFrequency.Disabled, + value: 'disabled', sensitive: false, } as jest.Mocked - await createInterpreter().interpretSettingUpdated(setting, user, OneDriveBackupFrequency.Disabled) + await createInterpreter().interpretSettingUpdated(setting, user, 'disabled') expect(domainEventPublisher.publish).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 () => { settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce(null) const setting = { - name: SettingName.OneDriveBackupFrequency, + name: SettingName.NAMES.OneDriveBackupFrequency, serverEncryptionVersion: 0, value: 'daily', sensitive: false, diff --git a/packages/auth/src/Domain/Setting/SettingInterpreter.ts b/packages/auth/src/Domain/Setting/SettingInterpreter.ts index 12df19e8d..bf23be594 100644 --- a/packages/auth/src/Domain/Setting/SettingInterpreter.ts +++ b/packages/auth/src/Domain/Setting/SettingInterpreter.ts @@ -1,15 +1,5 @@ import { DomainEventPublisherInterface } from '@standardnotes/domain-events' -import { EmailLevel } from '@standardnotes/domain-core' -import { - DropboxBackupFrequency, - EmailBackupFrequency, - GoogleDriveBackupFrequency, - LogSessionUserAgentOption, - MuteFailedBackupsEmailsOption, - MuteFailedCloudBackupsEmailsOption, - OneDriveBackupFrequency, - SettingName, -} from '@standardnotes/settings' +import { EmailLevel, SettingName } from '@standardnotes/domain-core' import { inject, injectable } from 'inversify' import { Logger } from 'winston' import TYPES from '../../Bootstrap/Types' @@ -23,22 +13,18 @@ import { SettingRepositoryInterface } from './SettingRepositoryInterface' @injectable() export class SettingInterpreter implements SettingInterpreterInterface { private readonly cloudBackupTokenSettings = [ - SettingName.DropboxBackupToken, - SettingName.GoogleDriveBackupToken, - SettingName.OneDriveBackupToken, + SettingName.NAMES.DropboxBackupToken, + SettingName.NAMES.GoogleDriveBackupToken, + SettingName.NAMES.OneDriveBackupToken, ] private readonly cloudBackupFrequencySettings = [ - SettingName.DropboxBackupFrequency, - SettingName.GoogleDriveBackupFrequency, - SettingName.OneDriveBackupFrequency, + SettingName.NAMES.DropboxBackupFrequency, + SettingName.NAMES.GoogleDriveBackupFrequency, + SettingName.NAMES.OneDriveBackupFrequency, ] - private readonly cloudBackupFrequencyDisabledValues = [ - DropboxBackupFrequency.Disabled, - GoogleDriveBackupFrequency.Disabled, - OneDriveBackupFrequency.Disabled, - ] + private readonly cloudBackupFrequencyDisabledValues = ['disabled'] private readonly emailSettingToSubscriptionRejectionLevelMap: Map = new Map([ [SettingName.MuteFailedBackupsEmails, EmailLevel.LEVELS.FailedEmailBackup], @@ -77,11 +63,11 @@ export class SettingInterpreter implements SettingInterpreterInterface { let userHasEmailsMuted = false let muteEmailsSettingUuid = '' const muteFailedEmailsBackupSetting = await this.settingRepository.findOneByNameAndUserUuid( - SettingName.MuteFailedBackupsEmails, + SettingName.NAMES.MuteFailedBackupsEmails, userUuid, ) if (muteFailedEmailsBackupSetting !== null) { - userHasEmailsMuted = muteFailedEmailsBackupSetting.value === MuteFailedBackupsEmailsOption.Muted + userHasEmailsMuted = muteFailedEmailsBackupSetting.value === 'muted' muteEmailsSettingUuid = muteFailedEmailsBackupSetting.uuid } @@ -100,21 +86,19 @@ export class SettingInterpreter implements SettingInterpreterInterface { } 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 { return ( - (this.cloudBackupFrequencySettings.includes(setting.name as SettingName) || - this.cloudBackupTokenSettings.includes(setting.name as SettingName)) && - !this.cloudBackupFrequencyDisabledValues.includes( - setting.value as DropboxBackupFrequency | OneDriveBackupFrequency | GoogleDriveBackupFrequency, - ) + (this.cloudBackupFrequencySettings.includes(setting.name) || + this.cloudBackupTokenSettings.includes(setting.name)) && + !this.cloudBackupFrequencyDisabledValues.includes(setting.value as string) ) } 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( @@ -144,29 +128,26 @@ export class SettingInterpreter implements SettingInterpreterInterface { let cloudProvider let tokenSettingName switch (setting.name) { - case SettingName.DropboxBackupToken: - case SettingName.DropboxBackupFrequency: + case SettingName.NAMES.DropboxBackupToken: + case SettingName.NAMES.DropboxBackupFrequency: cloudProvider = 'DROPBOX' - tokenSettingName = SettingName.DropboxBackupToken + tokenSettingName = SettingName.NAMES.DropboxBackupToken break - case SettingName.GoogleDriveBackupToken: - case SettingName.GoogleDriveBackupFrequency: + case SettingName.NAMES.GoogleDriveBackupToken: + case SettingName.NAMES.GoogleDriveBackupFrequency: cloudProvider = 'GOOGLE_DRIVE' - tokenSettingName = SettingName.GoogleDriveBackupToken + tokenSettingName = SettingName.NAMES.GoogleDriveBackupToken break - case SettingName.OneDriveBackupToken: - case SettingName.OneDriveBackupFrequency: + case SettingName.NAMES.OneDriveBackupToken: + case SettingName.NAMES.OneDriveBackupFrequency: cloudProvider = 'ONE_DRIVE' - tokenSettingName = SettingName.OneDriveBackupToken + tokenSettingName = SettingName.NAMES.OneDriveBackupToken break } let backupToken = null - if (this.cloudBackupFrequencySettings.includes(setting.name as SettingName)) { - const tokenSetting = await this.settingRepository.findLastByNameAndUserUuid( - tokenSettingName as SettingName, - userUuid, - ) + if (this.cloudBackupFrequencySettings.includes(setting.name)) { + const tokenSetting = await this.settingRepository.findLastByNameAndUserUuid(tokenSettingName as string, userUuid) if (tokenSetting !== null) { backupToken = await this.settingDecrypter.decryptSettingValue(tokenSetting, userUuid) } @@ -183,11 +164,11 @@ export class SettingInterpreter implements SettingInterpreterInterface { let userHasEmailsMuted = false let muteEmailsSettingUuid = '' const muteFailedCloudBackupSetting = await this.settingRepository.findOneByNameAndUserUuid( - SettingName.MuteFailedCloudBackupsEmails, + SettingName.NAMES.MuteFailedCloudBackupsEmails, userUuid, ) if (muteFailedCloudBackupSetting !== null) { - userHasEmailsMuted = muteFailedCloudBackupSetting.value === MuteFailedCloudBackupsEmailsOption.Muted + userHasEmailsMuted = muteFailedCloudBackupSetting.value === 'muted' muteEmailsSettingUuid = muteFailedCloudBackupSetting.uuid } diff --git a/packages/auth/src/Domain/Setting/SettingRepositoryInterface.ts b/packages/auth/src/Domain/Setting/SettingRepositoryInterface.ts index b09e4e1e1..edecd808e 100644 --- a/packages/auth/src/Domain/Setting/SettingRepositoryInterface.ts +++ b/packages/auth/src/Domain/Setting/SettingRepositoryInterface.ts @@ -1,16 +1,15 @@ import { ReadStream } from 'fs' -import { SettingName } from '@standardnotes/settings' import { DeleteSettingDto } from '../UseCase/DeleteSetting/DeleteSettingDto' import { Setting } from './Setting' export interface SettingRepositoryInterface { findOneByUuid(uuid: string): Promise - findOneByUuidAndNames(uuid: string, names: SettingName[]): Promise + findOneByUuidAndNames(uuid: string, names: string[]): Promise findOneByNameAndUserUuid(name: string, userUuid: string): Promise findLastByNameAndUserUuid(name: string, userUuid: string): Promise findAllByUserUuid(userUuid: string): Promise - streamAllByNameAndValue(name: SettingName, value: string): Promise + streamAllByNameAndValue(name: string, value: string): Promise deleteByUserUuid(dto: DeleteSettingDto): Promise save(setting: Setting): Promise } diff --git a/packages/auth/src/Domain/Setting/SettingService.spec.ts b/packages/auth/src/Domain/Setting/SettingService.spec.ts index 6888ef895..3f85bc469 100644 --- a/packages/auth/src/Domain/Setting/SettingService.spec.ts +++ b/packages/auth/src/Domain/Setting/SettingService.spec.ts @@ -1,6 +1,6 @@ import 'reflect-metadata' -import { LogSessionUserAgentOption, MuteSignInEmailsOption, SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { Logger } from 'winston' import { EncryptionVersion } from '../Encryption/EncryptionVersion' import { User } from '../User/User' @@ -54,9 +54,9 @@ describe('SettingService', () => { settingsAssociationService.getDefaultSettingsAndValuesForNewUser = jest.fn().mockReturnValue( new Map([ [ - SettingName.MuteSignInEmails, + SettingName.NAMES.MuteSignInEmails, { - value: MuteSignInEmailsOption.NotMuted, + value: 'not_muted', sensitive: 0, serverEncryptionVersion: EncryptionVersion.Unencrypted, }, @@ -67,11 +67,11 @@ describe('SettingService', () => { settingsAssociationService.getDefaultSettingsAndValuesForNewVaultAccount = jest.fn().mockReturnValue( new Map([ [ - SettingName.LogSessionUserAgent, + SettingName.NAMES.LogSessionUserAgent, { sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, - value: LogSessionUserAgentOption.Disabled, + value: 'disabled', }, ], ]), @@ -173,9 +173,7 @@ describe('SettingService', () => { settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValue(setting) - expect( - await createService().findSettingWithDecryptedValue({ userUuid: '1-2-3', settingName: 'test' as SettingName }), - ).toEqual({ + expect(await createService().findSettingWithDecryptedValue({ userUuid: '1-2-3', settingName: 'test' })).toEqual({ serverEncryptionVersion: 1, value: 'decrypted', }) diff --git a/packages/auth/src/Domain/Setting/SettingService.ts b/packages/auth/src/Domain/Setting/SettingService.ts index 09a1b76e4..654929f47 100644 --- a/packages/auth/src/Domain/Setting/SettingService.ts +++ b/packages/auth/src/Domain/Setting/SettingService.ts @@ -1,4 +1,3 @@ -import { SettingName } from '@standardnotes/settings' import { inject, injectable } from 'inversify' import { Logger } from 'winston' import TYPES from '../../Bootstrap/Types' @@ -74,7 +73,7 @@ export class SettingService implements SettingServiceInterface { const existing = await this.findSettingWithDecryptedValue({ userUuid: user.uuid, - settingName: props.name as SettingName, + settingName: props.name, settingUuid: props.uuid, }) diff --git a/packages/auth/src/Domain/Setting/SettingsAssociationService.spec.ts b/packages/auth/src/Domain/Setting/SettingsAssociationService.spec.ts index 2748072a7..08291e9f8 100644 --- a/packages/auth/src/Domain/Setting/SettingsAssociationService.spec.ts +++ b/packages/auth/src/Domain/Setting/SettingsAssociationService.spec.ts @@ -1,6 +1,6 @@ import 'reflect-metadata' -import { SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { PermissionName } from '@standardnotes/features' import { SettingsAssociationService } from './SettingsAssociationService' @@ -11,52 +11,54 @@ describe('SettingsAssociationService', () => { const createService = () => new SettingsAssociationService() 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', () => { - 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', () => { - 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', () => { - expect(createService().getEncryptionVersionForSetting(SettingName.EmailBackupFrequency)).toEqual( + expect(createService().getEncryptionVersionForSetting(SettingName.NAMES.EmailBackupFrequency)).toEqual( EncryptionVersion.Unencrypted, ) }) 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', () => { - 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', () => { const settings = createService().getDefaultSettingsAndValuesForNewUser() - const flatSettings = [...(settings as Map).keys()] + const flatSettings = [...(settings as Map).keys()] 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', () => { const settings = createService().getDefaultSettingsAndValuesForNewVaultAccount() - const flatSettings = [...(settings as Map).keys()] + const flatSettings = [...(settings as Map).keys()] 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', () => { - expect(createService().getPermissionAssociatedWithSetting(SettingName.EmailBackupFrequency)).toEqual( + expect(createService().getPermissionAssociatedWithSetting(SettingName.NAMES.EmailBackupFrequency)).toEqual( PermissionName.DailyEmailBackup, ) }) 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() }) }) diff --git a/packages/auth/src/Domain/Setting/SettingsAssociationService.ts b/packages/auth/src/Domain/Setting/SettingsAssociationService.ts index 5894dbf38..18d57be57 100644 --- a/packages/auth/src/Domain/Setting/SettingsAssociationService.ts +++ b/packages/auth/src/Domain/Setting/SettingsAssociationService.ts @@ -1,10 +1,5 @@ import { PermissionName } from '@standardnotes/features' -import { - LogSessionUserAgentOption, - MuteMarketingEmailsOption, - MuteSignInEmailsOption, - SettingName, -} from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { injectable } from 'inversify' import { EncryptionVersion } from '../Encryption/EncryptionVersion' @@ -15,79 +10,79 @@ import { SettingsAssociationServiceInterface } from './SettingsAssociationServic @injectable() export class SettingsAssociationService implements SettingsAssociationServiceInterface { private readonly UNENCRYPTED_SETTINGS = [ - SettingName.EmailBackupFrequency, - SettingName.MuteFailedBackupsEmails, - SettingName.MuteFailedCloudBackupsEmails, - SettingName.MuteSignInEmails, - SettingName.MuteMarketingEmails, - SettingName.DropboxBackupFrequency, - SettingName.GoogleDriveBackupFrequency, - SettingName.OneDriveBackupFrequency, - SettingName.LogSessionUserAgent, + SettingName.NAMES.EmailBackupFrequency, + SettingName.NAMES.MuteFailedBackupsEmails, + SettingName.NAMES.MuteFailedCloudBackupsEmails, + SettingName.NAMES.MuteSignInEmails, + SettingName.NAMES.MuteMarketingEmails, + SettingName.NAMES.DropboxBackupFrequency, + SettingName.NAMES.GoogleDriveBackupFrequency, + SettingName.NAMES.OneDriveBackupFrequency, + SettingName.NAMES.LogSessionUserAgent, ] private readonly UNSENSITIVE_SETTINGS = [ - SettingName.DropboxBackupFrequency, - SettingName.GoogleDriveBackupFrequency, - SettingName.OneDriveBackupFrequency, - SettingName.EmailBackupFrequency, - SettingName.MuteFailedBackupsEmails, - SettingName.MuteFailedCloudBackupsEmails, - SettingName.MuteSignInEmails, - SettingName.MuteMarketingEmails, - SettingName.ListedAuthorSecrets, - SettingName.LogSessionUserAgent, + SettingName.NAMES.DropboxBackupFrequency, + SettingName.NAMES.GoogleDriveBackupFrequency, + SettingName.NAMES.OneDriveBackupFrequency, + SettingName.NAMES.EmailBackupFrequency, + SettingName.NAMES.MuteFailedBackupsEmails, + SettingName.NAMES.MuteFailedCloudBackupsEmails, + SettingName.NAMES.MuteSignInEmails, + SettingName.NAMES.MuteMarketingEmails, + SettingName.NAMES.ListedAuthorSecrets, + SettingName.NAMES.LogSessionUserAgent, ] - private readonly CLIENT_IMMUTABLE_SETTINGS = [SettingName.ListedAuthorSecrets] + private readonly CLIENT_IMMUTABLE_SETTINGS = [SettingName.NAMES.ListedAuthorSecrets] - private readonly permissionsAssociatedWithSettings = new Map([ - [SettingName.EmailBackupFrequency, PermissionName.DailyEmailBackup], + private readonly permissionsAssociatedWithSettings = new Map([ + [SettingName.NAMES.EmailBackupFrequency, PermissionName.DailyEmailBackup], ]) - private readonly defaultSettings = new Map([ + private readonly defaultSettings = new Map([ [ - SettingName.MuteSignInEmails, + SettingName.NAMES.MuteSignInEmails, { sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, - value: MuteSignInEmailsOption.NotMuted, + value: 'not_muted', replaceable: false, }, ], [ - SettingName.MuteMarketingEmails, + SettingName.NAMES.MuteMarketingEmails, { sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, - value: MuteMarketingEmailsOption.NotMuted, + value: 'not_muted', replaceable: false, }, ], [ - SettingName.LogSessionUserAgent, + SettingName.NAMES.LogSessionUserAgent, { sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, - value: LogSessionUserAgentOption.Enabled, + value: 'enabled', replaceable: false, }, ], ]) - private readonly vaultAccountDefaultSettingsOverwrites = new Map([ + private readonly vaultAccountDefaultSettingsOverwrites = new Map([ [ - SettingName.LogSessionUserAgent, + SettingName.NAMES.LogSessionUserAgent, { sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, - value: LogSessionUserAgentOption.Disabled, + value: 'disabled', replaceable: false, }, ], ]) - isSettingMutableByClient(settingName: SettingName): boolean { + isSettingMutableByClient(settingName: string): boolean { if (this.CLIENT_IMMUTABLE_SETTINGS.includes(settingName)) { return false } @@ -95,7 +90,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt return true } - getSensitivityForSetting(settingName: SettingName): boolean { + getSensitivityForSetting(settingName: string): boolean { if (this.UNSENSITIVE_SETTINGS.includes(settingName)) { return false } @@ -103,7 +98,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt return true } - getEncryptionVersionForSetting(settingName: SettingName): EncryptionVersion { + getEncryptionVersionForSetting(settingName: string): EncryptionVersion { if (this.UNENCRYPTED_SETTINGS.includes(settingName)) { return EncryptionVersion.Unencrypted } @@ -111,7 +106,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt return EncryptionVersion.Default } - getPermissionAssociatedWithSetting(settingName: SettingName): PermissionName | undefined { + getPermissionAssociatedWithSetting(settingName: string): PermissionName | undefined { if (!this.permissionsAssociatedWithSettings.has(settingName)) { return undefined } @@ -119,11 +114,11 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt return this.permissionsAssociatedWithSettings.get(settingName) } - getDefaultSettingsAndValuesForNewUser(): Map { + getDefaultSettingsAndValuesForNewUser(): Map { return this.defaultSettings } - getDefaultSettingsAndValuesForNewVaultAccount(): Map { + getDefaultSettingsAndValuesForNewVaultAccount(): Map { const defaultVaultSettings = new Map(this.defaultSettings) for (const vaultAccountDefaultSettingOverwriteKey of this.vaultAccountDefaultSettingsOverwrites.keys()) { diff --git a/packages/auth/src/Domain/Setting/SettingsAssociationServiceInterface.ts b/packages/auth/src/Domain/Setting/SettingsAssociationServiceInterface.ts index 3e7cce194..f9e93937c 100644 --- a/packages/auth/src/Domain/Setting/SettingsAssociationServiceInterface.ts +++ b/packages/auth/src/Domain/Setting/SettingsAssociationServiceInterface.ts @@ -1,13 +1,12 @@ import { PermissionName } from '@standardnotes/features' -import { SettingName, SubscriptionSettingName } from '@standardnotes/settings' import { EncryptionVersion } from '../Encryption/EncryptionVersion' import { SettingDescription } from './SettingDescription' export interface SettingsAssociationServiceInterface { - getDefaultSettingsAndValuesForNewUser(): Map - getDefaultSettingsAndValuesForNewVaultAccount(): Map - getPermissionAssociatedWithSetting(settingName: SettingName): PermissionName | undefined - getEncryptionVersionForSetting(settingName: SettingName): EncryptionVersion - getSensitivityForSetting(settingName: SettingName): boolean - isSettingMutableByClient(settingName: SettingName | SubscriptionSettingName): boolean + getDefaultSettingsAndValuesForNewUser(): Map + getDefaultSettingsAndValuesForNewVaultAccount(): Map + getPermissionAssociatedWithSetting(settingName: string): PermissionName | undefined + getEncryptionVersionForSetting(settingName: string): EncryptionVersion + getSensitivityForSetting(settingName: string): boolean + isSettingMutableByClient(settingName: string): boolean } diff --git a/packages/auth/src/Domain/Setting/SubscriptionSettingService.spec.ts b/packages/auth/src/Domain/Setting/SubscriptionSettingService.spec.ts index 2642077e7..15ec65f3d 100644 --- a/packages/auth/src/Domain/Setting/SubscriptionSettingService.spec.ts +++ b/packages/auth/src/Domain/Setting/SubscriptionSettingService.spec.ts @@ -1,6 +1,6 @@ import 'reflect-metadata' -import { SubscriptionSettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { Logger } from 'winston' import { EncryptionVersion } from '../Encryption/EncryptionVersion' @@ -68,7 +68,7 @@ describe('SubscriptionSettingService', () => { subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue( new Map([ [ - SubscriptionSettingName.FileUploadBytesUsed, + SettingName.NAMES.FileUploadBytesUsed, { value: '0', sensitive: 0, @@ -102,7 +102,7 @@ describe('SubscriptionSettingService', () => { subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue( new Map([ [ - SubscriptionSettingName.FileUploadBytesUsed, + SettingName.NAMES.FileUploadBytesUsed, { value: '0', sensitive: 0, @@ -127,7 +127,7 @@ describe('SubscriptionSettingService', () => { subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue( new Map([ [ - SubscriptionSettingName.FileUploadBytesUsed, + SettingName.NAMES.FileUploadBytesUsed, { value: '0', sensitive: 0, @@ -152,7 +152,7 @@ describe('SubscriptionSettingService', () => { subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue( new Map([ [ - SubscriptionSettingName.FileUploadBytesUsed, + SettingName.NAMES.FileUploadBytesUsed, { value: '0', sensitive: 0, @@ -266,7 +266,7 @@ describe('SubscriptionSettingService', () => { await createService().findSubscriptionSettingWithDecryptedValue({ userSubscriptionUuid: '2-3-4', userUuid: '1-2-3', - subscriptionSettingName: 'test' as SubscriptionSettingName, + subscriptionSettingName: 'test', }), ).toEqual({ serverEncryptionVersion: 1, diff --git a/packages/auth/src/Domain/Setting/SubscriptionSettingService.ts b/packages/auth/src/Domain/Setting/SubscriptionSettingService.ts index 22ccd3c72..180bde6bc 100644 --- a/packages/auth/src/Domain/Setting/SubscriptionSettingService.ts +++ b/packages/auth/src/Domain/Setting/SubscriptionSettingService.ts @@ -1,5 +1,4 @@ import { SubscriptionName, Uuid } from '@standardnotes/common' -import { SubscriptionSettingName } from '@standardnotes/settings' import { inject, injectable } from 'inversify' import { Logger } from 'winston' @@ -98,7 +97,7 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt const existing = await this.findSubscriptionSettingWithDecryptedValue({ userUuid: (await userSubscription.user).uuid, userSubscriptionUuid: userSubscription.uuid, - subscriptionSettingName: props.name as SubscriptionSettingName, + subscriptionSettingName: props.name, settingUuid: props.uuid, }) @@ -128,7 +127,7 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt } private async findPreviousSubscriptionSetting( - settingName: SubscriptionSettingName, + settingName: string, currentUserSubscriptionUuid: Uuid, userUuid: Uuid, ): Promise { diff --git a/packages/auth/src/Domain/Setting/SubscriptionSettingsAssociationService.spec.ts b/packages/auth/src/Domain/Setting/SubscriptionSettingsAssociationService.spec.ts index 3ebd0d76f..36f5fbe31 100644 --- a/packages/auth/src/Domain/Setting/SubscriptionSettingsAssociationService.spec.ts +++ b/packages/auth/src/Domain/Setting/SubscriptionSettingsAssociationService.spec.ts @@ -1,7 +1,7 @@ import 'reflect-metadata' import { RoleName, SubscriptionName } from '@standardnotes/common' -import { SubscriptionSettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { PermissionName } from '@standardnotes/features' import { EncryptionVersion } from '../Encryption/EncryptionVersion' @@ -50,14 +50,11 @@ describe('SubscriptionSettingsAssociationService', () => { const flatSettings = [ ...( - settings as Map< - SubscriptionSettingName, - { value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion } - > + settings as Map ).keys(), ] 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, serverEncryptionVersion: 0, value: '107374182400', @@ -78,14 +75,11 @@ describe('SubscriptionSettingsAssociationService', () => { const flatSettings = [ ...( - settings as Map< - SubscriptionSettingName, - { value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion } - > + settings as Map ).keys(), ] 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, serverEncryptionVersion: 0, value: '104857600', diff --git a/packages/auth/src/Domain/Setting/SubscriptionSettingsAssociationService.ts b/packages/auth/src/Domain/Setting/SubscriptionSettingsAssociationService.ts index 8850acb15..72ac3a010 100644 --- a/packages/auth/src/Domain/Setting/SubscriptionSettingsAssociationService.ts +++ b/packages/auth/src/Domain/Setting/SubscriptionSettingsAssociationService.ts @@ -1,6 +1,6 @@ import { RoleName, SubscriptionName } from '@standardnotes/common' import { PermissionName } from '@standardnotes/features' -import { SubscriptionSettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { inject, injectable } from 'inversify' import TYPES from '../../Bootstrap/Types' @@ -19,15 +19,12 @@ export class SubscriptionSettingsAssociationService implements SubscriptionSetti @inject(TYPES.RoleRepository) private roleRepository: RoleRepositoryInterface, ) {} - private readonly settingsToSubscriptionNameMap = new Map< - SubscriptionName, - Map - >([ + private readonly settingsToSubscriptionNameMap = new Map>([ [ SubscriptionName.PlusPlan, new Map([ [ - SubscriptionSettingName.FileUploadBytesUsed, + SettingName.NAMES.FileUploadBytesUsed, { sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false }, ], ]), @@ -36,7 +33,7 @@ export class SubscriptionSettingsAssociationService implements SubscriptionSetti SubscriptionName.ProPlan, new Map([ [ - SubscriptionSettingName.FileUploadBytesUsed, + SettingName.NAMES.FileUploadBytesUsed, { sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false }, ], ]), @@ -45,14 +42,14 @@ export class SubscriptionSettingsAssociationService implements SubscriptionSetti async getDefaultSettingsAndValuesForSubscriptionName( subscriptionName: SubscriptionName, - ): Promise | undefined> { + ): Promise | undefined> { const defaultSettings = this.settingsToSubscriptionNameMap.get(subscriptionName) if (defaultSettings === undefined) { return undefined } - defaultSettings.set(SubscriptionSettingName.FileUploadBytesLimit, { + defaultSettings.set(SettingName.NAMES.FileUploadBytesLimit, { sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: (await this.getFileUploadLimit(subscriptionName)).toString(), diff --git a/packages/auth/src/Domain/Setting/SubscriptionSettingsAssociationServiceInterface.ts b/packages/auth/src/Domain/Setting/SubscriptionSettingsAssociationServiceInterface.ts index aa8250d8b..e01b8ab85 100644 --- a/packages/auth/src/Domain/Setting/SubscriptionSettingsAssociationServiceInterface.ts +++ b/packages/auth/src/Domain/Setting/SubscriptionSettingsAssociationServiceInterface.ts @@ -1,11 +1,10 @@ import { SubscriptionName } from '@standardnotes/common' -import { SubscriptionSettingName } from '@standardnotes/settings' import { SettingDescription } from './SettingDescription' export interface SubscriptionSettingsAssociationServiceInterface { getDefaultSettingsAndValuesForSubscriptionName( subscriptionName: SubscriptionName, - ): Promise | undefined> + ): Promise | undefined> getFileUploadLimit(subscriptionName: SubscriptionName): Promise } diff --git a/packages/auth/src/Domain/UseCase/CreateValetToken/CreateValetToken.ts b/packages/auth/src/Domain/UseCase/CreateValetToken/CreateValetToken.ts index 0d58ff935..4688f3825 100644 --- a/packages/auth/src/Domain/UseCase/CreateValetToken/CreateValetToken.ts +++ b/packages/auth/src/Domain/UseCase/CreateValetToken/CreateValetToken.ts @@ -3,7 +3,7 @@ import { SubscriptionName } from '@standardnotes/common' import { TimerInterface } from '@standardnotes/time' import { TokenEncoderInterface, ValetTokenData } from '@standardnotes/security' import { CreateValetTokenPayload, CreateValetTokenResponseData } from '@standardnotes/responses' -import { SubscriptionSettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import TYPES from '../../../Bootstrap/Types' import { UseCaseInterface } from '../UseCaseInterface' @@ -56,7 +56,7 @@ export class CreateValetToken implements UseCaseInterface { const uploadBytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({ userUuid: regularSubscriptionUserUuid, userSubscriptionUuid: regularSubscription.uuid, - subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed, + subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed, }) if (uploadBytesUsedSetting !== null) { uploadBytesUsed = +(uploadBytesUsedSetting.value as string) @@ -70,7 +70,7 @@ export class CreateValetToken implements UseCaseInterface { await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({ userUuid: regularSubscriptionUserUuid, userSubscriptionUuid: regularSubscription.uuid, - subscriptionSettingName: SubscriptionSettingName.FileUploadBytesLimit, + subscriptionSettingName: SettingName.NAMES.FileUploadBytesLimit, }) if (overwriteWithUserUploadBytesLimitSetting !== null) { uploadBytesLimit = +(overwriteWithUserUploadBytesLimitSetting.value as string) diff --git a/packages/auth/src/Domain/UseCase/GetSetting/GetSetting.spec.ts b/packages/auth/src/Domain/UseCase/GetSetting/GetSetting.spec.ts index 0d7ec9540..d955cf925 100644 --- a/packages/auth/src/Domain/UseCase/GetSetting/GetSetting.spec.ts +++ b/packages/auth/src/Domain/UseCase/GetSetting/GetSetting.spec.ts @@ -1,4 +1,4 @@ -import { SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import 'reflect-metadata' import { SettingProjector } from '../../../Projection/SettingProjector' import { Setting } from '../../Setting/Setting' @@ -45,12 +45,12 @@ describe('GetSetting', () => { it('should not retrieve a sensitive setting for user', async () => { setting = { sensitive: true, - name: SettingName.MfaSecret, + name: SettingName.NAMES.MfaSecret, } as jest.Mocked 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, sensitive: true, }) @@ -59,7 +59,7 @@ describe('GetSetting', () => { it('should retrieve a sensitive setting for user if explicitly told to', async () => { setting = { sensitive: true, - name: SettingName.MfaSecret, + name: SettingName.NAMES.MfaSecret, } as jest.Mocked settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(setting) diff --git a/packages/auth/src/Domain/UseCase/GetSetting/GetSetting.ts b/packages/auth/src/Domain/UseCase/GetSetting/GetSetting.ts index e78041fec..4a2cc15b7 100644 --- a/packages/auth/src/Domain/UseCase/GetSetting/GetSetting.ts +++ b/packages/auth/src/Domain/UseCase/GetSetting/GetSetting.ts @@ -1,4 +1,3 @@ -import { SettingName } from '@standardnotes/settings' import { inject, injectable } from 'inversify' import { GetSettingDto } from './GetSettingDto' import { GetSettingResponse } from './GetSettingResponse' @@ -19,7 +18,7 @@ export class GetSetting implements UseCaseInterface { const setting = await this.settingService.findSettingWithDecryptedValue({ userUuid, - settingName: settingName as SettingName, + settingName: settingName, }) if (setting === null) { diff --git a/packages/auth/src/Domain/UseCase/GetSettings/GetSettings.spec.ts b/packages/auth/src/Domain/UseCase/GetSettings/GetSettings.spec.ts index 60c92195e..a446242a0 100644 --- a/packages/auth/src/Domain/UseCase/GetSettings/GetSettings.spec.ts +++ b/packages/auth/src/Domain/UseCase/GetSettings/GetSettings.spec.ts @@ -1,6 +1,6 @@ import 'reflect-metadata' -import { SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { SettingProjector } from '../../../Projection/SettingProjector' import { Setting } from '../../Setting/Setting' @@ -31,7 +31,7 @@ describe('GetSettings', () => { } as jest.Mocked mfaSetting = { - name: SettingName.MfaSecret, + name: SettingName.NAMES.MfaSecret, updatedAt: 122, sensitive: true, } as jest.Mocked diff --git a/packages/auth/src/Domain/UseCase/GetSubscriptionSetting/GetSubscriptionSetting.spec.ts b/packages/auth/src/Domain/UseCase/GetSubscriptionSetting/GetSubscriptionSetting.spec.ts index 0d8058a34..2097142fd 100644 --- a/packages/auth/src/Domain/UseCase/GetSubscriptionSetting/GetSubscriptionSetting.spec.ts +++ b/packages/auth/src/Domain/UseCase/GetSubscriptionSetting/GetSubscriptionSetting.spec.ts @@ -1,6 +1,6 @@ import 'reflect-metadata' -import { SubscriptionSettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { SubscriptionSettingProjector } from '../../../Projection/SubscriptionSettingProjector' import { SubscriptionSetting } from '../../Setting/SubscriptionSetting' @@ -54,7 +54,7 @@ describe('GetSubscriptionSetting', () => { expect( await createUseCase().execute({ userUuid: '1-2-3', - subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed, + subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed, }), ).toEqual({ success: true, @@ -70,7 +70,7 @@ describe('GetSubscriptionSetting', () => { expect( await createUseCase().execute({ userUuid: '1-2-3', - subscriptionSettingName: SubscriptionSettingName.FileUploadBytesLimit, + subscriptionSettingName: SettingName.NAMES.FileUploadBytesLimit, }), ).toEqual({ success: false, @@ -86,7 +86,7 @@ describe('GetSubscriptionSetting', () => { expect( await createUseCase().execute({ userUuid: '1-2-3', - subscriptionSettingName: SubscriptionSettingName.FileUploadBytesLimit, + subscriptionSettingName: SettingName.NAMES.FileUploadBytesLimit, }), ).toEqual({ success: false, @@ -99,7 +99,7 @@ describe('GetSubscriptionSetting', () => { it('should not retrieve a sensitive setting for user', async () => { subscriptionSetting = { sensitive: true, - name: SubscriptionSettingName.FileUploadBytesLimit, + name: SettingName.NAMES.FileUploadBytesLimit, } as jest.Mocked subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest @@ -109,7 +109,7 @@ describe('GetSubscriptionSetting', () => { expect( await createUseCase().execute({ userUuid: '1-2-3', - subscriptionSettingName: SubscriptionSettingName.FileUploadBytesLimit, + subscriptionSettingName: SettingName.NAMES.FileUploadBytesLimit, }), ).toEqual({ success: true, @@ -120,7 +120,7 @@ describe('GetSubscriptionSetting', () => { it('should retrieve a sensitive setting for user if explicitly told to', async () => { subscriptionSetting = { sensitive: true, - name: SubscriptionSettingName.FileUploadBytesLimit, + name: SettingName.NAMES.FileUploadBytesLimit, } as jest.Mocked subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest @@ -130,7 +130,7 @@ describe('GetSubscriptionSetting', () => { expect( await createUseCase().execute({ userUuid: '1-2-3', - subscriptionSettingName: SubscriptionSettingName.FileUploadBytesLimit, + subscriptionSettingName: SettingName.NAMES.FileUploadBytesLimit, allowSensitiveRetrieval: true, }), ).toEqual({ diff --git a/packages/auth/src/Domain/UseCase/GetSubscriptionSetting/GetSubscriptionSettingDTO.ts b/packages/auth/src/Domain/UseCase/GetSubscriptionSetting/GetSubscriptionSettingDTO.ts index d2b9e8ae5..131e97327 100644 --- a/packages/auth/src/Domain/UseCase/GetSubscriptionSetting/GetSubscriptionSettingDTO.ts +++ b/packages/auth/src/Domain/UseCase/GetSubscriptionSetting/GetSubscriptionSettingDTO.ts @@ -1,8 +1,7 @@ import { Uuid } from '@standardnotes/common' -import { SubscriptionSettingName } from '@standardnotes/settings' export type GetSubscriptionSettingDTO = { userUuid: Uuid - subscriptionSettingName: SubscriptionSettingName + subscriptionSettingName: string allowSensitiveRetrieval?: boolean } diff --git a/packages/auth/src/Domain/UseCase/UpdateSetting/UpdateSetting.spec.ts b/packages/auth/src/Domain/UseCase/UpdateSetting/UpdateSetting.spec.ts index 8caf2ab15..ce5fd6659 100644 --- a/packages/auth/src/Domain/UseCase/UpdateSetting/UpdateSetting.spec.ts +++ b/packages/auth/src/Domain/UseCase/UpdateSetting/UpdateSetting.spec.ts @@ -13,7 +13,7 @@ import { SimpleSetting } from '../../Setting/SimpleSetting' import { User } from '../../User/User' import { UserRepositoryInterface } from '../../User/UserRepositoryInterface' import { UpdateSetting } from './UpdateSetting' -import { SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' describe('UpdateSetting', () => { let settingService: SettingServiceInterface @@ -59,7 +59,7 @@ describe('UpdateSetting', () => { it('should create a setting', async () => { const props = { - name: SettingName.ExtensionKey, + name: SettingName.NAMES.ExtensionKey, unencryptedValue: 'test-setting-value', serverEncryptionVersion: EncryptionVersion.Default, sensitive: false, @@ -88,7 +88,7 @@ describe('UpdateSetting', () => { userRepository.findOneByUuid = jest.fn().mockReturnValue(null) const props = { - name: SettingName.ExtensionKey, + name: SettingName.NAMES.ExtensionKey, unencryptedValue: 'test-setting-value', serverEncryptionVersion: EncryptionVersion.Unencrypted, sensitive: false, @@ -136,7 +136,7 @@ describe('UpdateSetting', () => { roleService.userHasPermission = jest.fn().mockReturnValue(false) const props = { - name: SettingName.ExtensionKey, + name: SettingName.NAMES.ExtensionKey, unencryptedValue: 'test-setting-value', serverEncryptionVersion: EncryptionVersion.Unencrypted, sensitive: false, @@ -159,7 +159,7 @@ describe('UpdateSetting', () => { settingsAssociationService.isSettingMutableByClient = jest.fn().mockReturnValue(false) const props = { - name: SettingName.ExtensionKey, + name: SettingName.NAMES.ExtensionKey, unencryptedValue: 'test-setting-value', serverEncryptionVersion: EncryptionVersion.Unencrypted, sensitive: false, diff --git a/packages/auth/src/Domain/UseCase/UpdateSetting/UpdateSetting.ts b/packages/auth/src/Domain/UseCase/UpdateSetting/UpdateSetting.ts index b626703f7..8edd36241 100644 --- a/packages/auth/src/Domain/UseCase/UpdateSetting/UpdateSetting.ts +++ b/packages/auth/src/Domain/UseCase/UpdateSetting/UpdateSetting.ts @@ -9,7 +9,7 @@ import { SettingProjector } from '../../../Projection/SettingProjector' import { Logger } from 'winston' import { SettingServiceInterface } from '../../Setting/SettingServiceInterface' import { User } from '../../User/User' -import { SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { RoleServiceInterface } from '../../Role/RoleServiceInterface' import { SettingsAssociationServiceInterface } from '../../Setting/SettingsAssociationServiceInterface' @@ -25,7 +25,7 @@ export class UpdateSetting implements UseCaseInterface { ) {} async execute(dto: UpdateSettingDto): Promise { - if (!Object.values(SettingName).includes(dto.props.name as SettingName)) { + if (!Object.values(SettingName.NAMES).includes(dto.props.name)) { return { success: false, 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 { success: false, error: { @@ -61,10 +61,8 @@ export class UpdateSetting implements UseCaseInterface { } } - props.serverEncryptionVersion = this.settingsAssociationService.getEncryptionVersionForSetting( - props.name as SettingName, - ) - props.sensitive = this.settingsAssociationService.getSensitivityForSetting(props.name as SettingName) + props.serverEncryptionVersion = this.settingsAssociationService.getEncryptionVersionForSetting(props.name) + props.sensitive = this.settingsAssociationService.getSensitivityForSetting(props.name) const response = await this.settingService.createOrReplace({ user, @@ -91,8 +89,8 @@ export class UpdateSetting implements UseCaseInterface { throw new Error(`Unrecognized status: ${exhaustiveCheck}!`) } - private async userHasPermissionToUpdateSetting(user: User, settingName: SettingName): Promise { - const settingIsMutableByClient = await this.settingsAssociationService.isSettingMutableByClient(settingName) + private async userHasPermissionToUpdateSetting(user: User, settingName: string): Promise { + const settingIsMutableByClient = this.settingsAssociationService.isSettingMutableByClient(settingName) if (!settingIsMutableByClient) { return false } diff --git a/packages/auth/src/Domain/UseCase/VerifyMFA.spec.ts b/packages/auth/src/Domain/UseCase/VerifyMFA.spec.ts index a2a8f19be..a16b48c6e 100644 --- a/packages/auth/src/Domain/UseCase/VerifyMFA.spec.ts +++ b/packages/auth/src/Domain/UseCase/VerifyMFA.spec.ts @@ -6,7 +6,7 @@ import { UserRepositoryInterface } from '../User/UserRepositoryInterface' import { VerifyMFA } from './VerifyMFA' import { Setting } from '../Setting/Setting' import { SettingServiceInterface } from '../Setting/SettingServiceInterface' -import { SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { SelectorInterface } from '@standardnotes/security' import { LockRepositoryInterface } from '../User/LockRepositoryInterface' @@ -36,7 +36,7 @@ describe('VerifyMFA', () => { lockRepository.lockSuccessfullOTP = jest.fn() setting = { - name: SettingName.MfaSecret, + name: SettingName.NAMES.MfaSecret, value: 'shhhh', } as jest.Mocked @@ -58,7 +58,7 @@ describe('VerifyMFA', () => { it('should pass MFA verification if user has MFA deleted', async () => { setting = { - name: SettingName.MfaSecret, + name: SettingName.NAMES.MfaSecret, value: null, } as jest.Mocked @@ -148,7 +148,7 @@ describe('VerifyMFA', () => { it('should not pass MFA verification if mfa is not correct', async () => { setting = { - name: SettingName.MfaSecret, + name: SettingName.NAMES.MfaSecret, value: 'shhhh2', } as jest.Mocked diff --git a/packages/auth/src/Domain/UseCase/VerifyMFA.ts b/packages/auth/src/Domain/UseCase/VerifyMFA.ts index 0a2a4f7c6..10bf49b65 100644 --- a/packages/auth/src/Domain/UseCase/VerifyMFA.ts +++ b/packages/auth/src/Domain/UseCase/VerifyMFA.ts @@ -1,6 +1,6 @@ import * as crypto from 'crypto' import { ErrorTag } from '@standardnotes/common' -import { SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { v4 as uuidv4 } from 'uuid' import { inject, injectable } from 'inversify' import { authenticator } from 'otplib' @@ -50,7 +50,7 @@ export class VerifyMFA implements UseCaseInterface { const mfaSecret = await this.settingService.findSettingWithDecryptedValue({ userUuid: user.uuid, - settingName: SettingName.MfaSecret, + settingName: SettingName.NAMES.MfaSecret, }) if (mfaSecret === null || mfaSecret.value === null) { return { diff --git a/packages/auth/src/Domain/UseCase/VerifyPredicate/VerifyPredicate.spec.ts b/packages/auth/src/Domain/UseCase/VerifyPredicate/VerifyPredicate.spec.ts index 21b9f66c4..eed7af587 100644 --- a/packages/auth/src/Domain/UseCase/VerifyPredicate/VerifyPredicate.spec.ts +++ b/packages/auth/src/Domain/UseCase/VerifyPredicate/VerifyPredicate.spec.ts @@ -8,7 +8,6 @@ import { UserSubscription } from '../../Subscription/UserSubscription' import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface' import { VerifyPredicate } from './VerifyPredicate' -import { EmailBackupFrequency } from '@standardnotes/settings' describe('VerifyPredicate', () => { let settingRepository: SettingRepositoryInterface @@ -30,7 +29,7 @@ describe('VerifyPredicate', () => { }) it('should tell that a user has enabled email backups', async () => { - setting = { value: EmailBackupFrequency.Weekly } as jest.Mocked + setting = { value: 'weekly' } as jest.Mocked settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(setting) expect( @@ -44,7 +43,7 @@ describe('VerifyPredicate', () => { }) it('should tell that a user has disabled email backups', async () => { - setting = { value: EmailBackupFrequency.Disabled } as jest.Mocked + setting = { value: 'disabled' } as jest.Mocked settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(setting) expect( diff --git a/packages/auth/src/Domain/UseCase/VerifyPredicate/VerifyPredicate.ts b/packages/auth/src/Domain/UseCase/VerifyPredicate/VerifyPredicate.ts index ebb5a10cb..504420adf 100644 --- a/packages/auth/src/Domain/UseCase/VerifyPredicate/VerifyPredicate.ts +++ b/packages/auth/src/Domain/UseCase/VerifyPredicate/VerifyPredicate.ts @@ -1,6 +1,6 @@ import { Uuid } from '@standardnotes/common' import { PredicateName, PredicateVerificationResult } from '@standardnotes/predicates' -import { EmailBackupFrequency, SettingName } from '@standardnotes/settings' +import { SettingName } from '@standardnotes/domain-core' import { inject, injectable } from 'inversify' import TYPES from '../../../Bootstrap/Types' @@ -40,9 +40,12 @@ export class VerifyPredicate implements UseCaseInterface { } private async hasUserEnabledEmailBackups(userUuid: Uuid): Promise { - 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 } diff --git a/packages/auth/src/Infra/MySQL/MySQLSettingRepository.ts b/packages/auth/src/Infra/MySQL/MySQLSettingRepository.ts index 0e1be9e81..1d4bca4ba 100644 --- a/packages/auth/src/Infra/MySQL/MySQLSettingRepository.ts +++ b/packages/auth/src/Infra/MySQL/MySQLSettingRepository.ts @@ -1,4 +1,3 @@ -import { SettingName } from '@standardnotes/settings' import { ReadStream } from 'fs' import { inject, injectable } from 'inversify' import { Repository } from 'typeorm' @@ -18,7 +17,7 @@ export class MySQLSettingRepository implements SettingRepositoryInterface { return this.ormRepository.save(setting) } - async findOneByUuidAndNames(uuid: string, names: SettingName[]): Promise { + async findOneByUuidAndNames(uuid: string, names: string[]): Promise { return this.ormRepository .createQueryBuilder('setting') .where('setting.uuid = :uuid AND setting.name IN (:...names)', { @@ -28,7 +27,7 @@ export class MySQLSettingRepository implements SettingRepositoryInterface { .getOne() } - async streamAllByNameAndValue(name: SettingName, value: string): Promise { + async streamAllByNameAndValue(name: string, value: string): Promise { return this.ormRepository .createQueryBuilder('setting') .where('setting.name = :name AND setting.value = :value', { diff --git a/packages/domain-core/src/Domain/Common/RoleNameCollection.spec.ts b/packages/domain-core/src/Domain/Common/RoleNameCollection.spec.ts index 484a5dfef..936c8d183 100644 --- a/packages/domain-core/src/Domain/Common/RoleNameCollection.spec.ts +++ b/packages/domain-core/src/Domain/Common/RoleNameCollection.spec.ts @@ -35,6 +35,19 @@ describe('RoleNameCollection', () => { 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', () => { const roles1 = [ RoleName.create(RoleName.NAMES.ProUser).getValue(), diff --git a/packages/domain-core/src/Domain/Common/Timestamps.spec.ts b/packages/domain-core/src/Domain/Common/Timestamps.spec.ts new file mode 100644 index 000000000..21cf25893 --- /dev/null +++ b/packages/domain-core/src/Domain/Common/Timestamps.spec.ts @@ -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() + }) +}) diff --git a/packages/domain-core/src/Domain/Common/Timestamps.ts b/packages/domain-core/src/Domain/Common/Timestamps.ts new file mode 100644 index 000000000..4682f0517 --- /dev/null +++ b/packages/domain-core/src/Domain/Common/Timestamps.ts @@ -0,0 +1,30 @@ +import { Result } from '../Core/Result' +import { ValueObject } from '../Core/ValueObject' +import { TimestampsProps } from './TimestampsProps' + +export class Timestamps extends ValueObject { + 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 { + if (isNaN(createdAt) || createdAt === null || createdAt === undefined) { + return Result.fail( + `Could not create Timestamps. Creation date should be a number, given: ${createdAt}`, + ) + } + if (isNaN(updatedAt) || updatedAt === null || updatedAt === undefined) { + return Result.fail(`Could not create Timestamps. Update date should be a number, given: ${createdAt}`) + } + + return Result.ok(new Timestamps({ createdAt, updatedAt })) + } +} diff --git a/packages/domain-core/src/Domain/Common/TimestampsProps.ts b/packages/domain-core/src/Domain/Common/TimestampsProps.ts new file mode 100644 index 000000000..ea52e32e4 --- /dev/null +++ b/packages/domain-core/src/Domain/Common/TimestampsProps.ts @@ -0,0 +1,4 @@ +export interface TimestampsProps { + createdAt: number + updatedAt: number +} diff --git a/packages/domain-core/src/Domain/Encryption/EncryptionVersion.spec.ts b/packages/domain-core/src/Domain/Encryption/EncryptionVersion.spec.ts new file mode 100644 index 000000000..125a95f0a --- /dev/null +++ b/packages/domain-core/src/Domain/Encryption/EncryptionVersion.spec.ts @@ -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() + }) +}) diff --git a/packages/domain-core/src/Domain/Encryption/EncryptionVersion.ts b/packages/domain-core/src/Domain/Encryption/EncryptionVersion.ts new file mode 100644 index 000000000..07e648211 --- /dev/null +++ b/packages/domain-core/src/Domain/Encryption/EncryptionVersion.ts @@ -0,0 +1,33 @@ +import { Result } from '../Core/Result' +import { ValueObject } from '../Core/ValueObject' +import { EncryptionVersionProps } from './EncryptionVersionProps' + +export class EncryptionVersion extends ValueObject { + 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 { + if ( + isNaN(version) || + version === null || + version === undefined || + !Object.values(this.VERSIONS).includes(version) + ) { + return Result.fail( + `Could not create EncryptionVersion. Version should be a number, given: ${version}`, + ) + } + + return Result.ok(new EncryptionVersion({ value: version })) + } +} diff --git a/packages/domain-core/src/Domain/Encryption/EncryptionVersionProps.ts b/packages/domain-core/src/Domain/Encryption/EncryptionVersionProps.ts new file mode 100644 index 000000000..d7965f802 --- /dev/null +++ b/packages/domain-core/src/Domain/Encryption/EncryptionVersionProps.ts @@ -0,0 +1,3 @@ +export interface EncryptionVersionProps { + value: number +} diff --git a/packages/domain-core/src/Domain/Setting/SettingName.ts b/packages/domain-core/src/Domain/Setting/SettingName.ts new file mode 100644 index 000000000..457cfdc3e --- /dev/null +++ b/packages/domain-core/src/Domain/Setting/SettingName.ts @@ -0,0 +1,48 @@ +import { Result } from '../Core/Result' +import { ValueObject } from '../Core/ValueObject' + +import { SettingNameProps } from './SettingNameProps' + +export class SettingName extends ValueObject { + 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 { + const isValidName = Object.values(this.NAMES).includes(name) + if (!isValidName) { + return Result.fail(`Invalid setting name: ${name}`) + } else { + return Result.ok(new SettingName({ value: name })) + } + } +} diff --git a/packages/domain-core/src/Domain/Setting/SettingNameProps.ts b/packages/domain-core/src/Domain/Setting/SettingNameProps.ts new file mode 100644 index 000000000..51f006672 --- /dev/null +++ b/packages/domain-core/src/Domain/Setting/SettingNameProps.ts @@ -0,0 +1,3 @@ +export interface SettingNameProps { + value: string +} diff --git a/packages/domain-core/src/Domain/index.ts b/packages/domain-core/src/Domain/index.ts index d50f9a794..6f7dc1290 100644 --- a/packages/domain-core/src/Domain/index.ts +++ b/packages/domain-core/src/Domain/index.ts @@ -8,6 +8,8 @@ export * from './Common/RoleNameCollection' export * from './Common/RoleNameCollectionProps' export * from './Common/Username' export * from './Common/UsernameProps' +export * from './Common/Timestamps' +export * from './Common/TimestampsProps' export * from './Common/Uuid' export * from './Common/UuidProps' @@ -23,8 +25,14 @@ export * from './Core/ValueObjectProps' export * from './Email/EmailLevel' export * from './Email/EmailLevelProps' +export * from './Encryption/EncryptionVersion' +export * from './Encryption/EncryptionVersionProps' + export * from './Mapping/MapperInterface' +export * from './Setting/SettingName' +export * from './Setting/SettingNameProps' + export * from './Subscription/SubscriptionPlanName' export * from './Subscription/SubscriptionPlanNameProps' diff --git a/packages/settings/bin/server.ts b/packages/settings/bin/server.ts new file mode 100644 index 000000000..82b96c95e --- /dev/null +++ b/packages/settings/bin/server.ts @@ -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, _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}`) +}) diff --git a/packages/settings/bin/worker.ts b/packages/settings/bin/worker.ts new file mode 100644 index 000000000..23fe62631 --- /dev/null +++ b/packages/settings/bin/worker.ts @@ -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) +}) diff --git a/packages/settings/jest.config.js b/packages/settings/jest.config.js index 09dfc80db..3d0ea8c87 100644 --- a/packages/settings/jest.config.js +++ b/packages/settings/jest.config.js @@ -7,4 +7,5 @@ module.exports = { transform: { ...tsjPreset.transform, }, + coveragePathIgnorePatterns: ['/Bootstrap/', '/Controller/', '/Infra/', '/Mapping/'], } diff --git a/packages/settings/package.json b/packages/settings/package.json index bee9407f9..d920bfa95 100644 --- a/packages/settings/package.json +++ b/packages/settings/package.json @@ -1,34 +1,64 @@ { - "name": "@standardnotes/settings", - "version": "1.18.4", + "name": "@standardnotes/settings-server", + "version": "1.0.0", "engines": { "node": ">=18.0.0 <19.0.0" }, - "description": "Settings SDK for Standard Notes projects", + "private": true, + "description": "Settings Server", "main": "dist/src/index.js", - "author": "Standard Notes", - "types": "dist/src/index.d.ts", - "files": [ - "dist/src/**/*.js", - "dist/src/**/*.d.ts" - ], - "publishConfig": { - "access": "public" - }, + "typings": "dist/src/index.d.ts", + "repository": "git@github.com:standardnotes/server.git", + "author": "Karol Sójko ", "license": "AGPL-3.0-or-later", "scripts": { "clean": "rm -fr dist", - "prestart": "yarn clean", - "start": "tsc -p tsconfig.json --watch", + "setup:env": "cp .env.sample .env", "build": "tsc --build", - "lint": "eslint . --ext .ts" + "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix", + "pretest": "yarn lint && yarn build", + "test": "jest --coverage --config=./jest.config.js --maxWorkers=50% --passWithNoTests", + "start": "yarn node dist/bin/server.js", + "worker": "yarn node dist/bin/worker.js" + }, + "dependencies": { + "@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": { - "@typescript-eslint/eslint-plugin": "^5.30.0", - "eslint-plugin-prettier": "^4.2.1", + "@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" - }, - "dependencies": { - "reflect-metadata": "^0.1.13" } } diff --git a/packages/settings/src/Bootstrap/Container.ts b/packages/settings/src/Bootstrap/Container.ts new file mode 100644 index 000000000..9e7c0925e --- /dev/null +++ b/packages/settings/src/Bootstrap/Container.ts @@ -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 { + 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(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(TYPES.SQS).toConstantValue(new AWS.SQS(sqsConfig)) + } + + // Map + container + .bind>(TYPES.SettingPersistenceMapper) + .toConstantValue(new SettingPersistenceMapper()) + + // ORM + container + .bind>(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(TYPES.SettingRepository) + .toConstantValue( + new MySQLSettingRepository( + container.get(TYPES.ORMSettingRepository), + container.get(TYPES.SettingPersistenceMapper), + ), + ) + + // use cases + + // Controller + container.bind(TYPES.SettingsController).toConstantValue(new SettingsController()) + + // Handlers + + // Services + container + .bind>(TYPES.CrossServiceTokenDecoder) + .toConstantValue(new TokenDecoder(container.get(TYPES.AUTH_JWT_SECRET))) + + // Middleware + container + .bind(TYPES.ApiGatewayAuthMiddleware) + .to(InversifyExpressApiGatewayAuthMiddleware) + + const eventHandlers: Map = new Map([]) + + if (env.get('SQS_QUEUE_URL', true)) { + container + .bind(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(TYPES.DomainEventSubscriberFactory) + .toConstantValue( + new SQSDomainEventSubscriberFactory( + container.get(TYPES.SQS), + container.get(TYPES.SQS_QUEUE_URL), + container.get(TYPES.DomainEventMessageHandler), + ), + ) + } else { + container + .bind(TYPES.DomainEventMessageHandler) + .toConstantValue(new RedisEventMessageHandler(eventHandlers, container.get(TYPES.Logger))) + container + .bind(TYPES.DomainEventSubscriberFactory) + .toConstantValue( + new RedisDomainEventSubscriberFactory( + container.get(TYPES.Redis), + container.get(TYPES.DomainEventMessageHandler), + container.get(TYPES.REDIS_EVENTS_CHANNEL), + ), + ) + } + + return container + } +} diff --git a/packages/settings/src/Bootstrap/DataSource.ts b/packages/settings/src/Bootstrap/DataSource.ts new file mode 100644 index 000000000..a9b55cea3 --- /dev/null +++ b/packages/settings/src/Bootstrap/DataSource.ts @@ -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: env.get('DB_DEBUG_LEVEL'), +}) diff --git a/packages/settings/src/Bootstrap/Env.ts b/packages/settings/src/Bootstrap/Env.ts new file mode 100644 index 000000000..b26b07aca --- /dev/null +++ b/packages/settings/src/Bootstrap/Env.ts @@ -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 = 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 process.env[key] + } +} diff --git a/packages/settings/src/Bootstrap/Types.ts b/packages/settings/src/Bootstrap/Types.ts new file mode 100644 index 000000000..3c2d7095e --- /dev/null +++ b/packages/settings/src/Bootstrap/Types.ts @@ -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 diff --git a/packages/settings/src/Controller/SettingsController.ts b/packages/settings/src/Controller/SettingsController.ts new file mode 100644 index 000000000..77e33d65f --- /dev/null +++ b/packages/settings/src/Controller/SettingsController.ts @@ -0,0 +1,3 @@ +export class SettingsController { + constructor() {} +} diff --git a/packages/settings/src/Domain/CloudProvider/CloudProvider.ts b/packages/settings/src/Domain/CloudProvider/CloudProvider.ts deleted file mode 100644 index 0493daa4f..000000000 --- a/packages/settings/src/Domain/CloudProvider/CloudProvider.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum CloudProvider { - Dropbox = 'Dropbox', - Google = 'Google Drive', - OneDrive = 'OneDrive', -} diff --git a/packages/settings/src/Domain/DropboxBackupFrequency/DropboxBackupFrequency.ts b/packages/settings/src/Domain/DropboxBackupFrequency/DropboxBackupFrequency.ts deleted file mode 100644 index 3350a526d..000000000 --- a/packages/settings/src/Domain/DropboxBackupFrequency/DropboxBackupFrequency.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum DropboxBackupFrequency { - Disabled = 'disabled', - Daily = 'daily', - Weekly = 'weekly', -} diff --git a/packages/settings/src/Domain/EmailBackupFrequency/EmailBackupFrequency.ts b/packages/settings/src/Domain/EmailBackupFrequency/EmailBackupFrequency.ts deleted file mode 100644 index 0aef1c067..000000000 --- a/packages/settings/src/Domain/EmailBackupFrequency/EmailBackupFrequency.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum EmailBackupFrequency { - Disabled = 'disabled', - Daily = 'daily', - Weekly = 'weekly', -} diff --git a/packages/settings/src/Domain/GoogleDriveBackupFrequency/GoogleDriveBackupFrequency.ts b/packages/settings/src/Domain/GoogleDriveBackupFrequency/GoogleDriveBackupFrequency.ts deleted file mode 100644 index c8fb7d0d4..000000000 --- a/packages/settings/src/Domain/GoogleDriveBackupFrequency/GoogleDriveBackupFrequency.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum GoogleDriveBackupFrequency { - Disabled = 'disabled', - Daily = 'daily', - Weekly = 'weekly', -} diff --git a/packages/settings/src/Domain/ListedAuthorSecretsData/ListedAuthorSecretsData.ts b/packages/settings/src/Domain/ListedAuthorSecretsData/ListedAuthorSecretsData.ts deleted file mode 100644 index b3a7d429f..000000000 --- a/packages/settings/src/Domain/ListedAuthorSecretsData/ListedAuthorSecretsData.ts +++ /dev/null @@ -1 +0,0 @@ -export type ListedAuthorSecretsData = Array<{ authorId: number; secret: string; hostUrl: string }> diff --git a/packages/settings/src/Domain/LogSessionUserAgent/LogSessionUserAgentOption.ts b/packages/settings/src/Domain/LogSessionUserAgent/LogSessionUserAgentOption.ts deleted file mode 100644 index 3ab70517a..000000000 --- a/packages/settings/src/Domain/LogSessionUserAgent/LogSessionUserAgentOption.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum LogSessionUserAgentOption { - Disabled = 'disabled', - Enabled = 'enabled', -} diff --git a/packages/settings/src/Domain/MuteFailedBackupsEmails/MuteFailedBackupsEmailsOption.ts b/packages/settings/src/Domain/MuteFailedBackupsEmails/MuteFailedBackupsEmailsOption.ts deleted file mode 100644 index 5df663ded..000000000 --- a/packages/settings/src/Domain/MuteFailedBackupsEmails/MuteFailedBackupsEmailsOption.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum MuteFailedBackupsEmailsOption { - Muted = 'muted', - NotMuted = 'not_muted', -} diff --git a/packages/settings/src/Domain/MuteFailedCloudBackupsEmails/MuteFailedCloudBackupsEmailsOption.ts b/packages/settings/src/Domain/MuteFailedCloudBackupsEmails/MuteFailedCloudBackupsEmailsOption.ts deleted file mode 100644 index ac69bcf8b..000000000 --- a/packages/settings/src/Domain/MuteFailedCloudBackupsEmails/MuteFailedCloudBackupsEmailsOption.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum MuteFailedCloudBackupsEmailsOption { - Muted = 'muted', - NotMuted = 'not_muted', -} diff --git a/packages/settings/src/Domain/MuteMarketingEmails/MuteMarketingEmailsOption.ts b/packages/settings/src/Domain/MuteMarketingEmails/MuteMarketingEmailsOption.ts deleted file mode 100644 index bdcac6fc3..000000000 --- a/packages/settings/src/Domain/MuteMarketingEmails/MuteMarketingEmailsOption.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum MuteMarketingEmailsOption { - Muted = 'muted', - NotMuted = 'not_muted', -} diff --git a/packages/settings/src/Domain/MuteSignInEmails/MuteSignInEmailsOption.ts b/packages/settings/src/Domain/MuteSignInEmails/MuteSignInEmailsOption.ts deleted file mode 100644 index 7028e2c71..000000000 --- a/packages/settings/src/Domain/MuteSignInEmails/MuteSignInEmailsOption.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum MuteSignInEmailsOption { - Muted = 'muted', - NotMuted = 'not_muted', -} diff --git a/packages/settings/src/Domain/OneDriveBackupFrequency/OneDriveBackupFrequency.ts b/packages/settings/src/Domain/OneDriveBackupFrequency/OneDriveBackupFrequency.ts deleted file mode 100644 index de60985c4..000000000 --- a/packages/settings/src/Domain/OneDriveBackupFrequency/OneDriveBackupFrequency.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum OneDriveBackupFrequency { - Disabled = 'disabled', - Daily = 'daily', - Weekly = 'weekly', -} diff --git a/packages/settings/src/Domain/Setting/SensitiveSettingName.ts b/packages/settings/src/Domain/Setting/SensitiveSettingName.ts deleted file mode 100644 index de0a88455..000000000 --- a/packages/settings/src/Domain/Setting/SensitiveSettingName.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { SettingName } from './SettingName' - -export type SensitiveSettingName = SettingName.MfaSecret | SettingName.ExtensionKey diff --git a/packages/settings/src/Domain/Setting/Setting.ts b/packages/settings/src/Domain/Setting/Setting.ts new file mode 100644 index 000000000..fd8c359bb --- /dev/null +++ b/packages/settings/src/Domain/Setting/Setting.ts @@ -0,0 +1,17 @@ +import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core' + +import { SettingProps } from './SettingProps' + +export class Setting extends Entity { + get id(): UniqueEntityId { + return this._id + } + + private constructor(props: SettingProps, id?: UniqueEntityId) { + super(props, id) + } + + static create(props: SettingProps, id?: UniqueEntityId): Result { + return Result.ok(new Setting(props, id)) + } +} diff --git a/packages/settings/src/Domain/Setting/SettingName.ts b/packages/settings/src/Domain/Setting/SettingName.ts deleted file mode 100644 index f9451eb94..000000000 --- a/packages/settings/src/Domain/Setting/SettingName.ts +++ /dev/null @@ -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', -} diff --git a/packages/settings/src/Domain/Setting/SettingProps.ts b/packages/settings/src/Domain/Setting/SettingProps.ts new file mode 100644 index 000000000..5ddf725d6 --- /dev/null +++ b/packages/settings/src/Domain/Setting/SettingProps.ts @@ -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 +} diff --git a/packages/settings/src/Domain/Setting/SettingRepositoryInterface.ts b/packages/settings/src/Domain/Setting/SettingRepositoryInterface.ts new file mode 100644 index 000000000..47e7ebcfa --- /dev/null +++ b/packages/settings/src/Domain/Setting/SettingRepositoryInterface.ts @@ -0,0 +1,8 @@ +import { SettingName, Uuid } from '@standardnotes/domain-core' + +import { Setting } from './Setting' + +export interface SettingRepositoryInterface { + findOneByNameAndValue(name: SettingName, value: string): Promise + setValueOnMultipleSettings(settingNames: string[], userUuid: Uuid, value: string | null): Promise +} diff --git a/packages/settings/src/Domain/Setting/SubscriptionSettingName.ts b/packages/settings/src/Domain/Setting/SubscriptionSettingName.ts deleted file mode 100644 index 35e8e23ea..000000000 --- a/packages/settings/src/Domain/Setting/SubscriptionSettingName.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum SubscriptionSettingName { - FileUploadBytesLimit = 'FILE_UPLOAD_BYTES_LIMIT', - FileUploadBytesUsed = 'FILE_UPLOAD_BYTES_USED', -} diff --git a/packages/settings/src/Domain/UseCase/GetSettings/GetSettings.ts b/packages/settings/src/Domain/UseCase/GetSettings/GetSettings.ts new file mode 100644 index 000000000..f37a46fcd --- /dev/null +++ b/packages/settings/src/Domain/UseCase/GetSettings/GetSettings.ts @@ -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 { + constructor( + @inject(TYPES.SettingRepository) private settingRepository: SettingRepositoryInterface, + @inject(TYPES.Crypter) private crypter: CrypterInterface, + ) {} + + async execute(dto: GetSettingsDto): Promise { + const userUuidOrError = Uuid.create(dto.userUuid) + if (userUuidOrError.isFailed()) { + + } + let settings = await this.settingRepository.findAllByUserUuid(userUuid) + } +} diff --git a/packages/settings/src/Domain/index.ts b/packages/settings/src/Domain/index.ts deleted file mode 100644 index ef06eb721..000000000 --- a/packages/settings/src/Domain/index.ts +++ /dev/null @@ -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' diff --git a/packages/settings/src/Infra/Http/MuteAllEmailsRequestParams.ts b/packages/settings/src/Infra/Http/MuteAllEmailsRequestParams.ts new file mode 100644 index 000000000..9c34a6618 --- /dev/null +++ b/packages/settings/src/Infra/Http/MuteAllEmailsRequestParams.ts @@ -0,0 +1,3 @@ +export interface MuteAllEmailsRequestParams { + unsubscribeToken: string +} diff --git a/packages/settings/src/Infra/InversifyExpress/InversifyExpressApiGatewayAuthMiddleware.ts b/packages/settings/src/Infra/InversifyExpress/InversifyExpressApiGatewayAuthMiddleware.ts new file mode 100644 index 000000000..069dcd891 --- /dev/null +++ b/packages/settings/src/Infra/InversifyExpress/InversifyExpressApiGatewayAuthMiddleware.ts @@ -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, + @inject(TYPES.Logger) private logger: Logger, + ) { + super() + } + + async handler(request: Request, response: Response, next: NextFunction): Promise { + 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) + } + } +} diff --git a/packages/settings/src/Infra/InversifyExpress/InversifyExpressHealthCheckController.ts b/packages/settings/src/Infra/InversifyExpress/InversifyExpressHealthCheckController.ts new file mode 100644 index 000000000..535288409 --- /dev/null +++ b/packages/settings/src/Infra/InversifyExpress/InversifyExpressHealthCheckController.ts @@ -0,0 +1,9 @@ +import { controller, httpGet } from 'inversify-express-utils' + +@controller('/healthcheck') +export class InversifyExpressHealthCheckController { + @httpGet('/') + public async get(): Promise { + return 'OK' + } +} diff --git a/packages/settings/src/Infra/InversifyExpress/InversifyExpressSettingsController.ts b/packages/settings/src/Infra/InversifyExpress/InversifyExpressSettingsController.ts new file mode 100644 index 000000000..ed13e92fc --- /dev/null +++ b/packages/settings/src/Infra/InversifyExpress/InversifyExpressSettingsController.ts @@ -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() + } +} diff --git a/packages/settings/src/Infra/MySQL/MySQLSettingRepository.ts b/packages/settings/src/Infra/MySQL/MySQLSettingRepository.ts new file mode 100644 index 000000000..1fb55fa95 --- /dev/null +++ b/packages/settings/src/Infra/MySQL/MySQLSettingRepository.ts @@ -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, + private settingMapper: MapperInterface, + ) {} + + async findOneByNameAndValue(name: SettingName, value: string): Promise { + 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 { + await this.ormRepository + .createQueryBuilder() + .update() + .set({ + value, + }) + .where('user_uuid = :userUuid', { userUuid: userUuid.value }) + .andWhere('name IN (:...settingNames)', { settingNames }) + .execute() + } +} diff --git a/packages/settings/src/Infra/TypeORM/TypeORMSetting.ts b/packages/settings/src/Infra/TypeORM/TypeORMSetting.ts new file mode 100644 index 000000000..2197e5e2f --- /dev/null +++ b/packages/settings/src/Infra/TypeORM/TypeORMSetting.ts @@ -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 +} diff --git a/packages/settings/src/Mapping/SettingPersistenceMapper.ts b/packages/settings/src/Mapping/SettingPersistenceMapper.ts new file mode 100644 index 000000000..831e9abca --- /dev/null +++ b/packages/settings/src/Mapping/SettingPersistenceMapper.ts @@ -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 { + 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 + } +} diff --git a/packages/settings/src/index.ts b/packages/settings/src/index.ts deleted file mode 100644 index 920deacdb..000000000 --- a/packages/settings/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Domain' diff --git a/packages/settings/tsconfig.json b/packages/settings/tsconfig.json index 8d2588d39..d87b89eeb 100644 --- a/packages/settings/tsconfig.json +++ b/packages/settings/tsconfig.json @@ -5,7 +5,9 @@ "outDir": "./dist", }, "include": [ - "src/**/*" + "src/**/*", + "bin/**/*", + "migrations/**/*", ], "references": [] } diff --git a/packages/syncing-server/package.json b/packages/syncing-server/package.json index f560fde17..412097e5a 100644 --- a/packages/syncing-server/package.json +++ b/packages/syncing-server/package.json @@ -35,7 +35,6 @@ "@standardnotes/payloads": "^1.5.1", "@standardnotes/responses": "^1.6.39", "@standardnotes/security": "workspace:*", - "@standardnotes/settings": "workspace:*", "@standardnotes/time": "workspace:*", "aws-sdk": "^2.1260.0", "axios": "^1.1.3", diff --git a/packages/syncing-server/src/Infra/HTTP/AuthHttpService.spec.ts b/packages/syncing-server/src/Infra/HTTP/AuthHttpService.spec.ts index 5e7fc84c4..40b6b7e90 100644 --- a/packages/syncing-server/src/Infra/HTTP/AuthHttpService.spec.ts +++ b/packages/syncing-server/src/Infra/HTTP/AuthHttpService.spec.ts @@ -1,9 +1,9 @@ import 'reflect-metadata' import { AxiosInstance } from 'axios' +import { SettingName } from '@standardnotes/domain-core' import { AuthHttpService } from './AuthHttpService' -import { SettingName } from '@standardnotes/settings' describe('AuthHttpService', () => { let httpClient: AxiosInstance @@ -49,7 +49,7 @@ describe('AuthHttpService', () => { }, }) - await createService().getUserSetting('1-2-3', SettingName.MuteFailedBackupsEmails) + await createService().getUserSetting('1-2-3', SettingName.NAMES.MuteFailedBackupsEmails) expect(httpClient.request).toHaveBeenCalledWith({ method: 'GET', @@ -64,7 +64,7 @@ describe('AuthHttpService', () => { it('should throw an error if a request to auth service in order to get user setting fails', async () => { let error = null try { - await createService().getUserSetting('1-2-3', SettingName.MuteFailedCloudBackupsEmails) + await createService().getUserSetting('1-2-3', SettingName.NAMES.MuteFailedCloudBackupsEmails) } catch (caughtError) { error = caughtError } diff --git a/yarn.lock b/yarn.lock index 3748a9664..b585ac11f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1880,6 +1880,21 @@ __metadata: languageName: node linkType: hard +"@standardnotes/api@npm:^1.20.13": + version: 1.20.13 + resolution: "@standardnotes/api@npm:1.20.13" + dependencies: + "@standardnotes/common": "npm:^1.45.0" + "@standardnotes/encryption": "npm:1.19.21" + "@standardnotes/models": "npm:1.38.0" + "@standardnotes/responses": "npm:1.12.9" + "@standardnotes/security": "npm:^1.7.0" + "@standardnotes/utils": "npm:1.13.0" + reflect-metadata: "npm:^0.1.13" + checksum: 67bdb982ec6851c2755b47d4671e68006381316e30c6d3104788483c8433f8c0801169f34e25e2da38427b54ef15170b13b35a346a8736ad81924bbef42204c4 + languageName: node + linkType: hard + "@standardnotes/auth-server@workspace:packages/auth": version: 0.0.0-use.local resolution: "@standardnotes/auth-server@workspace:packages/auth" @@ -1895,7 +1910,6 @@ __metadata: "@standardnotes/predicates": "workspace:*" "@standardnotes/responses": "npm:^1.6.39" "@standardnotes/security": "workspace:*" - "@standardnotes/settings": "workspace:*" "@standardnotes/sncrypto-common": "npm:^1.9.0" "@standardnotes/sncrypto-node": "workspace:*" "@standardnotes/time": "workspace:*" @@ -1949,7 +1963,7 @@ __metadata: languageName: node linkType: hard -"@standardnotes/common@npm:^1.19.1, @standardnotes/common@npm:^1.23.1, @standardnotes/common@npm:^1.39.0, @standardnotes/common@npm:^1.43.0, @standardnotes/common@workspace:*, @standardnotes/common@workspace:^, @standardnotes/common@workspace:packages/common": +"@standardnotes/common@npm:^1.19.1, @standardnotes/common@npm:^1.23.1, @standardnotes/common@npm:^1.39.0, @standardnotes/common@npm:^1.43.0, @standardnotes/common@npm:^1.45.0, @standardnotes/common@workspace:*, @standardnotes/common@workspace:^, @standardnotes/common@workspace:packages/common": version: 0.0.0-use.local resolution: "@standardnotes/common@workspace:packages/common" dependencies: @@ -2052,6 +2066,20 @@ __metadata: languageName: node linkType: hard +"@standardnotes/encryption@npm:1.19.21": + version: 1.19.21 + resolution: "@standardnotes/encryption@npm:1.19.21" + dependencies: + "@standardnotes/common": "npm:^1.45.0" + "@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" + checksum: c8c2c27bfee43ece3b508cb8e8b40bc3baf9040ef30380ad2dcfc80e1281b523d89d19dc3f5e9efd00ad662bbbe0e87462fee1599e4240a584032cc5647457dd + languageName: node + linkType: hard + "@standardnotes/event-store@workspace:packages/event-store": version: 0.0.0-use.local resolution: "@standardnotes/event-store@workspace:packages/event-store" @@ -2105,6 +2133,18 @@ __metadata: languageName: node linkType: hard +"@standardnotes/features@npm:1.55.3": + version: 1.55.3 + resolution: "@standardnotes/features@npm:1.55.3" + dependencies: + "@standardnotes/auth": "npm:^3.19.4" + "@standardnotes/common": "npm:^1.45.0" + "@standardnotes/security": "npm:^1.7.0" + reflect-metadata: "npm:^0.1.13" + checksum: b39fe2d49be6748758907a85fb1e74e4bf3d75d742eca1f9f133399059de24c37c4a3dd9ab61316f063c8161cc07229af1c18bc78b8cfa19fe7ba79dfe50fc76 + languageName: node + linkType: hard + "@standardnotes/files-server@workspace:packages/files": version: 0.0.0-use.local resolution: "@standardnotes/files-server@workspace:packages/files" @@ -2170,6 +2210,13 @@ __metadata: languageName: node linkType: hard +"@standardnotes/models@npm:1.38.0": + version: 1.38.0 + resolution: "@standardnotes/models@npm:1.38.0" + checksum: 2dc2ac957eac529f881cd80b0522a7ab9a7f6827e339007da6bf4752634fa916ddc799f535d5d12585bb996659e5fa01d17d2611ee36f95f340e1c4a4b99708e + languageName: node + linkType: hard + "@standardnotes/models@npm:^1.26.0": version: 1.28.0 resolution: "@standardnotes/models@npm:1.28.0" @@ -2234,6 +2281,18 @@ __metadata: languageName: node linkType: hard +"@standardnotes/responses@npm:1.12.9": + version: 1.12.9 + resolution: "@standardnotes/responses@npm:1.12.9" + dependencies: + "@standardnotes/common": "npm:^1.45.0" + "@standardnotes/features": "npm:1.55.3" + "@standardnotes/security": "npm:^1.7.0" + reflect-metadata: "npm:^0.1.13" + checksum: 353fe1ca6d97cf41548273f495d41fbf716ece18c996b5f608ddf9fd262a0a6c41bea5ed6b858fe541075fa7962b51cdb4b9411241330c6545f93469b9d9fd80 + languageName: node + linkType: hard + "@standardnotes/revisions-server@workspace:packages/revisions": version: 0.0.0-use.local resolution: "@standardnotes/revisions-server@workspace:packages/revisions" @@ -2313,7 +2372,7 @@ __metadata: languageName: unknown linkType: soft -"@standardnotes/security@npm:^1.1.0, @standardnotes/security@npm:^1.2.0, @standardnotes/security@workspace:*, @standardnotes/security@workspace:^, @standardnotes/security@workspace:packages/security": +"@standardnotes/security@npm:^1.1.0, @standardnotes/security@npm:^1.2.0, @standardnotes/security@npm:^1.7.0, @standardnotes/security@workspace:*, @standardnotes/security@workspace:^, @standardnotes/security@workspace:packages/security": version: 0.0.0-use.local resolution: "@standardnotes/security@workspace:packages/security" dependencies: @@ -2355,14 +2414,45 @@ __metadata: languageName: unknown linkType: soft -"@standardnotes/settings@workspace:*, @standardnotes/settings@workspace:packages/settings": +"@standardnotes/settings-server@workspace:packages/settings": version: 0.0.0-use.local - resolution: "@standardnotes/settings@workspace:packages/settings" + resolution: "@standardnotes/settings-server@workspace:packages/settings" dependencies: - "@typescript-eslint/eslint-plugin": "npm:^5.30.0" - eslint-plugin-prettier: "npm:^4.2.1" - reflect-metadata: "npm:^0.1.13" + "@newrelic/winston-enricher": "npm:^4.0.0" + "@sentry/node": "npm:^7.19.0" + "@standardnotes/api": "npm:^1.20.13" + "@standardnotes/domain-core": "workspace:^" + "@standardnotes/domain-events": "workspace:*" + "@standardnotes/domain-events-infra": "workspace:*" + "@standardnotes/security": "workspace:^" + "@standardnotes/time": "workspace:^" + "@types/cors": "npm:^2.8.9" + "@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": "npm:^5.29.0" + aws-sdk: "npm:^2.1260.0" + cors: "npm:2.8.5" + dotenv: "npm:^16.0.1" + eslint: "npm:^8.14.0" + eslint-plugin-prettier: "npm:^4.0.0" + 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: "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" + ts-jest: "npm:^29.0.3" + typeorm: "npm:^0.3.10" typescript: "npm:^4.8.4" + winston: "npm:^3.8.1" languageName: unknown linkType: soft @@ -2375,6 +2465,15 @@ __metadata: languageName: node linkType: hard +"@standardnotes/sncrypto-common@npm:1.13.3": + version: 1.13.3 + resolution: "@standardnotes/sncrypto-common@npm:1.13.3" + dependencies: + reflect-metadata: "npm:^0.1.13" + checksum: a73af909622d9014b149ab64c83efe89ed65e35b6a2056281fc873c3ddce2054d54032d457de4e768b2c1a0e61f6d0dea97f64bff12827f3c100889a65675a0e + languageName: node + linkType: hard + "@standardnotes/sncrypto-node@workspace:*, @standardnotes/sncrypto-node@workspace:packages/sncrypto-node": version: 0.0.0-use.local resolution: "@standardnotes/sncrypto-node@workspace:packages/sncrypto-node" @@ -2406,7 +2505,6 @@ __metadata: "@standardnotes/payloads": "npm:^1.5.1" "@standardnotes/responses": "npm:^1.6.39" "@standardnotes/security": "workspace:*" - "@standardnotes/settings": "workspace:*" "@standardnotes/time": "workspace:*" "@types/cors": "npm:^2.8.9" "@types/dotenv": "npm:^8.2.0" @@ -2489,6 +2587,18 @@ __metadata: languageName: node linkType: hard +"@standardnotes/utils@npm:1.13.0": + version: 1.13.0 + resolution: "@standardnotes/utils@npm:1.13.0" + dependencies: + "@standardnotes/common": "npm:^1.45.0" + dompurify: "npm:^2.4.1" + lodash: "npm:^4.17.21" + reflect-metadata: "npm:^0.1.13" + checksum: 1578e8adb7d5c506fbb84c01babd4fdeba2246e92c05ca83e6c39452eedd25f9590936c0aadfd940c9b3719e0568054e99e8986f18b01afbdc85b9a779287b5f + languageName: node + linkType: hard + "@standardnotes/websockets-server@workspace:packages/websockets": version: 0.0.0-use.local resolution: "@standardnotes/websockets-server@workspace:packages/websockets" @@ -4874,6 +4984,13 @@ __metadata: languageName: node linkType: hard +"dompurify@npm:^2.4.1": + version: 2.4.1 + resolution: "dompurify@npm:2.4.1" + checksum: ddc06333563ee84ab39267f8258e5c3e15ca53f038e1468338e97f687cbff2c7f882342087081a7a3154c1fe87b70579bcbda20bbe87a84d2bb498d1ca645208 + languageName: node + linkType: hard + "dot-prop@npm:^5.1.0": version: 5.3.0 resolution: "dot-prop@npm:5.3.0"