From c7d575a0ffc7eb3e8799c3835da5727584f4f67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20S=C3=B3jko?= Date: Mon, 29 May 2023 14:45:49 +0200 Subject: [PATCH] feat: add files server as a service to home-server (#614) * wip: add files server as a service to home-server * wip: introduce home-server controllers without inversify-express-utils decorators. Move in progress * fix(auth): move remaining home server controllers * fix(syncing-server): home server controllers * fix(revisions): home server controllers * fix: specs * fix: import for legacy controller * fix: remove router debug --- .pnp.cjs | 1 + packages/api-gateway/src/Bootstrap/index.ts | 1 - packages/api-gateway/src/Controller/index.ts | 1 - packages/auth/src/Bootstrap/Container.ts | 105 +++--- packages/auth/src/Bootstrap/Types.ts | 35 +- .../HomeServer/HomeServerAdminController.ts | 121 +++++++ .../HomeServer/HomeServerAuthController.ts | 299 ++++++++++++++++++ .../HomeServerAuthenticatorsController.ts | 74 +++++ .../HomeServerFeaturesController.ts | 42 +++ .../HomeServer/HomeServerListedController.ts | 42 +++ .../HomeServer/HomeServerOfflineController.ts | 128 ++++++++ .../HomeServer/HomeServerSessionController.ts | 153 +++++++++ .../HomeServerSessionsController.ts | 75 +++++ .../HomeServerSettingsController.ts | 153 +++++++++ ...HomeServerSubscriptionInvitesController.ts | 74 +++++ ...omeServerSubscriptionSettingsController.ts | 28 ++ .../HomeServerSubscriptionTokensController.ts | 106 +++++++ .../HomeServerUserRequestsController.ts | 28 ++ .../HomeServer/HomeServerUsersController.ts | 236 ++++++++++++++ .../HomeServerValetTokenController.ts | 60 ++++ .../HomeServerWebSocketsController.ts | 55 ++++ .../InversifyExpressAdminController.spec.ts | 6 - .../InversifyExpressAdminController.ts | 117 ++----- .../InversifyExpressAuthController.ts | 293 ++--------------- ...nversifyExpressAuthenticatorsController.ts | 68 +--- ...InversifyExpressFeaturesController.spec.ts | 7 +- .../InversifyExpressFeaturesController.ts | 38 +-- .../InversifyExpressListedController.spec.ts | 7 +- .../InversifyExpressListedController.ts | 39 +-- .../InversifyExpressOfflineController.spec.ts | 7 - .../InversifyExpressOfflineController.ts | 131 ++------ .../InversifyExpressSessionController.spec.ts | 12 +- .../InversifyExpressSessionController.ts | 147 +-------- ...InversifyExpressSessionsController.spec.ts | 6 - .../InversifyExpressSessionsController.ts | 68 +--- ...InversifyExpressSettingsController.spec.ts | 7 +- .../InversifyExpressSettingsController.ts | 150 ++------- ...ifyExpressSubscriptionInvitesController.ts | 73 +---- ...ressSubscriptionSettingsController.spec.ts | 7 +- ...fyExpressSubscriptionSettingsController.ts | 27 +- ...xpressSubscriptionTokensController.spec.ts | 8 +- ...sifyExpressSubscriptionTokensController.ts | 109 ++----- .../InversifyExpressUserRequestsController.ts | 25 +- .../InversifyExpressUsersController.spec.ts | 7 +- .../InversifyExpressUsersController.ts | 242 ++------------ ...versifyExpressValetTokenController.spec.ts | 7 +- .../InversifyExpressValetTokenController.ts | 59 +--- .../InversifyExpressWebSocketsController.ts | 49 +-- .../src/Domain/Service/ServiceIdentifier.ts | 1 + packages/files/bin/server.ts | 8 +- packages/files/bin/worker.ts | 6 +- packages/files/package.json | 1 + packages/files/src/Bootstrap/Container.ts | 246 +++++++------- packages/files/src/Bootstrap/Service.ts | 29 ++ packages/files/src/Bootstrap/Types.ts | 80 ++--- packages/files/src/Bootstrap/index.ts | 2 + .../Controller/HealthCheckController.spec.ts | 12 - .../src/Domain/Event/DomainEventFactory.ts | 2 +- .../AccountDeletionRequestedEventHandler.ts | 6 +- ...scriptionInvitationCanceledEventHandler.ts | 6 +- .../CreateUploadSession.ts | 6 +- .../FinishUploadSession.ts | 10 +- .../GetFileMetadata/GetFileMetadata.ts | 4 +- .../MarkFilesToBeRemoved.ts | 4 +- .../Domain/UseCase/RemoveFile/RemoveFile.ts | 8 +- .../StreamDownloadFile/StreamDownloadFile.ts | 4 +- .../UploadFileChunk/UploadFileChunk.ts | 6 +- .../files/src/Infra/FS/FSFileDownloader.ts | 2 +- packages/files/src/Infra/FS/FSFileRemover.ts | 2 +- packages/files/src/Infra/FS/FSFileUploader.ts | 4 +- .../InversifyExpressFilesController.spec.ts} | 18 +- .../InversifyExpressFilesController.ts} | 32 +- ...ersifyExpressHealthCheckController.spec.ts | 12 + .../InversifyExpressHealthCheckController.ts} | 2 +- .../ValetTokenAuthMiddleware.spec.ts | 0 .../Middleware}/ValetTokenAuthMiddleware.ts | 6 +- .../files/src/Infra/InversifyExpress/index.ts | 1 + .../src/Infra/Redis/RedisUploadRepository.ts | 2 +- .../files/src/Infra/S3/S3FileDownloader.ts | 4 +- packages/files/src/Infra/S3/S3FileRemover.ts | 4 +- packages/files/src/Infra/S3/S3FileUploader.ts | 4 +- packages/files/src/index.ts | 2 + packages/home-server/.env.sample | 1 + packages/home-server/bin/server.ts | 3 + packages/home-server/package.json | 1 + packages/revisions/src/Bootstrap/Container.ts | 6 +- packages/revisions/src/Bootstrap/Types.ts | 2 +- .../HomeServerRevisionsController.ts | 47 +++ .../InversifyExpressRevisionsController.ts | 44 +-- .../syncing-server/src/Bootstrap/Container.ts | 6 +- .../syncing-server/src/Bootstrap/Types.ts | 2 +- .../HomeServer/HomeServerItemsController.ts | 85 +++++ .../InversifyExpressItemsController.spec.ts | 14 +- .../InversifyExpressItemsController.ts | 78 +---- yarn.lock | 3 +- 95 files changed, 2463 insertions(+), 1898 deletions(-) create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerAdminController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerAuthController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerAuthenticatorsController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerFeaturesController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerListedController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerOfflineController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSessionController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSessionsController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSettingsController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionInvitesController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionSettingsController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionTokensController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerUserRequestsController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerUsersController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerValetTokenController.ts create mode 100644 packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerWebSocketsController.ts create mode 100644 packages/files/src/Bootstrap/Service.ts create mode 100644 packages/files/src/Bootstrap/index.ts delete mode 100644 packages/files/src/Controller/HealthCheckController.spec.ts rename packages/files/src/{Controller/FilesController.spec.ts => Infra/InversifyExpress/InversifyExpressFilesController.spec.ts} (94%) rename packages/files/src/{Controller/FilesController.ts => Infra/InversifyExpress/InversifyExpressFilesController.ts} (79%) create mode 100644 packages/files/src/Infra/InversifyExpress/InversifyExpressHealthCheckController.spec.ts rename packages/files/src/{Controller/HealthCheckController.ts => Infra/InversifyExpress/InversifyExpressHealthCheckController.ts} (76%) rename packages/files/src/{Controller => Infra/InversifyExpress/Middleware}/ValetTokenAuthMiddleware.spec.ts (100%) rename packages/files/src/{Controller => Infra/InversifyExpress/Middleware}/ValetTokenAuthMiddleware.ts (93%) create mode 100644 packages/files/src/Infra/InversifyExpress/index.ts create mode 100644 packages/files/src/index.ts create mode 100644 packages/revisions/src/Infra/InversifyExpress/HomeServer/HomeServerRevisionsController.ts create mode 100644 packages/syncing-server/src/Infra/InversifyExpressUtils/HomeServer/HomeServerItemsController.ts diff --git a/.pnp.cjs b/.pnp.cjs index 19060b4ec..510b86b82 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -4626,6 +4626,7 @@ const RAW_RUNTIME_STATE = ["@standardnotes/auth-server", "workspace:packages/auth"],\ ["@standardnotes/domain-core", "workspace:packages/domain-core"],\ ["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\ + ["@standardnotes/files-server", "workspace:packages/files"],\ ["@standardnotes/revisions-server", "workspace:packages/revisions"],\ ["@standardnotes/syncing-server", "workspace:packages/syncing-server"],\ ["@types/cors", "npm:2.8.13"],\ diff --git a/packages/api-gateway/src/Bootstrap/index.ts b/packages/api-gateway/src/Bootstrap/index.ts index b80889bf5..df1a81eab 100644 --- a/packages/api-gateway/src/Bootstrap/index.ts +++ b/packages/api-gateway/src/Bootstrap/index.ts @@ -1,3 +1,2 @@ -export * from './Container' export * from './Service' export * from './Types' diff --git a/packages/api-gateway/src/Controller/index.ts b/packages/api-gateway/src/Controller/index.ts index 8caa7e23f..f93454850 100644 --- a/packages/api-gateway/src/Controller/index.ts +++ b/packages/api-gateway/src/Controller/index.ts @@ -1,6 +1,5 @@ export * from './AuthMiddleware' export * from './HealthCheckController' -export * from './LegacyController' export * from './SubscriptionTokenAuthMiddleware' export * from './TokenAuthenticationMethod' export * from './WebSocketAuthMiddleware' diff --git a/packages/auth/src/Bootstrap/Container.ts b/packages/auth/src/Bootstrap/Container.ts index 94be535c1..a7b88de1f 100644 --- a/packages/auth/src/Bootstrap/Container.ts +++ b/packages/auth/src/Bootstrap/Container.ts @@ -228,29 +228,28 @@ import { TypeORMEphemeralSessionRepository } from '../Infra/TypeORM/TypeORMEphem import { TypeORMOfflineSubscriptionTokenRepository } from '../Infra/TypeORM/TypeORMOfflineSubscriptionTokenRepository' import { TypeORMPKCERepository } from '../Infra/TypeORM/TypeORMPKCERepository' import { TypeORMSubscriptionTokenRepository } from '../Infra/TypeORM/TypeORMSubscriptionTokenRepository' -import { InversifyExpressAuthController } from '../Infra/InversifyExpressUtils/InversifyExpressAuthController' -import { InversifyExpressAuthenticatorsController } from '../Infra/InversifyExpressUtils/InversifyExpressAuthenticatorsController' -import { InversifyExpressSubscriptionInvitesController } from '../Infra/InversifyExpressUtils/InversifyExpressSubscriptionInvitesController' -import { InversifyExpressUserRequestsController } from '../Infra/InversifyExpressUtils/InversifyExpressUserRequestsController' -import { InversifyExpressWebSocketsController } from '../Infra/InversifyExpressUtils/InversifyExpressWebSocketsController' -import { InversifyExpressSessionsController } from '../Infra/InversifyExpressUtils/InversifyExpressSessionsController' -import { InversifyExpressValetTokenController } from '../Infra/InversifyExpressUtils/InversifyExpressValetTokenController' -import { InversifyExpressUsersController } from '../Infra/InversifyExpressUtils/InversifyExpressUsersController' -import { InversifyExpressAdminController } from '../Infra/InversifyExpressUtils/InversifyExpressAdminController' -import { InversifyExpressSubscriptionTokensController } from '../Infra/InversifyExpressUtils/InversifyExpressSubscriptionTokensController' -import { InversifyExpressSubscriptionSettingsController } from '../Infra/InversifyExpressUtils/InversifyExpressSubscriptionSettingsController' -import { InversifyExpressSettingsController } from '../Infra/InversifyExpressUtils/InversifyExpressSettingsController' import { SessionMiddleware } from '../Infra/InversifyExpressUtils/Middleware/SessionMiddleware' import { ApiGatewayOfflineAuthMiddleware } from '../Infra/InversifyExpressUtils/Middleware/ApiGatewayOfflineAuthMiddleware' import { OfflineUserAuthMiddleware } from '../Infra/InversifyExpressUtils/Middleware/OfflineUserAuthMiddleware' import { LockMiddleware } from '../Infra/InversifyExpressUtils/Middleware/LockMiddleware' -import { InversifyExpressSessionController } from '../Infra/InversifyExpressUtils/InversifyExpressSessionController' -import { InversifyExpressOfflineController } from '../Infra/InversifyExpressUtils/InversifyExpressOfflineController' -import { InversifyExpressListedController } from '../Infra/InversifyExpressUtils/InversifyExpressListedController' -import { InversifyExpressInternalController } from '../Infra/InversifyExpressUtils/InversifyExpressInternalController' -import { InversifyExpressFeaturesController } from '../Infra/InversifyExpressUtils/InversifyExpressFeaturesController' import { RequiredCrossServiceTokenMiddleware } from '../Infra/InversifyExpressUtils/Middleware/RequiredCrossServiceTokenMiddleware' import { OptionalCrossServiceTokenMiddleware } from '../Infra/InversifyExpressUtils/Middleware/OptionalCrossServiceTokenMiddleware' +import { HomeServerSettingsController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerSettingsController' +import { HomeServerAdminController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerAdminController' +import { HomeServerAuthController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerAuthController' +import { HomeServerAuthenticatorsController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerAuthenticatorsController' +import { HomeServerFeaturesController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerFeaturesController' +import { HomeServerListedController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerListedController' +import { HomeServerOfflineController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerOfflineController' +import { HomeServerSessionController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerSessionController' +import { HomeServerSubscriptionInvitesController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionInvitesController' +import { HomeServerSubscriptionSettingsController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionSettingsController' +import { HomeServerSubscriptionTokensController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionTokensController' +import { HomeServerUserRequestsController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerUserRequestsController' +import { HomeServerUsersController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerUsersController' +import { HomeServerValetTokenController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerValetTokenController' +import { HomeServerWebSocketsController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerWebSocketsController' +import { HomeServerSessionsController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerSessionsController' // eslint-disable-next-line @typescript-eslint/no-var-requires const newrelicFormatter = require('@newrelic/winston-enricher') @@ -1012,9 +1011,9 @@ export class ContainerConfigLoader { } container - .bind(TYPES.Auth_InversifyExpressAuthController) + .bind(TYPES.Auth_HomeServerAuthController) .toConstantValue( - new InversifyExpressAuthController( + new HomeServerAuthController( container.get(TYPES.Auth_VerifyMFA), container.get(TYPES.Auth_SignIn), container.get(TYPES.Auth_GetUserKeyParams), @@ -1029,42 +1028,42 @@ export class ContainerConfigLoader { // Inversify Controllers if (isConfiguredForHomeServer) { container - .bind(TYPES.Auth_InversifyExpressAuthenticatorsController) + .bind(TYPES.Auth_HomeServerAuthenticatorsController) .toConstantValue( - new InversifyExpressAuthenticatorsController( + new HomeServerAuthenticatorsController( container.get(TYPES.Auth_AuthenticatorsController), container.get(TYPES.Auth_ControllerContainer), ), ) container - .bind(TYPES.Auth_InversifyExpressSubscriptionInvitesController) + .bind(TYPES.Auth_HomeServerSubscriptionInvitesController) .toConstantValue( - new InversifyExpressSubscriptionInvitesController( + new HomeServerSubscriptionInvitesController( container.get(TYPES.Auth_SubscriptionInvitesController), container.get(TYPES.Auth_ControllerContainer), ), ) container - .bind(TYPES.Auth_InversifyExpressUserRequestsController) + .bind(TYPES.Auth_HomeServerUserRequestsController) .toConstantValue( - new InversifyExpressUserRequestsController( + new HomeServerUserRequestsController( container.get(TYPES.Auth_UserRequestsController), container.get(TYPES.Auth_ControllerContainer), ), ) container - .bind(TYPES.Auth_InversifyExpressWebSocketsController) + .bind(TYPES.Auth_HomeServerWebSocketsController) .toConstantValue( - new InversifyExpressWebSocketsController( + new HomeServerWebSocketsController( container.get(TYPES.Auth_CreateCrossServiceToken), container.get(TYPES.Auth_WebSocketConnectionTokenDecoder), container.get(TYPES.Auth_ControllerContainer), ), ) container - .bind(TYPES.Auth_SessionsController) + .bind(TYPES.Auth_HomeServerSessionsController) .toConstantValue( - new InversifyExpressSessionsController( + new HomeServerSessionsController( container.get(TYPES.Auth_GetActiveSessionsForUser), container.get(TYPES.Auth_AuthenticateRequest), container.get(TYPES.Auth_SessionProjector), @@ -1073,17 +1072,17 @@ export class ContainerConfigLoader { ), ) container - .bind(TYPES.Auth_InversifyExpressValetTokenController) + .bind(TYPES.Auth_HomeServerValetTokenController) .toConstantValue( - new InversifyExpressValetTokenController( + new HomeServerValetTokenController( container.get(TYPES.Auth_CreateValetToken), container.get(TYPES.Auth_ControllerContainer), ), ) container - .bind(TYPES.Auth_InversifyExpressUsersController) + .bind(TYPES.Auth_HomeServerUsersController) .toConstantValue( - new InversifyExpressUsersController( + new HomeServerUsersController( container.get(TYPES.Auth_UpdateUser), container.get(TYPES.Auth_GetUserKeyParams), container.get(TYPES.Auth_DeleteAccount), @@ -1095,9 +1094,9 @@ export class ContainerConfigLoader { ), ) container - .bind(TYPES.Auth_InversifyExpressAdminController) + .bind(TYPES.Auth_HomeServerAdminController) .toConstantValue( - new InversifyExpressAdminController( + new HomeServerAdminController( container.get(TYPES.Auth_DeleteSetting), container.get(TYPES.Auth_UserRepository), container.get(TYPES.Auth_CreateSubscriptionToken), @@ -1106,9 +1105,9 @@ export class ContainerConfigLoader { ), ) container - .bind(TYPES.Auth_InversifyExpressSubscriptionTokensController) + .bind(TYPES.Auth_HomeServerSubscriptionTokensController) .toConstantValue( - new InversifyExpressSubscriptionTokensController( + new HomeServerSubscriptionTokensController( container.get(TYPES.Auth_CreateSubscriptionToken), container.get(TYPES.Auth_AuthenticateSubscriptionToken), container.get(TYPES.Auth_SettingService), @@ -1120,17 +1119,17 @@ export class ContainerConfigLoader { ), ) container - .bind(TYPES.Auth_InversifyExpressSubscriptionSettingsController) + .bind(TYPES.Auth_HomeServerSubscriptionSettingsController) .toConstantValue( - new InversifyExpressSubscriptionSettingsController( + new HomeServerSubscriptionSettingsController( container.get(TYPES.Auth_GetSetting), container.get(TYPES.Auth_ControllerContainer), ), ) container - .bind(TYPES.Auth_InversifyExpressSettingsController) + .bind(TYPES.Auth_HomeServerSettingsController) .toConstantValue( - new InversifyExpressSettingsController( + new HomeServerSettingsController( container.get(TYPES.Auth_GetSettings), container.get(TYPES.Auth_GetSetting), container.get(TYPES.Auth_UpdateSetting), @@ -1139,9 +1138,9 @@ export class ContainerConfigLoader { ), ) container - .bind(TYPES.Auth_InversifyExpressSessionController) + .bind(TYPES.Auth_HomeServerSessionController) .toConstantValue( - new InversifyExpressSessionController( + new HomeServerSessionController( container.get(TYPES.Auth_DeleteSessionForUser), container.get(TYPES.Auth_DeletePreviousSessionsForUser), container.get(TYPES.Auth_RefreshSessionToken), @@ -1149,9 +1148,9 @@ export class ContainerConfigLoader { ), ) container - .bind(TYPES.Auth_InversifyExpressOfflineController) + .bind(TYPES.Auth_HomeServerOfflineController) .toConstantValue( - new InversifyExpressOfflineController( + new HomeServerOfflineController( container.get(TYPES.Auth_GetUserFeatures), container.get(TYPES.Auth_GetUserOfflineSubscription), container.get(TYPES.Auth_CreateOfflineSubscriptionToken), @@ -1163,25 +1162,17 @@ export class ContainerConfigLoader { ), ) container - .bind(TYPES.Auth_InversifyExpressListedController) + .bind(TYPES.Auth_HomeServerListedController) .toConstantValue( - new InversifyExpressListedController( + new HomeServerListedController( container.get(TYPES.Auth_CreateListedAccount), container.get(TYPES.Auth_ControllerContainer), ), ) container - .bind(TYPES.Auth_InversifyExpressInternalController) + .bind(TYPES.Auth_HomeServerFeaturesController) .toConstantValue( - new InversifyExpressInternalController( - container.get(TYPES.Auth_GetUserFeatures), - container.get(TYPES.Auth_GetSetting), - ), - ) - container - .bind(TYPES.Auth_InversifyExpressFeaturesController) - .toConstantValue( - new InversifyExpressFeaturesController( + new HomeServerFeaturesController( container.get(TYPES.Auth_GetUserFeatures), container.get(TYPES.Auth_ControllerContainer), ), diff --git a/packages/auth/src/Bootstrap/Types.ts b/packages/auth/src/Bootstrap/Types.ts index e1d41fc78..c4ae58ed7 100644 --- a/packages/auth/src/Bootstrap/Types.ts +++ b/packages/auth/src/Bootstrap/Types.ts @@ -216,25 +216,22 @@ const TYPES = { Auth_ProtocolVersionSelector: Symbol.for('Auth_ProtocolVersionSelector'), Auth_BooleanSelector: Symbol.for('Auth_BooleanSelector'), Auth_UserSubscriptionService: Symbol.for('Auth_UserSubscriptionService'), - Auth_InversifyExpressAuthController: Symbol.for('Auth_InversifyExpressAuthController'), - Auth_InversifyExpressAuthenticatorsController: Symbol.for('Auth_InversifyExpressAuthenticatorsController'), - Auth_InversifyExpressSubscriptionInvitesController: Symbol.for('Auth_InversifyExpressSubscriptionInvitesController'), - Auth_InversifyExpressUserRequestsController: Symbol.for('Auth_InversifyExpressUserRequestsController'), - Auth_InversifyExpressWebSocketsController: Symbol.for('Auth_InversifyExpressWebSocketsController'), - Auth_SessionsController: Symbol.for('Auth_SessionsController'), - Auth_InversifyExpressValetTokenController: Symbol.for('Auth_InversifyExpressValetTokenController'), - Auth_InversifyExpressUsersController: Symbol.for('Auth_InversifyExpressUsersController'), - Auth_InversifyExpressAdminController: Symbol.for('Auth_InversifyExpressAdminController'), - Auth_InversifyExpressSubscriptionTokensController: Symbol.for('Auth_InversifyExpressSubscriptionTokensController'), - Auth_InversifyExpressSubscriptionSettingsController: Symbol.for( - 'Auth_InversifyExpressSubscriptionSettingsController', - ), - Auth_InversifyExpressSettingsController: Symbol.for('Auth_InversifyExpressSettingsController'), - Auth_InversifyExpressSessionController: Symbol.for('Auth_InversifyExpressSessionController'), - Auth_InversifyExpressOfflineController: Symbol.for('Auth_InversifyExpressOfflineController'), - Auth_InversifyExpressListedController: Symbol.for('Auth_InversifyExpressListedController'), - Auth_InversifyExpressInternalController: Symbol.for('Auth_InversifyExpressInternalController'), - Auth_InversifyExpressFeaturesController: Symbol.for('Auth_InversifyExpressFeaturesController'), + Auth_HomeServerAuthController: Symbol.for('Auth_HomeServerAuthController'), + Auth_HomeServerAuthenticatorsController: Symbol.for('Auth_HomeServerAuthenticatorsController'), + Auth_HomeServerSubscriptionInvitesController: Symbol.for('Auth_HomeServerSubscriptionInvitesController'), + Auth_HomeServerUserRequestsController: Symbol.for('Auth_HomeServerUserRequestsController'), + Auth_HomeServerWebSocketsController: Symbol.for('Auth_HomeServerWebSocketsController'), + Auth_HomeServerSessionsController: Symbol.for('Auth_HomeServerSessionsController'), + Auth_HomeServerValetTokenController: Symbol.for('Auth_HomeServerValetTokenController'), + Auth_HomeServerUsersController: Symbol.for('Auth_HomeServerUsersController'), + Auth_HomeServerAdminController: Symbol.for('Auth_HomeServerAdminController'), + Auth_HomeServerSubscriptionTokensController: Symbol.for('Auth_HomeServerSubscriptionTokensController'), + Auth_HomeServerSubscriptionSettingsController: Symbol.for('Auth_HomeServerSubscriptionSettingsController'), + Auth_HomeServerSettingsController: Symbol.for('Auth_HomeServerSettingsController'), + Auth_HomeServerSessionController: Symbol.for('Auth_HomeServerSessionController'), + Auth_HomeServerOfflineController: Symbol.for('Auth_HomeServerOfflineController'), + Auth_HomeServerListedController: Symbol.for('Auth_HomeServerListedController'), + Auth_HomeServerFeaturesController: Symbol.for('Auth_HomeServerFeaturesController'), } export default TYPES diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerAdminController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerAdminController.ts new file mode 100644 index 000000000..114b43c3c --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerAdminController.ts @@ -0,0 +1,121 @@ +import { ControllerContainerInterface, Username } from '@standardnotes/domain-core' +import { BaseHttpController, results } from 'inversify-express-utils' +import { SettingName } from '@standardnotes/settings' +import { Request } from 'express' + +import { CreateOfflineSubscriptionToken } from '../../../Domain/UseCase/CreateOfflineSubscriptionToken/CreateOfflineSubscriptionToken' +import { CreateSubscriptionToken } from '../../../Domain/UseCase/CreateSubscriptionToken/CreateSubscriptionToken' +import { DeleteSetting } from '../../../Domain/UseCase/DeleteSetting/DeleteSetting' +import { UserRepositoryInterface } from '../../../Domain/User/UserRepositoryInterface' + +export class HomeServerAdminController extends BaseHttpController { + constructor( + protected doDeleteSetting: DeleteSetting, + protected userRepository: UserRepositoryInterface, + protected createSubscriptionToken: CreateSubscriptionToken, + protected createOfflineSubscriptionToken: CreateOfflineSubscriptionToken, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('admin.getUser', this.getUser.bind(this)) + this.controllerContainer.register('admin.deleteMFASetting', this.deleteMFASetting.bind(this)) + this.controllerContainer.register('admin.createToken', this.createToken.bind(this)) + this.controllerContainer.register('admin.createOfflineToken', this.createOfflineToken.bind(this)) + this.controllerContainer.register('admin.disableEmailBackups', this.disableEmailBackups.bind(this)) + } + } + + async getUser(request: Request): Promise { + const usernameOrError = Username.create(request.params.email ?? '') + if (usernameOrError.isFailed()) { + return this.json( + { + error: { + message: 'Missing email parameter.', + }, + }, + 400, + ) + } + const username = usernameOrError.getValue() + + const user = await this.userRepository.findOneByUsernameOrEmail(username) + + if (!user) { + return this.json( + { + error: { + message: `No user with email '${username.value}'.`, + }, + }, + 400, + ) + } + + return this.json({ + uuid: user.uuid, + }) + } + + async deleteMFASetting(request: Request): Promise { + const { userUuid } = request.params + const { uuid, updatedAt } = request.body + + const result = await this.doDeleteSetting.execute({ + uuid, + userUuid, + settingName: SettingName.NAMES.MfaSecret, + timestamp: updatedAt, + softDelete: true, + }) + + if (result.success) { + return this.json(result) + } + + return this.json(result, 400) + } + + async createToken(request: Request): Promise { + const { userUuid } = request.params + const result = await this.createSubscriptionToken.execute({ + userUuid, + }) + + return this.json({ + token: result.subscriptionToken.token, + }) + } + + async createOfflineToken(request: Request): Promise { + const { email } = request.params + const result = await this.createOfflineSubscriptionToken.execute({ + userEmail: email, + }) + + if (!result.success) { + return this.badRequest() + } + + return this.json({ + token: result.offlineSubscriptionToken.token, + }) + } + + async disableEmailBackups(request: Request): Promise { + const { userUuid } = request.params + + const result = await this.doDeleteSetting.execute({ + userUuid, + settingName: SettingName.NAMES.EmailBackupFrequency, + }) + + if (result.success) { + return this.ok() + } + + return this.badRequest('No email backups found') + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerAuthController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerAuthController.ts new file mode 100644 index 000000000..ee4142d18 --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerAuthController.ts @@ -0,0 +1,299 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { Request, Response } from 'express' +import { Logger } from 'winston' + +import { ClearLoginAttempts } from '../../../Domain/UseCase/ClearLoginAttempts' +import { GetUserKeyParams } from '../../../Domain/UseCase/GetUserKeyParams/GetUserKeyParams' +import { IncreaseLoginAttempts } from '../../../Domain/UseCase/IncreaseLoginAttempts' +import { SignIn } from '../../../Domain/UseCase/SignIn' +import { VerifyMFA } from '../../../Domain/UseCase/VerifyMFA' +import { AuthController } from '../../../Controller/AuthController' +import { BaseHttpController, results } from 'inversify-express-utils' + +export class HomeServerAuthController extends BaseHttpController { + constructor( + protected verifyMFA: VerifyMFA, + protected signInUseCase: SignIn, + protected getUserKeyParams: GetUserKeyParams, + protected clearLoginAttempts: ClearLoginAttempts, + protected increaseLoginAttempts: IncreaseLoginAttempts, + protected logger: Logger, + protected authController: AuthController, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.params', this.params.bind(this)) + this.controllerContainer.register('auth.signIn', this.signIn.bind(this)) + this.controllerContainer.register('auth.pkceParams', this.pkceParams.bind(this)) + this.controllerContainer.register('auth.pkceSignIn', this.pkceSignIn.bind(this)) + this.controllerContainer.register('auth.users.register', this.register.bind(this)) + this.controllerContainer.register('auth.generateRecoveryCodes', this.generateRecoveryCodes.bind(this)) + this.controllerContainer.register('auth.signInWithRecoveryCodes', this.recoveryLogin.bind(this)) + this.controllerContainer.register('auth.recoveryKeyParams', this.recoveryParams.bind(this)) + this.controllerContainer.register('auth.signOut', this.signOut.bind(this)) + } + } + + async params(request: Request, response: Response): Promise { + if (response.locals.session) { + const result = await this.getUserKeyParams.execute({ + email: response.locals.user.email, + authenticated: true, + }) + + return this.json(result.keyParams) + } + + if (!request.query.email) { + return this.json( + { + error: { + message: 'Please provide an email address.', + }, + }, + 400, + ) + } + + const verifyMFAResponse = await this.verifyMFA.execute({ + email: request.query.email, + requestParams: request.query, + preventOTPFromFurtherUsage: false, + }) + + if (!verifyMFAResponse.success) { + return this.json( + { + error: { + tag: verifyMFAResponse.errorTag, + message: verifyMFAResponse.errorMessage, + payload: verifyMFAResponse.errorPayload, + }, + }, + 401, + ) + } + + const result = await this.getUserKeyParams.execute({ + email: request.query.email, + authenticated: false, + }) + + return this.json(result.keyParams) + } + + async signIn(request: Request): Promise { + if (!request.body.email || !request.body.password) { + this.logger.debug('/auth/sign_in request missing credentials: %O', request.body) + + return this.json( + { + error: { + tag: 'invalid-auth', + message: 'Invalid login credentials.', + }, + }, + 401, + ) + } + + const verifyMFAResponse = await this.verifyMFA.execute({ + email: request.body.email, + requestParams: request.body, + preventOTPFromFurtherUsage: true, + }) + + if (!verifyMFAResponse.success) { + return this.json( + { + error: { + tag: verifyMFAResponse.errorTag, + message: verifyMFAResponse.errorMessage, + payload: verifyMFAResponse.errorPayload, + }, + }, + 401, + ) + } + + const signInResult = await this.signInUseCase.execute({ + apiVersion: request.body.api, + userAgent: request.headers['user-agent'], + email: request.body.email, + password: request.body.password, + ephemeralSession: request.body.ephemeral ?? false, + }) + + if (!signInResult.success) { + await this.increaseLoginAttempts.execute({ email: request.body.email }) + + return this.json( + { + error: { + message: signInResult.errorMessage, + }, + }, + signInResult.errorCode ?? 401, + ) + } + + await this.clearLoginAttempts.execute({ email: request.body.email }) + + return this.json(signInResult.authResponse) + } + + async pkceParams(request: Request, response: Response): Promise { + if (!request.body.code_challenge) { + return this.json( + { + error: { + message: 'Please provide the code challenge parameter.', + }, + }, + 400, + ) + } + + if (response.locals.session) { + const result = await this.getUserKeyParams.execute({ + email: response.locals.user.email, + authenticated: true, + codeChallenge: request.body.code_challenge as string, + }) + + return this.json(result.keyParams) + } + + if (!request.body.email) { + return this.json( + { + error: { + message: 'Please provide an email address.', + }, + }, + 400, + ) + } + + const verifyMFAResponse = await this.verifyMFA.execute({ + email: request.body.email, + requestParams: request.body, + preventOTPFromFurtherUsage: true, + }) + + if (!verifyMFAResponse.success) { + return this.json( + { + error: { + tag: verifyMFAResponse.errorTag, + message: verifyMFAResponse.errorMessage, + payload: verifyMFAResponse.errorPayload, + }, + }, + 401, + ) + } + + const result = await this.getUserKeyParams.execute({ + email: request.body.email, + authenticated: false, + codeChallenge: request.body.code_challenge as string, + }) + + return this.json(result.keyParams) + } + + async pkceSignIn(request: Request): Promise { + if (!request.body.email || !request.body.password || !request.body.code_verifier) { + this.logger.debug('/auth/sign_in request missing credentials: %O', request.body) + + return this.json( + { + error: { + tag: 'invalid-auth', + message: 'Invalid login credentials.', + }, + }, + 401, + ) + } + + const signInResult = await this.signInUseCase.execute({ + apiVersion: request.body.api, + userAgent: request.headers['user-agent'], + email: request.body.email, + password: request.body.password, + ephemeralSession: request.body.ephemeral ?? false, + codeVerifier: request.body.code_verifier, + }) + + if (!signInResult.success) { + await this.increaseLoginAttempts.execute({ email: request.body.email }) + + return this.json( + { + error: { + message: signInResult.errorMessage, + }, + }, + 401, + ) + } + + await this.clearLoginAttempts.execute({ email: request.body.email }) + + return this.json(signInResult.authResponse) + } + + async generateRecoveryCodes(_request: Request, response: Response): Promise { + const result = await this.authController.generateRecoveryCodes({ + userUuid: response.locals.user.uuid, + }) + + return this.json(result.data, result.status) + } + + async recoveryLogin(request: Request): Promise { + const result = await this.authController.signInWithRecoveryCodes({ + apiVersion: request.body.api_version, + userAgent: request.headers['user-agent'], + codeVerifier: request.body.code_verifier, + username: request.body.username, + recoveryCodes: request.body.recovery_codes, + password: request.body.password, + }) + + return this.json(result.data, result.status) + } + + async recoveryParams(request: Request): Promise { + const result = await this.authController.recoveryKeyParams({ + apiVersion: request.body.api_version, + username: request.body.username, + codeChallenge: request.body.code_challenge, + recoveryCodes: request.body.recovery_codes, + }) + + return this.json(result.data, result.status) + } + + async signOut(request: Request, response: Response): Promise { + const result = await this.authController.signOut({ + readOnlyAccess: response.locals.readOnlyAccess, + authorizationHeader: request.headers.authorization, + }) + + return this.json(result.data, result.status) + } + + async register(request: Request): Promise { + const response = await this.authController.register({ + ...request.body, + userAgent: request.headers['user-agent'], + }) + + return this.json(response.data, response.status) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerAuthenticatorsController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerAuthenticatorsController.ts new file mode 100644 index 000000000..e65abe6a8 --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerAuthenticatorsController.ts @@ -0,0 +1,74 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { Request, Response } from 'express' + +import { AuthenticatorsController } from '../../../Controller/AuthenticatorsController' +import { BaseHttpController, results } from 'inversify-express-utils' + +export class HomeServerAuthenticatorsController extends BaseHttpController { + constructor( + protected authenticatorsController: AuthenticatorsController, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.authenticators.list', this.list.bind(this)) + this.controllerContainer.register('auth.authenticators.delete', this.delete.bind(this)) + this.controllerContainer.register( + 'auth.authenticators.generateRegistrationOptions', + this.generateRegistrationOptions.bind(this), + ) + this.controllerContainer.register( + 'auth.authenticators.verifyRegistrationResponse', + this.verifyRegistration.bind(this), + ) + this.controllerContainer.register( + 'auth.authenticators.generateAuthenticationOptions', + this.generateAuthenticationOptions.bind(this), + ) + } + } + + async list(_request: Request, response: Response): Promise { + const result = await this.authenticatorsController.list({ + userUuid: response.locals.user.uuid, + }) + + return this.json(result.data, result.status) + } + + async delete(request: Request, response: Response): Promise { + const result = await this.authenticatorsController.delete({ + userUuid: response.locals.user.uuid, + authenticatorId: request.params.authenticatorId, + }) + + return this.json(result.data, result.status) + } + + async generateRegistrationOptions(_request: Request, response: Response): Promise { + const result = await this.authenticatorsController.generateRegistrationOptions({ + username: response.locals.user.email, + userUuid: response.locals.user.uuid, + }) + + return this.json(result.data, result.status) + } + + async verifyRegistration(request: Request, response: Response): Promise { + const result = await this.authenticatorsController.verifyRegistrationResponse({ + userUuid: response.locals.user.uuid, + attestationResponse: request.body.attestationResponse, + }) + + return this.json(result.data, result.status) + } + + async generateAuthenticationOptions(request: Request): Promise { + const result = await this.authenticatorsController.generateAuthenticationOptions({ + username: request.body.username, + }) + + return this.json(result.data, result.status) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerFeaturesController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerFeaturesController.ts new file mode 100644 index 000000000..327c8f6bd --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerFeaturesController.ts @@ -0,0 +1,42 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { Request, Response } from 'express' + +import { GetUserFeatures } from '../../../Domain/UseCase/GetUserFeatures/GetUserFeatures' +import { BaseHttpController, results } from 'inversify-express-utils' + +export class HomeServerFeaturesController extends BaseHttpController { + constructor( + protected doGetUserFeatures: GetUserFeatures, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.users.getFeatures', this.getFeatures.bind(this)) + } + } + + async getFeatures(request: Request, response: Response): Promise { + if (request.params.userUuid !== response.locals.user.uuid) { + return this.json( + { + error: { + message: 'Operation not allowed.', + }, + }, + 401, + ) + } + + const result = await this.doGetUserFeatures.execute({ + userUuid: request.params.userUuid, + offline: false, + }) + + if (result.success) { + return this.json(result) + } + + return this.json(result, 400) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerListedController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerListedController.ts new file mode 100644 index 000000000..c236d465a --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerListedController.ts @@ -0,0 +1,42 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { ErrorTag } from '@standardnotes/responses' +import { Request, Response } from 'express' + +import { CreateListedAccount } from '../../../Domain/UseCase/CreateListedAccount/CreateListedAccount' +import { BaseHttpController, results } from 'inversify-express-utils' + +export class HomeServerListedController extends BaseHttpController { + constructor( + protected doCreateListedAccount: CreateListedAccount, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.users.createListedAccount', this.createListedAccount.bind(this)) + } + } + + async createListedAccount(_request: Request, response: Response): Promise { + if (response.locals.readOnlyAccess) { + return this.json( + { + error: { + tag: ErrorTag.ReadOnlyAccess, + message: 'Session has read-only access.', + }, + }, + 401, + ) + } + + await this.doCreateListedAccount.execute({ + userUuid: response.locals.user.uuid, + userEmail: response.locals.user.email, + }) + + return this.json({ + message: 'Listed account creation requested successfully.', + }) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerOfflineController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerOfflineController.ts new file mode 100644 index 000000000..3ff19d116 --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerOfflineController.ts @@ -0,0 +1,128 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { Request, Response } from 'express' +import { TokenEncoderInterface, OfflineUserTokenData } from '@standardnotes/security' +import { Logger } from 'winston' +import { BaseHttpController, results } from 'inversify-express-utils' + +import { AuthenticateOfflineSubscriptionToken } from '../../../Domain/UseCase/AuthenticateOfflineSubscriptionToken/AuthenticateOfflineSubscriptionToken' +import { CreateOfflineSubscriptionToken } from '../../../Domain/UseCase/CreateOfflineSubscriptionToken/CreateOfflineSubscriptionToken' +import { GetUserFeatures } from '../../../Domain/UseCase/GetUserFeatures/GetUserFeatures' +import { GetUserOfflineSubscription } from '../../../Domain/UseCase/GetUserOfflineSubscription/GetUserOfflineSubscription' + +export class HomeServerOfflineController extends BaseHttpController { + constructor( + protected doGetUserFeatures: GetUserFeatures, + protected getUserOfflineSubscription: GetUserOfflineSubscription, + protected createOfflineSubscriptionToken: CreateOfflineSubscriptionToken, + protected authenticateToken: AuthenticateOfflineSubscriptionToken, + protected tokenEncoder: TokenEncoderInterface, + protected jwtTTL: number, + protected logger: Logger, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.offline.features', this.getOfflineFeatures.bind(this)) + this.controllerContainer.register('auth.offline.subscriptionTokens.create', this.createToken.bind(this)) + this.controllerContainer.register('auth.users.getOfflineSubscriptionByToken', this.getSubscription.bind(this)) + } + } + + async getOfflineFeatures(_request: Request, response: Response): Promise { + const result = await this.doGetUserFeatures.execute({ + email: response.locals.offlineUserEmail, + offline: true, + }) + + if (result.success) { + return this.json(result) + } + + return this.json(result, 400) + } + + async createToken(request: Request): Promise { + if (!request.body.email) { + return this.json( + { + error: { + tag: 'invalid-request', + message: 'Invalid request parameters.', + }, + }, + 400, + ) + } + + const response = await this.createOfflineSubscriptionToken.execute({ + userEmail: request.body.email, + }) + + if (!response.success) { + return this.json({ success: false, error: { tag: response.error } }) + } + + return this.json({ success: true }) + } + + async validate(request: Request): Promise { + if (!request.body.email) { + this.logger.debug('[Offline Subscription Token Validation] Missing email') + + return this.json( + { + error: { + tag: 'invalid-request', + message: 'Invalid request parameters.', + }, + }, + 400, + ) + } + + const authenticateTokenResponse = await this.authenticateToken.execute({ + token: request.params.token, + userEmail: request.body.email, + }) + + if (!authenticateTokenResponse.success) { + this.logger.debug('[Offline Subscription Token Validation] invalid token') + + return this.json( + { + error: { + tag: 'invalid-auth', + message: 'Invalid login credentials.', + }, + }, + 401, + ) + } + + const offlineAuthTokenData: OfflineUserTokenData = { + userEmail: authenticateTokenResponse.email, + featuresToken: authenticateTokenResponse.featuresToken, + } + + const authToken = this.tokenEncoder.encodeExpirableToken(offlineAuthTokenData, this.jwtTTL) + + this.logger.debug( + `[Offline Subscription Token Validation] authenticated token for user ${authenticateTokenResponse.email}`, + ) + + return this.json({ authToken }) + } + + async getSubscription(_request: Request, response: Response): Promise { + const result = await this.getUserOfflineSubscription.execute({ + userEmail: response.locals.userEmail, + }) + + if (result.success) { + return this.json(result) + } + + return this.json(result, 400) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSessionController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSessionController.ts new file mode 100644 index 000000000..ccb867406 --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSessionController.ts @@ -0,0 +1,153 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { Request, Response } from 'express' +import { BaseHttpController, results } from 'inversify-express-utils' +import { ErrorTag } from '@standardnotes/responses' + +import { DeletePreviousSessionsForUser } from '../../../Domain/UseCase/DeletePreviousSessionsForUser' +import { DeleteSessionForUser } from '../../../Domain/UseCase/DeleteSessionForUser' +import { RefreshSessionToken } from '../../../Domain/UseCase/RefreshSessionToken' + +export class HomeServerSessionController extends BaseHttpController { + constructor( + protected deleteSessionForUser: DeleteSessionForUser, + protected deletePreviousSessionsForUser: DeletePreviousSessionsForUser, + protected refreshSessionToken: RefreshSessionToken, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.sessions.delete', this.deleteSession.bind(this)) + this.controllerContainer.register('auth.sessions.deleteAll', this.deleteAllSessions.bind(this)) + this.controllerContainer.register('auth.sessions.refresh', this.refresh.bind(this)) + } + } + + async deleteSession(request: Request, response: Response): Promise { + if (response.locals.readOnlyAccess) { + return this.json( + { + error: { + tag: ErrorTag.ReadOnlyAccess, + message: 'Session has read-only access.', + }, + }, + 401, + ) + } + + if (!request.body.uuid) { + return this.json( + { + error: { + message: 'Please provide the session identifier.', + }, + }, + 400, + ) + } + + if (request.body.uuid === response.locals.session.uuid) { + return this.json( + { + error: { + message: 'You can not delete your current session.', + }, + }, + 400, + ) + } + + const useCaseResponse = await this.deleteSessionForUser.execute({ + userUuid: response.locals.user.uuid, + sessionUuid: request.body.uuid, + }) + + if (!useCaseResponse.success) { + return this.json( + { + error: { + message: useCaseResponse.errorMessage, + }, + }, + 400, + ) + } + + response.setHeader('x-invalidate-cache', response.locals.user.uuid) + + return this.statusCode(204) + } + + async deleteAllSessions( + _request: Request, + response: Response, + ): Promise { + if (response.locals.readOnlyAccess) { + return this.json( + { + error: { + tag: ErrorTag.ReadOnlyAccess, + message: 'Session has read-only access.', + }, + }, + 401, + ) + } + + if (!response.locals.user) { + return this.json( + { + error: { + message: 'No session exists with the provided identifier.', + }, + }, + 401, + ) + } + + await this.deletePreviousSessionsForUser.execute({ + userUuid: response.locals.user.uuid, + currentSessionUuid: response.locals.session.uuid, + }) + + response.setHeader('x-invalidate-cache', response.locals.user.uuid) + + return this.statusCode(204) + } + + async refresh(request: Request, response: Response): Promise { + if (!request.body.access_token || !request.body.refresh_token) { + return this.json( + { + error: { + message: 'Please provide all required parameters.', + }, + }, + 400, + ) + } + + const result = await this.refreshSessionToken.execute({ + accessToken: request.body.access_token, + refreshToken: request.body.refresh_token, + }) + + if (!result.success) { + return this.json( + { + error: { + tag: result.errorTag, + message: result.errorMessage, + }, + }, + 400, + ) + } + + response.setHeader('x-invalidate-cache', result.userUuid as string) + return this.json({ + session: result.sessionPayload, + }) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSessionsController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSessionsController.ts new file mode 100644 index 000000000..23734a248 --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSessionsController.ts @@ -0,0 +1,75 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { Request, Response } from 'express' + +import { AuthenticateRequest } from '../../../Domain/UseCase/AuthenticateRequest' +import { CreateCrossServiceToken } from '../../../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken' +import { GetActiveSessionsForUser } from '../../../Domain/UseCase/GetActiveSessionsForUser' +import { ProjectorInterface } from '../../../Projection/ProjectorInterface' +import { Session } from '../../../Domain/Session/Session' +import { BaseHttpController, results } from 'inversify-express-utils' +import { User } from '../../../Domain/User/User' +import { SessionProjector } from '../../../Projection/SessionProjector' + +export class HomeServerSessionsController extends BaseHttpController { + constructor( + protected getActiveSessionsForUser: GetActiveSessionsForUser, + protected authenticateRequest: AuthenticateRequest, + protected sessionProjector: ProjectorInterface, + protected createCrossServiceToken: CreateCrossServiceToken, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.sessions.list', this.getSessions.bind(this)) + this.controllerContainer.register('auth.sessions.validate', this.validate.bind(this)) + } + } + + async validate(request: Request): Promise { + const authenticateRequestResponse = await this.authenticateRequest.execute({ + authorizationHeader: request.headers.authorization, + }) + + if (!authenticateRequestResponse.success) { + return this.json( + { + error: { + tag: authenticateRequestResponse.errorTag, + message: authenticateRequestResponse.errorMessage, + }, + }, + authenticateRequestResponse.responseCode, + ) + } + + const user = authenticateRequestResponse.user as User + + const result = await this.createCrossServiceToken.execute({ + user, + session: authenticateRequestResponse.session, + }) + + return this.json({ authToken: result.token }) + } + + async getSessions(_request: Request, response: Response): Promise { + if (response.locals.readOnlyAccess) { + return this.json([]) + } + + const useCaseResponse = await this.getActiveSessionsForUser.execute({ + userUuid: response.locals.user.uuid, + }) + + return this.json( + useCaseResponse.sessions.map((session) => + this.sessionProjector.projectCustom( + SessionProjector.CURRENT_SESSION_PROJECTION.toString(), + session, + response.locals.session, + ), + ), + ) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSettingsController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSettingsController.ts new file mode 100644 index 000000000..9f4daab81 --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSettingsController.ts @@ -0,0 +1,153 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { ErrorTag } from '@standardnotes/responses' +import { Request, Response } from 'express' + +import { DeleteSetting } from '../../../Domain/UseCase/DeleteSetting/DeleteSetting' +import { GetSetting } from '../../../Domain/UseCase/GetSetting/GetSetting' +import { GetSettings } from '../../../Domain/UseCase/GetSettings/GetSettings' +import { UpdateSetting } from '../../../Domain/UseCase/UpdateSetting/UpdateSetting' +import { BaseHttpController, results } from 'inversify-express-utils' +import { EncryptionVersion } from '../../../Domain/Encryption/EncryptionVersion' + +export class HomeServerSettingsController extends BaseHttpController { + constructor( + protected doGetSettings: GetSettings, + protected doGetSetting: GetSetting, + protected doUpdateSetting: UpdateSetting, + protected doDeleteSetting: DeleteSetting, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.users.getSettings', this.getSettings.bind(this)) + this.controllerContainer.register('auth.users.getSetting', this.getSetting.bind(this)) + this.controllerContainer.register('auth.users.updateSetting', this.updateSetting.bind(this)) + this.controllerContainer.register('auth.users.deleteSetting', this.deleteSetting.bind(this)) + } + } + + async getSettings(request: Request, response: Response): Promise { + if (request.params.userUuid !== response.locals.user.uuid) { + return this.json( + { + error: { + message: 'Operation not allowed.', + }, + }, + 401, + ) + } + + const { userUuid } = request.params + const result = await this.doGetSettings.execute({ userUuid }) + + return this.json(result) + } + + async getSetting(request: Request, response: Response): Promise { + if (request.params.userUuid !== response.locals.user.uuid) { + return this.json( + { + error: { + message: 'Operation not allowed.', + }, + }, + 401, + ) + } + + const { userUuid, settingName } = request.params + const result = await this.doGetSetting.execute({ userUuid, settingName: settingName.toUpperCase() }) + + if (result.success) { + return this.json(result) + } + + return this.json(result, 400) + } + + async updateSetting(request: Request, response: Response): Promise { + if (response.locals.readOnlyAccess) { + return this.json( + { + error: { + tag: ErrorTag.ReadOnlyAccess, + message: 'Session has read-only access.', + }, + }, + 401, + ) + } + + if (request.params.userUuid !== response.locals.user.uuid) { + return this.json( + { + error: { + message: 'Operation not allowed.', + }, + }, + 401, + ) + } + + const { name, value, serverEncryptionVersion = EncryptionVersion.Default, sensitive = false } = request.body + + const props = { + name, + unencryptedValue: value, + serverEncryptionVersion, + sensitive, + } + + const { userUuid } = request.params + const result = await this.doUpdateSetting.execute({ + userUuid, + props, + }) + + if (result.success) { + return this.json({ setting: result.setting }, result.statusCode) + } + + return this.json(result, result.statusCode) + } + + async deleteSetting(request: Request, response: Response): Promise { + if (response.locals.readOnlyAccess) { + return this.json( + { + error: { + tag: ErrorTag.ReadOnlyAccess, + message: 'Session has read-only access.', + }, + }, + 401, + ) + } + + if (request.params.userUuid !== response.locals.user.uuid) { + return this.json( + { + error: { + message: 'Operation not allowed.', + }, + }, + 401, + ) + } + + const { userUuid, settingName } = request.params + + const result = await this.doDeleteSetting.execute({ + userUuid, + settingName, + }) + + if (result.success) { + return this.json(result) + } + + return this.json(result, 400) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionInvitesController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionInvitesController.ts new file mode 100644 index 000000000..929d11e44 --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionInvitesController.ts @@ -0,0 +1,74 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { Request, Response } from 'express' +import { BaseHttpController, results } from 'inversify-express-utils' +import { ApiVersion } from '@standardnotes/api' + +import { SubscriptionInvitesController } from '../../../Controller/SubscriptionInvitesController' +import { Role } from '../../../Domain/Role/Role' + +export class HomeServerSubscriptionInvitesController extends BaseHttpController { + constructor( + protected subscriptionInvitesController: SubscriptionInvitesController, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.subscriptionInvites.accept', this.acceptInvite.bind(this)) + this.controllerContainer.register('auth.subscriptionInvites.declineInvite', this.declineInvite.bind(this)) + this.controllerContainer.register('auth.subscriptionInvites.create', this.inviteToSubscriptionSharing.bind(this)) + this.controllerContainer.register('auth.subscriptionInvites.delete', this.cancelSubscriptionSharing.bind(this)) + this.controllerContainer.register('auth.subscriptionInvites.list', this.listInvites.bind(this)) + } + } + + async acceptInvite(request: Request, response: Response): Promise { + const result = await this.subscriptionInvitesController.acceptInvite({ + api: request.query.api as ApiVersion, + inviteUuid: request.params.inviteUuid, + }) + + response.setHeader('x-invalidate-cache', response.locals.user.uuid) + + return this.json(result.data, result.status) + } + + async declineInvite(request: Request): Promise { + const response = await this.subscriptionInvitesController.declineInvite({ + api: request.query.api as ApiVersion, + inviteUuid: request.params.inviteUuid, + }) + + return this.json(response.data, response.status) + } + + async inviteToSubscriptionSharing(request: Request, response: Response): Promise { + const result = await this.subscriptionInvitesController.invite({ + ...request.body, + inviterEmail: response.locals.user.email, + inviterUuid: response.locals.user.uuid, + inviterRoles: response.locals.roles.map((role: Role) => role.name), + }) + + return this.json(result.data, result.status) + } + + async cancelSubscriptionSharing(request: Request, response: Response): Promise { + const result = await this.subscriptionInvitesController.cancelInvite({ + ...request.body, + inviteUuid: request.params.inviteUuid, + inviterEmail: response.locals.user.email, + }) + + return this.json(result.data, result.status) + } + + async listInvites(request: Request, response: Response): Promise { + const result = await this.subscriptionInvitesController.listInvites({ + ...request.body, + inviterEmail: response.locals.user.email, + }) + + return this.json(result.data, result.status) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionSettingsController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionSettingsController.ts new file mode 100644 index 000000000..2a5bb1a50 --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionSettingsController.ts @@ -0,0 +1,28 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { BaseHttpController, results } from 'inversify-express-utils' +import { Request, Response } from 'express' + +import { GetSetting } from '../../../Domain/UseCase/GetSetting/GetSetting' + +export class HomeServerSubscriptionSettingsController extends BaseHttpController { + constructor(protected doGetSetting: GetSetting, private controllerContainer?: ControllerContainerInterface) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.users.getSubscriptionSetting', this.getSubscriptionSetting.bind(this)) + } + } + + async getSubscriptionSetting(request: Request, response: Response): Promise { + const result = await this.doGetSetting.execute({ + userUuid: response.locals.user.uuid, + settingName: request.params.subscriptionSettingName.toUpperCase(), + }) + + if (result.success) { + return this.json(result) + } + + return this.json(result, 400) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionTokensController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionTokensController.ts new file mode 100644 index 000000000..9d388d5de --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSubscriptionTokensController.ts @@ -0,0 +1,106 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { ErrorTag } from '@standardnotes/responses' +import { Role, TokenEncoderInterface, CrossServiceTokenData } from '@standardnotes/security' +import { BaseHttpController, results } from 'inversify-express-utils' +import { Request, Response } from 'express' + +import { SettingServiceInterface } from '../../../Domain/Setting/SettingServiceInterface' +import { AuthenticateSubscriptionToken } from '../../../Domain/UseCase/AuthenticateSubscriptionToken/AuthenticateSubscriptionToken' +import { CreateSubscriptionToken } from '../../../Domain/UseCase/CreateSubscriptionToken/CreateSubscriptionToken' +import { ProjectorInterface } from '../../../Projection/ProjectorInterface' +import { SettingName } from '@standardnotes/settings' +import { User } from '../../../Domain/User/User' + +export class HomeServerSubscriptionTokensController extends BaseHttpController { + constructor( + protected createSubscriptionToken: CreateSubscriptionToken, + protected authenticateToken: AuthenticateSubscriptionToken, + protected settingService: SettingServiceInterface, + protected userProjector: ProjectorInterface, + protected roleProjector: ProjectorInterface, + protected tokenEncoder: TokenEncoderInterface, + protected jwtTTL: number, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.subscription-tokens.create', this.createToken.bind(this)) + } + } + + async createToken(_request: Request, response: Response): Promise { + if (response.locals.readOnlyAccess) { + return this.json( + { + error: { + tag: ErrorTag.ReadOnlyAccess, + message: 'Session has read-only access.', + }, + }, + 401, + ) + } + + const result = await this.createSubscriptionToken.execute({ + userUuid: response.locals.user.uuid, + }) + + return this.json({ + token: result.subscriptionToken.token, + }) + } + + async validate(request: Request): Promise { + const authenticateTokenResponse = await this.authenticateToken.execute({ + token: request.params.token, + }) + + if (!authenticateTokenResponse.success) { + return this.json( + { + error: { + tag: 'invalid-auth', + message: 'Invalid login credentials.', + }, + }, + 401, + ) + } + + const user = authenticateTokenResponse.user as User + let extensionKey = undefined + const extensionKeySetting = await this.settingService.findSettingWithDecryptedValue({ + settingName: SettingName.create(SettingName.NAMES.ExtensionKey).getValue(), + userUuid: user.uuid, + }) + if (extensionKeySetting !== null) { + extensionKey = extensionKeySetting.value as string + } + + const roles = await user.roles + + const authTokenData: CrossServiceTokenData = { + user: await this.projectUser(user), + roles: await this.projectRoles(roles), + extensionKey, + } + + const authToken = this.tokenEncoder.encodeExpirableToken(authTokenData, this.jwtTTL) + + return this.json({ authToken }) + } + + private async projectUser(user: User): Promise<{ uuid: string; email: string }> { + return <{ uuid: string; email: string }>await this.userProjector.projectSimple(user) + } + + private async projectRoles(roles: Array): Promise> { + const roleProjections = [] + for (const role of roles) { + roleProjections.push(<{ uuid: string; name: string }>await this.roleProjector.projectSimple(role)) + } + + return roleProjections + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerUserRequestsController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerUserRequestsController.ts new file mode 100644 index 000000000..af3896e32 --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerUserRequestsController.ts @@ -0,0 +1,28 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { BaseHttpController, results } from 'inversify-express-utils' +import { Request, Response } from 'express' + +import { UserRequestsController } from '../../../Controller/UserRequestsController' + +export class HomeServerUserRequestsController extends BaseHttpController { + constructor( + protected userRequestsController: UserRequestsController, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.users.createRequest', this.submitRequest.bind(this)) + } + } + + async submitRequest(request: Request, response: Response): Promise { + const result = await this.userRequestsController.submitUserRequest({ + requestType: request.body.requestType, + userUuid: response.locals.user.uuid, + userEmail: response.locals.user.email, + }) + + return this.json(result.data, result.status) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerUsersController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerUsersController.ts new file mode 100644 index 000000000..67d499eb4 --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerUsersController.ts @@ -0,0 +1,236 @@ +import { ControllerContainerInterface, Username } from '@standardnotes/domain-core' +import { Request, Response } from 'express' +import { BaseHttpController, results } from 'inversify-express-utils' + +import { ChangeCredentials } from '../../../Domain/UseCase/ChangeCredentials/ChangeCredentials' +import { ClearLoginAttempts } from '../../../Domain/UseCase/ClearLoginAttempts' +import { DeleteAccount } from '../../../Domain/UseCase/DeleteAccount/DeleteAccount' +import { GetUserKeyParams } from '../../../Domain/UseCase/GetUserKeyParams/GetUserKeyParams' +import { GetUserSubscription } from '../../../Domain/UseCase/GetUserSubscription/GetUserSubscription' +import { IncreaseLoginAttempts } from '../../../Domain/UseCase/IncreaseLoginAttempts' +import { UpdateUser } from '../../../Domain/UseCase/UpdateUser' +import { ErrorTag } from '@standardnotes/responses' + +export class HomeServerUsersController extends BaseHttpController { + constructor( + protected updateUser: UpdateUser, + protected getUserKeyParams: GetUserKeyParams, + protected doDeleteAccount: DeleteAccount, + protected doGetUserSubscription: GetUserSubscription, + protected clearLoginAttempts: ClearLoginAttempts, + protected increaseLoginAttempts: IncreaseLoginAttempts, + protected changeCredentialsUseCase: ChangeCredentials, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.users.update', this.update.bind(this)) + this.controllerContainer.register('auth.users.getKeyParams', this.keyParams.bind(this)) + this.controllerContainer.register('auth.users.getSubscription', this.getSubscription.bind(this)) + this.controllerContainer.register('auth.users.updateCredentials', this.changeCredentials.bind(this)) + } + } + + async update(request: Request, response: Response): Promise { + if (response.locals.readOnlyAccess) { + return this.json( + { + error: { + tag: ErrorTag.ReadOnlyAccess, + message: 'Session has read-only access.', + }, + }, + 401, + ) + } + + if (request.params.userId !== response.locals.user.uuid) { + return this.json( + { + error: { + message: 'Operation not allowed.', + }, + }, + 401, + ) + } + + const updateResult = await this.updateUser.execute({ + user: response.locals.user, + updatedWithUserAgent: request.headers['user-agent'], + apiVersion: request.body.api, + pwFunc: request.body.pw_func, + pwAlg: request.body.pw_alg, + pwCost: request.body.pw_cost, + pwKeySize: request.body.pw_key_size, + pwNonce: request.body.pw_nonce, + pwSalt: request.body.pw_salt, + kpOrigination: request.body.origination, + kpCreated: request.body.created, + version: request.body.version, + }) + + if (updateResult.success) { + response.setHeader('x-invalidate-cache', response.locals.user.uuid) + + return this.json(updateResult.authResponse) + } + + return this.json( + { + error: { + message: 'Could not update user.', + }, + }, + 400, + ) + } + + async keyParams(request: Request): Promise { + const email = 'email' in request.query ? request.query.email : undefined + const userUuid = 'uuid' in request.query ? request.query.uuid : undefined + + if (!email && !userUuid) { + return this.json( + { + error: { + message: 'Missing mandatory request query parameters.', + }, + }, + 400, + ) + } + + const result = await this.getUserKeyParams.execute({ + email, + userUuid, + authenticated: request.query.authenticated === 'true', + }) + + return this.json(result.keyParams) + } + + async deleteAccount(request: Request): Promise { + const result = await this.doDeleteAccount.execute({ + email: request.params.email, + }) + + return this.json({ message: result.message }, result.responseCode) + } + + async getSubscription(request: Request, response: Response): Promise { + if (request.params.userUuid !== response.locals.user.uuid) { + return this.json( + { + error: { + message: 'Operation not allowed.', + }, + }, + 401, + ) + } + + const result = await this.doGetUserSubscription.execute({ + userUuid: request.params.userUuid, + }) + + if (result.success) { + return this.json(result) + } + + return this.json(result, 400) + } + + async changeCredentials(request: Request, response: Response): Promise { + if (response.locals.readOnlyAccess) { + return this.json( + { + error: { + tag: ErrorTag.ReadOnlyAccess, + message: 'Session has read-only access.', + }, + }, + 401, + ) + } + + if (!request.body.current_password) { + return this.json( + { + error: { + message: + 'Your current password is required to change your password. Please update your application if you do not see this option.', + }, + }, + 400, + ) + } + + if (!request.body.new_password) { + return this.json( + { + error: { + message: 'Your new password is required to change your password. Please try again.', + }, + }, + 400, + ) + } + + if (!request.body.pw_nonce) { + return this.json( + { + error: { + message: 'The change password request is missing new auth parameters. Please try again.', + }, + }, + 400, + ) + } + const usernameOrError = Username.create(response.locals.user.email) + if (usernameOrError.isFailed()) { + return this.json( + { + error: { + message: 'Invalid username.', + }, + }, + 400, + ) + } + const username = usernameOrError.getValue() + + const changeCredentialsResult = await this.changeCredentialsUseCase.execute({ + username, + apiVersion: request.body.api, + currentPassword: request.body.current_password, + newPassword: request.body.new_password, + newEmail: request.body.new_email, + pwNonce: request.body.pw_nonce, + kpCreated: request.body.created, + kpOrigination: request.body.origination, + updatedWithUserAgent: request.headers['user-agent'], + protocolVersion: request.body.version, + }) + + if (!changeCredentialsResult.success) { + await this.increaseLoginAttempts.execute({ email: response.locals.user.email }) + + return this.json( + { + error: { + message: changeCredentialsResult.errorMessage, + }, + }, + 401, + ) + } + + await this.clearLoginAttempts.execute({ email: response.locals.user.email }) + + response.setHeader('x-invalidate-cache', response.locals.user.uuid) + + return this.json(changeCredentialsResult.authResponse) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerValetTokenController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerValetTokenController.ts new file mode 100644 index 000000000..e87a49117 --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerValetTokenController.ts @@ -0,0 +1,60 @@ +import { ControllerContainerInterface, Uuid } from '@standardnotes/domain-core' +import { Request, Response } from 'express' +import { BaseHttpController, results } from 'inversify-express-utils' + +import { CreateValetToken } from '../../../Domain/UseCase/CreateValetToken/CreateValetToken' +import { CreateValetTokenPayload, ErrorTag } from '@standardnotes/responses' +import { ValetTokenOperation } from '@standardnotes/security' + +export class HomeServerValetTokenController extends BaseHttpController { + constructor(protected createValetKey: CreateValetToken, private controllerContainer?: ControllerContainerInterface) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.valet-tokens.create', this.create.bind(this)) + } + } + + public async create(request: Request, response: Response): Promise { + const payload: CreateValetTokenPayload = request.body + + if (response.locals.readOnlyAccess && payload.operation !== 'read') { + return this.json( + { + error: { + tag: ErrorTag.ReadOnlyAccess, + message: 'Session has read-only access.', + }, + }, + 401, + ) + } + + for (const resource of payload.resources) { + const resourceUuidOrError = Uuid.create(resource.remoteIdentifier) + if (resourceUuidOrError.isFailed()) { + return this.json( + { + error: { + tag: ErrorTag.ParametersInvalid, + message: 'Invalid remote resource identifier.', + }, + }, + 400, + ) + } + } + + const createValetKeyResponse = await this.createValetKey.execute({ + userUuid: response.locals.user.uuid, + operation: payload.operation as ValetTokenOperation, + resources: payload.resources, + }) + + if (!createValetKeyResponse.success) { + return this.json(createValetKeyResponse, 403) + } + + return this.json(createValetKeyResponse) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerWebSocketsController.ts b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerWebSocketsController.ts new file mode 100644 index 000000000..ba79aa811 --- /dev/null +++ b/packages/auth/src/Infra/InversifyExpressUtils/HomeServer/HomeServerWebSocketsController.ts @@ -0,0 +1,55 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { TokenDecoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security' +import { Request } from 'express' +import { BaseHttpController, results } from 'inversify-express-utils' + +import { CreateCrossServiceToken } from '../../../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken' +import { ErrorTag } from '@standardnotes/responses' + +export class HomeServerWebSocketsController extends BaseHttpController { + constructor( + protected createCrossServiceToken: CreateCrossServiceToken, + protected tokenDecoder: TokenDecoderInterface, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('auth.webSockets.validateToken', this.validateToken.bind(this)) + } + } + + async validateToken(request: Request): Promise { + if (!request.headers.authorization) { + return this.json( + { + error: { + tag: ErrorTag.AuthInvalid, + message: 'Invalid authorization token.', + }, + }, + 401, + ) + } + + const token: WebSocketConnectionTokenData | undefined = this.tokenDecoder.decodeToken(request.headers.authorization) + + if (token === undefined) { + return this.json( + { + error: { + tag: ErrorTag.AuthInvalid, + message: 'Invalid authorization token.', + }, + }, + 401, + ) + } + + const result = await this.createCrossServiceToken.execute({ + userUuid: token.userUuid, + }) + + return this.json({ authToken: result.token }) + } +} diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAdminController.spec.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAdminController.spec.ts index 4832299d8..97544af0c 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAdminController.spec.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAdminController.spec.ts @@ -8,7 +8,6 @@ import * as express from 'express' import { DeleteSetting } from '../../Domain/UseCase/DeleteSetting/DeleteSetting' import { CreateSubscriptionToken } from '../../Domain/UseCase/CreateSubscriptionToken/CreateSubscriptionToken' import { CreateOfflineSubscriptionToken } from '../../Domain/UseCase/CreateOfflineSubscriptionToken/CreateOfflineSubscriptionToken' -import { ControllerContainerInterface } from '@standardnotes/domain-core' describe('InversifyExpressAdminController', () => { let deleteSetting: DeleteSetting @@ -17,7 +16,6 @@ describe('InversifyExpressAdminController', () => { let createOfflineSubscriptionToken: CreateOfflineSubscriptionToken let request: express.Request let user: User - let controllerContainer: ControllerContainerInterface const createController = () => new InversifyExpressAdminController( @@ -25,7 +23,6 @@ describe('InversifyExpressAdminController', () => { userRepository, createSubscriptionToken, createOfflineSubscriptionToken, - controllerContainer, ) beforeEach(() => { @@ -58,9 +55,6 @@ describe('InversifyExpressAdminController', () => { body: {}, params: {}, } as jest.Mocked - - controllerContainer = {} as jest.Mocked - controllerContainer.register = jest.fn() }) it('should return error if missing email parameter', async () => { diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAdminController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAdminController.ts index d491190e6..937dc02bb 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAdminController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAdminController.ts @@ -1,9 +1,6 @@ -import { ControllerContainerInterface, Username } from '@standardnotes/domain-core' -import { SettingName } from '@standardnotes/settings' import { Request } from 'express' import { inject } from 'inversify' import { - BaseHttpController, controller, httpDelete, httpGet, @@ -12,124 +9,48 @@ import { results, } from 'inversify-express-utils' import TYPES from '../../Bootstrap/Types' +import { HomeServerAdminController } from './HomeServer/HomeServerAdminController' import { CreateOfflineSubscriptionToken } from '../../Domain/UseCase/CreateOfflineSubscriptionToken/CreateOfflineSubscriptionToken' import { CreateSubscriptionToken } from '../../Domain/UseCase/CreateSubscriptionToken/CreateSubscriptionToken' import { DeleteSetting } from '../../Domain/UseCase/DeleteSetting/DeleteSetting' import { UserRepositoryInterface } from '../../Domain/User/UserRepositoryInterface' @controller('/admin') -export class InversifyExpressAdminController extends BaseHttpController { +export class InversifyExpressAdminController extends HomeServerAdminController { constructor( - @inject(TYPES.Auth_DeleteSetting) private doDeleteSetting: DeleteSetting, - @inject(TYPES.Auth_UserRepository) private userRepository: UserRepositoryInterface, - @inject(TYPES.Auth_CreateSubscriptionToken) private createSubscriptionToken: CreateSubscriptionToken, + @inject(TYPES.Auth_DeleteSetting) override doDeleteSetting: DeleteSetting, + @inject(TYPES.Auth_UserRepository) override userRepository: UserRepositoryInterface, + @inject(TYPES.Auth_CreateSubscriptionToken) override createSubscriptionToken: CreateSubscriptionToken, @inject(TYPES.Auth_CreateOfflineSubscriptionToken) - private createOfflineSubscriptionToken: CreateOfflineSubscriptionToken, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, + override createOfflineSubscriptionToken: CreateOfflineSubscriptionToken, ) { - super() - - this.controllerContainer.register('admin.getUser', this.getUser.bind(this)) - this.controllerContainer.register('admin.deleteMFASetting', this.deleteMFASetting.bind(this)) - this.controllerContainer.register('admin.createToken', this.createToken.bind(this)) - this.controllerContainer.register('admin.createOfflineToken', this.createOfflineToken.bind(this)) - this.controllerContainer.register('admin.disableEmailBackups', this.disableEmailBackups.bind(this)) + super(doDeleteSetting, userRepository, createSubscriptionToken, createOfflineSubscriptionToken) } @httpGet('/user/:email') - async getUser(request: Request): Promise { - const usernameOrError = Username.create(request.params.email ?? '') - if (usernameOrError.isFailed()) { - return this.json( - { - error: { - message: 'Missing email parameter.', - }, - }, - 400, - ) - } - const username = usernameOrError.getValue() - - const user = await this.userRepository.findOneByUsernameOrEmail(username) - - if (!user) { - return this.json( - { - error: { - message: `No user with email '${username.value}'.`, - }, - }, - 400, - ) - } - - return this.json({ - uuid: user.uuid, - }) + override async getUser(request: Request): Promise { + return super.getUser(request) } @httpDelete('/users/:userUuid/mfa') - async deleteMFASetting(request: Request): Promise { - const { userUuid } = request.params - const { uuid, updatedAt } = request.body - - const result = await this.doDeleteSetting.execute({ - uuid, - userUuid, - settingName: SettingName.NAMES.MfaSecret, - timestamp: updatedAt, - softDelete: true, - }) - - if (result.success) { - return this.json(result) - } - - return this.json(result, 400) + override async deleteMFASetting(request: Request): Promise { + return super.deleteMFASetting(request) } @httpPost('/users/:userUuid/subscription-token') - async createToken(request: Request): Promise { - const { userUuid } = request.params - const result = await this.createSubscriptionToken.execute({ - userUuid, - }) - - return this.json({ - token: result.subscriptionToken.token, - }) + override async createToken(request: Request): Promise { + return super.createToken(request) } @httpPost('/users/:email/offline-subscription-token') - async createOfflineToken(request: Request): Promise { - const { email } = request.params - const result = await this.createOfflineSubscriptionToken.execute({ - userEmail: email, - }) - - if (!result.success) { - return this.badRequest() - } - - return this.json({ - token: result.offlineSubscriptionToken.token, - }) + override async createOfflineToken(request: Request): Promise { + return super.createOfflineToken(request) } @httpPost('/users/:userUuid/email-backups') - async disableEmailBackups(request: Request): Promise { - const { userUuid } = request.params - - const result = await this.doDeleteSetting.execute({ - userUuid, - settingName: SettingName.NAMES.EmailBackupFrequency, - }) - - if (result.success) { - return this.ok() - } - - return this.badRequest('No email backups found') + override async disableEmailBackups( + request: Request, + ): Promise { + return super.disableEmailBackups(request) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAuthController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAuthController.ts index 64491bc58..4b4a68fd1 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAuthController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAuthController.ts @@ -1,6 +1,5 @@ import { Request, Response } from 'express' import { - BaseHttpController, controller, httpGet, httpPost, @@ -16,301 +15,65 @@ import { IncreaseLoginAttempts } from '../../Domain/UseCase/IncreaseLoginAttempt import { Logger } from 'winston' import { GetUserKeyParams } from '../../Domain/UseCase/GetUserKeyParams/GetUserKeyParams' import { AuthController } from '../../Controller/AuthController' -import { ControllerContainerInterface } from '@standardnotes/domain-core' import { inject } from 'inversify' +import { HomeServerAuthController } from './HomeServer/HomeServerAuthController' @controller('/auth') -export class InversifyExpressAuthController extends BaseHttpController { +export class InversifyExpressAuthController extends HomeServerAuthController { constructor( - @inject(TYPES.Auth_VerifyMFA) private verifyMFA: VerifyMFA, - @inject(TYPES.Auth_SignIn) private signInUseCase: SignIn, - @inject(TYPES.Auth_GetUserKeyParams) private getUserKeyParams: GetUserKeyParams, - @inject(TYPES.Auth_ClearLoginAttempts) private clearLoginAttempts: ClearLoginAttempts, - @inject(TYPES.Auth_IncreaseLoginAttempts) private increaseLoginAttempts: IncreaseLoginAttempts, - @inject(TYPES.Auth_Logger) private logger: Logger, - @inject(TYPES.Auth_AuthController) private authController: AuthController, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, + @inject(TYPES.Auth_VerifyMFA) override verifyMFA: VerifyMFA, + @inject(TYPES.Auth_SignIn) override signInUseCase: SignIn, + @inject(TYPES.Auth_GetUserKeyParams) override getUserKeyParams: GetUserKeyParams, + @inject(TYPES.Auth_ClearLoginAttempts) override clearLoginAttempts: ClearLoginAttempts, + @inject(TYPES.Auth_IncreaseLoginAttempts) override increaseLoginAttempts: IncreaseLoginAttempts, + @inject(TYPES.Auth_Logger) override logger: Logger, + @inject(TYPES.Auth_AuthController) override authController: AuthController, ) { - super() - - this.controllerContainer.register('auth.params', this.params.bind(this)) - this.controllerContainer.register('auth.signIn', this.signIn.bind(this)) - this.controllerContainer.register('auth.pkceParams', this.pkceParams.bind(this)) - this.controllerContainer.register('auth.pkceSignIn', this.pkceSignIn.bind(this)) - this.controllerContainer.register('auth.users.register', this.register.bind(this)) - this.controllerContainer.register('auth.generateRecoveryCodes', this.generateRecoveryCodes.bind(this)) - this.controllerContainer.register('auth.signInWithRecoveryCodes', this.recoveryLogin.bind(this)) - this.controllerContainer.register('auth.recoveryKeyParams', this.recoveryParams.bind(this)) - this.controllerContainer.register('auth.signOut', this.signOut.bind(this)) + super(verifyMFA, signInUseCase, getUserKeyParams, clearLoginAttempts, increaseLoginAttempts, logger, authController) } @httpGet('/params', TYPES.Auth_OptionalCrossServiceTokenMiddleware) - async params(request: Request, response: Response): Promise { - if (response.locals.session) { - const result = await this.getUserKeyParams.execute({ - email: response.locals.user.email, - authenticated: true, - }) - - return this.json(result.keyParams) - } - - if (!request.query.email) { - return this.json( - { - error: { - message: 'Please provide an email address.', - }, - }, - 400, - ) - } - - const verifyMFAResponse = await this.verifyMFA.execute({ - email: request.query.email, - requestParams: request.query, - preventOTPFromFurtherUsage: false, - }) - - if (!verifyMFAResponse.success) { - return this.json( - { - error: { - tag: verifyMFAResponse.errorTag, - message: verifyMFAResponse.errorMessage, - payload: verifyMFAResponse.errorPayload, - }, - }, - 401, - ) - } - - const result = await this.getUserKeyParams.execute({ - email: request.query.email, - authenticated: false, - }) - - return this.json(result.keyParams) + override async params(request: Request, response: Response): Promise { + return super.params(request, response) } @httpPost('/sign_in', TYPES.Auth_LockMiddleware) - async signIn(request: Request): Promise { - if (!request.body.email || !request.body.password) { - this.logger.debug('/auth/sign_in request missing credentials: %O', request.body) - - return this.json( - { - error: { - tag: 'invalid-auth', - message: 'Invalid login credentials.', - }, - }, - 401, - ) - } - - const verifyMFAResponse = await this.verifyMFA.execute({ - email: request.body.email, - requestParams: request.body, - preventOTPFromFurtherUsage: true, - }) - - if (!verifyMFAResponse.success) { - return this.json( - { - error: { - tag: verifyMFAResponse.errorTag, - message: verifyMFAResponse.errorMessage, - payload: verifyMFAResponse.errorPayload, - }, - }, - 401, - ) - } - - const signInResult = await this.signInUseCase.execute({ - apiVersion: request.body.api, - userAgent: request.headers['user-agent'], - email: request.body.email, - password: request.body.password, - ephemeralSession: request.body.ephemeral ?? false, - }) - - if (!signInResult.success) { - await this.increaseLoginAttempts.execute({ email: request.body.email }) - - return this.json( - { - error: { - message: signInResult.errorMessage, - }, - }, - signInResult.errorCode ?? 401, - ) - } - - await this.clearLoginAttempts.execute({ email: request.body.email }) - - return this.json(signInResult.authResponse) + override async signIn(request: Request): Promise { + return super.signIn(request) } @httpPost('/pkce_params', TYPES.Auth_OptionalCrossServiceTokenMiddleware) - async pkceParams(request: Request, response: Response): Promise { - if (!request.body.code_challenge) { - return this.json( - { - error: { - message: 'Please provide the code challenge parameter.', - }, - }, - 400, - ) - } - - if (response.locals.session) { - const result = await this.getUserKeyParams.execute({ - email: response.locals.user.email, - authenticated: true, - codeChallenge: request.body.code_challenge as string, - }) - - return this.json(result.keyParams) - } - - if (!request.body.email) { - return this.json( - { - error: { - message: 'Please provide an email address.', - }, - }, - 400, - ) - } - - const verifyMFAResponse = await this.verifyMFA.execute({ - email: request.body.email, - requestParams: request.body, - preventOTPFromFurtherUsage: true, - }) - - if (!verifyMFAResponse.success) { - return this.json( - { - error: { - tag: verifyMFAResponse.errorTag, - message: verifyMFAResponse.errorMessage, - payload: verifyMFAResponse.errorPayload, - }, - }, - 401, - ) - } - - const result = await this.getUserKeyParams.execute({ - email: request.body.email, - authenticated: false, - codeChallenge: request.body.code_challenge as string, - }) - - return this.json(result.keyParams) + override async pkceParams(request: Request, response: Response): Promise { + return super.pkceParams(request, response) } @httpPost('/pkce_sign_in', TYPES.Auth_LockMiddleware) - async pkceSignIn(request: Request): Promise { - if (!request.body.email || !request.body.password || !request.body.code_verifier) { - this.logger.debug('/auth/sign_in request missing credentials: %O', request.body) - - return this.json( - { - error: { - tag: 'invalid-auth', - message: 'Invalid login credentials.', - }, - }, - 401, - ) - } - - const signInResult = await this.signInUseCase.execute({ - apiVersion: request.body.api, - userAgent: request.headers['user-agent'], - email: request.body.email, - password: request.body.password, - ephemeralSession: request.body.ephemeral ?? false, - codeVerifier: request.body.code_verifier, - }) - - if (!signInResult.success) { - await this.increaseLoginAttempts.execute({ email: request.body.email }) - - return this.json( - { - error: { - message: signInResult.errorMessage, - }, - }, - 401, - ) - } - - await this.clearLoginAttempts.execute({ email: request.body.email }) - - return this.json(signInResult.authResponse) + override async pkceSignIn(request: Request): Promise { + return super.pkceSignIn(request) } @httpPost('/recovery/codes', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async generateRecoveryCodes(_request: Request, response: Response): Promise { - const result = await this.authController.generateRecoveryCodes({ - userUuid: response.locals.user.uuid, - }) - - return this.json(result.data, result.status) + override async generateRecoveryCodes(_request: Request, response: Response): Promise { + return super.generateRecoveryCodes(_request, response) } @httpPost('/recovery/login', TYPES.Auth_LockMiddleware) - async recoveryLogin(request: Request): Promise { - const result = await this.authController.signInWithRecoveryCodes({ - apiVersion: request.body.api_version, - userAgent: request.headers['user-agent'], - codeVerifier: request.body.code_verifier, - username: request.body.username, - recoveryCodes: request.body.recovery_codes, - password: request.body.password, - }) - - return this.json(result.data, result.status) + override async recoveryLogin(request: Request): Promise { + return super.recoveryLogin(request) } @httpPost('/recovery/params') - async recoveryParams(request: Request): Promise { - const result = await this.authController.recoveryKeyParams({ - apiVersion: request.body.api_version, - username: request.body.username, - codeChallenge: request.body.code_challenge, - recoveryCodes: request.body.recovery_codes, - }) - - return this.json(result.data, result.status) + override async recoveryParams(request: Request): Promise { + return super.recoveryParams(request) } @httpPost('/sign_out', TYPES.Auth_OptionalCrossServiceTokenMiddleware) - async signOut(request: Request, response: Response): Promise { - const result = await this.authController.signOut({ - readOnlyAccess: response.locals.readOnlyAccess, - authorizationHeader: request.headers.authorization, - }) - - return this.json(result.data, result.status) + override async signOut(request: Request, response: Response): Promise { + return super.signOut(request, response) } @httpPost('/') - async register(request: Request): Promise { - const response = await this.authController.register({ - ...request.body, - userAgent: request.headers['user-agent'], - }) - - return this.json(response.data, response.status) + override async register(request: Request): Promise { + return super.register(request) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAuthenticatorsController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAuthenticatorsController.ts index 41b3a3872..dd07db6e1 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAuthenticatorsController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAuthenticatorsController.ts @@ -1,6 +1,5 @@ import { Request, Response } from 'express' import { - BaseHttpController, controller, httpDelete, httpGet, @@ -10,78 +9,39 @@ import { } from 'inversify-express-utils' import TYPES from '../../Bootstrap/Types' import { AuthenticatorsController } from '../../Controller/AuthenticatorsController' -import { ControllerContainerInterface } from '@standardnotes/domain-core' import { inject } from 'inversify' +import { HomeServerAuthenticatorsController } from './HomeServer/HomeServerAuthenticatorsController' @controller('/authenticators') -export class InversifyExpressAuthenticatorsController extends BaseHttpController { +export class InversifyExpressAuthenticatorsController extends HomeServerAuthenticatorsController { constructor( - @inject(TYPES.Auth_AuthenticatorsController) private authenticatorsController: AuthenticatorsController, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, + @inject(TYPES.Auth_AuthenticatorsController) override authenticatorsController: AuthenticatorsController, ) { - super() - - this.controllerContainer.register('auth.authenticators.list', this.list.bind(this)) - this.controllerContainer.register('auth.authenticators.delete', this.delete.bind(this)) - this.controllerContainer.register( - 'auth.authenticators.generateRegistrationOptions', - this.generateRegistrationOptions.bind(this), - ) - this.controllerContainer.register( - 'auth.authenticators.verifyRegistrationResponse', - this.verifyRegistration.bind(this), - ) - this.controllerContainer.register( - 'auth.authenticators.generateAuthenticationOptions', - this.generateAuthenticationOptions.bind(this), - ) + super(authenticatorsController) } @httpGet('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async list(_request: Request, response: Response): Promise { - const result = await this.authenticatorsController.list({ - userUuid: response.locals.user.uuid, - }) - - return this.json(result.data, result.status) + override async list(_request: Request, response: Response): Promise { + return super.list(_request, response) } @httpDelete('/:authenticatorId', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async delete(request: Request, response: Response): Promise { - const result = await this.authenticatorsController.delete({ - userUuid: response.locals.user.uuid, - authenticatorId: request.params.authenticatorId, - }) - - return this.json(result.data, result.status) + override async delete(request: Request, response: Response): Promise { + return super.delete(request, response) } @httpGet('/generate-registration-options', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async generateRegistrationOptions(_request: Request, response: Response): Promise { - const result = await this.authenticatorsController.generateRegistrationOptions({ - username: response.locals.user.email, - userUuid: response.locals.user.uuid, - }) - - return this.json(result.data, result.status) + override async generateRegistrationOptions(_request: Request, response: Response): Promise { + return super.generateRegistrationOptions(_request, response) } @httpPost('/verify-registration', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async verifyRegistration(request: Request, response: Response): Promise { - const result = await this.authenticatorsController.verifyRegistrationResponse({ - userUuid: response.locals.user.uuid, - attestationResponse: request.body.attestationResponse, - }) - - return this.json(result.data, result.status) + override async verifyRegistration(request: Request, response: Response): Promise { + return super.verifyRegistration(request, response) } @httpPost('/generate-authentication-options') - async generateAuthenticationOptions(request: Request): Promise { - const result = await this.authenticatorsController.generateAuthenticationOptions({ - username: request.body.username, - }) - - return this.json(result.data, result.status) + override async generateAuthenticationOptions(request: Request): Promise { + return super.generateAuthenticationOptions(request) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressFeaturesController.spec.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressFeaturesController.spec.ts index 44c8e2cbd..2a365df93 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressFeaturesController.spec.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressFeaturesController.spec.ts @@ -6,7 +6,6 @@ import { InversifyExpressFeaturesController } from './InversifyExpressFeaturesCo import { results } from 'inversify-express-utils' import { User } from '../../Domain/User/User' import { GetUserFeatures } from '../../Domain/UseCase/GetUserFeatures/GetUserFeatures' -import { ControllerContainerInterface } from '@standardnotes/domain-core' describe('InversifyExpressFeaturesController', () => { let getUserFeatures: GetUserFeatures @@ -14,14 +13,10 @@ describe('InversifyExpressFeaturesController', () => { let request: express.Request let response: express.Response let user: User - let controllerContainer: ControllerContainerInterface - const createController = () => new InversifyExpressFeaturesController(getUserFeatures, controllerContainer) + const createController = () => new InversifyExpressFeaturesController(getUserFeatures) beforeEach(() => { - controllerContainer = {} as jest.Mocked - controllerContainer.register = jest.fn() - user = {} as jest.Mocked user.uuid = '123' diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressFeaturesController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressFeaturesController.ts index 27b632c02..601e18099 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressFeaturesController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressFeaturesController.ts @@ -1,7 +1,6 @@ import { Request, Response } from 'express' import { inject } from 'inversify' import { - BaseHttpController, controller, httpGet, // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -9,41 +8,16 @@ import { } from 'inversify-express-utils' import TYPES from '../../Bootstrap/Types' import { GetUserFeatures } from '../../Domain/UseCase/GetUserFeatures/GetUserFeatures' -import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { HomeServerFeaturesController } from './HomeServer/HomeServerFeaturesController' @controller('/users/:userUuid/features') -export class InversifyExpressFeaturesController extends BaseHttpController { - constructor( - @inject(TYPES.Auth_GetUserFeatures) private doGetUserFeatures: GetUserFeatures, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, - ) { - super() - - this.controllerContainer.register('auth.users.getFeatures', this.getFeatures.bind(this)) +export class InversifyExpressFeaturesController extends HomeServerFeaturesController { + constructor(@inject(TYPES.Auth_GetUserFeatures) override doGetUserFeatures: GetUserFeatures) { + super(doGetUserFeatures) } @httpGet('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async getFeatures(request: Request, response: Response): Promise { - if (request.params.userUuid !== response.locals.user.uuid) { - return this.json( - { - error: { - message: 'Operation not allowed.', - }, - }, - 401, - ) - } - - const result = await this.doGetUserFeatures.execute({ - userUuid: request.params.userUuid, - offline: false, - }) - - if (result.success) { - return this.json(result) - } - - return this.json(result, 400) + override async getFeatures(request: Request, response: Response): Promise { + return super.getFeatures(request, response) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressListedController.spec.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressListedController.spec.ts index 0103d7927..05d21824b 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressListedController.spec.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressListedController.spec.ts @@ -6,7 +6,6 @@ import { results } from 'inversify-express-utils' import { InversifyExpressListedController } from './InversifyExpressListedController' import { User } from '../../Domain/User/User' import { CreateListedAccount } from '../../Domain/UseCase/CreateListedAccount/CreateListedAccount' -import { ControllerContainerInterface } from '@standardnotes/domain-core' describe('InversifyExpressListedController', () => { let createListedAccount: CreateListedAccount @@ -14,14 +13,10 @@ describe('InversifyExpressListedController', () => { let request: express.Request let response: express.Response let user: User - let controllerContainer: ControllerContainerInterface - const createController = () => new InversifyExpressListedController(createListedAccount, controllerContainer) + const createController = () => new InversifyExpressListedController(createListedAccount) beforeEach(() => { - controllerContainer = {} as jest.Mocked - controllerContainer.register = jest.fn() - user = {} as jest.Mocked user.uuid = '123' diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressListedController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressListedController.ts index e7b1dd53f..31aa565ff 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressListedController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressListedController.ts @@ -1,44 +1,19 @@ import { inject } from 'inversify' // eslint-disable-next-line @typescript-eslint/no-unused-vars -import { BaseHttpController, controller, httpPost, results } from 'inversify-express-utils' +import { controller, httpPost, results } from 'inversify-express-utils' import { Request, Response } from 'express' import TYPES from '../../Bootstrap/Types' import { CreateListedAccount } from '../../Domain/UseCase/CreateListedAccount/CreateListedAccount' -import { ErrorTag } from '@standardnotes/responses' -import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { HomeServerListedController } from './HomeServer/HomeServerListedController' @controller('/listed') -export class InversifyExpressListedController extends BaseHttpController { - constructor( - @inject(TYPES.Auth_CreateListedAccount) private doCreateListedAccount: CreateListedAccount, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, - ) { - super() - - this.controllerContainer.register('auth.users.createListedAccount', this.createListedAccount.bind(this)) +export class InversifyExpressListedController extends HomeServerListedController { + constructor(@inject(TYPES.Auth_CreateListedAccount) override doCreateListedAccount: CreateListedAccount) { + super(doCreateListedAccount) } @httpPost('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async createListedAccount(_request: Request, response: Response): Promise { - if (response.locals.readOnlyAccess) { - return this.json( - { - error: { - tag: ErrorTag.ReadOnlyAccess, - message: 'Session has read-only access.', - }, - }, - 401, - ) - } - - await this.doCreateListedAccount.execute({ - userUuid: response.locals.user.uuid, - userEmail: response.locals.user.email, - }) - - return this.json({ - message: 'Listed account creation requested successfully.', - }) + override async createListedAccount(_request: Request, response: Response): Promise { + return super.createListedAccount(_request, response) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressOfflineController.spec.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressOfflineController.spec.ts index 43babd79f..5fd81f3ab 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressOfflineController.spec.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressOfflineController.spec.ts @@ -14,7 +14,6 @@ import { GetUserOfflineSubscription } from '../../Domain/UseCase/GetUserOfflineS import { OfflineUserTokenData, TokenEncoderInterface } from '@standardnotes/security' import { SubscriptionName } from '@standardnotes/common' import { Logger } from 'winston' -import { ControllerContainerInterface } from '@standardnotes/domain-core' describe('InversifyExpressOfflineController', () => { let getUserFeatures: GetUserFeatures @@ -29,8 +28,6 @@ describe('InversifyExpressOfflineController', () => { let response: express.Response let user: User - let controllerContainer: ControllerContainerInterface - const createController = () => new InversifyExpressOfflineController( getUserFeatures, @@ -40,13 +37,9 @@ describe('InversifyExpressOfflineController', () => { tokenEncoder, jwtTTL, logger, - controllerContainer, ) beforeEach(() => { - controllerContainer = {} as jest.Mocked - controllerContainer.register = jest.fn() - user = {} as jest.Mocked user.uuid = '123' diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressOfflineController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressOfflineController.ts index 7969f66da..cbda870cd 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressOfflineController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressOfflineController.ts @@ -1,7 +1,6 @@ import { Request, Response } from 'express' import { inject } from 'inversify' import { - BaseHttpController, controller, httpGet, httpPost, @@ -10,132 +9,54 @@ import { } from 'inversify-express-utils' import { Logger } from 'winston' import { OfflineUserTokenData, TokenEncoderInterface } from '@standardnotes/security' -import { ControllerContainerInterface } from '@standardnotes/domain-core' import TYPES from '../../Bootstrap/Types' import { AuthenticateOfflineSubscriptionToken } from '../../Domain/UseCase/AuthenticateOfflineSubscriptionToken/AuthenticateOfflineSubscriptionToken' import { CreateOfflineSubscriptionToken } from '../../Domain/UseCase/CreateOfflineSubscriptionToken/CreateOfflineSubscriptionToken' import { GetUserFeatures } from '../../Domain/UseCase/GetUserFeatures/GetUserFeatures' import { GetUserOfflineSubscription } from '../../Domain/UseCase/GetUserOfflineSubscription/GetUserOfflineSubscription' +import { HomeServerOfflineController } from './HomeServer/HomeServerOfflineController' @controller('/offline') -export class InversifyExpressOfflineController extends BaseHttpController { +export class InversifyExpressOfflineController extends HomeServerOfflineController { constructor( - @inject(TYPES.Auth_GetUserFeatures) private doGetUserFeatures: GetUserFeatures, - @inject(TYPES.Auth_GetUserOfflineSubscription) private getUserOfflineSubscription: GetUserOfflineSubscription, + @inject(TYPES.Auth_GetUserFeatures) override doGetUserFeatures: GetUserFeatures, + @inject(TYPES.Auth_GetUserOfflineSubscription) override getUserOfflineSubscription: GetUserOfflineSubscription, @inject(TYPES.Auth_CreateOfflineSubscriptionToken) - private createOfflineSubscriptionToken: CreateOfflineSubscriptionToken, + override createOfflineSubscriptionToken: CreateOfflineSubscriptionToken, @inject(TYPES.Auth_AuthenticateOfflineSubscriptionToken) - private authenticateToken: AuthenticateOfflineSubscriptionToken, - @inject(TYPES.Auth_OfflineUserTokenEncoder) private tokenEncoder: TokenEncoderInterface, - @inject(TYPES.Auth_AUTH_JWT_TTL) private jwtTTL: number, - @inject(TYPES.Auth_Logger) private logger: Logger, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, + override authenticateToken: AuthenticateOfflineSubscriptionToken, + @inject(TYPES.Auth_OfflineUserTokenEncoder) override tokenEncoder: TokenEncoderInterface, + @inject(TYPES.Auth_AUTH_JWT_TTL) override jwtTTL: number, + @inject(TYPES.Auth_Logger) override logger: Logger, ) { - super() - - this.controllerContainer.register('auth.offline.features', this.getOfflineFeatures.bind(this)) - this.controllerContainer.register('auth.offline.subscriptionTokens.create', this.createToken.bind(this)) - this.controllerContainer.register('auth.users.getOfflineSubscriptionByToken', this.getSubscription.bind(this)) + super( + doGetUserFeatures, + getUserOfflineSubscription, + createOfflineSubscriptionToken, + authenticateToken, + tokenEncoder, + jwtTTL, + logger, + ) } @httpGet('/features', TYPES.Auth_OfflineUserAuthMiddleware) - async getOfflineFeatures(_request: Request, response: Response): Promise { - const result = await this.doGetUserFeatures.execute({ - email: response.locals.offlineUserEmail, - offline: true, - }) - - if (result.success) { - return this.json(result) - } - - return this.json(result, 400) + override async getOfflineFeatures(_request: Request, response: Response): Promise { + return super.getOfflineFeatures(_request, response) } @httpPost('/subscription-tokens') - async createToken(request: Request): Promise { - if (!request.body.email) { - return this.json( - { - error: { - tag: 'invalid-request', - message: 'Invalid request parameters.', - }, - }, - 400, - ) - } - - const response = await this.createOfflineSubscriptionToken.execute({ - userEmail: request.body.email, - }) - - if (!response.success) { - return this.json({ success: false, error: { tag: response.error } }) - } - - return this.json({ success: true }) + override async createToken(request: Request): Promise { + return super.createToken(request) } @httpPost('/subscription-tokens/:token/validate') - async validate(request: Request): Promise { - if (!request.body.email) { - this.logger.debug('[Offline Subscription Token Validation] Missing email') - - return this.json( - { - error: { - tag: 'invalid-request', - message: 'Invalid request parameters.', - }, - }, - 400, - ) - } - - const authenticateTokenResponse = await this.authenticateToken.execute({ - token: request.params.token, - userEmail: request.body.email, - }) - - if (!authenticateTokenResponse.success) { - this.logger.debug('[Offline Subscription Token Validation] invalid token') - - return this.json( - { - error: { - tag: 'invalid-auth', - message: 'Invalid login credentials.', - }, - }, - 401, - ) - } - - const offlineAuthTokenData: OfflineUserTokenData = { - userEmail: authenticateTokenResponse.email, - featuresToken: authenticateTokenResponse.featuresToken, - } - - const authToken = this.tokenEncoder.encodeExpirableToken(offlineAuthTokenData, this.jwtTTL) - - this.logger.debug( - `[Offline Subscription Token Validation] authenticated token for user ${authenticateTokenResponse.email}`, - ) - - return this.json({ authToken }) + override async validate(request: Request): Promise { + return super.validate(request) } @httpGet('/users/subscription', TYPES.Auth_ApiGatewayOfflineAuthMiddleware) - async getSubscription(_request: Request, response: Response): Promise { - const result = await this.getUserOfflineSubscription.execute({ - userEmail: response.locals.userEmail, - }) - - if (result.success) { - return this.json(result) - } - - return this.json(result, 400) + override async getSubscription(_request: Request, response: Response): Promise { + return super.getSubscription(_request, response) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionController.spec.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionController.spec.ts index 2fb9a142d..8801d902c 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionController.spec.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionController.spec.ts @@ -4,7 +4,6 @@ import * as express from 'express' import { InversifyExpressSessionController } from './InversifyExpressSessionController' import { results } from 'inversify-express-utils' -import { ControllerContainerInterface } from '@standardnotes/domain-core' import { DeletePreviousSessionsForUser } from '../../Domain/UseCase/DeletePreviousSessionsForUser' import { DeleteSessionForUser } from '../../Domain/UseCase/DeleteSessionForUser' import { RefreshSessionToken } from '../../Domain/UseCase/RefreshSessionToken' @@ -15,20 +14,11 @@ describe('InversifyExpressSessionController', () => { let refreshSessionToken: RefreshSessionToken let request: express.Request let response: express.Response - let controllerContainer: ControllerContainerInterface const createController = () => - new InversifyExpressSessionController( - deleteSessionForUser, - deletePreviousSessionsForUser, - refreshSessionToken, - controllerContainer, - ) + new InversifyExpressSessionController(deleteSessionForUser, deletePreviousSessionsForUser, refreshSessionToken) beforeEach(() => { - controllerContainer = {} as jest.Mocked - controllerContainer.register = jest.fn() - deleteSessionForUser = {} as jest.Mocked deleteSessionForUser.execute = jest.fn().mockReturnValue({ success: true }) diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionController.ts index 7b1fdf967..f88f37558 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionController.ts @@ -1,8 +1,6 @@ -import { ErrorTag } from '@standardnotes/responses' import { Request, Response } from 'express' import { inject } from 'inversify' import { - BaseHttpController, controller, httpDelete, httpPost, @@ -13,152 +11,37 @@ import TYPES from '../../Bootstrap/Types' import { DeletePreviousSessionsForUser } from '../../Domain/UseCase/DeletePreviousSessionsForUser' import { DeleteSessionForUser } from '../../Domain/UseCase/DeleteSessionForUser' import { RefreshSessionToken } from '../../Domain/UseCase/RefreshSessionToken' -import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { HomeServerSessionController } from './HomeServer/HomeServerSessionController' @controller('/session') -export class InversifyExpressSessionController extends BaseHttpController { +export class InversifyExpressSessionController extends HomeServerSessionController { constructor( - @inject(TYPES.Auth_DeleteSessionForUser) private deleteSessionForUser: DeleteSessionForUser, + @inject(TYPES.Auth_DeleteSessionForUser) override deleteSessionForUser: DeleteSessionForUser, @inject(TYPES.Auth_DeletePreviousSessionsForUser) - private deletePreviousSessionsForUser: DeletePreviousSessionsForUser, - @inject(TYPES.Auth_RefreshSessionToken) private refreshSessionToken: RefreshSessionToken, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, + override deletePreviousSessionsForUser: DeletePreviousSessionsForUser, + @inject(TYPES.Auth_RefreshSessionToken) override refreshSessionToken: RefreshSessionToken, ) { - super() - - this.controllerContainer.register('auth.sessions.delete', this.deleteSession.bind(this)) - this.controllerContainer.register('auth.sessions.deleteAll', this.deleteAllSessions.bind(this)) - this.controllerContainer.register('auth.sessions.refresh', this.refresh.bind(this)) + super(deleteSessionForUser, deletePreviousSessionsForUser, refreshSessionToken) } @httpDelete('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware, TYPES.Auth_SessionMiddleware) - async deleteSession(request: Request, response: Response): Promise { - if (response.locals.readOnlyAccess) { - return this.json( - { - error: { - tag: ErrorTag.ReadOnlyAccess, - message: 'Session has read-only access.', - }, - }, - 401, - ) - } - - if (!request.body.uuid) { - return this.json( - { - error: { - message: 'Please provide the session identifier.', - }, - }, - 400, - ) - } - - if (request.body.uuid === response.locals.session.uuid) { - return this.json( - { - error: { - message: 'You can not delete your current session.', - }, - }, - 400, - ) - } - - const useCaseResponse = await this.deleteSessionForUser.execute({ - userUuid: response.locals.user.uuid, - sessionUuid: request.body.uuid, - }) - - if (!useCaseResponse.success) { - return this.json( - { - error: { - message: useCaseResponse.errorMessage, - }, - }, - 400, - ) - } - - response.setHeader('x-invalidate-cache', response.locals.user.uuid) - - return this.statusCode(204) + override async deleteSession( + request: Request, + response: Response, + ): Promise { + return super.deleteSession(request, response) } @httpDelete('/all', TYPES.Auth_RequiredCrossServiceTokenMiddleware, TYPES.Auth_SessionMiddleware) - async deleteAllSessions( + override async deleteAllSessions( _request: Request, response: Response, ): Promise { - if (response.locals.readOnlyAccess) { - return this.json( - { - error: { - tag: ErrorTag.ReadOnlyAccess, - message: 'Session has read-only access.', - }, - }, - 401, - ) - } - - if (!response.locals.user) { - return this.json( - { - error: { - message: 'No session exists with the provided identifier.', - }, - }, - 401, - ) - } - - await this.deletePreviousSessionsForUser.execute({ - userUuid: response.locals.user.uuid, - currentSessionUuid: response.locals.session.uuid, - }) - - response.setHeader('x-invalidate-cache', response.locals.user.uuid) - - return this.statusCode(204) + return super.deleteAllSessions(_request, response) } @httpPost('/refresh') - async refresh(request: Request, response: Response): Promise { - if (!request.body.access_token || !request.body.refresh_token) { - return this.json( - { - error: { - message: 'Please provide all required parameters.', - }, - }, - 400, - ) - } - - const result = await this.refreshSessionToken.execute({ - accessToken: request.body.access_token, - refreshToken: request.body.refresh_token, - }) - - if (!result.success) { - return this.json( - { - error: { - tag: result.errorTag, - message: result.errorMessage, - }, - }, - 400, - ) - } - - response.setHeader('x-invalidate-cache', result.userUuid as string) - return this.json({ - session: result.sessionPayload, - }) + override async refresh(request: Request, response: Response): Promise { + return super.refresh(request, response) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionsController.spec.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionsController.spec.ts index ae3ab302d..8a23d5da4 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionsController.spec.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionsController.spec.ts @@ -4,7 +4,6 @@ import * as express from 'express' import { InversifyExpressSessionsController } from './InversifyExpressSessionsController' import { results } from 'inversify-express-utils' -import { ControllerContainerInterface } from '@standardnotes/domain-core' import { User } from '@standardnotes/responses' import { AuthenticateRequest } from '../../Domain/UseCase/AuthenticateRequest' @@ -22,7 +21,6 @@ describe('InversifyExpressSessionsController', () => { let response: express.Response let user: User let createCrossServiceToken: CreateCrossServiceToken - let controllerContainer: ControllerContainerInterface const createController = () => new InversifyExpressSessionsController( @@ -30,13 +28,9 @@ describe('InversifyExpressSessionsController', () => { authenticateRequest, sessionProjector, createCrossServiceToken, - controllerContainer, ) beforeEach(() => { - controllerContainer = {} as jest.Mocked - controllerContainer.register = jest.fn() - session = {} as jest.Mocked user = {} as jest.Mocked diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionsController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionsController.ts index dfcd9b9d6..515d915c3 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionsController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionsController.ts @@ -1,85 +1,39 @@ import { Request, Response } from 'express' import { inject } from 'inversify' import { - BaseHttpController, controller, httpGet, httpPost, // eslint-disable-next-line @typescript-eslint/no-unused-vars results, } from 'inversify-express-utils' -import { ControllerContainerInterface } from '@standardnotes/domain-core' import TYPES from '../../Bootstrap/Types' import { AuthenticateRequest } from '../../Domain/UseCase/AuthenticateRequest' import { CreateCrossServiceToken } from '../../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken' import { GetActiveSessionsForUser } from '../../Domain/UseCase/GetActiveSessionsForUser' import { ProjectorInterface } from '../../Projection/ProjectorInterface' -import { SessionProjector } from '../../Projection/SessionProjector' -import { User } from '../../Domain/User/User' import { Session } from '../../Domain/Session/Session' +import { HomeServerSessionsController } from './HomeServer/HomeServerSessionsController' @controller('/sessions') -export class InversifyExpressSessionsController extends BaseHttpController { +export class InversifyExpressSessionsController extends HomeServerSessionsController { constructor( - @inject(TYPES.Auth_GetActiveSessionsForUser) private getActiveSessionsForUser: GetActiveSessionsForUser, - @inject(TYPES.Auth_AuthenticateRequest) private authenticateRequest: AuthenticateRequest, - @inject(TYPES.Auth_SessionProjector) private sessionProjector: ProjectorInterface, - @inject(TYPES.Auth_CreateCrossServiceToken) private createCrossServiceToken: CreateCrossServiceToken, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, + @inject(TYPES.Auth_GetActiveSessionsForUser) override getActiveSessionsForUser: GetActiveSessionsForUser, + @inject(TYPES.Auth_AuthenticateRequest) override authenticateRequest: AuthenticateRequest, + @inject(TYPES.Auth_SessionProjector) override sessionProjector: ProjectorInterface, + @inject(TYPES.Auth_CreateCrossServiceToken) override createCrossServiceToken: CreateCrossServiceToken, ) { - super() - - this.controllerContainer.register('auth.sessions.list', this.getSessions.bind(this)) - this.controllerContainer.register('auth.sessions.validate', this.validate.bind(this)) + super(getActiveSessionsForUser, authenticateRequest, sessionProjector, createCrossServiceToken) } @httpPost('/validate') - async validate(request: Request): Promise { - const authenticateRequestResponse = await this.authenticateRequest.execute({ - authorizationHeader: request.headers.authorization, - }) - - if (!authenticateRequestResponse.success) { - return this.json( - { - error: { - tag: authenticateRequestResponse.errorTag, - message: authenticateRequestResponse.errorMessage, - }, - }, - authenticateRequestResponse.responseCode, - ) - } - - const user = authenticateRequestResponse.user as User - - const result = await this.createCrossServiceToken.execute({ - user, - session: authenticateRequestResponse.session, - }) - - return this.json({ authToken: result.token }) + override async validate(request: Request): Promise { + return super.validate(request) } @httpGet('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware, TYPES.Auth_SessionMiddleware) - async getSessions(_request: Request, response: Response): Promise { - if (response.locals.readOnlyAccess) { - return this.json([]) - } - - const useCaseResponse = await this.getActiveSessionsForUser.execute({ - userUuid: response.locals.user.uuid, - }) - - return this.json( - useCaseResponse.sessions.map((session) => - this.sessionProjector.projectCustom( - SessionProjector.CURRENT_SESSION_PROJECTION.toString(), - session, - response.locals.session, - ), - ), - ) + override async getSessions(_request: Request, response: Response): Promise { + return super.getSessions(_request, response) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSettingsController.spec.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSettingsController.spec.ts index 696366d52..a5b5167e6 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSettingsController.spec.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSettingsController.spec.ts @@ -4,7 +4,6 @@ import * as express from 'express' import { InversifyExpressSettingsController } from './InversifyExpressSettingsController' import { results } from 'inversify-express-utils' -import { ControllerContainerInterface } from '@standardnotes/domain-core' import { EncryptionVersion } from '../../Domain/Encryption/EncryptionVersion' import { DeleteSetting } from '../../Domain/UseCase/DeleteSetting/DeleteSetting' import { GetSetting } from '../../Domain/UseCase/GetSetting/GetSetting' @@ -21,15 +20,11 @@ describe('InversifyExpressSettingsController', () => { let request: express.Request let response: express.Response let user: User - let controllerContainer: ControllerContainerInterface const createController = () => - new InversifyExpressSettingsController(getSettings, getSetting, updateSetting, deleteSetting, controllerContainer) + new InversifyExpressSettingsController(getSettings, getSetting, updateSetting, deleteSetting) beforeEach(() => { - controllerContainer = {} as jest.Mocked - controllerContainer.register = jest.fn() - deleteSetting = {} as jest.Mocked deleteSetting.execute = jest.fn().mockReturnValue({ success: true }) diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSettingsController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSettingsController.ts index 40258e123..8f4bfb8f1 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSettingsController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSettingsController.ts @@ -1,8 +1,6 @@ -import { ErrorTag } from '@standardnotes/responses' import { Request, Response } from 'express' import { inject } from 'inversify' import { - BaseHttpController, controller, httpDelete, httpGet, @@ -11,155 +9,43 @@ import { results, } from 'inversify-express-utils' import TYPES from '../../Bootstrap/Types' -import { EncryptionVersion } from '../../Domain/Encryption/EncryptionVersion' import { DeleteSetting } from '../../Domain/UseCase/DeleteSetting/DeleteSetting' import { GetSetting } from '../../Domain/UseCase/GetSetting/GetSetting' import { GetSettings } from '../../Domain/UseCase/GetSettings/GetSettings' import { UpdateSetting } from '../../Domain/UseCase/UpdateSetting/UpdateSetting' -import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { HomeServerSettingsController } from './HomeServer/HomeServerSettingsController' @controller('/users/:userUuid') -export class InversifyExpressSettingsController extends BaseHttpController { +export class InversifyExpressSettingsController extends HomeServerSettingsController { constructor( - @inject(TYPES.Auth_GetSettings) private doGetSettings: GetSettings, - @inject(TYPES.Auth_GetSetting) private doGetSetting: GetSetting, - @inject(TYPES.Auth_UpdateSetting) private doUpdateSetting: UpdateSetting, - @inject(TYPES.Auth_DeleteSetting) private doDeleteSetting: DeleteSetting, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, + @inject(TYPES.Auth_GetSettings) override doGetSettings: GetSettings, + @inject(TYPES.Auth_GetSetting) override doGetSetting: GetSetting, + @inject(TYPES.Auth_UpdateSetting) override doUpdateSetting: UpdateSetting, + @inject(TYPES.Auth_DeleteSetting) override doDeleteSetting: DeleteSetting, ) { - super() - - this.controllerContainer.register('auth.users.getSettings', this.getSettings.bind(this)) - this.controllerContainer.register('auth.users.getSetting', this.getSetting.bind(this)) - this.controllerContainer.register('auth.users.updateSetting', this.updateSetting.bind(this)) - this.controllerContainer.register('auth.users.deleteSetting', this.deleteSetting.bind(this)) + super(doGetSettings, doGetSetting, doUpdateSetting, doDeleteSetting) } @httpGet('/settings', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async getSettings(request: Request, response: Response): Promise { - if (request.params.userUuid !== response.locals.user.uuid) { - return this.json( - { - error: { - message: 'Operation not allowed.', - }, - }, - 401, - ) - } - - const { userUuid } = request.params - const result = await this.doGetSettings.execute({ userUuid }) - - return this.json(result) + override async getSettings(request: Request, response: Response): Promise { + return super.getSettings(request, response) } @httpGet('/settings/:settingName', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async getSetting(request: Request, response: Response): Promise { - if (request.params.userUuid !== response.locals.user.uuid) { - return this.json( - { - error: { - message: 'Operation not allowed.', - }, - }, - 401, - ) - } - - const { userUuid, settingName } = request.params - const result = await this.doGetSetting.execute({ userUuid, settingName: settingName.toUpperCase() }) - - if (result.success) { - return this.json(result) - } - - return this.json(result, 400) + override async getSetting(request: Request, response: Response): Promise { + return super.getSetting(request, response) } @httpPut('/settings', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async updateSetting(request: Request, response: Response): Promise { - if (response.locals.readOnlyAccess) { - return this.json( - { - error: { - tag: ErrorTag.ReadOnlyAccess, - message: 'Session has read-only access.', - }, - }, - 401, - ) - } - - if (request.params.userUuid !== response.locals.user.uuid) { - return this.json( - { - error: { - message: 'Operation not allowed.', - }, - }, - 401, - ) - } - - const { name, value, serverEncryptionVersion = EncryptionVersion.Default, sensitive = false } = request.body - - const props = { - name, - unencryptedValue: value, - serverEncryptionVersion, - sensitive, - } - - const { userUuid } = request.params - const result = await this.doUpdateSetting.execute({ - userUuid, - props, - }) - - if (result.success) { - return this.json({ setting: result.setting }, result.statusCode) - } - - return this.json(result, result.statusCode) + override async updateSetting( + request: Request, + response: Response, + ): Promise { + return super.updateSetting(request, response) } @httpDelete('/settings/:settingName', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async deleteSetting(request: Request, response: Response): Promise { - if (response.locals.readOnlyAccess) { - return this.json( - { - error: { - tag: ErrorTag.ReadOnlyAccess, - message: 'Session has read-only access.', - }, - }, - 401, - ) - } - - if (request.params.userUuid !== response.locals.user.uuid) { - return this.json( - { - error: { - message: 'Operation not allowed.', - }, - }, - 401, - ) - } - - const { userUuid, settingName } = request.params - - const result = await this.doDeleteSetting.execute({ - userUuid, - settingName, - }) - - if (result.success) { - return this.json(result) - } - - return this.json(result, 400) + override async deleteSetting(request: Request, response: Response): Promise { + return super.deleteSetting(request, response) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionInvitesController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionInvitesController.ts index 564d38bf4..5f4e920b8 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionInvitesController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionInvitesController.ts @@ -1,8 +1,5 @@ -import { ApiVersion } from '@standardnotes/api' -import { Role } from '@standardnotes/security' import { Request, Response } from 'express' import { - BaseHttpController, controller, httpDelete, httpGet, @@ -10,79 +7,43 @@ import { // eslint-disable-next-line @typescript-eslint/no-unused-vars results, } from 'inversify-express-utils' -import TYPES from '../../Bootstrap/Types' -import { SubscriptionInvitesController } from '../../Controller/SubscriptionInvitesController' -import { ControllerContainerInterface } from '@standardnotes/domain-core' import { inject } from 'inversify' +import TYPES from '../../Bootstrap/Types' +import { SubscriptionInvitesController } from '../../Controller/SubscriptionInvitesController' +import { HomeServerSubscriptionInvitesController } from './HomeServer/HomeServerSubscriptionInvitesController' + @controller('/subscription-invites') -export class InversifyExpressSubscriptionInvitesController extends BaseHttpController { +export class InversifyExpressSubscriptionInvitesController extends HomeServerSubscriptionInvitesController { constructor( @inject(TYPES.Auth_SubscriptionInvitesController) - private subscriptionInvitesController: SubscriptionInvitesController, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, + override subscriptionInvitesController: SubscriptionInvitesController, ) { - super() - - this.controllerContainer.register('auth.subscriptionInvites.accept', this.acceptInvite.bind(this)) - this.controllerContainer.register('auth.subscriptionInvites.declineInvite', this.declineInvite.bind(this)) - this.controllerContainer.register('auth.subscriptionInvites.create', this.inviteToSubscriptionSharing.bind(this)) - this.controllerContainer.register('auth.subscriptionInvites.delete', this.cancelSubscriptionSharing.bind(this)) - this.controllerContainer.register('auth.subscriptionInvites.list', this.listInvites.bind(this)) + super(subscriptionInvitesController) } @httpPost('/:inviteUuid/accept', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async acceptInvite(request: Request, response: Response): Promise { - const result = await this.subscriptionInvitesController.acceptInvite({ - api: request.query.api as ApiVersion, - inviteUuid: request.params.inviteUuid, - }) - - response.setHeader('x-invalidate-cache', response.locals.user.uuid) - - return this.json(result.data, result.status) + override async acceptInvite(request: Request, response: Response): Promise { + return super.acceptInvite(request, response) } @httpGet('/:inviteUuid/decline') - async declineInvite(request: Request): Promise { - const response = await this.subscriptionInvitesController.declineInvite({ - api: request.query.api as ApiVersion, - inviteUuid: request.params.inviteUuid, - }) - - return this.json(response.data, response.status) + override async declineInvite(request: Request): Promise { + return super.declineInvite(request) } @httpPost('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async inviteToSubscriptionSharing(request: Request, response: Response): Promise { - const result = await this.subscriptionInvitesController.invite({ - ...request.body, - inviterEmail: response.locals.user.email, - inviterUuid: response.locals.user.uuid, - inviterRoles: response.locals.roles.map((role: Role) => role.name), - }) - - return this.json(result.data, result.status) + override async inviteToSubscriptionSharing(request: Request, response: Response): Promise { + return super.inviteToSubscriptionSharing(request, response) } @httpDelete('/:inviteUuid', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async cancelSubscriptionSharing(request: Request, response: Response): Promise { - const result = await this.subscriptionInvitesController.cancelInvite({ - ...request.body, - inviteUuid: request.params.inviteUuid, - inviterEmail: response.locals.user.email, - }) - - return this.json(result.data, result.status) + override async cancelSubscriptionSharing(request: Request, response: Response): Promise { + return super.cancelSubscriptionSharing(request, response) } @httpGet('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async listInvites(request: Request, response: Response): Promise { - const result = await this.subscriptionInvitesController.listInvites({ - ...request.body, - inviterEmail: response.locals.user.email, - }) - - return this.json(result.data, result.status) + override async listInvites(request: Request, response: Response): Promise { + return super.listInvites(request, response) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionSettingsController.spec.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionSettingsController.spec.ts index 96331b592..22add6290 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionSettingsController.spec.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionSettingsController.spec.ts @@ -4,7 +4,6 @@ import * as express from 'express' import { results } from 'inversify-express-utils' import { InversifyExpressSubscriptionSettingsController } from './InversifyExpressSubscriptionSettingsController' -import { ControllerContainerInterface } from '@standardnotes/domain-core' import { User } from '../../Domain/User/User' import { GetSetting } from '../../Domain/UseCase/GetSetting/GetSetting' @@ -14,14 +13,10 @@ describe('InversifyExpressSubscriptionSettingsController', () => { let request: express.Request let response: express.Response let user: User - let controllerContainer: ControllerContainerInterface - const createController = () => new InversifyExpressSubscriptionSettingsController(getSetting, controllerContainer) + const createController = () => new InversifyExpressSubscriptionSettingsController(getSetting) beforeEach(() => { - controllerContainer = {} as jest.Mocked - controllerContainer.register = jest.fn() - user = {} as jest.Mocked user.uuid = '123' diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionSettingsController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionSettingsController.ts index e5323563b..65693808d 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionSettingsController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionSettingsController.ts @@ -1,7 +1,6 @@ import { Request, Response } from 'express' import { inject } from 'inversify' import { - BaseHttpController, controller, httpGet, // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -9,30 +8,16 @@ import { } from 'inversify-express-utils' import TYPES from '../../Bootstrap/Types' import { GetSetting } from '../../Domain/UseCase/GetSetting/GetSetting' -import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { HomeServerSubscriptionSettingsController } from './HomeServer/HomeServerSubscriptionSettingsController' @controller('/users/:userUuid') -export class InversifyExpressSubscriptionSettingsController extends BaseHttpController { - constructor( - @inject(TYPES.Auth_GetSetting) private doGetSetting: GetSetting, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, - ) { - super() - - this.controllerContainer.register('auth.users.getSubscriptionSetting', this.getSubscriptionSetting.bind(this)) +export class InversifyExpressSubscriptionSettingsController extends HomeServerSubscriptionSettingsController { + constructor(@inject(TYPES.Auth_GetSetting) override doGetSetting: GetSetting) { + super(doGetSetting) } @httpGet('/subscription-settings/:subscriptionSettingName', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async getSubscriptionSetting(request: Request, response: Response): Promise { - const result = await this.doGetSetting.execute({ - userUuid: response.locals.user.uuid, - settingName: request.params.subscriptionSettingName.toUpperCase(), - }) - - if (result.success) { - return this.json(result) - } - - return this.json(result, 400) + override async getSubscriptionSetting(request: Request, response: Response): Promise { + return super.getSubscriptionSetting(request, response) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionTokensController.spec.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionTokensController.spec.ts index d7d93c832..ccadb3009 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionTokensController.spec.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionTokensController.spec.ts @@ -5,7 +5,7 @@ import { results } from 'inversify-express-utils' import { InversifyExpressSubscriptionTokensController } from './InversifyExpressSubscriptionTokensController' import { CrossServiceTokenData, TokenEncoderInterface } from '@standardnotes/security' -import { ControllerContainerInterface } from '@standardnotes/domain-core' + import { Setting } from '../../Domain/Setting/Setting' import { SettingServiceInterface } from '../../Domain/Setting/SettingServiceInterface' import { AuthenticateSubscriptionToken } from '../../Domain/UseCase/AuthenticateSubscriptionToken/AuthenticateSubscriptionToken' @@ -30,8 +30,6 @@ describe('InversifyExpressSubscriptionTokensController', () => { let user: User let role: Role - let controllerContainer: ControllerContainerInterface - const createController = () => new InversifyExpressSubscriptionTokensController( createSubscriptionToken, @@ -41,13 +39,9 @@ describe('InversifyExpressSubscriptionTokensController', () => { roleProjector, tokenEncoder, jwtTTL, - controllerContainer, ) beforeEach(() => { - controllerContainer = {} as jest.Mocked - controllerContainer.register = jest.fn() - user = {} as jest.Mocked user.uuid = '123' user.roles = Promise.resolve([role]) diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionTokensController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionTokensController.ts index ce3e1b636..ba8952fc9 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionTokensController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionTokensController.ts @@ -1,10 +1,7 @@ import { CrossServiceTokenData, TokenEncoderInterface } from '@standardnotes/security' -import { ErrorTag } from '@standardnotes/responses' -import { SettingName } from '@standardnotes/settings' import { Request, Response } from 'express' import { inject } from 'inversify' import { - BaseHttpController, controller, httpPost, // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -18,99 +15,37 @@ import { AuthenticateSubscriptionToken } from '../../Domain/UseCase/Authenticate import { CreateSubscriptionToken } from '../../Domain/UseCase/CreateSubscriptionToken/CreateSubscriptionToken' import { User } from '../../Domain/User/User' import { ProjectorInterface } from '../../Projection/ProjectorInterface' -import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { HomeServerSubscriptionTokensController } from './HomeServer/HomeServerSubscriptionTokensController' @controller('/subscription-tokens') -export class InversifyExpressSubscriptionTokensController extends BaseHttpController { +export class InversifyExpressSubscriptionTokensController extends HomeServerSubscriptionTokensController { constructor( - @inject(TYPES.Auth_CreateSubscriptionToken) private createSubscriptionToken: CreateSubscriptionToken, - @inject(TYPES.Auth_AuthenticateSubscriptionToken) private authenticateToken: AuthenticateSubscriptionToken, - @inject(TYPES.Auth_SettingService) private settingService: SettingServiceInterface, - @inject(TYPES.Auth_UserProjector) private userProjector: ProjectorInterface, - @inject(TYPES.Auth_RoleProjector) private roleProjector: ProjectorInterface, - @inject(TYPES.Auth_CrossServiceTokenEncoder) private tokenEncoder: TokenEncoderInterface, - @inject(TYPES.Auth_AUTH_JWT_TTL) private jwtTTL: number, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, + @inject(TYPES.Auth_CreateSubscriptionToken) override createSubscriptionToken: CreateSubscriptionToken, + @inject(TYPES.Auth_AuthenticateSubscriptionToken) override authenticateToken: AuthenticateSubscriptionToken, + @inject(TYPES.Auth_SettingService) override settingService: SettingServiceInterface, + @inject(TYPES.Auth_UserProjector) override userProjector: ProjectorInterface, + @inject(TYPES.Auth_RoleProjector) override roleProjector: ProjectorInterface, + @inject(TYPES.Auth_CrossServiceTokenEncoder) override tokenEncoder: TokenEncoderInterface, + @inject(TYPES.Auth_AUTH_JWT_TTL) override jwtTTL: number, ) { - super() - - this.controllerContainer.register('auth.subscription-tokens.create', this.createToken.bind(this)) + super( + createSubscriptionToken, + authenticateToken, + settingService, + userProjector, + roleProjector, + tokenEncoder, + jwtTTL, + ) } @httpPost('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async createToken(_request: Request, response: Response): Promise { - if (response.locals.readOnlyAccess) { - return this.json( - { - error: { - tag: ErrorTag.ReadOnlyAccess, - message: 'Session has read-only access.', - }, - }, - 401, - ) - } - - const result = await this.createSubscriptionToken.execute({ - userUuid: response.locals.user.uuid, - }) - - return this.json({ - token: result.subscriptionToken.token, - }) + override async createToken(_request: Request, response: Response): Promise { + return super.createToken(_request, response) } @httpPost('/:token/validate') - async validate(request: Request): Promise { - const authenticateTokenResponse = await this.authenticateToken.execute({ - token: request.params.token, - }) - - if (!authenticateTokenResponse.success) { - return this.json( - { - error: { - tag: 'invalid-auth', - message: 'Invalid login credentials.', - }, - }, - 401, - ) - } - - const user = authenticateTokenResponse.user as User - let extensionKey = undefined - const extensionKeySetting = await this.settingService.findSettingWithDecryptedValue({ - settingName: SettingName.create(SettingName.NAMES.ExtensionKey).getValue(), - userUuid: user.uuid, - }) - if (extensionKeySetting !== null) { - extensionKey = extensionKeySetting.value as string - } - - const roles = await user.roles - - const authTokenData: CrossServiceTokenData = { - user: await this.projectUser(user), - roles: await this.projectRoles(roles), - extensionKey, - } - - const authToken = this.tokenEncoder.encodeExpirableToken(authTokenData, this.jwtTTL) - - return this.json({ authToken }) - } - - private async projectUser(user: User): Promise<{ uuid: string; email: string }> { - return <{ uuid: string; email: string }>await this.userProjector.projectSimple(user) - } - - private async projectRoles(roles: Array): Promise> { - const roleProjections = [] - for (const role of roles) { - roleProjections.push(<{ uuid: string; name: string }>await this.roleProjector.projectSimple(role)) - } - - return roleProjections + override async validate(request: Request): Promise { + return super.validate(request) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUserRequestsController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUserRequestsController.ts index ddaf573da..29ce89d15 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUserRequestsController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUserRequestsController.ts @@ -1,30 +1,19 @@ import { Request, Response } from 'express' -import { BaseHttpController, results, httpPost, controller } from 'inversify-express-utils' +import { results, httpPost, controller } from 'inversify-express-utils' import TYPES from '../../Bootstrap/Types' import { UserRequestsController } from '../../Controller/UserRequestsController' -import { ControllerContainerInterface } from '@standardnotes/domain-core' import { inject } from 'inversify' +import { HomeServerUserRequestsController } from './HomeServer/HomeServerUserRequestsController' @controller('/users/:userUuid/requests') -export class InversifyExpressUserRequestsController extends BaseHttpController { - constructor( - @inject(TYPES.Auth_UserRequestsController) private userRequestsController: UserRequestsController, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, - ) { - super() - - this.controllerContainer.register('auth.users.createRequest', this.submitRequest.bind(this)) +export class InversifyExpressUserRequestsController extends HomeServerUserRequestsController { + constructor(@inject(TYPES.Auth_UserRequestsController) override userRequestsController: UserRequestsController) { + super(userRequestsController) } @httpPost('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async submitRequest(request: Request, response: Response): Promise { - const result = await this.userRequestsController.submitUserRequest({ - requestType: request.body.requestType, - userUuid: response.locals.user.uuid, - userEmail: response.locals.user.email, - }) - - return this.json(result.data, result.status) + override async submitRequest(request: Request, response: Response): Promise { + return super.submitRequest(request, response) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUsersController.spec.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUsersController.spec.ts index b1093e450..378879cd4 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUsersController.spec.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUsersController.spec.ts @@ -4,7 +4,7 @@ import * as express from 'express' import { InversifyExpressUsersController } from './InversifyExpressUsersController' import { results } from 'inversify-express-utils' -import { ControllerContainerInterface, Username } from '@standardnotes/domain-core' +import { Username } from '@standardnotes/domain-core' import { DeleteAccount } from '../../Domain/UseCase/DeleteAccount/DeleteAccount' import { ChangeCredentials } from '../../Domain/UseCase/ChangeCredentials/ChangeCredentials' import { ClearLoginAttempts } from '../../Domain/UseCase/ClearLoginAttempts' @@ -28,7 +28,6 @@ describe('InversifyExpressUsersController', () => { let request: express.Request let response: express.Response let user: User - let controllerContainer: ControllerContainerInterface const createController = () => new InversifyExpressUsersController( @@ -39,13 +38,9 @@ describe('InversifyExpressUsersController', () => { clearLoginAttempts, increaseLoginAttempts, changeCredentials, - controllerContainer, ) beforeEach(() => { - controllerContainer = {} as jest.Mocked - controllerContainer.register = jest.fn() - updateUser = {} as jest.Mocked updateUser.execute = jest.fn() diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUsersController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUsersController.ts index 8f741767f..407ec539f 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUsersController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUsersController.ts @@ -1,8 +1,6 @@ import { Request, Response } from 'express' import { inject } from 'inversify' -import { ErrorTag } from '@standardnotes/responses' import { - BaseHttpController, controller, httpDelete, httpGet, @@ -19,232 +17,52 @@ import { GetUserSubscription } from '../../Domain/UseCase/GetUserSubscription/Ge import { ClearLoginAttempts } from '../../Domain/UseCase/ClearLoginAttempts' import { IncreaseLoginAttempts } from '../../Domain/UseCase/IncreaseLoginAttempts' import { ChangeCredentials } from '../../Domain/UseCase/ChangeCredentials/ChangeCredentials' -import { ControllerContainerInterface, Username } from '@standardnotes/domain-core' +import { HomeServerUsersController } from './HomeServer/HomeServerUsersController' @controller('/users') -export class InversifyExpressUsersController extends BaseHttpController { +export class InversifyExpressUsersController extends HomeServerUsersController { constructor( - @inject(TYPES.Auth_UpdateUser) private updateUser: UpdateUser, - @inject(TYPES.Auth_GetUserKeyParams) private getUserKeyParams: GetUserKeyParams, - @inject(TYPES.Auth_DeleteAccount) private doDeleteAccount: DeleteAccount, - @inject(TYPES.Auth_GetUserSubscription) private doGetUserSubscription: GetUserSubscription, - @inject(TYPES.Auth_ClearLoginAttempts) private clearLoginAttempts: ClearLoginAttempts, - @inject(TYPES.Auth_IncreaseLoginAttempts) private increaseLoginAttempts: IncreaseLoginAttempts, - @inject(TYPES.Auth_ChangeCredentials) private changeCredentialsUseCase: ChangeCredentials, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, + @inject(TYPES.Auth_UpdateUser) override updateUser: UpdateUser, + @inject(TYPES.Auth_GetUserKeyParams) override getUserKeyParams: GetUserKeyParams, + @inject(TYPES.Auth_DeleteAccount) override doDeleteAccount: DeleteAccount, + @inject(TYPES.Auth_GetUserSubscription) override doGetUserSubscription: GetUserSubscription, + @inject(TYPES.Auth_ClearLoginAttempts) override clearLoginAttempts: ClearLoginAttempts, + @inject(TYPES.Auth_IncreaseLoginAttempts) override increaseLoginAttempts: IncreaseLoginAttempts, + @inject(TYPES.Auth_ChangeCredentials) override changeCredentialsUseCase: ChangeCredentials, ) { - super() - - this.controllerContainer.register('auth.users.update', this.update.bind(this)) - this.controllerContainer.register('auth.users.getKeyParams', this.keyParams.bind(this)) - this.controllerContainer.register('auth.users.getSubscription', this.getSubscription.bind(this)) - this.controllerContainer.register('auth.users.updateCredentials', this.changeCredentials.bind(this)) - } - - @httpPatch('/:userId', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async update(request: Request, response: Response): Promise { - if (response.locals.readOnlyAccess) { - return this.json( - { - error: { - tag: ErrorTag.ReadOnlyAccess, - message: 'Session has read-only access.', - }, - }, - 401, - ) - } - - if (request.params.userId !== response.locals.user.uuid) { - return this.json( - { - error: { - message: 'Operation not allowed.', - }, - }, - 401, - ) - } - - const updateResult = await this.updateUser.execute({ - user: response.locals.user, - updatedWithUserAgent: request.headers['user-agent'], - apiVersion: request.body.api, - pwFunc: request.body.pw_func, - pwAlg: request.body.pw_alg, - pwCost: request.body.pw_cost, - pwKeySize: request.body.pw_key_size, - pwNonce: request.body.pw_nonce, - pwSalt: request.body.pw_salt, - kpOrigination: request.body.origination, - kpCreated: request.body.created, - version: request.body.version, - }) - - if (updateResult.success) { - response.setHeader('x-invalidate-cache', response.locals.user.uuid) - - return this.json(updateResult.authResponse) - } - - return this.json( - { - error: { - message: 'Could not update user.', - }, - }, - 400, + super( + updateUser, + getUserKeyParams, + doDeleteAccount, + doGetUserSubscription, + clearLoginAttempts, + increaseLoginAttempts, + changeCredentialsUseCase, ) } + @httpPatch('/:userId', TYPES.Auth_RequiredCrossServiceTokenMiddleware) + override async update(request: Request, response: Response): Promise { + return super.update(request, response) + } + @httpGet('/params') - async keyParams(request: Request): Promise { - const email = 'email' in request.query ? request.query.email : undefined - const userUuid = 'uuid' in request.query ? request.query.uuid : undefined - - if (!email && !userUuid) { - return this.json( - { - error: { - message: 'Missing mandatory request query parameters.', - }, - }, - 400, - ) - } - - const result = await this.getUserKeyParams.execute({ - email, - userUuid, - authenticated: request.query.authenticated === 'true', - }) - - return this.json(result.keyParams) + override async keyParams(request: Request): Promise { + return super.keyParams(request) } @httpDelete('/:email') - async deleteAccount(request: Request): Promise { - const result = await this.doDeleteAccount.execute({ - email: request.params.email, - }) - - return this.json({ message: result.message }, result.responseCode) + override async deleteAccount(request: Request): Promise { + return super.deleteAccount(request) } @httpGet('/:userUuid/subscription', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async getSubscription(request: Request, response: Response): Promise { - if (request.params.userUuid !== response.locals.user.uuid) { - return this.json( - { - error: { - message: 'Operation not allowed.', - }, - }, - 401, - ) - } - - const result = await this.doGetUserSubscription.execute({ - userUuid: request.params.userUuid, - }) - - if (result.success) { - return this.json(result) - } - - return this.json(result, 400) + override async getSubscription(request: Request, response: Response): Promise { + return super.getSubscription(request, response) } @httpPut('/:userId/attributes/credentials', TYPES.Auth_RequiredCrossServiceTokenMiddleware) - async changeCredentials(request: Request, response: Response): Promise { - if (response.locals.readOnlyAccess) { - return this.json( - { - error: { - tag: ErrorTag.ReadOnlyAccess, - message: 'Session has read-only access.', - }, - }, - 401, - ) - } - - if (!request.body.current_password) { - return this.json( - { - error: { - message: - 'Your current password is required to change your password. Please update your application if you do not see this option.', - }, - }, - 400, - ) - } - - if (!request.body.new_password) { - return this.json( - { - error: { - message: 'Your new password is required to change your password. Please try again.', - }, - }, - 400, - ) - } - - if (!request.body.pw_nonce) { - return this.json( - { - error: { - message: 'The change password request is missing new auth parameters. Please try again.', - }, - }, - 400, - ) - } - const usernameOrError = Username.create(response.locals.user.email) - if (usernameOrError.isFailed()) { - return this.json( - { - error: { - message: 'Invalid username.', - }, - }, - 400, - ) - } - const username = usernameOrError.getValue() - - const changeCredentialsResult = await this.changeCredentialsUseCase.execute({ - username, - apiVersion: request.body.api, - currentPassword: request.body.current_password, - newPassword: request.body.new_password, - newEmail: request.body.new_email, - pwNonce: request.body.pw_nonce, - kpCreated: request.body.created, - kpOrigination: request.body.origination, - updatedWithUserAgent: request.headers['user-agent'], - protocolVersion: request.body.version, - }) - - if (!changeCredentialsResult.success) { - await this.increaseLoginAttempts.execute({ email: response.locals.user.email }) - - return this.json( - { - error: { - message: changeCredentialsResult.errorMessage, - }, - }, - 401, - ) - } - - await this.clearLoginAttempts.execute({ email: response.locals.user.email }) - - response.setHeader('x-invalidate-cache', response.locals.user.uuid) - - return this.json(changeCredentialsResult.authResponse) + override async changeCredentials(request: Request, response: Response): Promise { + return super.changeCredentials(request, response) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressValetTokenController.spec.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressValetTokenController.spec.ts index abb1e3069..a42626bcf 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressValetTokenController.spec.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressValetTokenController.spec.ts @@ -4,20 +4,15 @@ import { Request, Response } from 'express' import { results } from 'inversify-express-utils' import { InversifyExpressValetTokenController } from './InversifyExpressValetTokenController' import { CreateValetToken } from '../../Domain/UseCase/CreateValetToken/CreateValetToken' -import { ControllerContainerInterface } from '@standardnotes/domain-core' describe('InversifyExpressValetTokenController', () => { let createValetToken: CreateValetToken let request: Request let response: Response - let controllerContainer: ControllerContainerInterface - const createController = () => new InversifyExpressValetTokenController(createValetToken, controllerContainer) + const createController = () => new InversifyExpressValetTokenController(createValetToken) beforeEach(() => { - controllerContainer = {} as jest.Mocked - controllerContainer.register = jest.fn() - createValetToken = {} as jest.Mocked createValetToken.execute = jest.fn().mockReturnValue({ success: true, valetToken: 'foobar' }) diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressValetTokenController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressValetTokenController.ts index 4a24d3044..a64e39a07 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressValetTokenController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressValetTokenController.ts @@ -1,70 +1,23 @@ import { inject } from 'inversify' import { Request, Response } from 'express' import { - BaseHttpController, controller, httpPost, // eslint-disable-next-line @typescript-eslint/no-unused-vars results, } from 'inversify-express-utils' -import { CreateValetTokenPayload, ErrorTag } from '@standardnotes/responses' -import { ValetTokenOperation } from '@standardnotes/security' -import { ControllerContainerInterface, Uuid } from '@standardnotes/domain-core' import TYPES from '../../Bootstrap/Types' import { CreateValetToken } from '../../Domain/UseCase/CreateValetToken/CreateValetToken' +import { HomeServerValetTokenController } from './HomeServer/HomeServerValetTokenController' @controller('/valet-tokens', TYPES.Auth_RequiredCrossServiceTokenMiddleware) -export class InversifyExpressValetTokenController extends BaseHttpController { - constructor( - @inject(TYPES.Auth_CreateValetToken) private createValetKey: CreateValetToken, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, - ) { - super() - - this.controllerContainer.register('auth.valet-tokens.create', this.create.bind(this)) +export class InversifyExpressValetTokenController extends HomeServerValetTokenController { + constructor(@inject(TYPES.Auth_CreateValetToken) override createValetKey: CreateValetToken) { + super(createValetKey) } @httpPost('/') - public async create(request: Request, response: Response): Promise { - const payload: CreateValetTokenPayload = request.body - - if (response.locals.readOnlyAccess && payload.operation !== 'read') { - return this.json( - { - error: { - tag: ErrorTag.ReadOnlyAccess, - message: 'Session has read-only access.', - }, - }, - 401, - ) - } - - for (const resource of payload.resources) { - const resourceUuidOrError = Uuid.create(resource.remoteIdentifier) - if (resourceUuidOrError.isFailed()) { - return this.json( - { - error: { - tag: ErrorTag.ParametersInvalid, - message: 'Invalid remote resource identifier.', - }, - }, - 400, - ) - } - } - - const createValetKeyResponse = await this.createValetKey.execute({ - userUuid: response.locals.user.uuid, - operation: payload.operation as ValetTokenOperation, - resources: payload.resources, - }) - - if (!createValetKeyResponse.success) { - return this.json(createValetKeyResponse, 403) - } - - return this.json(createValetKeyResponse) + override async create(request: Request, response: Response): Promise { + return super.create(request, response) } } diff --git a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressWebSocketsController.ts b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressWebSocketsController.ts index 3ca18bb5f..87386f58d 100644 --- a/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressWebSocketsController.ts +++ b/packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressWebSocketsController.ts @@ -1,63 +1,28 @@ -import { ErrorTag } from '@standardnotes/responses' import { TokenDecoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security' import { Request } from 'express' import { - BaseHttpController, controller, httpPost, // eslint-disable-next-line @typescript-eslint/no-unused-vars results, } from 'inversify-express-utils' import { CreateCrossServiceToken } from '../../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken' -import { ControllerContainerInterface } from '@standardnotes/domain-core' import { inject } from 'inversify' import TYPES from '../../Bootstrap/Types' +import { HomeServerWebSocketsController } from './HomeServer/HomeServerWebSocketsController' @controller('/sockets') -export class InversifyExpressWebSocketsController extends BaseHttpController { +export class InversifyExpressWebSocketsController extends HomeServerWebSocketsController { constructor( - @inject(TYPES.Auth_CreateCrossServiceToken) private createCrossServiceToken: CreateCrossServiceToken, + @inject(TYPES.Auth_CreateCrossServiceToken) override createCrossServiceToken: CreateCrossServiceToken, @inject(TYPES.Auth_WebSocketConnectionTokenDecoder) - private tokenDecoder: TokenDecoderInterface, - @inject(TYPES.Auth_ControllerContainer) private controllerContainer: ControllerContainerInterface, + override tokenDecoder: TokenDecoderInterface, ) { - super() - - this.controllerContainer.register('auth.webSockets.validateToken', this.validateToken.bind(this)) + super(createCrossServiceToken, tokenDecoder) } @httpPost('/tokens/validate') - async validateToken(request: Request): Promise { - if (!request.headers.authorization) { - return this.json( - { - error: { - tag: ErrorTag.AuthInvalid, - message: 'Invalid authorization token.', - }, - }, - 401, - ) - } - - const token: WebSocketConnectionTokenData | undefined = this.tokenDecoder.decodeToken(request.headers.authorization) - - if (token === undefined) { - return this.json( - { - error: { - tag: ErrorTag.AuthInvalid, - message: 'Invalid authorization token.', - }, - }, - 401, - ) - } - - const result = await this.createCrossServiceToken.execute({ - userUuid: token.userUuid, - }) - - return this.json({ authToken: result.token }) + override async validateToken(request: Request): Promise { + return super.validateToken(request) } } diff --git a/packages/domain-core/src/Domain/Service/ServiceIdentifier.ts b/packages/domain-core/src/Domain/Service/ServiceIdentifier.ts index 64b98e863..5c3953862 100644 --- a/packages/domain-core/src/Domain/Service/ServiceIdentifier.ts +++ b/packages/domain-core/src/Domain/Service/ServiceIdentifier.ts @@ -8,6 +8,7 @@ export class ServiceIdentifier extends ValueObject { Auth: 'Auth', SyncingServer: 'SyncingServer', Revisions: 'Revisions', + Files: 'Files', } get value(): string { diff --git a/packages/files/bin/server.ts b/packages/files/bin/server.ts index 930a9ff82..8f698b228 100644 --- a/packages/files/bin/server.ts +++ b/packages/files/bin/server.ts @@ -4,8 +4,8 @@ import 'newrelic' import * as busboy from 'connect-busboy' -import '../src/Controller/HealthCheckController' -import '../src/Controller/FilesController' +import '../src/Infra/InversifyExpress/InversifyExpressHealthCheckController' +import '../src/Infra/InversifyExpress/InversifyExpressFilesController' import helmet from 'helmet' import * as cors from 'cors' @@ -28,7 +28,7 @@ void container.load().then((container) => { server.setConfig((app) => { app.use((_request: Request, response: Response, next: NextFunction) => { - response.setHeader('X-Files-Version', container.get(TYPES.VERSION)) + response.setHeader('X-Files-Version', container.get(TYPES.Files_VERSION)) next() }) app.use( @@ -74,7 +74,7 @@ void container.load().then((container) => { ) }) - const logger: winston.Logger = container.get(TYPES.Logger) + const logger: winston.Logger = container.get(TYPES.Files_Logger) server.setErrorConfig((app) => { app.use((error: Record, _request: Request, response: Response, _next: NextFunction) => { diff --git a/packages/files/bin/worker.ts b/packages/files/bin/worker.ts index 67004db20..7e7dfcf00 100644 --- a/packages/files/bin/worker.ts +++ b/packages/files/bin/worker.ts @@ -18,11 +18,13 @@ void container.load().then((container) => { const env: Env = new Env() env.load() - const logger: Logger = container.get(TYPES.Logger) + const logger: Logger = container.get(TYPES.Files_Logger) logger.info('Starting worker...') - const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory) + const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get( + TYPES.Files_DomainEventSubscriberFactory, + ) subscriberFactory.create().start() setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000) diff --git a/packages/files/package.json b/packages/files/package.json index 2d764881c..dfa2974ee 100644 --- a/packages/files/package.json +++ b/packages/files/package.json @@ -18,6 +18,7 @@ "setup:env": "cp .env.sample .env", "build": "tsc --build", "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --fix --ext .ts", "pretest": "yarn lint && yarn build", "test": "jest --coverage --config=./jest.config.js --maxWorkers=50%", "start": "yarn node dist/bin/server.js", diff --git a/packages/files/src/Bootstrap/Container.ts b/packages/files/src/Bootstrap/Container.ts index de44383a5..bc430d049 100644 --- a/packages/files/src/Bootstrap/Container.ts +++ b/packages/files/src/Bootstrap/Container.ts @@ -8,12 +8,14 @@ import { Container } from 'inversify' import { Env } from './Env' import TYPES from './Types' import { UploadFileChunk } from '../Domain/UseCase/UploadFileChunk/UploadFileChunk' -import { ValetTokenAuthMiddleware } from '../Controller/ValetTokenAuthMiddleware' +import { ValetTokenAuthMiddleware } from '../Infra/InversifyExpress/Middleware/ValetTokenAuthMiddleware' import { TokenDecoder, TokenDecoderInterface, ValetTokenData } from '@standardnotes/security' import { Timer, TimerInterface } from '@standardnotes/time' import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface' import { DomainEventFactory } from '../Domain/Event/DomainEventFactory' import { + DirectCallDomainEventPublisher, + DirectCallEventMessageHandler, SNSDomainEventPublisher, SQSDomainEventSubscriberFactory, SQSEventMessageHandler, @@ -38,6 +40,7 @@ import { RemoveFile } from '../Domain/UseCase/RemoveFile/RemoveFile' import { DomainEventHandlerInterface, DomainEventMessageHandlerInterface, + DomainEventPublisherInterface, DomainEventSubscriberFactoryInterface, } from '@standardnotes/domain-events' import { MarkFilesToBeRemoved } from '../Domain/UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved' @@ -46,7 +49,10 @@ import { SharedSubscriptionInvitationCanceledEventHandler } from '../Domain/Hand import { InMemoryUploadRepository } from '../Infra/InMemory/InMemoryUploadRepository' export class ContainerConfigLoader { - async load(): Promise { + async load(configuration?: { directCallDomainEventPublisher?: DirectCallDomainEventPublisher }): Promise { + const directCallDomainEventPublisher = + configuration?.directCallDomainEventPublisher ?? new DirectCallDomainEventPublisher() + const env: Env = new Env() env.load() @@ -55,32 +61,88 @@ export class ContainerConfigLoader { const isConfiguredForHomeServer = env.get('CACHE_TYPE') === 'memory' const logger = this.createLogger({ env }) - container.bind(TYPES.Logger).toConstantValue(logger) + container.bind(TYPES.Files_Logger).toConstantValue(logger) - // env vars - container.bind(TYPES.S3_BUCKET_NAME).toConstantValue(env.get('S3_BUCKET_NAME', true)) - container.bind(TYPES.S3_AWS_REGION).toConstantValue(env.get('S3_AWS_REGION', true)) - container.bind(TYPES.VALET_TOKEN_SECRET).toConstantValue(env.get('VALET_TOKEN_SECRET')) - container.bind(TYPES.SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN')) - container.bind(TYPES.SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true)) - container.bind(TYPES.REDIS_URL).toConstantValue(env.get('REDIS_URL')) - container.bind(TYPES.MAX_CHUNK_BYTES).toConstantValue(+env.get('MAX_CHUNK_BYTES')) - container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION')) - container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL')) - container - .bind(TYPES.FILE_UPLOAD_PATH) - .toConstantValue(env.get('FILE_UPLOAD_PATH', true) ?? `${__dirname}/../../uploads`) + container.bind(TYPES.Files_Timer).toConstantValue(new Timer()) - const redisUrl = container.get(TYPES.REDIS_URL) as string - const isRedisInClusterMode = redisUrl.indexOf(',') > 0 - let redis - if (isRedisInClusterMode) { - redis = new Redis.Cluster(redisUrl.split(',')) + if (isConfiguredForHomeServer) { + container + .bind(TYPES.Files_UploadRepository) + .toConstantValue(new InMemoryUploadRepository(container.get(TYPES.Files_Timer))) + + container + .bind(TYPES.Files_DomainEventPublisher) + .toConstantValue(directCallDomainEventPublisher) } else { - redis = new Redis(redisUrl) + container.bind(TYPES.Files_S3_BUCKET_NAME).toConstantValue(env.get('S3_BUCKET_NAME', true)) + container.bind(TYPES.Files_S3_AWS_REGION).toConstantValue(env.get('S3_AWS_REGION', true)) + container.bind(TYPES.Files_SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN')) + container.bind(TYPES.Files_SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true)) + container.bind(TYPES.Files_SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL')) + container.bind(TYPES.Files_REDIS_URL).toConstantValue(env.get('REDIS_URL')) + + const redisUrl = container.get(TYPES.Files_REDIS_URL) as string + const isRedisInClusterMode = redisUrl.indexOf(',') > 0 + let redis + if (isRedisInClusterMode) { + redis = new Redis.Cluster(redisUrl.split(',')) + } else { + redis = new Redis(redisUrl) + } + + container.bind(TYPES.Files_Redis).toConstantValue(redis) + + if (env.get('SNS_TOPIC_ARN', true)) { + const snsConfig: SNSClientConfig = { + apiVersion: 'latest', + region: env.get('SNS_AWS_REGION', true), + } + if (env.get('SNS_ENDPOINT', true)) { + snsConfig.endpoint = env.get('SNS_ENDPOINT', true) + } + if (env.get('SNS_ACCESS_KEY_ID', true) && env.get('SNS_SECRET_ACCESS_KEY', true)) { + snsConfig.credentials = { + accessKeyId: env.get('SNS_ACCESS_KEY_ID', true), + secretAccessKey: env.get('SNS_SECRET_ACCESS_KEY', true), + } + } + container.bind(TYPES.Files_SNS).toConstantValue(new SNSClient(snsConfig)) + } + + if (env.get('SQS_QUEUE_URL', true)) { + const sqsConfig: SQSClientConfig = { + region: env.get('SQS_AWS_REGION', true), + } + if (env.get('SQS_ENDPOINT', true)) { + sqsConfig.endpoint = env.get('SQS_ENDPOINT', 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.Files_SQS).toConstantValue(new SQSClient(sqsConfig)) + } + + container.bind(TYPES.Files_UploadRepository).to(RedisUploadRepository) + + container + .bind(TYPES.Files_DomainEventPublisher) + .toConstantValue( + new SNSDomainEventPublisher(container.get(TYPES.Files_SNS), container.get(TYPES.Files_SNS_TOPIC_ARN)), + ) } - container.bind(TYPES.Redis).toConstantValue(redis) + // env vars + container.bind(TYPES.Files_VALET_TOKEN_SECRET).toConstantValue(env.get('VALET_TOKEN_SECRET')) + container + .bind(TYPES.Files_MAX_CHUNK_BYTES) + .toConstantValue(env.get('MAX_CHUNK_BYTES', true) ? +env.get('MAX_CHUNK_BYTES', true) : 100000000) + container.bind(TYPES.Files_VERSION).toConstantValue(env.get('VERSION', true) ?? 'development') + container + .bind(TYPES.Files_FILE_UPLOAD_PATH) + .toConstantValue(env.get('FILE_UPLOAD_PATH', true) ?? `${__dirname}/../../uploads`) if (env.get('S3_AWS_REGION', true) || env.get('S3_ENDPOINT', true)) { const s3Opts: S3ClientConfig = { @@ -93,115 +155,83 @@ export class ContainerConfigLoader { s3Opts.endpoint = env.get('S3_ENDPOINT', true) } const s3Client = new S3Client(s3Opts) - container.bind(TYPES.S3).toConstantValue(s3Client) - container.bind(TYPES.FileDownloader).to(S3FileDownloader) - container.bind(TYPES.FileUploader).to(S3FileUploader) - container.bind(TYPES.FileRemover).to(S3FileRemover) + container.bind(TYPES.Files_S3).toConstantValue(s3Client) + container.bind(TYPES.Files_FileDownloader).to(S3FileDownloader) + container.bind(TYPES.Files_FileUploader).to(S3FileUploader) + container.bind(TYPES.Files_FileRemover).to(S3FileRemover) } else { - container.bind(TYPES.FileDownloader).to(FSFileDownloader) + container.bind(TYPES.Files_FileDownloader).to(FSFileDownloader) container - .bind(TYPES.FileUploader) - .toConstantValue(new FSFileUploader(container.get(TYPES.FILE_UPLOAD_PATH), container.get(TYPES.Logger))) - container.bind(TYPES.FileRemover).to(FSFileRemover) - } - - if (env.get('SNS_TOPIC_ARN', true)) { - const snsConfig: SNSClientConfig = { - apiVersion: 'latest', - region: env.get('SNS_AWS_REGION', true), - } - if (env.get('SNS_ENDPOINT', true)) { - snsConfig.endpoint = env.get('SNS_ENDPOINT', true) - } - if (env.get('SNS_ACCESS_KEY_ID', true) && env.get('SNS_SECRET_ACCESS_KEY', true)) { - snsConfig.credentials = { - accessKeyId: env.get('SNS_ACCESS_KEY_ID', true), - secretAccessKey: env.get('SNS_SECRET_ACCESS_KEY', true), - } - } - container.bind(TYPES.SNS).toConstantValue(new SNSClient(snsConfig)) - } - - if (env.get('SQS_QUEUE_URL', true)) { - const sqsConfig: SQSClientConfig = { - region: env.get('SQS_AWS_REGION', true), - } - if (env.get('SQS_ENDPOINT', true)) { - sqsConfig.endpoint = env.get('SQS_ENDPOINT', 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 SQSClient(sqsConfig)) + .bind(TYPES.Files_FileUploader) + .toConstantValue( + new FSFileUploader(container.get(TYPES.Files_FILE_UPLOAD_PATH), container.get(TYPES.Files_Logger)), + ) + container.bind(TYPES.Files_FileRemover).to(FSFileRemover) } // use cases - container.bind(TYPES.UploadFileChunk).to(UploadFileChunk) - container.bind(TYPES.StreamDownloadFile).to(StreamDownloadFile) - container.bind(TYPES.CreateUploadSession).to(CreateUploadSession) - container.bind(TYPES.FinishUploadSession).to(FinishUploadSession) - container.bind(TYPES.GetFileMetadata).to(GetFileMetadata) - container.bind(TYPES.RemoveFile).to(RemoveFile) - container.bind(TYPES.MarkFilesToBeRemoved).to(MarkFilesToBeRemoved) + container.bind(TYPES.Files_UploadFileChunk).to(UploadFileChunk) + container.bind(TYPES.Files_StreamDownloadFile).to(StreamDownloadFile) + container.bind(TYPES.Files_CreateUploadSession).to(CreateUploadSession) + container.bind(TYPES.Files_FinishUploadSession).to(FinishUploadSession) + container.bind(TYPES.Files_GetFileMetadata).to(GetFileMetadata) + container.bind(TYPES.Files_RemoveFile).to(RemoveFile) + container.bind(TYPES.Files_MarkFilesToBeRemoved).to(MarkFilesToBeRemoved) // middleware - container.bind(TYPES.ValetTokenAuthMiddleware).to(ValetTokenAuthMiddleware) + container.bind(TYPES.Files_ValetTokenAuthMiddleware).to(ValetTokenAuthMiddleware) // services container - .bind>(TYPES.ValetTokenDecoder) - .toConstantValue(new TokenDecoder(container.get(TYPES.VALET_TOKEN_SECRET))) - container.bind(TYPES.Timer).toConstantValue(new Timer()) - container.bind(TYPES.DomainEventFactory).to(DomainEventFactory) - - // repositories - if (isConfiguredForHomeServer) { - container - .bind(TYPES.UploadRepository) - .toConstantValue(new InMemoryUploadRepository(container.get(TYPES.Timer))) - } else { - container.bind(TYPES.UploadRepository).to(RedisUploadRepository) - } - - container - .bind(TYPES.DomainEventPublisher) - .toConstantValue(new SNSDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN))) + .bind>(TYPES.Files_ValetTokenDecoder) + .toConstantValue(new TokenDecoder(container.get(TYPES.Files_VALET_TOKEN_SECRET))) + container.bind(TYPES.Files_DomainEventFactory).to(DomainEventFactory) // Handlers container - .bind(TYPES.AccountDeletionRequestedEventHandler) + .bind(TYPES.Files_AccountDeletionRequestedEventHandler) .to(AccountDeletionRequestedEventHandler) container - .bind(TYPES.SharedSubscriptionInvitationCanceledEventHandler) + .bind( + TYPES.Files_SharedSubscriptionInvitationCanceledEventHandler, + ) .to(SharedSubscriptionInvitationCanceledEventHandler) const eventHandlers: Map = new Map([ - ['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)], + ['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Files_AccountDeletionRequestedEventHandler)], [ 'SHARED_SUBSCRIPTION_INVITATION_CANCELED', - container.get(TYPES.SharedSubscriptionInvitationCanceledEventHandler), + container.get(TYPES.Files_SharedSubscriptionInvitationCanceledEventHandler), ], ]) - 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), - ), + if (isConfiguredForHomeServer) { + const directCallEventMessageHandler = new DirectCallEventMessageHandler( + eventHandlers, + container.get(TYPES.Files_Logger), ) + directCallDomainEventPublisher.register(directCallEventMessageHandler) + container + .bind(TYPES.Files_DomainEventMessageHandler) + .toConstantValue(directCallEventMessageHandler) + } else { + container + .bind(TYPES.Files_DomainEventMessageHandler) + .toConstantValue( + env.get('NEW_RELIC_ENABLED', true) === 'true' + ? new SQSNewRelicEventMessageHandler(eventHandlers, container.get(TYPES.Files_Logger)) + : new SQSEventMessageHandler(eventHandlers, container.get(TYPES.Files_Logger)), + ) + container + .bind(TYPES.Files_DomainEventSubscriberFactory) + .toConstantValue( + new SQSDomainEventSubscriberFactory( + container.get(TYPES.Files_SQS), + container.get(TYPES.Files_SQS_QUEUE_URL), + container.get(TYPES.Files_DomainEventMessageHandler), + ), + ) + } return container } diff --git a/packages/files/src/Bootstrap/Service.ts b/packages/files/src/Bootstrap/Service.ts new file mode 100644 index 000000000..0bcfc790c --- /dev/null +++ b/packages/files/src/Bootstrap/Service.ts @@ -0,0 +1,29 @@ +import { ServiceContainerInterface, ServiceIdentifier, ServiceInterface } from '@standardnotes/domain-core' +import { DirectCallDomainEventPublisher } from '@standardnotes/domain-events-infra' + +import { ContainerConfigLoader } from './Container' + +export class Service implements ServiceInterface { + constructor( + private serviceContainer: ServiceContainerInterface, + private directCallDomainEventPublisher: DirectCallDomainEventPublisher, + ) { + this.serviceContainer.register(this.getId(), this) + } + + async handleRequest(_request: never, _response: never, _endpointOrMethodIdentifier: string): Promise { + throw new Error('Requests are handled via inversify-express at ApiGateway level') + } + + async getContainer(): Promise { + const config = new ContainerConfigLoader() + + return config.load({ + directCallDomainEventPublisher: this.directCallDomainEventPublisher, + }) + } + + getId(): ServiceIdentifier { + return ServiceIdentifier.create(ServiceIdentifier.NAMES.Files).getValue() + } +} diff --git a/packages/files/src/Bootstrap/Types.ts b/packages/files/src/Bootstrap/Types.ts index 8f0dbc8a6..8ae24fe10 100644 --- a/packages/files/src/Bootstrap/Types.ts +++ b/packages/files/src/Bootstrap/Types.ts @@ -1,55 +1,57 @@ const TYPES = { - Logger: Symbol.for('Logger'), - HTTPClient: Symbol.for('HTTPClient'), - Redis: Symbol.for('Redis'), - S3: Symbol.for('S3'), - SNS: Symbol.for('SNS'), - SQS: Symbol.for('SQS'), + Files_Logger: Symbol.for('Files_Logger'), + Files_HTTPClient: Symbol.for('Files_HTTPClient'), + Files_Redis: Symbol.for('Files_Redis'), + Files_S3: Symbol.for('Files_S3'), + Files_SNS: Symbol.for('Files_SNS'), + Files_SQS: Symbol.for('Files_SQS'), // use cases - UploadFileChunk: Symbol.for('UploadFileChunk'), - StreamDownloadFile: Symbol.for('StreamDownloadFile'), - CreateUploadSession: Symbol.for('CreateUploadSession'), - FinishUploadSession: Symbol.for('FinishUploadSession'), - GetFileMetadata: Symbol.for('GetFileMetadata'), - RemoveFile: Symbol.for('RemoveFile'), - MarkFilesToBeRemoved: Symbol.for('MarkFilesToBeRemoved'), + Files_UploadFileChunk: Symbol.for('Files_UploadFileChunk'), + Files_StreamDownloadFile: Symbol.for('Files_StreamDownloadFile'), + Files_CreateUploadSession: Symbol.for('Files_CreateUploadSession'), + Files_FinishUploadSession: Symbol.for('Files_FinishUploadSession'), + Files_GetFileMetadata: Symbol.for('Files_GetFileMetadata'), + Files_RemoveFile: Symbol.for('Files_RemoveFile'), + Files_MarkFilesToBeRemoved: Symbol.for('Files_MarkFilesToBeRemoved'), // services - ValetTokenDecoder: Symbol.for('ValetTokenDecoder'), - Timer: Symbol.for('Timer'), - DomainEventFactory: Symbol.for('DomainEventFactory'), - DomainEventPublisher: Symbol.for('DomainEventPublisher'), - FileUploader: Symbol.for('FileUploader'), - FileDownloader: Symbol.for('FileDownloader'), - FileRemover: Symbol.for('FileRemover'), + Files_ValetTokenDecoder: Symbol.for('Files_ValetTokenDecoder'), + Files_Timer: Symbol.for('Files_Timer'), + Files_DomainEventFactory: Symbol.for('Files_DomainEventFactory'), + Files_DomainEventPublisher: Symbol.for('Files_DomainEventPublisher'), + Files_FileUploader: Symbol.for('Files_FileUploader'), + Files_FileDownloader: Symbol.for('Files_FileDownloader'), + Files_FileRemover: Symbol.for('Files_FileRemover'), // repositories - UploadRepository: Symbol.for('UploadRepository'), + Files_UploadRepository: Symbol.for('Files_UploadRepository'), // middleware - ValetTokenAuthMiddleware: Symbol.for('ValetTokenAuthMiddleware'), + Files_ValetTokenAuthMiddleware: Symbol.for('Files_ValetTokenAuthMiddleware'), // env vars - S3_ENDPOINT: Symbol.for('S3_ENDPOINT'), - S3_BUCKET_NAME: Symbol.for('S3_BUCKET_NAME'), - S3_AWS_REGION: Symbol.for('S3_AWS_REGION'), - SNS_TOPIC_ARN: Symbol.for('SNS_TOPIC_ARN'), - SNS_AWS_REGION: Symbol.for('SNS_AWS_REGION'), - SQS_QUEUE_URL: Symbol.for('SQS_QUEUE_URL'), - SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'), - VALET_TOKEN_SECRET: Symbol.for('VALET_TOKEN_SECRET'), - REDIS_URL: Symbol.for('REDIS_URL'), - MAX_CHUNK_BYTES: Symbol.for('MAX_CHUNK_BYTES'), - VERSION: Symbol.for('VERSION'), - NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'), - FILE_UPLOAD_PATH: Symbol.for('FILE_UPLOAD_PATH'), + Files_S3_ENDPOINT: Symbol.for('Files_S3_ENDPOINT'), + Files_S3_BUCKET_NAME: Symbol.for('Files_S3_BUCKET_NAME'), + Files_S3_AWS_REGION: Symbol.for('Files_S3_AWS_REGION'), + Files_SNS_TOPIC_ARN: Symbol.for('Files_SNS_TOPIC_ARN'), + Files_SNS_AWS_REGION: Symbol.for('Files_SNS_AWS_REGION'), + Files_SQS_QUEUE_URL: Symbol.for('Files_SQS_QUEUE_URL'), + Files_SQS_AWS_REGION: Symbol.for('Files_SQS_AWS_REGION'), + Files_VALET_TOKEN_SECRET: Symbol.for('Files_VALET_TOKEN_SECRET'), + Files_REDIS_URL: Symbol.for('Files_REDIS_URL'), + Files_MAX_CHUNK_BYTES: Symbol.for('Files_MAX_CHUNK_BYTES'), + Files_VERSION: Symbol.for('Files_VERSION'), + Files_NEW_RELIC_ENABLED: Symbol.for('Files_NEW_RELIC_ENABLED'), + Files_FILE_UPLOAD_PATH: Symbol.for('Files_FILE_UPLOAD_PATH'), // Handlers - DomainEventMessageHandler: Symbol.for('DomainEventMessageHandler'), - DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'), - AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'), - SharedSubscriptionInvitationCanceledEventHandler: Symbol.for('SharedSubscriptionInvitationCanceledEventHandler'), + Files_DomainEventMessageHandler: Symbol.for('Files_DomainEventMessageHandler'), + Files_DomainEventSubscriberFactory: Symbol.for('Files_DomainEventSubscriberFactory'), + Files_AccountDeletionRequestedEventHandler: Symbol.for('Files_AccountDeletionRequestedEventHandler'), + Files_SharedSubscriptionInvitationCanceledEventHandler: Symbol.for( + 'Files_SharedSubscriptionInvitationCanceledEventHandler', + ), } export default TYPES diff --git a/packages/files/src/Bootstrap/index.ts b/packages/files/src/Bootstrap/index.ts new file mode 100644 index 000000000..df1a81eab --- /dev/null +++ b/packages/files/src/Bootstrap/index.ts @@ -0,0 +1,2 @@ +export * from './Service' +export * from './Types' diff --git a/packages/files/src/Controller/HealthCheckController.spec.ts b/packages/files/src/Controller/HealthCheckController.spec.ts deleted file mode 100644 index 9ead2fccb..000000000 --- a/packages/files/src/Controller/HealthCheckController.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import 'reflect-metadata' - -import { HealthCheckController } from './HealthCheckController' - -describe('HealthCheckController', () => { - const createController = () => new HealthCheckController() - - it('should return OK', async () => { - const response = (await createController().get()) as string - expect(response).toEqual('OK') - }) -}) diff --git a/packages/files/src/Domain/Event/DomainEventFactory.ts b/packages/files/src/Domain/Event/DomainEventFactory.ts index 478d86c4f..6b426e173 100644 --- a/packages/files/src/Domain/Event/DomainEventFactory.ts +++ b/packages/files/src/Domain/Event/DomainEventFactory.ts @@ -7,7 +7,7 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface' @injectable() export class DomainEventFactory implements DomainEventFactoryInterface { - constructor(@inject(TYPES.Timer) private timer: TimerInterface) {} + constructor(@inject(TYPES.Files_Timer) private timer: TimerInterface) {} createFileRemovedEvent(payload: { userUuid: string diff --git a/packages/files/src/Domain/Handler/AccountDeletionRequestedEventHandler.ts b/packages/files/src/Domain/Handler/AccountDeletionRequestedEventHandler.ts index 15bfd2146..618c5a420 100644 --- a/packages/files/src/Domain/Handler/AccountDeletionRequestedEventHandler.ts +++ b/packages/files/src/Domain/Handler/AccountDeletionRequestedEventHandler.ts @@ -12,9 +12,9 @@ import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesT @injectable() export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface { constructor( - @inject(TYPES.MarkFilesToBeRemoved) private markFilesToBeRemoved: MarkFilesToBeRemoved, - @inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface, - @inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface, + @inject(TYPES.Files_MarkFilesToBeRemoved) private markFilesToBeRemoved: MarkFilesToBeRemoved, + @inject(TYPES.Files_DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface, + @inject(TYPES.Files_DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface, ) {} async handle(event: AccountDeletionRequestedEvent): Promise { diff --git a/packages/files/src/Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler.ts b/packages/files/src/Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler.ts index bc7d2ea09..b71587498 100644 --- a/packages/files/src/Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler.ts +++ b/packages/files/src/Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler.ts @@ -12,9 +12,9 @@ import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesT @injectable() export class SharedSubscriptionInvitationCanceledEventHandler implements DomainEventHandlerInterface { constructor( - @inject(TYPES.MarkFilesToBeRemoved) private markFilesToBeRemoved: MarkFilesToBeRemoved, - @inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface, - @inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface, + @inject(TYPES.Files_MarkFilesToBeRemoved) private markFilesToBeRemoved: MarkFilesToBeRemoved, + @inject(TYPES.Files_DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface, + @inject(TYPES.Files_DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface, ) {} async handle(event: SharedSubscriptionInvitationCanceledEvent): Promise { diff --git a/packages/files/src/Domain/UseCase/CreateUploadSession/CreateUploadSession.ts b/packages/files/src/Domain/UseCase/CreateUploadSession/CreateUploadSession.ts index d6865fe3c..912379ffa 100644 --- a/packages/files/src/Domain/UseCase/CreateUploadSession/CreateUploadSession.ts +++ b/packages/files/src/Domain/UseCase/CreateUploadSession/CreateUploadSession.ts @@ -11,9 +11,9 @@ import { UploadRepositoryInterface } from '../../Upload/UploadRepositoryInterfac @injectable() export class CreateUploadSession implements UseCaseInterface { constructor( - @inject(TYPES.FileUploader) private fileUploader: FileUploaderInterface, - @inject(TYPES.UploadRepository) private uploadRepository: UploadRepositoryInterface, - @inject(TYPES.Logger) private logger: Logger, + @inject(TYPES.Files_FileUploader) private fileUploader: FileUploaderInterface, + @inject(TYPES.Files_UploadRepository) private uploadRepository: UploadRepositoryInterface, + @inject(TYPES.Files_Logger) private logger: Logger, ) {} async execute(dto: CreateUploadSessionDTO): Promise { diff --git a/packages/files/src/Domain/UseCase/FinishUploadSession/FinishUploadSession.ts b/packages/files/src/Domain/UseCase/FinishUploadSession/FinishUploadSession.ts index 2fbe5724d..7e7abad5e 100644 --- a/packages/files/src/Domain/UseCase/FinishUploadSession/FinishUploadSession.ts +++ b/packages/files/src/Domain/UseCase/FinishUploadSession/FinishUploadSession.ts @@ -13,11 +13,11 @@ import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInter @injectable() export class FinishUploadSession implements UseCaseInterface { constructor( - @inject(TYPES.FileUploader) private fileUploader: FileUploaderInterface, - @inject(TYPES.UploadRepository) private uploadRepository: UploadRepositoryInterface, - @inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface, - @inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface, - @inject(TYPES.Logger) private logger: Logger, + @inject(TYPES.Files_FileUploader) private fileUploader: FileUploaderInterface, + @inject(TYPES.Files_UploadRepository) private uploadRepository: UploadRepositoryInterface, + @inject(TYPES.Files_DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface, + @inject(TYPES.Files_DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface, + @inject(TYPES.Files_Logger) private logger: Logger, ) {} async execute(dto: FinishUploadSessionDTO): Promise { diff --git a/packages/files/src/Domain/UseCase/GetFileMetadata/GetFileMetadata.ts b/packages/files/src/Domain/UseCase/GetFileMetadata/GetFileMetadata.ts index bca0d05db..280a86a5b 100644 --- a/packages/files/src/Domain/UseCase/GetFileMetadata/GetFileMetadata.ts +++ b/packages/files/src/Domain/UseCase/GetFileMetadata/GetFileMetadata.ts @@ -9,8 +9,8 @@ import { GetFileMetadataResponse } from './GetFileMetadataResponse' @injectable() export class GetFileMetadata implements UseCaseInterface { constructor( - @inject(TYPES.FileDownloader) private fileDownloader: FileDownloaderInterface, - @inject(TYPES.Logger) private logger: Logger, + @inject(TYPES.Files_FileDownloader) private fileDownloader: FileDownloaderInterface, + @inject(TYPES.Files_Logger) private logger: Logger, ) {} async execute(dto: GetFileMetadataDTO): Promise { diff --git a/packages/files/src/Domain/UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved.ts b/packages/files/src/Domain/UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved.ts index a6c7af331..8f0015472 100644 --- a/packages/files/src/Domain/UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved.ts +++ b/packages/files/src/Domain/UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved.ts @@ -10,8 +10,8 @@ import { MarkFilesToBeRemovedResponse } from './MarkFilesToBeRemovedResponse' @injectable() export class MarkFilesToBeRemoved implements UseCaseInterface { constructor( - @inject(TYPES.FileRemover) private fileRemover: FileRemoverInterface, - @inject(TYPES.Logger) private logger: Logger, + @inject(TYPES.Files_FileRemover) private fileRemover: FileRemoverInterface, + @inject(TYPES.Files_Logger) private logger: Logger, ) {} async execute(dto: MarkFilesToBeRemovedDTO): Promise { diff --git a/packages/files/src/Domain/UseCase/RemoveFile/RemoveFile.ts b/packages/files/src/Domain/UseCase/RemoveFile/RemoveFile.ts index 4e75b09f1..66d802c08 100644 --- a/packages/files/src/Domain/UseCase/RemoveFile/RemoveFile.ts +++ b/packages/files/src/Domain/UseCase/RemoveFile/RemoveFile.ts @@ -12,10 +12,10 @@ import { RemoveFileResponse } from './RemoveFileResponse' @injectable() export class RemoveFile implements UseCaseInterface { constructor( - @inject(TYPES.FileRemover) private fileRemover: FileRemoverInterface, - @inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface, - @inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface, - @inject(TYPES.Logger) private logger: Logger, + @inject(TYPES.Files_FileRemover) private fileRemover: FileRemoverInterface, + @inject(TYPES.Files_DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface, + @inject(TYPES.Files_DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface, + @inject(TYPES.Files_Logger) private logger: Logger, ) {} async execute(dto: RemoveFileDTO): Promise { diff --git a/packages/files/src/Domain/UseCase/StreamDownloadFile/StreamDownloadFile.ts b/packages/files/src/Domain/UseCase/StreamDownloadFile/StreamDownloadFile.ts index eb27a87b0..5687664ef 100644 --- a/packages/files/src/Domain/UseCase/StreamDownloadFile/StreamDownloadFile.ts +++ b/packages/files/src/Domain/UseCase/StreamDownloadFile/StreamDownloadFile.ts @@ -9,8 +9,8 @@ import { StreamDownloadFileResponse } from './StreamDownloadFileResponse' @injectable() export class StreamDownloadFile implements UseCaseInterface { constructor( - @inject(TYPES.FileDownloader) private fileDownloader: FileDownloaderInterface, - @inject(TYPES.Logger) private logger: Logger, + @inject(TYPES.Files_FileDownloader) private fileDownloader: FileDownloaderInterface, + @inject(TYPES.Files_Logger) private logger: Logger, ) {} async execute(dto: StreamDownloadFileDTO): Promise { diff --git a/packages/files/src/Domain/UseCase/UploadFileChunk/UploadFileChunk.ts b/packages/files/src/Domain/UseCase/UploadFileChunk/UploadFileChunk.ts index 5a177a55a..54e840c9c 100644 --- a/packages/files/src/Domain/UseCase/UploadFileChunk/UploadFileChunk.ts +++ b/packages/files/src/Domain/UseCase/UploadFileChunk/UploadFileChunk.ts @@ -11,9 +11,9 @@ import { UploadRepositoryInterface } from '../../Upload/UploadRepositoryInterfac @injectable() export class UploadFileChunk implements UseCaseInterface { constructor( - @inject(TYPES.FileUploader) private fileUploader: FileUploaderInterface, - @inject(TYPES.UploadRepository) private uploadRepository: UploadRepositoryInterface, - @inject(TYPES.Logger) private logger: Logger, + @inject(TYPES.Files_FileUploader) private fileUploader: FileUploaderInterface, + @inject(TYPES.Files_UploadRepository) private uploadRepository: UploadRepositoryInterface, + @inject(TYPES.Files_Logger) private logger: Logger, ) {} async execute(dto: UploadFileChunkDTO): Promise { diff --git a/packages/files/src/Infra/FS/FSFileDownloader.ts b/packages/files/src/Infra/FS/FSFileDownloader.ts index 8d6fc0106..c6db73e9a 100644 --- a/packages/files/src/Infra/FS/FSFileDownloader.ts +++ b/packages/files/src/Infra/FS/FSFileDownloader.ts @@ -7,7 +7,7 @@ import TYPES from '../../Bootstrap/Types' @injectable() export class FSFileDownloader implements FileDownloaderInterface { - constructor(@inject(TYPES.FILE_UPLOAD_PATH) private fileUploadPath: string) {} + constructor(@inject(TYPES.Files_FILE_UPLOAD_PATH) private fileUploadPath: string) {} async getFileSize(filePath: string): Promise { return (await promises.stat(`${this.fileUploadPath}/${filePath}`)).size diff --git a/packages/files/src/Infra/FS/FSFileRemover.ts b/packages/files/src/Infra/FS/FSFileRemover.ts index 8ae509413..b7a67d438 100644 --- a/packages/files/src/Infra/FS/FSFileRemover.ts +++ b/packages/files/src/Infra/FS/FSFileRemover.ts @@ -7,7 +7,7 @@ import TYPES from '../../Bootstrap/Types' @injectable() export class FSFileRemover implements FileRemoverInterface { - constructor(@inject(TYPES.FILE_UPLOAD_PATH) private fileUploadPath: string) {} + constructor(@inject(TYPES.Files_FILE_UPLOAD_PATH) private fileUploadPath: string) {} async markFilesToBeRemoved(userUuid: string): Promise> { await promises.rmdir(`${this.fileUploadPath}/${userUuid}`) diff --git a/packages/files/src/Infra/FS/FSFileUploader.ts b/packages/files/src/Infra/FS/FSFileUploader.ts index 9a901681a..88a358c6b 100644 --- a/packages/files/src/Infra/FS/FSFileUploader.ts +++ b/packages/files/src/Infra/FS/FSFileUploader.ts @@ -13,8 +13,8 @@ export class FSFileUploader implements FileUploaderInterface { private inMemoryChunks: Map> constructor( - @inject(TYPES.FILE_UPLOAD_PATH) private fileUploadPath: string, - @inject(TYPES.Logger) private logger: Logger, + @inject(TYPES.Files_FILE_UPLOAD_PATH) private fileUploadPath: string, + @inject(TYPES.Files_Logger) private logger: Logger, ) { this.inMemoryChunks = new Map>() } diff --git a/packages/files/src/Controller/FilesController.spec.ts b/packages/files/src/Infra/InversifyExpress/InversifyExpressFilesController.spec.ts similarity index 94% rename from packages/files/src/Controller/FilesController.spec.ts rename to packages/files/src/Infra/InversifyExpress/InversifyExpressFilesController.spec.ts index 6d45a3c82..b2c83ef5c 100644 --- a/packages/files/src/Controller/FilesController.spec.ts +++ b/packages/files/src/Infra/InversifyExpress/InversifyExpressFilesController.spec.ts @@ -1,20 +1,20 @@ import 'reflect-metadata' -import { CreateUploadSession } from '../Domain/UseCase/CreateUploadSession/CreateUploadSession' -import { FinishUploadSession } from '../Domain/UseCase/FinishUploadSession/FinishUploadSession' -import { StreamDownloadFile } from '../Domain/UseCase/StreamDownloadFile/StreamDownloadFile' -import { UploadFileChunk } from '../Domain/UseCase/UploadFileChunk/UploadFileChunk' +import { CreateUploadSession } from '../../Domain/UseCase/CreateUploadSession/CreateUploadSession' +import { FinishUploadSession } from '../../Domain/UseCase/FinishUploadSession/FinishUploadSession' +import { StreamDownloadFile } from '../../Domain/UseCase/StreamDownloadFile/StreamDownloadFile' +import { UploadFileChunk } from '../../Domain/UseCase/UploadFileChunk/UploadFileChunk' import { Request, Response } from 'express' import { Writable, Readable } from 'stream' -import { FilesController } from './FilesController' -import { GetFileMetadata } from '../Domain/UseCase/GetFileMetadata/GetFileMetadata' +import { InversifyExpressFilesController } from './InversifyExpressFilesController' +import { GetFileMetadata } from '../../Domain/UseCase/GetFileMetadata/GetFileMetadata' import { results } from 'inversify-express-utils' -import { RemoveFile } from '../Domain/UseCase/RemoveFile/RemoveFile' +import { RemoveFile } from '../../Domain/UseCase/RemoveFile/RemoveFile' import { ValetTokenOperation } from '@standardnotes/security' import { BadRequestErrorMessageResult } from 'inversify-express-utils/lib/results' -describe('FilesController', () => { +describe('InversifyExpressFilesController', () => { let uploadFileChunk: UploadFileChunk let createUploadSession: CreateUploadSession let finishUploadSession: FinishUploadSession @@ -27,7 +27,7 @@ describe('FilesController', () => { const maxChunkBytes = 100_000 const createController = () => - new FilesController( + new InversifyExpressFilesController( uploadFileChunk, createUploadSession, finishUploadSession, diff --git a/packages/files/src/Controller/FilesController.ts b/packages/files/src/Infra/InversifyExpress/InversifyExpressFilesController.ts similarity index 79% rename from packages/files/src/Controller/FilesController.ts rename to packages/files/src/Infra/InversifyExpress/InversifyExpressFilesController.ts index 5834756b3..722acf5a2 100644 --- a/packages/files/src/Controller/FilesController.ts +++ b/packages/files/src/Infra/InversifyExpress/InversifyExpressFilesController.ts @@ -2,25 +2,25 @@ import { BaseHttpController, controller, httpDelete, httpGet, httpPost, results import { Request, Response } from 'express' import { inject } from 'inversify' import { Writable } from 'stream' -import TYPES from '../Bootstrap/Types' -import { UploadFileChunk } from '../Domain/UseCase/UploadFileChunk/UploadFileChunk' -import { StreamDownloadFile } from '../Domain/UseCase/StreamDownloadFile/StreamDownloadFile' -import { CreateUploadSession } from '../Domain/UseCase/CreateUploadSession/CreateUploadSession' -import { FinishUploadSession } from '../Domain/UseCase/FinishUploadSession/FinishUploadSession' -import { GetFileMetadata } from '../Domain/UseCase/GetFileMetadata/GetFileMetadata' -import { RemoveFile } from '../Domain/UseCase/RemoveFile/RemoveFile' +import TYPES from '../../Bootstrap/Types' +import { UploadFileChunk } from '../../Domain/UseCase/UploadFileChunk/UploadFileChunk' +import { StreamDownloadFile } from '../../Domain/UseCase/StreamDownloadFile/StreamDownloadFile' +import { CreateUploadSession } from '../../Domain/UseCase/CreateUploadSession/CreateUploadSession' +import { FinishUploadSession } from '../../Domain/UseCase/FinishUploadSession/FinishUploadSession' +import { GetFileMetadata } from '../../Domain/UseCase/GetFileMetadata/GetFileMetadata' +import { RemoveFile } from '../../Domain/UseCase/RemoveFile/RemoveFile' import { ValetTokenOperation } from '@standardnotes/security' -@controller('/v1/files', TYPES.ValetTokenAuthMiddleware) -export class FilesController extends BaseHttpController { +@controller('/v1/files', TYPES.Files_ValetTokenAuthMiddleware) +export class InversifyExpressFilesController extends BaseHttpController { constructor( - @inject(TYPES.UploadFileChunk) private uploadFileChunk: UploadFileChunk, - @inject(TYPES.CreateUploadSession) private createUploadSession: CreateUploadSession, - @inject(TYPES.FinishUploadSession) private finishUploadSession: FinishUploadSession, - @inject(TYPES.StreamDownloadFile) private streamDownloadFile: StreamDownloadFile, - @inject(TYPES.GetFileMetadata) private getFileMetadata: GetFileMetadata, - @inject(TYPES.RemoveFile) private removeFile: RemoveFile, - @inject(TYPES.MAX_CHUNK_BYTES) private maxChunkBytes: number, + @inject(TYPES.Files_UploadFileChunk) private uploadFileChunk: UploadFileChunk, + @inject(TYPES.Files_CreateUploadSession) private createUploadSession: CreateUploadSession, + @inject(TYPES.Files_FinishUploadSession) private finishUploadSession: FinishUploadSession, + @inject(TYPES.Files_StreamDownloadFile) private streamDownloadFile: StreamDownloadFile, + @inject(TYPES.Files_GetFileMetadata) private getFileMetadata: GetFileMetadata, + @inject(TYPES.Files_RemoveFile) private removeFile: RemoveFile, + @inject(TYPES.Files_MAX_CHUNK_BYTES) private maxChunkBytes: number, ) { super() } diff --git a/packages/files/src/Infra/InversifyExpress/InversifyExpressHealthCheckController.spec.ts b/packages/files/src/Infra/InversifyExpress/InversifyExpressHealthCheckController.spec.ts new file mode 100644 index 000000000..308d8ccbc --- /dev/null +++ b/packages/files/src/Infra/InversifyExpress/InversifyExpressHealthCheckController.spec.ts @@ -0,0 +1,12 @@ +import 'reflect-metadata' + +import { InversifyExpressHealthCheckController } from './InversifyExpressHealthCheckController' + +describe('InversifyExpressHealthCheckController', () => { + const createController = () => new InversifyExpressHealthCheckController() + + it('should return OK', async () => { + const response = (await createController().get()) as string + expect(response).toEqual('OK') + }) +}) diff --git a/packages/files/src/Controller/HealthCheckController.ts b/packages/files/src/Infra/InversifyExpress/InversifyExpressHealthCheckController.ts similarity index 76% rename from packages/files/src/Controller/HealthCheckController.ts rename to packages/files/src/Infra/InversifyExpress/InversifyExpressHealthCheckController.ts index 431e74056..535288409 100644 --- a/packages/files/src/Controller/HealthCheckController.ts +++ b/packages/files/src/Infra/InversifyExpress/InversifyExpressHealthCheckController.ts @@ -1,7 +1,7 @@ import { controller, httpGet } from 'inversify-express-utils' @controller('/healthcheck') -export class HealthCheckController { +export class InversifyExpressHealthCheckController { @httpGet('/') public async get(): Promise { return 'OK' diff --git a/packages/files/src/Controller/ValetTokenAuthMiddleware.spec.ts b/packages/files/src/Infra/InversifyExpress/Middleware/ValetTokenAuthMiddleware.spec.ts similarity index 100% rename from packages/files/src/Controller/ValetTokenAuthMiddleware.spec.ts rename to packages/files/src/Infra/InversifyExpress/Middleware/ValetTokenAuthMiddleware.spec.ts diff --git a/packages/files/src/Controller/ValetTokenAuthMiddleware.ts b/packages/files/src/Infra/InversifyExpress/Middleware/ValetTokenAuthMiddleware.ts similarity index 93% rename from packages/files/src/Controller/ValetTokenAuthMiddleware.ts rename to packages/files/src/Infra/InversifyExpress/Middleware/ValetTokenAuthMiddleware.ts index e8cac43ea..8cb5c842d 100644 --- a/packages/files/src/Controller/ValetTokenAuthMiddleware.ts +++ b/packages/files/src/Infra/InversifyExpress/Middleware/ValetTokenAuthMiddleware.ts @@ -4,13 +4,13 @@ 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' +import TYPES from '../../../Bootstrap/Types' @injectable() export class ValetTokenAuthMiddleware extends BaseMiddleware { constructor( - @inject(TYPES.ValetTokenDecoder) private tokenDecoder: TokenDecoderInterface, - @inject(TYPES.Logger) private logger: Logger, + @inject(TYPES.Files_ValetTokenDecoder) private tokenDecoder: TokenDecoderInterface, + @inject(TYPES.Files_Logger) private logger: Logger, ) { super() } diff --git a/packages/files/src/Infra/InversifyExpress/index.ts b/packages/files/src/Infra/InversifyExpress/index.ts new file mode 100644 index 000000000..e26976a72 --- /dev/null +++ b/packages/files/src/Infra/InversifyExpress/index.ts @@ -0,0 +1 @@ +export * from './InversifyExpressFilesController' diff --git a/packages/files/src/Infra/Redis/RedisUploadRepository.ts b/packages/files/src/Infra/Redis/RedisUploadRepository.ts index 931650d8d..0289bbabf 100644 --- a/packages/files/src/Infra/Redis/RedisUploadRepository.ts +++ b/packages/files/src/Infra/Redis/RedisUploadRepository.ts @@ -10,7 +10,7 @@ export class RedisUploadRepository implements UploadRepositoryInterface { private readonly UPLOAD_CHUNKS_PREFIX = 'upload-chunks' private readonly UPLOAD_SESSION_DEFAULT_TTL = 7200 - constructor(@inject(TYPES.Redis) private redisClient: IORedis.Redis) {} + constructor(@inject(TYPES.Files_Redis) private redisClient: IORedis.Redis) {} async storeUploadSession(filePath: string, uploadId: string): Promise { await this.redisClient.setex(`${this.UPLOAD_SESSION_PREFIX}:${filePath}`, this.UPLOAD_SESSION_DEFAULT_TTL, uploadId) diff --git a/packages/files/src/Infra/S3/S3FileDownloader.ts b/packages/files/src/Infra/S3/S3FileDownloader.ts index 165c5a25f..f3ed638c8 100644 --- a/packages/files/src/Infra/S3/S3FileDownloader.ts +++ b/packages/files/src/Infra/S3/S3FileDownloader.ts @@ -8,8 +8,8 @@ import { FileDownloaderInterface } from '../../Domain/Services/FileDownloaderInt @injectable() export class S3FileDownloader implements FileDownloaderInterface { constructor( - @inject(TYPES.S3) private s3Client: S3Client, - @inject(TYPES.S3_BUCKET_NAME) private s3BuckeName: string, + @inject(TYPES.Files_S3) private s3Client: S3Client, + @inject(TYPES.Files_S3_BUCKET_NAME) private s3BuckeName: string, ) {} async createDownloadStream(filePath: string, startRange: number, endRange: number): Promise { diff --git a/packages/files/src/Infra/S3/S3FileRemover.ts b/packages/files/src/Infra/S3/S3FileRemover.ts index 4505bbca3..2817c4ddd 100644 --- a/packages/files/src/Infra/S3/S3FileRemover.ts +++ b/packages/files/src/Infra/S3/S3FileRemover.ts @@ -14,8 +14,8 @@ import { RemovedFileDescription } from '../../Domain/File/RemovedFileDescription @injectable() export class S3FileRemover implements FileRemoverInterface { constructor( - @inject(TYPES.S3) private s3Client: S3Client, - @inject(TYPES.S3_BUCKET_NAME) private s3BuckeName: string, + @inject(TYPES.Files_S3) private s3Client: S3Client, + @inject(TYPES.Files_S3_BUCKET_NAME) private s3BuckeName: string, ) {} async markFilesToBeRemoved(userUuid: string): Promise> { diff --git a/packages/files/src/Infra/S3/S3FileUploader.ts b/packages/files/src/Infra/S3/S3FileUploader.ts index 338665352..bb49e7bd2 100644 --- a/packages/files/src/Infra/S3/S3FileUploader.ts +++ b/packages/files/src/Infra/S3/S3FileUploader.ts @@ -15,8 +15,8 @@ import { ChunkId } from '../../Domain/Upload/ChunkId' @injectable() export class S3FileUploader implements FileUploaderInterface { constructor( - @inject(TYPES.S3) private s3Client: S3Client, - @inject(TYPES.S3_BUCKET_NAME) private s3BuckeName: string, + @inject(TYPES.Files_S3) private s3Client: S3Client, + @inject(TYPES.Files_S3_BUCKET_NAME) private s3BuckeName: string, ) {} async createUploadSession(filePath: string): Promise { diff --git a/packages/files/src/index.ts b/packages/files/src/index.ts new file mode 100644 index 000000000..e03cb105a --- /dev/null +++ b/packages/files/src/index.ts @@ -0,0 +1,2 @@ +export * from './Bootstrap' +export * from './Infra/InversifyExpress' diff --git a/packages/home-server/.env.sample b/packages/home-server/.env.sample index e0831c013..7de2b7a46 100644 --- a/packages/home-server/.env.sample +++ b/packages/home-server/.env.sample @@ -15,5 +15,6 @@ JWT_SECRET= AUTH_JWT_SECRET= ENCRYPTION_SERVER_KEY= PSEUDO_KEY_PARAMS_KEY= +VALET_TOKEN_SECRET= FILES_SERVER_URL= diff --git a/packages/home-server/bin/server.ts b/packages/home-server/bin/server.ts index 2080f40a2..cc3ac8b35 100644 --- a/packages/home-server/bin/server.ts +++ b/packages/home-server/bin/server.ts @@ -2,6 +2,7 @@ import 'reflect-metadata' import { ControllerContainer, ServiceContainer } from '@standardnotes/domain-core' import { Service as ApiGatewayService, TYPES as ApiGatewayTYPES } from '@standardnotes/api-gateway' +import { Service as FilesService } from '@standardnotes/files-server' import { DirectCallDomainEventPublisher } from '@standardnotes/domain-events-infra' import { Service as AuthService } from '@standardnotes/auth-server' import { Service as SyncingService } from '@standardnotes/syncing-server' @@ -26,12 +27,14 @@ const startServer = async (): Promise => { const authService = new AuthService(serviceContainer, controllerContainer, directCallDomainEventPublisher) const syncingService = new SyncingService(serviceContainer, controllerContainer, directCallDomainEventPublisher) const revisionsService = new RevisionsService(serviceContainer, controllerContainer, directCallDomainEventPublisher) + const filesService = new FilesService(serviceContainer, directCallDomainEventPublisher) const container = Container.merge( (await apiGatewayService.getContainer()) as Container, (await authService.getContainer()) as Container, (await syncingService.getContainer()) as Container, (await revisionsService.getContainer()) as Container, + (await filesService.getContainer()) as Container, ) const env: Env = new Env() diff --git a/packages/home-server/package.json b/packages/home-server/package.json index 49289d7e3..20ac14a42 100644 --- a/packages/home-server/package.json +++ b/packages/home-server/package.json @@ -22,6 +22,7 @@ "@standardnotes/auth-server": "workspace:^", "@standardnotes/domain-core": "workspace:^", "@standardnotes/domain-events-infra": "workspace:^", + "@standardnotes/files-server": "workspace:^", "@standardnotes/revisions-server": "workspace:^", "@standardnotes/syncing-server": "workspace:^", "cors": "2.8.5", diff --git a/packages/revisions/src/Bootstrap/Container.ts b/packages/revisions/src/Bootstrap/Container.ts index 130c41a2c..d70a1ffa4 100644 --- a/packages/revisions/src/Bootstrap/Container.ts +++ b/packages/revisions/src/Bootstrap/Container.ts @@ -45,7 +45,7 @@ import { CopyRevisions } from '../Domain/UseCase/CopyRevisions/CopyRevisions' import { FSDumpRepository } from '../Infra/FS/FSDumpRepository' import { S3DumpRepository } from '../Infra/S3/S3ItemDumpRepository' import { RevisionItemStringMapper } from '../Mapping/RevisionItemStringMapper' -import { InversifyExpressRevisionsController } from '../Infra/InversifyExpress/InversifyExpressRevisionsController' +import { HomeServerRevisionsController } from '../Infra/InversifyExpress/HomeServer/HomeServerRevisionsController' // eslint-disable-next-line @typescript-eslint/no-var-requires const newrelicFormatter = require('@newrelic/winston-enricher') @@ -337,9 +337,9 @@ export class ContainerConfigLoader { // Inversify Controllers if (isConfiguredForHomeServer) { container - .bind(TYPES.Revisions_InversifyExpressRevisionsController) + .bind(TYPES.Revisions_HomeServerRevisionsController) .toConstantValue( - new InversifyExpressRevisionsController( + new HomeServerRevisionsController( container.get(TYPES.Revisions_RevisionsController), container.get(TYPES.Revisions_ControllerContainer), ), diff --git a/packages/revisions/src/Bootstrap/Types.ts b/packages/revisions/src/Bootstrap/Types.ts index 6669d157c..7b744fce5 100644 --- a/packages/revisions/src/Bootstrap/Types.ts +++ b/packages/revisions/src/Bootstrap/Types.ts @@ -43,7 +43,7 @@ const TYPES = { Revisions_DomainEventMessageHandler: Symbol.for('Revisions_DomainEventMessageHandler'), Revisions_Timer: Symbol.for('Revisions_Timer'), // Inversify Express Controllers - Revisions_InversifyExpressRevisionsController: Symbol.for('Revisions_InversifyExpressRevisionsController'), + Revisions_HomeServerRevisionsController: Symbol.for('Revisions_HomeServerRevisionsController'), } export default TYPES diff --git a/packages/revisions/src/Infra/InversifyExpress/HomeServer/HomeServerRevisionsController.ts b/packages/revisions/src/Infra/InversifyExpress/HomeServer/HomeServerRevisionsController.ts new file mode 100644 index 000000000..eca99e991 --- /dev/null +++ b/packages/revisions/src/Infra/InversifyExpress/HomeServer/HomeServerRevisionsController.ts @@ -0,0 +1,47 @@ +import { BaseHttpController, results } from 'inversify-express-utils' +import { Request, Response } from 'express' +import { ControllerContainerInterface } from '@standardnotes/domain-core' + +import { RevisionsController } from '../../../Controller/RevisionsController' + +export class HomeServerRevisionsController extends BaseHttpController { + constructor( + protected revisionsController: RevisionsController, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('revisions.revisions.getRevisions', this.getRevisions.bind(this)) + this.controllerContainer.register('revisions.revisions.getRevision', this.getRevision.bind(this)) + this.controllerContainer.register('revisions.revisions.deleteRevision', this.deleteRevision.bind(this)) + } + } + + async getRevisions(req: Request, response: Response): Promise { + const result = await this.revisionsController.getRevisions({ + itemUuid: req.params.itemUuid, + userUuid: response.locals.user.uuid, + }) + + return this.json(result.data, result.status) + } + + async getRevision(req: Request, response: Response): Promise { + const result = await this.revisionsController.getRevision({ + revisionUuid: req.params.uuid, + userUuid: response.locals.user.uuid, + }) + + return this.json(result.data, result.status) + } + + async deleteRevision(req: Request, response: Response): Promise { + const result = await this.revisionsController.deleteRevision({ + revisionUuid: req.params.uuid, + userUuid: response.locals.user.uuid, + }) + + return this.json(result.data, result.status) + } +} diff --git a/packages/revisions/src/Infra/InversifyExpress/InversifyExpressRevisionsController.ts b/packages/revisions/src/Infra/InversifyExpress/InversifyExpressRevisionsController.ts index 4370ed993..01389d700 100644 --- a/packages/revisions/src/Infra/InversifyExpress/InversifyExpressRevisionsController.ts +++ b/packages/revisions/src/Infra/InversifyExpress/InversifyExpressRevisionsController.ts @@ -1,51 +1,29 @@ import { Request, Response } from 'express' -import { BaseHttpController, controller, httpDelete, httpGet, results } from 'inversify-express-utils' +import { controller, httpDelete, httpGet, results } from 'inversify-express-utils' import { inject } from 'inversify' -import { ControllerContainerInterface } from '@standardnotes/domain-core' import TYPES from '../../Bootstrap/Types' import { RevisionsController } from '../../Controller/RevisionsController' +import { HomeServerRevisionsController } from './HomeServer/HomeServerRevisionsController' @controller('/items/:itemUuid/revisions', TYPES.Revisions_ApiGatewayAuthMiddleware) -export class InversifyExpressRevisionsController extends BaseHttpController { - constructor( - @inject(TYPES.Revisions_RevisionsController) private revisionsController: RevisionsController, - @inject(TYPES.Revisions_ControllerContainer) private controllerContainer: ControllerContainerInterface, - ) { - super() - - this.controllerContainer.register('revisions.revisions.getRevisions', this.getRevisions.bind(this)) - this.controllerContainer.register('revisions.revisions.getRevision', this.getRevision.bind(this)) - this.controllerContainer.register('revisions.revisions.deleteRevision', this.deleteRevision.bind(this)) +export class InversifyExpressRevisionsController extends HomeServerRevisionsController { + constructor(@inject(TYPES.Revisions_RevisionsController) override revisionsController: RevisionsController) { + super(revisionsController) } @httpGet('/') - public async getRevisions(req: Request, response: Response): Promise { - const result = await this.revisionsController.getRevisions({ - itemUuid: req.params.itemUuid, - userUuid: response.locals.user.uuid, - }) - - return this.json(result.data, result.status) + override async getRevisions(req: Request, response: Response): Promise { + return super.getRevisions(req, response) } @httpGet('/:uuid') - public async getRevision(req: Request, response: Response): Promise { - const result = await this.revisionsController.getRevision({ - revisionUuid: req.params.uuid, - userUuid: response.locals.user.uuid, - }) - - return this.json(result.data, result.status) + override async getRevision(req: Request, response: Response): Promise { + return super.getRevision(req, response) } @httpDelete('/:uuid') - public async deleteRevision(req: Request, response: Response): Promise { - const result = await this.revisionsController.deleteRevision({ - revisionUuid: req.params.uuid, - userUuid: response.locals.user.uuid, - }) - - return this.json(result.data, result.status) + override async deleteRevision(req: Request, response: Response): Promise { + return super.deleteRevision(req, response) } } diff --git a/packages/syncing-server/src/Bootstrap/Container.ts b/packages/syncing-server/src/Bootstrap/Container.ts index bc516466b..03a79e7b9 100644 --- a/packages/syncing-server/src/Bootstrap/Container.ts +++ b/packages/syncing-server/src/Bootstrap/Container.ts @@ -71,7 +71,7 @@ import { FSItemBackupService } from '../Infra/FS/FSItemBackupService' import { AuthHttpService } from '../Infra/HTTP/AuthHttpService' import { S3ItemBackupService } from '../Infra/S3/S3ItemBackupService' import { ControllerContainer, ControllerContainerInterface } from '@standardnotes/domain-core' -import { InversifyExpressItemsController } from '../Infra/InversifyExpressUtils/InversifyExpressItemsController' +import { HomeServerItemsController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerItemsController' // eslint-disable-next-line @typescript-eslint/no-var-requires const newrelicFormatter = require('@newrelic/winston-enricher') @@ -499,9 +499,9 @@ export class ContainerConfigLoader { if (isConfiguredForHomeServer) { container - .bind(TYPES.Sync_InversifyExpressItemsController) + .bind(TYPES.Sync_HomeServerItemsController) .toConstantValue( - new InversifyExpressItemsController( + new HomeServerItemsController( container.get(TYPES.Sync_SyncItems), container.get(TYPES.Sync_CheckIntegrity), container.get(TYPES.Sync_GetItem), diff --git a/packages/syncing-server/src/Bootstrap/Types.ts b/packages/syncing-server/src/Bootstrap/Types.ts index a626da18f..af1665f29 100644 --- a/packages/syncing-server/src/Bootstrap/Types.ts +++ b/packages/syncing-server/src/Bootstrap/Types.ts @@ -67,7 +67,7 @@ const TYPES = { Sync_ItemFactory: Symbol.for('Sync_ItemFactory'), Sync_ItemTransferCalculator: Symbol.for('Sync_ItemTransferCalculator'), Sync_ControllerContainer: Symbol.for('Sync_ControllerContainer'), - Sync_InversifyExpressItemsController: Symbol.for('Sync_InversifyExpressItemsController'), + Sync_HomeServerItemsController: Symbol.for('Sync_HomeServerItemsController'), } export default TYPES diff --git a/packages/syncing-server/src/Infra/InversifyExpressUtils/HomeServer/HomeServerItemsController.ts b/packages/syncing-server/src/Infra/InversifyExpressUtils/HomeServer/HomeServerItemsController.ts new file mode 100644 index 000000000..9e0a7268c --- /dev/null +++ b/packages/syncing-server/src/Infra/InversifyExpressUtils/HomeServer/HomeServerItemsController.ts @@ -0,0 +1,85 @@ +import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { BaseHttpController, results } from 'inversify-express-utils' +import { Request, Response } from 'express' + +import { Item } from '../../../Domain/Item/Item' +import { SyncResponseFactoryResolverInterface } from '../../../Domain/Item/SyncResponse/SyncResponseFactoryResolverInterface' +import { CheckIntegrity } from '../../../Domain/UseCase/CheckIntegrity/CheckIntegrity' +import { GetItem } from '../../../Domain/UseCase/GetItem/GetItem' +import { SyncItems } from '../../../Domain/UseCase/SyncItems' +import { ItemProjection } from '../../../Projection/ItemProjection' +import { ProjectorInterface } from '../../../Projection/ProjectorInterface' +import { ApiVersion } from '../../../Domain/Api/ApiVersion' + +export class HomeServerItemsController extends BaseHttpController { + constructor( + protected syncItems: SyncItems, + protected checkIntegrity: CheckIntegrity, + protected getItem: GetItem, + protected itemProjector: ProjectorInterface, + protected syncResponseFactoryResolver: SyncResponseFactoryResolverInterface, + private controllerContainer?: ControllerContainerInterface, + ) { + super() + + if (this.controllerContainer !== undefined) { + this.controllerContainer.register('sync.items.sync', this.sync.bind(this)) + this.controllerContainer.register('sync.items.check_integrity', this.checkItemsIntegrity.bind(this)) + this.controllerContainer.register('sync.items.get_item', this.getSingleItem.bind(this)) + } + } + + async sync(request: Request, response: Response): Promise { + let itemHashes = [] + if ('items' in request.body) { + itemHashes = request.body.items + } + + const syncResult = await this.syncItems.execute({ + userUuid: response.locals.user.uuid, + itemHashes, + computeIntegrityHash: request.body.compute_integrity === true, + syncToken: request.body.sync_token, + cursorToken: request.body.cursor_token, + limit: request.body.limit, + contentType: request.body.content_type, + apiVersion: request.body.api ?? ApiVersion.v20161215, + readOnlyAccess: response.locals.readOnlyAccess, + sessionUuid: response.locals.session ? response.locals.session.uuid : null, + }) + + const syncResponse = await this.syncResponseFactoryResolver + .resolveSyncResponseFactoryVersion(request.body.api) + .createResponse(syncResult) + + return this.json(syncResponse) + } + + async checkItemsIntegrity(request: Request, response: Response): Promise { + let integrityPayloads = [] + if ('integrityPayloads' in request.body) { + integrityPayloads = request.body.integrityPayloads + } + + const result = await this.checkIntegrity.execute({ + userUuid: response.locals.user.uuid, + integrityPayloads, + freeUser: response.locals.freeUser, + }) + + return this.json(result) + } + + async getSingleItem(request: Request, response: Response): Promise { + const result = await this.getItem.execute({ + userUuid: response.locals.user.uuid, + itemUuid: request.params.uuid, + }) + + if (!result.success) { + return this.notFound() + } + + return this.json({ item: await this.itemProjector.projectFull(result.item) }) + } +} diff --git a/packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressItemsController.spec.ts b/packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressItemsController.spec.ts index 31d5f3327..7239ffc38 100644 --- a/packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressItemsController.spec.ts +++ b/packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressItemsController.spec.ts @@ -15,7 +15,6 @@ import { SyncResponseFactoryResolverInterface } from '../../Domain/Item/SyncResp import { CheckIntegrity } from '../../Domain/UseCase/CheckIntegrity/CheckIntegrity' import { GetItem } from '../../Domain/UseCase/GetItem/GetItem' import { SyncItems } from '../../Domain/UseCase/SyncItems' -import { ControllerContainerInterface } from '@standardnotes/domain-core' describe('InversifyExpressItemsController', () => { let syncItems: SyncItems @@ -27,22 +26,11 @@ describe('InversifyExpressItemsController', () => { let syncResponceFactoryResolver: SyncResponseFactoryResolverInterface let syncResponseFactory: SyncResponseFactoryInterface let syncResponse: SyncResponse20200115 - let controllerContainer: ControllerContainerInterface const createController = () => - new InversifyExpressItemsController( - syncItems, - checkIntegrity, - getItem, - itemProjector, - syncResponceFactoryResolver, - controllerContainer, - ) + new InversifyExpressItemsController(syncItems, checkIntegrity, getItem, itemProjector, syncResponceFactoryResolver) beforeEach(() => { - controllerContainer = {} as jest.Mocked - controllerContainer.register = jest.fn() - itemProjector = {} as jest.Mocked> itemProjector.projectFull = jest.fn().mockReturnValue({ foo: 'bar' }) diff --git a/packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressItemsController.ts b/packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressItemsController.ts index a7dd81888..7a13b23f8 100644 --- a/packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressItemsController.ts +++ b/packages/syncing-server/src/Infra/InversifyExpressUtils/InversifyExpressItemsController.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express' import { inject } from 'inversify' -import { BaseHttpController, controller, httpGet, httpPost, results } from 'inversify-express-utils' +import { controller, httpGet, httpPost, results } from 'inversify-express-utils' import TYPES from '../../Bootstrap/Types' import { Item } from '../../Domain/Item/Item' @@ -10,84 +10,36 @@ import { GetItem } from '../../Domain/UseCase/GetItem/GetItem' import { SyncItems } from '../../Domain/UseCase/SyncItems' import { ItemProjection } from '../../Projection/ItemProjection' import { ProjectorInterface } from '../../Projection/ProjectorInterface' -import { ApiVersion } from '../../Domain/Api/ApiVersion' -import { ControllerContainerInterface } from '@standardnotes/domain-core' +import { HomeServerItemsController } from './HomeServer/HomeServerItemsController' @controller('/items', TYPES.Sync_AuthMiddleware) -export class InversifyExpressItemsController extends BaseHttpController { +export class InversifyExpressItemsController extends HomeServerItemsController { constructor( - @inject(TYPES.Sync_SyncItems) private syncItems: SyncItems, - @inject(TYPES.Sync_CheckIntegrity) private checkIntegrity: CheckIntegrity, - @inject(TYPES.Sync_GetItem) private getItem: GetItem, - @inject(TYPES.Sync_ItemProjector) private itemProjector: ProjectorInterface, + @inject(TYPES.Sync_SyncItems) override syncItems: SyncItems, + @inject(TYPES.Sync_CheckIntegrity) override checkIntegrity: CheckIntegrity, + @inject(TYPES.Sync_GetItem) override getItem: GetItem, + @inject(TYPES.Sync_ItemProjector) override itemProjector: ProjectorInterface, @inject(TYPES.Sync_SyncResponseFactoryResolver) - private syncResponseFactoryResolver: SyncResponseFactoryResolverInterface, - @inject(TYPES.Sync_ControllerContainer) private controllerContainer: ControllerContainerInterface, + override syncResponseFactoryResolver: SyncResponseFactoryResolverInterface, ) { - super() - - this.controllerContainer.register('sync.items.sync', this.sync.bind(this)) - this.controllerContainer.register('sync.items.check_integrity', this.checkItemsIntegrity.bind(this)) - this.controllerContainer.register('sync.items.get_item', this.getSingleItem.bind(this)) + super(syncItems, checkIntegrity, getItem, itemProjector, syncResponseFactoryResolver) } @httpPost('/sync') - public async sync(request: Request, response: Response): Promise { - let itemHashes = [] - if ('items' in request.body) { - itemHashes = request.body.items - } - - const syncResult = await this.syncItems.execute({ - userUuid: response.locals.user.uuid, - itemHashes, - computeIntegrityHash: request.body.compute_integrity === true, - syncToken: request.body.sync_token, - cursorToken: request.body.cursor_token, - limit: request.body.limit, - contentType: request.body.content_type, - apiVersion: request.body.api ?? ApiVersion.v20161215, - readOnlyAccess: response.locals.readOnlyAccess, - sessionUuid: response.locals.session ? response.locals.session.uuid : null, - }) - - const syncResponse = await this.syncResponseFactoryResolver - .resolveSyncResponseFactoryVersion(request.body.api) - .createResponse(syncResult) - - return this.json(syncResponse) + override async sync(request: Request, response: Response): Promise { + return super.sync(request, response) } @httpPost('/check-integrity') - public async checkItemsIntegrity(request: Request, response: Response): Promise { - let integrityPayloads = [] - if ('integrityPayloads' in request.body) { - integrityPayloads = request.body.integrityPayloads - } - - const result = await this.checkIntegrity.execute({ - userUuid: response.locals.user.uuid, - integrityPayloads, - freeUser: response.locals.freeUser, - }) - - return this.json(result) + override async checkItemsIntegrity(request: Request, response: Response): Promise { + return super.checkItemsIntegrity(request, response) } @httpGet('/:uuid') - public async getSingleItem( + override async getSingleItem( request: Request, response: Response, ): Promise { - const result = await this.getItem.execute({ - userUuid: response.locals.user.uuid, - itemUuid: request.params.uuid, - }) - - if (!result.success) { - return this.notFound() - } - - return this.json({ item: await this.itemProjector.projectFull(result.item) }) + return super.getSingleItem(request, response) } } diff --git a/yarn.lock b/yarn.lock index 8aae9fc16..30b2be6f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4188,7 +4188,7 @@ __metadata: languageName: node linkType: hard -"@standardnotes/files-server@workspace:packages/files": +"@standardnotes/files-server@workspace:^, @standardnotes/files-server@workspace:packages/files": version: 0.0.0-use.local resolution: "@standardnotes/files-server@workspace:packages/files" dependencies: @@ -4249,6 +4249,7 @@ __metadata: "@standardnotes/auth-server": "workspace:^" "@standardnotes/domain-core": "workspace:^" "@standardnotes/domain-events-infra": "workspace:^" + "@standardnotes/files-server": "workspace:^" "@standardnotes/revisions-server": "workspace:^" "@standardnotes/syncing-server": "workspace:^" "@types/cors": "npm:^2.8.9"