Forráskód Böngészése

feat: refactor auth middleware to handle required and optional cross service token scenarios (#612)

* wip: fix variable name

* wip: remove redundant middleware in auth

* fix: auth middleware refactor

* fix(auth): fetching user for key params

* fix(auth): specs

* fix(auth): registering session controller endpoints
Karol Sójko 2 éve
szülő
commit
1e4c7d0f31
45 módosított fájl, 297 hozzáadás és 474 törlés
  1. 8 2
      packages/api-gateway/src/Bootstrap/Container.ts
  2. 2 1
      packages/api-gateway/src/Bootstrap/Types.ts
  3. 30 24
      packages/api-gateway/src/Controller/AuthMiddleware.ts
  4. 3 3
      packages/api-gateway/src/Controller/LegacyController.ts
  5. 51 0
      packages/api-gateway/src/Controller/OptionalCrossServiceTokenMiddleware.ts
  6. 57 0
      packages/api-gateway/src/Controller/RequiredCrossServiceTokenMiddleware.ts
  7. 3 3
      packages/api-gateway/src/Controller/v1/ActionsController.ts
  8. 4 4
      packages/api-gateway/src/Controller/v1/AuthenticatorsController.ts
  9. 1 1
      packages/api-gateway/src/Controller/v1/FilesController.ts
  10. 1 1
      packages/api-gateway/src/Controller/v1/ItemsController.ts
  11. 1 1
      packages/api-gateway/src/Controller/v1/RevisionsController.ts
  12. 3 3
      packages/api-gateway/src/Controller/v1/SessionsController.ts
  13. 4 4
      packages/api-gateway/src/Controller/v1/SubscriptionInvitesController.ts
  14. 1 1
      packages/api-gateway/src/Controller/v1/TokensController.ts
  15. 15 15
      packages/api-gateway/src/Controller/v1/UsersController.ts
  16. 1 1
      packages/api-gateway/src/Controller/v1/WebSocketsController.ts
  17. 4 4
      packages/api-gateway/src/Controller/v2/ActionsControllerV2.ts
  18. 1 1
      packages/api-gateway/src/Controller/v2/RevisionsControllerV2.ts
  19. 7 7
      packages/auth/src/Bootstrap/Container.ts
  20. 2 3
      packages/auth/src/Bootstrap/Types.ts
  21. 2 4
      packages/auth/src/Domain/UseCase/GetUserKeyParams/GetUserKeyParams.spec.ts
  22. 0 10
      packages/auth/src/Domain/UseCase/GetUserKeyParams/GetUserKeyParams.ts
  23. 0 3
      packages/auth/src/Domain/UseCase/GetUserKeyParams/GetUserKeyParamsDTOV1Unchallenged.ts
  24. 0 3
      packages/auth/src/Domain/UseCase/GetUserKeyParams/GetUserKeyParamsDTOV2Challenged.ts
  25. 4 6
      packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAuthController.ts
  26. 4 4
      packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAuthenticatorsController.ts
  27. 1 1
      packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressFeaturesController.ts
  28. 1 1
      packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressListedController.ts
  29. 5 5
      packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionController.ts
  30. 1 1
      packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionsController.ts
  31. 4 4
      packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSettingsController.ts
  32. 4 4
      packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionInvitesController.ts
  33. 1 1
      packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionSettingsController.ts
  34. 1 1
      packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionTokensController.ts
  35. 1 1
      packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUserRequestsController.ts
  36. 3 3
      packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUsersController.ts
  37. 1 1
      packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressValetTokenController.ts
  38. 0 99
      packages/auth/src/Infra/InversifyExpressUtils/Middleware/ApiGatewayAuthMiddleware.spec.ts
  39. 6 19
      packages/auth/src/Infra/InversifyExpressUtils/Middleware/ApiGatewayAuthMiddleware.ts
  40. 0 79
      packages/auth/src/Infra/InversifyExpressUtils/Middleware/AuthMiddleware.spec.ts
  41. 0 45
      packages/auth/src/Infra/InversifyExpressUtils/Middleware/AuthMiddleware.ts
  42. 0 68
      packages/auth/src/Infra/InversifyExpressUtils/Middleware/AuthMiddlewareWithoutResponse.spec.ts
  43. 0 32
      packages/auth/src/Infra/InversifyExpressUtils/Middleware/AuthMiddlewareWithoutResponse.ts
  44. 27 0
      packages/auth/src/Infra/InversifyExpressUtils/Middleware/OptionalCrossServiceTokenMiddleware.ts
  45. 32 0
      packages/auth/src/Infra/InversifyExpressUtils/Middleware/RequiredCrossServiceTokenMiddleware.ts

+ 8 - 2
packages/api-gateway/src/Bootstrap/Container.ts

@@ -8,7 +8,6 @@ import { Timer, TimerInterface } from '@standardnotes/time'
 
 import { Env } from './Env'
 import { TYPES } from './Types'
-import { AuthMiddleware } from '../Controller/AuthMiddleware'
 import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface'
 import { HttpServiceProxy } from '../Service/Http/HttpServiceProxy'
 import { SubscriptionTokenAuthMiddleware } from '../Controller/SubscriptionTokenAuthMiddleware'
@@ -20,6 +19,8 @@ import { DirectCallServiceProxy } from '../Service/Proxy/DirectCallServiceProxy'
 import { ServiceContainerInterface } from '@standardnotes/domain-core'
 import { EndpointResolverInterface } from '../Service/Resolver/EndpointResolverInterface'
 import { EndpointResolver } from '../Service/Resolver/EndpointResolver'
+import { RequiredCrossServiceTokenMiddleware } from '../Controller/RequiredCrossServiceTokenMiddleware'
+import { OptionalCrossServiceTokenMiddleware } from '../Controller/OptionalCrossServiceTokenMiddleware'
 
 // eslint-disable-next-line @typescript-eslint/no-var-requires
 const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -77,7 +78,12 @@ export class ContainerConfigLoader {
     container.bind(TYPES.CROSS_SERVICE_TOKEN_CACHE_TTL).toConstantValue(+env.get('CROSS_SERVICE_TOKEN_CACHE_TTL', true))
 
     // Middleware
-    container.bind<AuthMiddleware>(TYPES.AuthMiddleware).to(AuthMiddleware)
+    container
+      .bind<RequiredCrossServiceTokenMiddleware>(TYPES.RequiredCrossServiceTokenMiddleware)
+      .to(RequiredCrossServiceTokenMiddleware)
+    container
+      .bind<OptionalCrossServiceTokenMiddleware>(TYPES.OptionalCrossServiceTokenMiddleware)
+      .to(OptionalCrossServiceTokenMiddleware)
     container.bind<WebSocketAuthMiddleware>(TYPES.WebSocketAuthMiddleware).to(WebSocketAuthMiddleware)
     container
       .bind<SubscriptionTokenAuthMiddleware>(TYPES.SubscriptionTokenAuthMiddleware)

+ 2 - 1
packages/api-gateway/src/Bootstrap/Types.ts

@@ -15,7 +15,8 @@ export const TYPES = {
   VERSION: Symbol.for('VERSION'),
   CROSS_SERVICE_TOKEN_CACHE_TTL: Symbol.for('CROSS_SERVICE_TOKEN_CACHE_TTL'),
   // Middleware
-  AuthMiddleware: Symbol.for('AuthMiddleware'),
+  RequiredCrossServiceTokenMiddleware: Symbol.for('RequiredCrossServiceTokenMiddleware'),
+  OptionalCrossServiceTokenMiddleware: Symbol.for('OptionalCrossServiceTokenMiddleware'),
   WebSocketAuthMiddleware: Symbol.for('WebSocketAuthMiddleware'),
   SubscriptionTokenAuthMiddleware: Symbol.for('SubscriptionTokenAuthMiddleware'),
   // Services

+ 30 - 24
packages/api-gateway/src/Controller/AuthMiddleware.ts

@@ -2,43 +2,33 @@ import { CrossServiceTokenData } from '@standardnotes/security'
 import { RoleName } from '@standardnotes/domain-core'
 import { TimerInterface } from '@standardnotes/time'
 import { NextFunction, Request, Response } from 'express'
-import { inject, injectable } from 'inversify'
 import { BaseMiddleware } from 'inversify-express-utils'
 import { verify } from 'jsonwebtoken'
 import { AxiosError } from 'axios'
 import { Logger } from 'winston'
 
-import { TYPES } from '../Bootstrap/Types'
 import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
 import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface'
 
-@injectable()
-export class AuthMiddleware extends BaseMiddleware {
+export abstract class AuthMiddleware extends BaseMiddleware {
   constructor(
-    @inject(TYPES.ServiceProxy) private serviceProxy: ServiceProxyInterface,
-    @inject(TYPES.AUTH_JWT_SECRET) private jwtSecret: string,
-    @inject(TYPES.CROSS_SERVICE_TOKEN_CACHE_TTL) private crossServiceTokenCacheTTL: number,
-    @inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
-    @inject(TYPES.Timer) private timer: TimerInterface,
-    @inject(TYPES.Logger) private logger: Logger,
+    private serviceProxy: ServiceProxyInterface,
+    private jwtSecret: string,
+    private crossServiceTokenCacheTTL: number,
+    private crossServiceTokenCache: CrossServiceTokenCacheInterface,
+    private timer: TimerInterface,
+    private logger: Logger,
   ) {
     super()
   }
 
   async handler(request: Request, response: Response, next: NextFunction): Promise<void> {
-    const authHeaderValue = request.headers.authorization as string
-
-    if (!authHeaderValue) {
-      response.status(401).send({
-        error: {
-          tag: 'invalid-auth',
-          message: 'Invalid login credentials.',
-        },
-      })
-
+    if (!this.handleMissingAuthHeader(request.headers.authorization, response, next)) {
       return
     }
 
+    const authHeaderValue = request.headers.authorization as string
+
     try {
       let crossServiceTokenFetchedFromCache = true
       let crossServiceToken = null
@@ -49,10 +39,7 @@ export class AuthMiddleware extends BaseMiddleware {
       if (crossServiceToken === null) {
         const authResponse = await this.serviceProxy.validateSession(authHeaderValue)
 
-        if (authResponse.status > 200) {
-          response.setHeader('content-type', authResponse.headers.contentType)
-          response.status(authResponse.status).send(authResponse.data)
-
+        if (!this.handleSessionValidationResponse(authResponse, response, next)) {
           return
         }
 
@@ -78,6 +65,7 @@ export class AuthMiddleware extends BaseMiddleware {
       }
 
       response.locals.user = decodedToken.user
+      response.locals.session = decodedToken.session
       response.locals.roles = decodedToken.roles
     } catch (error) {
       const errorMessage = (error as AxiosError).isAxiosError
@@ -105,6 +93,24 @@ export class AuthMiddleware extends BaseMiddleware {
     return next()
   }
 
+  protected abstract handleSessionValidationResponse(
+    authResponse: {
+      status: number
+      data: unknown
+      headers: {
+        contentType: string
+      }
+    },
+    response: Response,
+    next: NextFunction,
+  ): boolean
+
+  protected abstract handleMissingAuthHeader(
+    authHeaderValue: string | undefined,
+    response: Response,
+    next: NextFunction,
+  ): boolean
+
   private getCrossServiceTokenCacheExpireTimestamp(token: CrossServiceTokenData): number {
     const crossServiceTokenDefaultCacheExpiration = this.timer.getTimestampInSeconds() + this.crossServiceTokenCacheTTL
 

+ 3 - 3
packages/api-gateway/src/Controller/LegacyController.ts

@@ -29,17 +29,17 @@ export class LegacyController extends BaseHttpController {
     ])
   }
 
-  @httpPost('/items/sync', TYPES.AuthMiddleware)
+  @httpPost('/items/sync', TYPES.RequiredCrossServiceTokenMiddleware)
   async legacyItemsSync(request: Request, response: Response): Promise<void> {
     await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
   }
 
-  @httpGet('/items/:item_id/revisions', TYPES.AuthMiddleware)
+  @httpGet('/items/:item_id/revisions', TYPES.RequiredCrossServiceTokenMiddleware)
   async legacyGetRevisions(request: Request, response: Response): Promise<void> {
     await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
   }
 
-  @httpGet('/items/:item_id/revisions/:id', TYPES.AuthMiddleware)
+  @httpGet('/items/:item_id/revisions/:id', TYPES.RequiredCrossServiceTokenMiddleware)
   async legacyGetRevision(request: Request, response: Response): Promise<void> {
     await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
   }

+ 51 - 0
packages/api-gateway/src/Controller/OptionalCrossServiceTokenMiddleware.ts

@@ -0,0 +1,51 @@
+import { TimerInterface } from '@standardnotes/time'
+import { NextFunction, Response } from 'express'
+import { inject, injectable } from 'inversify'
+import { Logger } from 'winston'
+
+import { TYPES } from '../Bootstrap/Types'
+import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
+import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface'
+import { AuthMiddleware } from './AuthMiddleware'
+
+@injectable()
+export class OptionalCrossServiceTokenMiddleware extends AuthMiddleware {
+  constructor(
+    @inject(TYPES.ServiceProxy) serviceProxy: ServiceProxyInterface,
+    @inject(TYPES.AUTH_JWT_SECRET) jwtSecret: string,
+    @inject(TYPES.CROSS_SERVICE_TOKEN_CACHE_TTL) crossServiceTokenCacheTTL: number,
+    @inject(TYPES.CrossServiceTokenCache) crossServiceTokenCache: CrossServiceTokenCacheInterface,
+    @inject(TYPES.Timer) timer: TimerInterface,
+    @inject(TYPES.Logger) logger: Logger,
+  ) {
+    super(serviceProxy, jwtSecret, crossServiceTokenCacheTTL, crossServiceTokenCache, timer, logger)
+  }
+
+  protected override handleSessionValidationResponse(
+    authResponse: { status: number; data: unknown; headers: { contentType: string } },
+    _response: Response,
+    next: NextFunction,
+  ): boolean {
+    if (authResponse.status > 200) {
+      next()
+
+      return false
+    }
+
+    return true
+  }
+
+  protected override handleMissingAuthHeader(
+    authHeaderValue: string | undefined,
+    _response: Response,
+    next: NextFunction,
+  ): boolean {
+    if (!authHeaderValue) {
+      next()
+
+      return false
+    }
+
+    return true
+  }
+}

+ 57 - 0
packages/api-gateway/src/Controller/RequiredCrossServiceTokenMiddleware.ts

@@ -0,0 +1,57 @@
+import { TimerInterface } from '@standardnotes/time'
+import { NextFunction, Response } from 'express'
+import { inject, injectable } from 'inversify'
+import { Logger } from 'winston'
+
+import { TYPES } from '../Bootstrap/Types'
+import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
+import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface'
+import { AuthMiddleware } from './AuthMiddleware'
+
+@injectable()
+export class RequiredCrossServiceTokenMiddleware extends AuthMiddleware {
+  constructor(
+    @inject(TYPES.ServiceProxy) serviceProxy: ServiceProxyInterface,
+    @inject(TYPES.AUTH_JWT_SECRET) jwtSecret: string,
+    @inject(TYPES.CROSS_SERVICE_TOKEN_CACHE_TTL) crossServiceTokenCacheTTL: number,
+    @inject(TYPES.CrossServiceTokenCache) crossServiceTokenCache: CrossServiceTokenCacheInterface,
+    @inject(TYPES.Timer) timer: TimerInterface,
+    @inject(TYPES.Logger) logger: Logger,
+  ) {
+    super(serviceProxy, jwtSecret, crossServiceTokenCacheTTL, crossServiceTokenCache, timer, logger)
+  }
+
+  protected override handleSessionValidationResponse(
+    authResponse: { status: number; data: unknown; headers: { contentType: string } },
+    response: Response,
+    _next: NextFunction,
+  ): boolean {
+    if (authResponse.status > 200) {
+      response.setHeader('content-type', authResponse.headers.contentType)
+      response.status(authResponse.status).send(authResponse.data)
+
+      return false
+    }
+
+    return true
+  }
+
+  protected override handleMissingAuthHeader(
+    authHeaderValue: string | undefined,
+    response: Response,
+    _next: NextFunction,
+  ): boolean {
+    if (!authHeaderValue) {
+      response.status(401).send({
+        error: {
+          tag: 'invalid-auth',
+          message: 'Invalid login credentials.',
+        },
+      })
+
+      return false
+    }
+
+    return true
+  }
+}

+ 3 - 3
packages/api-gateway/src/Controller/v1/ActionsController.ts

@@ -24,7 +24,7 @@ export class ActionsController extends BaseHttpController {
     )
   }
 
-  @httpGet('/login-params')
+  @httpGet('/login-params', TYPES.OptionalCrossServiceTokenMiddleware)
   async loginParams(request: Request, response: Response): Promise<void> {
     await this.serviceProxy.callAuthServer(
       request,
@@ -34,7 +34,7 @@ export class ActionsController extends BaseHttpController {
     )
   }
 
-  @httpPost('/logout')
+  @httpPost('/logout', TYPES.OptionalCrossServiceTokenMiddleware)
   async logout(request: Request, response: Response): Promise<void> {
     await this.serviceProxy.callAuthServer(
       request,
@@ -54,7 +54,7 @@ export class ActionsController extends BaseHttpController {
     )
   }
 
-  @httpPost('/recovery/codes', TYPES.AuthMiddleware)
+  @httpPost('/recovery/codes', TYPES.RequiredCrossServiceTokenMiddleware)
   async recoveryCodes(request: Request, response: Response): Promise<void> {
     await this.serviceProxy.callAuthServer(
       request,

+ 4 - 4
packages/api-gateway/src/Controller/v1/AuthenticatorsController.ts

@@ -15,7 +15,7 @@ export class AuthenticatorsController extends BaseHttpController {
     super()
   }
 
-  @httpDelete('/:authenticatorId', TYPES.AuthMiddleware)
+  @httpDelete('/:authenticatorId', TYPES.RequiredCrossServiceTokenMiddleware)
   async delete(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -29,7 +29,7 @@ export class AuthenticatorsController extends BaseHttpController {
     )
   }
 
-  @httpGet('/', TYPES.AuthMiddleware)
+  @httpGet('/', TYPES.RequiredCrossServiceTokenMiddleware)
   async list(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -39,7 +39,7 @@ export class AuthenticatorsController extends BaseHttpController {
     )
   }
 
-  @httpGet('/generate-registration-options', TYPES.AuthMiddleware)
+  @httpGet('/generate-registration-options', TYPES.RequiredCrossServiceTokenMiddleware)
   async generateRegistrationOptions(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -59,7 +59,7 @@ export class AuthenticatorsController extends BaseHttpController {
     )
   }
 
-  @httpPost('/verify-registration', TYPES.AuthMiddleware)
+  @httpPost('/verify-registration', TYPES.RequiredCrossServiceTokenMiddleware)
   async verifyRegistration(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,

+ 1 - 1
packages/api-gateway/src/Controller/v1/FilesController.ts

@@ -15,7 +15,7 @@ export class FilesController extends BaseHttpController {
     super()
   }
 
-  @httpPost('/valet-tokens', TYPES.AuthMiddleware)
+  @httpPost('/valet-tokens', TYPES.RequiredCrossServiceTokenMiddleware)
   async createToken(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,

+ 1 - 1
packages/api-gateway/src/Controller/v1/ItemsController.ts

@@ -5,7 +5,7 @@ import { TYPES } from '../../Bootstrap/Types'
 import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
 import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
 
-@controller('/v1/items', TYPES.AuthMiddleware)
+@controller('/v1/items', TYPES.RequiredCrossServiceTokenMiddleware)
 export class ItemsController extends BaseHttpController {
   constructor(
     @inject(TYPES.ServiceProxy) private serviceProxy: ServiceProxyInterface,

+ 1 - 1
packages/api-gateway/src/Controller/v1/RevisionsController.ts

@@ -1,7 +1,7 @@
 import { BaseHttpController, controller, httpDelete, httpGet, results } from 'inversify-express-utils'
 import { TYPES } from '../../Bootstrap/Types'
 
-@controller('/v1/items/:item_id/revisions', TYPES.AuthMiddleware)
+@controller('/v1/items/:item_id/revisions', TYPES.RequiredCrossServiceTokenMiddleware)
 export class RevisionsController extends BaseHttpController {
   @httpGet('/')
   async getRevisions(): Promise<results.JsonResult> {

+ 3 - 3
packages/api-gateway/src/Controller/v1/SessionsController.ts

@@ -14,7 +14,7 @@ export class SessionsController extends BaseHttpController {
     super()
   }
 
-  @httpGet('/', TYPES.AuthMiddleware)
+  @httpGet('/', TYPES.RequiredCrossServiceTokenMiddleware)
   async getSessions(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -23,7 +23,7 @@ export class SessionsController extends BaseHttpController {
     )
   }
 
-  @httpDelete('/:uuid', TYPES.AuthMiddleware)
+  @httpDelete('/:uuid', TYPES.RequiredCrossServiceTokenMiddleware)
   async deleteSession(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -35,7 +35,7 @@ export class SessionsController extends BaseHttpController {
     )
   }
 
-  @httpDelete('/', TYPES.AuthMiddleware)
+  @httpDelete('/', TYPES.RequiredCrossServiceTokenMiddleware)
   async deleteSessions(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,

+ 4 - 4
packages/api-gateway/src/Controller/v1/SubscriptionInvitesController.ts

@@ -15,7 +15,7 @@ export class SubscriptionInvitesController extends BaseHttpController {
     super()
   }
 
-  @httpPost('/', TYPES.AuthMiddleware)
+  @httpPost('/', TYPES.RequiredCrossServiceTokenMiddleware)
   async inviteToSubscriptionSharing(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -25,7 +25,7 @@ export class SubscriptionInvitesController extends BaseHttpController {
     )
   }
 
-  @httpGet('/', TYPES.AuthMiddleware)
+  @httpGet('/', TYPES.RequiredCrossServiceTokenMiddleware)
   async listInvites(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -35,7 +35,7 @@ export class SubscriptionInvitesController extends BaseHttpController {
     )
   }
 
-  @httpDelete('/:inviteUuid', TYPES.AuthMiddleware)
+  @httpDelete('/:inviteUuid', TYPES.RequiredCrossServiceTokenMiddleware)
   async cancelSubscriptionSharing(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -48,7 +48,7 @@ export class SubscriptionInvitesController extends BaseHttpController {
     )
   }
 
-  @httpPost('/:inviteUuid/accept', TYPES.AuthMiddleware)
+  @httpPost('/:inviteUuid/accept', TYPES.RequiredCrossServiceTokenMiddleware)
   async acceptInvite(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,

+ 1 - 1
packages/api-gateway/src/Controller/v1/TokensController.ts

@@ -15,7 +15,7 @@ export class TokensController extends BaseHttpController {
     super()
   }
 
-  @httpPost('/', TYPES.AuthMiddleware)
+  @httpPost('/', TYPES.RequiredCrossServiceTokenMiddleware)
   async createToken(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,

+ 15 - 15
packages/api-gateway/src/Controller/v1/UsersController.ts

@@ -37,7 +37,7 @@ export class UsersController extends BaseHttpController {
     await this.httpService.callPaymentsServer(request, response, 'api/pro_users/send-activation-code', request.body)
   }
 
-  @httpPatch('/:userId', TYPES.AuthMiddleware)
+  @httpPatch('/:userId', TYPES.RequiredCrossServiceTokenMiddleware)
   async updateUser(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -47,7 +47,7 @@ export class UsersController extends BaseHttpController {
     )
   }
 
-  @httpPut('/:userUuid/password', TYPES.AuthMiddleware)
+  @httpPut('/:userUuid/password', TYPES.RequiredCrossServiceTokenMiddleware)
   async changePassword(request: Request, response: Response): Promise<void> {
     this.logger.debug(
       '[DEPRECATED] use endpoint /v1/users/:userUuid/attributes/credentials instead of /v1/users/:userUuid/password',
@@ -65,7 +65,7 @@ export class UsersController extends BaseHttpController {
     )
   }
 
-  @httpPut('/:userUuid/attributes/credentials', TYPES.AuthMiddleware)
+  @httpPut('/:userUuid/attributes/credentials', TYPES.RequiredCrossServiceTokenMiddleware)
   async changeCredentials(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -79,7 +79,7 @@ export class UsersController extends BaseHttpController {
     )
   }
 
-  @httpGet('/:userId/params', TYPES.AuthMiddleware)
+  @httpGet('/:userId/params', TYPES.RequiredCrossServiceTokenMiddleware)
   async getKeyParams(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -88,12 +88,12 @@ export class UsersController extends BaseHttpController {
     )
   }
 
-  @all('/:userId/mfa', TYPES.AuthMiddleware)
+  @all('/:userId/mfa', TYPES.RequiredCrossServiceTokenMiddleware)
   async blockMFA(): Promise<results.StatusCodeResult> {
     return this.statusCode(401)
   }
 
-  @httpPost('/:userUuid/integrations/listed', TYPES.AuthMiddleware)
+  @httpPost('/:userUuid/integrations/listed', TYPES.RequiredCrossServiceTokenMiddleware)
   async createListedAccount(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -113,7 +113,7 @@ export class UsersController extends BaseHttpController {
     )
   }
 
-  @httpGet('/:userUuid/settings', TYPES.AuthMiddleware)
+  @httpGet('/:userUuid/settings', TYPES.RequiredCrossServiceTokenMiddleware)
   async listSettings(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -126,7 +126,7 @@ export class UsersController extends BaseHttpController {
     )
   }
 
-  @httpPut('/:userUuid/settings', TYPES.AuthMiddleware)
+  @httpPut('/:userUuid/settings', TYPES.RequiredCrossServiceTokenMiddleware)
   async putSetting(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -140,7 +140,7 @@ export class UsersController extends BaseHttpController {
     )
   }
 
-  @httpGet('/:userUuid/settings/:settingName', TYPES.AuthMiddleware)
+  @httpGet('/:userUuid/settings/:settingName', TYPES.RequiredCrossServiceTokenMiddleware)
   async getSetting(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -154,7 +154,7 @@ export class UsersController extends BaseHttpController {
     )
   }
 
-  @httpDelete('/:userUuid/settings/:settingName', TYPES.AuthMiddleware)
+  @httpDelete('/:userUuid/settings/:settingName', TYPES.RequiredCrossServiceTokenMiddleware)
   async deleteSetting(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -169,7 +169,7 @@ export class UsersController extends BaseHttpController {
     )
   }
 
-  @httpGet('/:userUuid/subscription-settings/:subscriptionSettingName', TYPES.AuthMiddleware)
+  @httpGet('/:userUuid/subscription-settings/:subscriptionSettingName', TYPES.RequiredCrossServiceTokenMiddleware)
   async getSubscriptionSetting(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -183,7 +183,7 @@ export class UsersController extends BaseHttpController {
     )
   }
 
-  @httpGet('/:userUuid/features', TYPES.AuthMiddleware)
+  @httpGet('/:userUuid/features', TYPES.RequiredCrossServiceTokenMiddleware)
   async getFeatures(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -196,7 +196,7 @@ export class UsersController extends BaseHttpController {
     )
   }
 
-  @httpGet('/:userUuid/subscription', TYPES.AuthMiddleware)
+  @httpGet('/:userUuid/subscription', TYPES.RequiredCrossServiceTokenMiddleware)
   async getSubscription(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,
@@ -232,12 +232,12 @@ export class UsersController extends BaseHttpController {
     )
   }
 
-  @httpDelete('/:userUuid', TYPES.AuthMiddleware)
+  @httpDelete('/:userUuid', TYPES.RequiredCrossServiceTokenMiddleware)
   async deleteUser(request: Request, response: Response): Promise<void> {
     await this.httpService.callPaymentsServer(request, response, 'api/account', request.body)
   }
 
-  @httpPost('/:userUuid/requests', TYPES.AuthMiddleware)
+  @httpPost('/:userUuid/requests', TYPES.RequiredCrossServiceTokenMiddleware)
   async submitRequest(request: Request, response: Response): Promise<void> {
     await this.httpService.callAuthServer(
       request,

+ 1 - 1
packages/api-gateway/src/Controller/v1/WebSocketsController.ts

@@ -17,7 +17,7 @@ export class WebSocketsController extends BaseHttpController {
     super()
   }
 
-  @httpPost('/tokens', TYPES.AuthMiddleware)
+  @httpPost('/tokens', TYPES.RequiredCrossServiceTokenMiddleware)
   async createWebSocketConnectionToken(request: Request, response: Response): Promise<void> {
     await this.httpService.callWebSocketServer(
       request,

+ 4 - 4
packages/api-gateway/src/Controller/v2/ActionsControllerV2.ts

@@ -9,7 +9,7 @@ import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolv
 @controller('/v2')
 export class ActionsControllerV2 extends BaseHttpController {
   constructor(
-    @inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
+    @inject(TYPES.ServiceProxy) private serviceProxy: ServiceProxyInterface,
     @inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
   ) {
     super()
@@ -17,7 +17,7 @@ export class ActionsControllerV2 extends BaseHttpController {
 
   @httpPost('/login')
   async login(request: Request, response: Response): Promise<void> {
-    await this.httpService.callAuthServer(
+    await this.serviceProxy.callAuthServer(
       request,
       response,
       this.endpointResolver.resolveEndpointOrMethodIdentifier('POST', 'auth/pkce_sign_in'),
@@ -25,9 +25,9 @@ export class ActionsControllerV2 extends BaseHttpController {
     )
   }
 
-  @httpPost('/login-params')
+  @httpPost('/login-params', TYPES.OptionalCrossServiceTokenMiddleware)
   async loginParams(request: Request, response: Response): Promise<void> {
-    await this.httpService.callAuthServer(
+    await this.serviceProxy.callAuthServer(
       request,
       response,
       this.endpointResolver.resolveEndpointOrMethodIdentifier('POST', 'auth/pkce_params'),

+ 1 - 1
packages/api-gateway/src/Controller/v2/RevisionsControllerV2.ts

@@ -6,7 +6,7 @@ import { TYPES } from '../../Bootstrap/Types'
 import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
 import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
 
-@controller('/v2/items/:itemUuid/revisions', TYPES.AuthMiddleware)
+@controller('/v2/items/:itemUuid/revisions', TYPES.RequiredCrossServiceTokenMiddleware)
 export class RevisionsControllerV2 extends BaseHttpController {
   constructor(
     @inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,

+ 7 - 7
packages/auth/src/Bootstrap/Container.ts

@@ -241,17 +241,16 @@ import { InversifyExpressSubscriptionTokensController } from '../Infra/Inversify
 import { InversifyExpressSubscriptionSettingsController } from '../Infra/InversifyExpressUtils/InversifyExpressSubscriptionSettingsController'
 import { InversifyExpressSettingsController } from '../Infra/InversifyExpressUtils/InversifyExpressSettingsController'
 import { SessionMiddleware } from '../Infra/InversifyExpressUtils/Middleware/SessionMiddleware'
-import { ApiGatewayAuthMiddleware } from '../Infra/InversifyExpressUtils/Middleware/ApiGatewayAuthMiddleware'
 import { ApiGatewayOfflineAuthMiddleware } from '../Infra/InversifyExpressUtils/Middleware/ApiGatewayOfflineAuthMiddleware'
-import { AuthMiddleware } from '../Infra/InversifyExpressUtils/Middleware/AuthMiddleware'
 import { OfflineUserAuthMiddleware } from '../Infra/InversifyExpressUtils/Middleware/OfflineUserAuthMiddleware'
-import { AuthMiddlewareWithoutResponse } from '../Infra/InversifyExpressUtils/Middleware/AuthMiddlewareWithoutResponse'
 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'
 
 // eslint-disable-next-line @typescript-eslint/no-var-requires
 const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -448,13 +447,14 @@ export class ContainerConfigLoader {
       )
 
     // Middleware
-    container.bind<AuthMiddleware>(TYPES.Auth_AuthMiddleware).to(AuthMiddleware)
     container.bind<SessionMiddleware>(TYPES.Auth_SessionMiddleware).to(SessionMiddleware)
     container.bind<LockMiddleware>(TYPES.Auth_LockMiddleware).to(LockMiddleware)
     container
-      .bind<AuthMiddlewareWithoutResponse>(TYPES.Auth_AuthMiddlewareWithoutResponse)
-      .to(AuthMiddlewareWithoutResponse)
-    container.bind<ApiGatewayAuthMiddleware>(TYPES.Auth_ApiGatewayAuthMiddleware).to(ApiGatewayAuthMiddleware)
+      .bind<RequiredCrossServiceTokenMiddleware>(TYPES.Auth_RequiredCrossServiceTokenMiddleware)
+      .to(RequiredCrossServiceTokenMiddleware)
+    container
+      .bind<OptionalCrossServiceTokenMiddleware>(TYPES.Auth_OptionalCrossServiceTokenMiddleware)
+      .to(OptionalCrossServiceTokenMiddleware)
     container
       .bind<ApiGatewayOfflineAuthMiddleware>(TYPES.Auth_ApiGatewayOfflineAuthMiddleware)
       .to(ApiGatewayOfflineAuthMiddleware)

+ 2 - 3
packages/auth/src/Bootstrap/Types.ts

@@ -51,11 +51,10 @@ const TYPES = {
   Auth_ORMAuthenticatorChallengeRepository: Symbol.for('Auth_ORMAuthenticatorChallengeRepository'),
   Auth_ORMCacheEntryRepository: Symbol.for('Auth_ORMCacheEntryRepository'),
   // Middleware
-  Auth_AuthMiddleware: Symbol.for('Auth_AuthMiddleware'),
-  Auth_ApiGatewayAuthMiddleware: Symbol.for('Auth_ApiGatewayAuthMiddleware'),
+  Auth_RequiredCrossServiceTokenMiddleware: Symbol.for('Auth_RequiredCrossServiceTokenMiddleware'),
+  Auth_OptionalCrossServiceTokenMiddleware: Symbol.for('Auth_OptionalCrossServiceTokenMiddleware'),
   Auth_ApiGatewayOfflineAuthMiddleware: Symbol.for('Auth_ApiGatewayOfflineAuthMiddleware'),
   Auth_OfflineUserAuthMiddleware: Symbol.for('Auth_OfflineUserAuthMiddleware'),
-  Auth_AuthMiddlewareWithoutResponse: Symbol.for('Auth_AuthMiddlewareWithoutResponse'),
   Auth_LockMiddleware: Symbol.for('Auth_LockMiddleware'),
   Auth_SessionMiddleware: Symbol.for('Auth_SessionMiddleware'),
   // Projectors

+ 2 - 4
packages/auth/src/Domain/UseCase/GetUserKeyParams/GetUserKeyParams.spec.ts

@@ -35,9 +35,7 @@ describe('GetUserKeyParams', () => {
   })
 
   it('should get key params for an authenticated user - searching by email', async () => {
-    expect(
-      await createUseCase().execute({ email: 'test@test.te', authenticated: true, authenticatedUser: user }),
-    ).toEqual({
+    expect(await createUseCase().execute({ email: 'test@test.te', authenticated: true })).toEqual({
       keyParams: {
         foo: 'bar',
       },
@@ -63,7 +61,7 @@ describe('GetUserKeyParams', () => {
   })
 
   it('should get key params for an authenticated user - searching by uuid', async () => {
-    expect(await createUseCase().execute({ userUuid: '1-2-3', authenticated: true, authenticatedUser: user })).toEqual({
+    expect(await createUseCase().execute({ userUuid: '1-2-3', authenticated: true })).toEqual({
       keyParams: {
         foo: 'bar',
       },

+ 0 - 10
packages/auth/src/Domain/UseCase/GetUserKeyParams/GetUserKeyParams.ts

@@ -22,16 +22,6 @@ export class GetUserKeyParams implements UseCaseInterface {
   ) {}
 
   async execute(dto: GetUserKeyParamsDTO): Promise<GetUserKeyParamsResponse> {
-    if (dto.authenticatedUser) {
-      this.logger.debug(`Creating key params for authenticated user ${dto.authenticatedUser.email}`)
-
-      const keyParams = await this.createKeyParams(dto, dto.authenticatedUser, true)
-
-      return {
-        keyParams,
-      }
-    }
-
     let user: User | null = null
     if (dto.email !== undefined) {
       const usernameOrError = Username.create(dto.email)

+ 0 - 3
packages/auth/src/Domain/UseCase/GetUserKeyParams/GetUserKeyParamsDTOV1Unchallenged.ts

@@ -1,8 +1,5 @@
-import { User } from '../../User/User'
-
 export type GetUserKeyParamsDTOV1Unchallenged = {
   authenticated: boolean
   email?: string
   userUuid?: string
-  authenticatedUser?: User
 }

+ 0 - 3
packages/auth/src/Domain/UseCase/GetUserKeyParams/GetUserKeyParamsDTOV2Challenged.ts

@@ -1,9 +1,6 @@
-import { User } from '../../User/User'
-
 export type GetUserKeyParamsDTOV2Challenged = {
   authenticated: boolean
   codeChallenge: string
   email?: string
   userUuid?: string
-  authenticatedUser?: User
 }

+ 4 - 6
packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAuthController.ts

@@ -44,13 +44,12 @@ export class InversifyExpressAuthController extends BaseHttpController {
     this.controllerContainer.register('auth.signOut', this.signOut.bind(this))
   }
 
-  @httpGet('/params', TYPES.Auth_AuthMiddlewareWithoutResponse)
+  @httpGet('/params', TYPES.Auth_OptionalCrossServiceTokenMiddleware)
   async params(request: Request, response: Response): Promise<results.JsonResult> {
     if (response.locals.session) {
       const result = await this.getUserKeyParams.execute({
         email: response.locals.user.email,
         authenticated: true,
-        authenticatedUser: response.locals.user,
       })
 
       return this.json(result.keyParams)
@@ -155,7 +154,7 @@ export class InversifyExpressAuthController extends BaseHttpController {
     return this.json(signInResult.authResponse)
   }
 
-  @httpPost('/pkce_params', TYPES.Auth_AuthMiddlewareWithoutResponse)
+  @httpPost('/pkce_params', TYPES.Auth_OptionalCrossServiceTokenMiddleware)
   async pkceParams(request: Request, response: Response): Promise<results.JsonResult> {
     if (!request.body.code_challenge) {
       return this.json(
@@ -172,7 +171,6 @@ export class InversifyExpressAuthController extends BaseHttpController {
       const result = await this.getUserKeyParams.execute({
         email: response.locals.user.email,
         authenticated: true,
-        authenticatedUser: response.locals.user,
         codeChallenge: request.body.code_challenge as string,
       })
 
@@ -261,7 +259,7 @@ export class InversifyExpressAuthController extends BaseHttpController {
     return this.json(signInResult.authResponse)
   }
 
-  @httpPost('/recovery/codes', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpPost('/recovery/codes', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async generateRecoveryCodes(_request: Request, response: Response): Promise<results.JsonResult> {
     const result = await this.authController.generateRecoveryCodes({
       userUuid: response.locals.user.uuid,
@@ -296,7 +294,7 @@ export class InversifyExpressAuthController extends BaseHttpController {
     return this.json(result.data, result.status)
   }
 
-  @httpPost('/sign_out', TYPES.Auth_AuthMiddlewareWithoutResponse)
+  @httpPost('/sign_out', TYPES.Auth_OptionalCrossServiceTokenMiddleware)
   async signOut(request: Request, response: Response): Promise<results.JsonResult | void> {
     const result = await this.authController.signOut({
       readOnlyAccess: response.locals.readOnlyAccess,

+ 4 - 4
packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressAuthenticatorsController.ts

@@ -37,7 +37,7 @@ export class InversifyExpressAuthenticatorsController extends BaseHttpController
     )
   }
 
-  @httpGet('/', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpGet('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async list(_request: Request, response: Response): Promise<results.JsonResult> {
     const result = await this.authenticatorsController.list({
       userUuid: response.locals.user.uuid,
@@ -46,7 +46,7 @@ export class InversifyExpressAuthenticatorsController extends BaseHttpController
     return this.json(result.data, result.status)
   }
 
-  @httpDelete('/:authenticatorId', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpDelete('/:authenticatorId', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async delete(request: Request, response: Response): Promise<results.JsonResult> {
     const result = await this.authenticatorsController.delete({
       userUuid: response.locals.user.uuid,
@@ -56,7 +56,7 @@ export class InversifyExpressAuthenticatorsController extends BaseHttpController
     return this.json(result.data, result.status)
   }
 
-  @httpGet('/generate-registration-options', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpGet('/generate-registration-options', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async generateRegistrationOptions(_request: Request, response: Response): Promise<results.JsonResult> {
     const result = await this.authenticatorsController.generateRegistrationOptions({
       username: response.locals.user.email,
@@ -66,7 +66,7 @@ export class InversifyExpressAuthenticatorsController extends BaseHttpController
     return this.json(result.data, result.status)
   }
 
-  @httpPost('/verify-registration', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpPost('/verify-registration', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async verifyRegistration(request: Request, response: Response): Promise<results.JsonResult> {
     const result = await this.authenticatorsController.verifyRegistrationResponse({
       userUuid: response.locals.user.uuid,

+ 1 - 1
packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressFeaturesController.ts

@@ -22,7 +22,7 @@ export class InversifyExpressFeaturesController extends BaseHttpController {
     this.controllerContainer.register('auth.users.getFeatures', this.getFeatures.bind(this))
   }
 
-  @httpGet('/', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpGet('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async getFeatures(request: Request, response: Response): Promise<results.JsonResult> {
     if (request.params.userUuid !== response.locals.user.uuid) {
       return this.json(

+ 1 - 1
packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressListedController.ts

@@ -18,7 +18,7 @@ export class InversifyExpressListedController extends BaseHttpController {
     this.controllerContainer.register('auth.users.createListedAccount', this.createListedAccount.bind(this))
   }
 
-  @httpPost('/', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpPost('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async createListedAccount(_request: Request, response: Response): Promise<results.JsonResult> {
     if (response.locals.readOnlyAccess) {
       return this.json(

+ 5 - 5
packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionController.ts

@@ -26,12 +26,12 @@ export class InversifyExpressSessionController extends BaseHttpController {
   ) {
     super()
 
-    this.controllerContainer.register('auth.session.delete', this.deleteSession.bind(this))
-    this.controllerContainer.register('auth.session.deleteAll', this.deleteAllSessions.bind(this))
-    this.controllerContainer.register('auth.session.refresh', this.refresh.bind(this))
+    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))
   }
 
-  @httpDelete('/', TYPES.Auth_AuthMiddleware, TYPES.Auth_SessionMiddleware)
+  @httpDelete('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware, TYPES.Auth_SessionMiddleware)
   async deleteSession(request: Request, response: Response): Promise<results.JsonResult | void> {
     if (response.locals.readOnlyAccess) {
       return this.json(
@@ -87,7 +87,7 @@ export class InversifyExpressSessionController extends BaseHttpController {
     response.status(204).send()
   }
 
-  @httpDelete('/all', TYPES.Auth_AuthMiddleware, TYPES.Auth_SessionMiddleware)
+  @httpDelete('/all', TYPES.Auth_RequiredCrossServiceTokenMiddleware, TYPES.Auth_SessionMiddleware)
   async deleteAllSessions(_request: Request, response: Response): Promise<results.JsonResult | void> {
     if (response.locals.readOnlyAccess) {
       return this.json(

+ 1 - 1
packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSessionsController.ts

@@ -62,7 +62,7 @@ export class InversifyExpressSessionsController extends BaseHttpController {
     return this.json({ authToken: result.token })
   }
 
-  @httpGet('/', TYPES.Auth_AuthMiddleware, TYPES.Auth_SessionMiddleware)
+  @httpGet('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware, TYPES.Auth_SessionMiddleware)
   async getSessions(_request: Request, response: Response): Promise<results.JsonResult> {
     if (response.locals.readOnlyAccess) {
       return this.json([])

+ 4 - 4
packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSettingsController.ts

@@ -35,7 +35,7 @@ export class InversifyExpressSettingsController extends BaseHttpController {
     this.controllerContainer.register('auth.users.deleteSetting', this.deleteSetting.bind(this))
   }
 
-  @httpGet('/settings', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpGet('/settings', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async getSettings(request: Request, response: Response): Promise<results.JsonResult> {
     if (request.params.userUuid !== response.locals.user.uuid) {
       return this.json(
@@ -54,7 +54,7 @@ export class InversifyExpressSettingsController extends BaseHttpController {
     return this.json(result)
   }
 
-  @httpGet('/settings/:settingName', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpGet('/settings/:settingName', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async getSetting(request: Request, response: Response): Promise<results.JsonResult> {
     if (request.params.userUuid !== response.locals.user.uuid) {
       return this.json(
@@ -77,7 +77,7 @@ export class InversifyExpressSettingsController extends BaseHttpController {
     return this.json(result, 400)
   }
 
-  @httpPut('/settings', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpPut('/settings', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async updateSetting(request: Request, response: Response): Promise<results.JsonResult | results.StatusCodeResult> {
     if (response.locals.readOnlyAccess) {
       return this.json(
@@ -124,7 +124,7 @@ export class InversifyExpressSettingsController extends BaseHttpController {
     return this.json(result, result.statusCode)
   }
 
-  @httpDelete('/settings/:settingName', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpDelete('/settings/:settingName', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async deleteSetting(request: Request, response: Response): Promise<results.JsonResult> {
     if (response.locals.readOnlyAccess) {
       return this.json(

+ 4 - 4
packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionInvitesController.ts

@@ -31,7 +31,7 @@ export class InversifyExpressSubscriptionInvitesController extends BaseHttpContr
     this.controllerContainer.register('auth.subscriptionInvites.list', this.listInvites.bind(this))
   }
 
-  @httpPost('/:inviteUuid/accept', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpPost('/:inviteUuid/accept', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async acceptInvite(request: Request, response: Response): Promise<void> {
     const result = await this.subscriptionInvitesController.acceptInvite({
       api: request.query.api as ApiVersion,
@@ -52,7 +52,7 @@ export class InversifyExpressSubscriptionInvitesController extends BaseHttpContr
     return this.json(response.data, response.status)
   }
 
-  @httpPost('/', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpPost('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async inviteToSubscriptionSharing(request: Request, response: Response): Promise<results.JsonResult> {
     const result = await this.subscriptionInvitesController.invite({
       ...request.body,
@@ -64,7 +64,7 @@ export class InversifyExpressSubscriptionInvitesController extends BaseHttpContr
     return this.json(result.data, result.status)
   }
 
-  @httpDelete('/:inviteUuid', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpDelete('/:inviteUuid', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async cancelSubscriptionSharing(request: Request, response: Response): Promise<results.JsonResult> {
     const result = await this.subscriptionInvitesController.cancelInvite({
       ...request.body,
@@ -75,7 +75,7 @@ export class InversifyExpressSubscriptionInvitesController extends BaseHttpContr
     return this.json(result.data, result.status)
   }
 
-  @httpGet('/', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpGet('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async listInvites(request: Request, response: Response): Promise<results.JsonResult> {
     const result = await this.subscriptionInvitesController.listInvites({
       ...request.body,

+ 1 - 1
packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionSettingsController.ts

@@ -22,7 +22,7 @@ export class InversifyExpressSubscriptionSettingsController extends BaseHttpCont
     this.controllerContainer.register('auth.users.getSubscriptionSetting', this.getSubscriptionSetting.bind(this))
   }
 
-  @httpGet('/subscription-settings/:subscriptionSettingName', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpGet('/subscription-settings/:subscriptionSettingName', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async getSubscriptionSetting(request: Request, response: Response): Promise<results.JsonResult> {
     const result = await this.doGetSetting.execute({
       userUuid: response.locals.user.uuid,

+ 1 - 1
packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionTokensController.ts

@@ -37,7 +37,7 @@ export class InversifyExpressSubscriptionTokensController extends BaseHttpContro
     this.controllerContainer.register('auth.subscription-tokens.create', this.createToken.bind(this))
   }
 
-  @httpPost('/', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpPost('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async createToken(_request: Request, response: Response): Promise<results.JsonResult> {
     if (response.locals.readOnlyAccess) {
       return this.json(

+ 1 - 1
packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUserRequestsController.ts

@@ -17,7 +17,7 @@ export class InversifyExpressUserRequestsController extends BaseHttpController {
     this.controllerContainer.register('auth.users.createRequest', this.submitRequest.bind(this))
   }
 
-  @httpPost('/', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpPost('/', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async submitRequest(request: Request, response: Response): Promise<results.JsonResult> {
     const result = await this.userRequestsController.submitUserRequest({
       requestType: request.body.requestType,

+ 3 - 3
packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressUsersController.ts

@@ -41,7 +41,7 @@ export class InversifyExpressUsersController extends BaseHttpController {
     this.controllerContainer.register('auth.users.updateCredentials', this.changeCredentials.bind(this))
   }
 
-  @httpPatch('/:userId', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpPatch('/:userId', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async update(request: Request, response: Response): Promise<results.JsonResult | void> {
     if (response.locals.readOnlyAccess) {
       return this.json(
@@ -132,7 +132,7 @@ export class InversifyExpressUsersController extends BaseHttpController {
     return this.json({ message: result.message }, result.responseCode)
   }
 
-  @httpGet('/:userUuid/subscription', TYPES.Auth_ApiGatewayAuthMiddleware)
+  @httpGet('/:userUuid/subscription', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async getSubscription(request: Request, response: Response): Promise<results.JsonResult> {
     if (request.params.userUuid !== response.locals.user.uuid) {
       return this.json(
@@ -156,7 +156,7 @@ export class InversifyExpressUsersController extends BaseHttpController {
     return this.json(result, 400)
   }
 
-  @httpPut('/:userId/attributes/credentials', TYPES.Auth_AuthMiddleware)
+  @httpPut('/:userId/attributes/credentials', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
   async changeCredentials(request: Request, response: Response): Promise<results.JsonResult | void> {
     if (response.locals.readOnlyAccess) {
       return this.json(

+ 1 - 1
packages/auth/src/Infra/InversifyExpressUtils/InversifyExpressValetTokenController.ts

@@ -13,7 +13,7 @@ import { ControllerContainerInterface, Uuid } from '@standardnotes/domain-core'
 import TYPES from '../../Bootstrap/Types'
 import { CreateValetToken } from '../../Domain/UseCase/CreateValetToken/CreateValetToken'
 
-@controller('/valet-tokens', TYPES.Auth_ApiGatewayAuthMiddleware)
+@controller('/valet-tokens', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
 export class InversifyExpressValetTokenController extends BaseHttpController {
   constructor(
     @inject(TYPES.Auth_CreateValetToken) private createValetKey: CreateValetToken,

+ 0 - 99
packages/auth/src/Infra/InversifyExpressUtils/Middleware/ApiGatewayAuthMiddleware.spec.ts

@@ -1,99 +0,0 @@
-import 'reflect-metadata'
-
-import { ApiGatewayAuthMiddleware } from './ApiGatewayAuthMiddleware'
-import { NextFunction, Request, Response } from 'express'
-import { Logger } from 'winston'
-import { CrossServiceTokenData, TokenDecoderInterface } from '@standardnotes/security'
-import { RoleName } from '@standardnotes/domain-core'
-
-describe('ApiGatewayAuthMiddleware', () => {
-  let tokenDecoder: TokenDecoderInterface<CrossServiceTokenData>
-  let request: Request
-  let response: Response
-  let next: NextFunction
-
-  const logger = {
-    debug: jest.fn(),
-  } as unknown as jest.Mocked<Logger>
-
-  const createMiddleware = () => new ApiGatewayAuthMiddleware(tokenDecoder, logger)
-
-  beforeEach(() => {
-    tokenDecoder = {} as jest.Mocked<TokenDecoderInterface<CrossServiceTokenData>>
-    tokenDecoder.decodeToken = jest.fn().mockReturnValue({
-      user: {
-        uuid: '1-2-3',
-        email: 'test@test.te',
-      },
-      roles: [
-        {
-          uuid: 'a-b-c',
-          name: RoleName.NAMES.CoreUser,
-        },
-      ],
-    })
-
-    request = {
-      headers: {},
-    } as jest.Mocked<Request>
-    response = {
-      locals: {},
-    } as jest.Mocked<Response>
-    response.status = jest.fn().mockReturnThis()
-    response.send = jest.fn()
-    next = jest.fn()
-  })
-
-  it('should authorize user', async () => {
-    request.headers['x-auth-token'] = 'auth-jwt-token'
-
-    await createMiddleware().handler(request, response, next)
-
-    expect(response.locals.user).toEqual({
-      uuid: '1-2-3',
-      email: 'test@test.te',
-    })
-    expect(response.locals.roles).toEqual([
-      {
-        uuid: 'a-b-c',
-        name: RoleName.NAMES.CoreUser,
-      },
-    ])
-
-    expect(next).toHaveBeenCalled()
-  })
-
-  it('should not authorize if request is missing auth jwt token in headers', async () => {
-    await createMiddleware().handler(request, response, next)
-
-    expect(response.status).toHaveBeenCalledWith(401)
-    expect(next).not.toHaveBeenCalled()
-  })
-
-  it('should not authorize if auth jwt token is malformed', async () => {
-    request.headers['x-auth-token'] = 'auth-jwt-token'
-
-    tokenDecoder.decodeToken = jest.fn().mockReturnValue(undefined)
-
-    await createMiddleware().handler(request, response, next)
-
-    expect(response.status).toHaveBeenCalledWith(401)
-    expect(next).not.toHaveBeenCalled()
-  })
-
-  it('should pass the error to next middleware if one occurres', async () => {
-    request.headers['x-auth-token'] = 'auth-jwt-token'
-
-    const error = new Error('Ooops')
-
-    tokenDecoder.decodeToken = jest.fn().mockImplementation(() => {
-      throw error
-    })
-
-    await createMiddleware().handler(request, response, next)
-
-    expect(response.status).not.toHaveBeenCalled()
-
-    expect(next).toHaveBeenCalledWith(error)
-  })
-})

+ 6 - 19
packages/auth/src/Infra/InversifyExpressUtils/Middleware/ApiGatewayAuthMiddleware.ts

@@ -1,31 +1,16 @@
 import { CrossServiceTokenData, TokenDecoderInterface } from '@standardnotes/security'
 import { NextFunction, Request, Response } from 'express'
-import { inject, injectable } from 'inversify'
 import { BaseMiddleware } from 'inversify-express-utils'
 import { Logger } from 'winston'
-import TYPES from '../../../Bootstrap/Types'
-
-@injectable()
-export class ApiGatewayAuthMiddleware extends BaseMiddleware {
-  constructor(
-    @inject(TYPES.Auth_CrossServiceTokenDecoder) private tokenDecoder: TokenDecoderInterface<CrossServiceTokenData>,
-    @inject(TYPES.Auth_Logger) private logger: Logger,
-  ) {
+
+export abstract class ApiGatewayAuthMiddleware extends BaseMiddleware {
+  constructor(private tokenDecoder: TokenDecoderInterface<CrossServiceTokenData>, private logger: Logger) {
     super()
   }
 
   async handler(request: Request, response: Response, next: NextFunction): Promise<void> {
     try {
-      if (!request.headers['x-auth-token']) {
-        this.logger.debug('ApiGatewayAuthMiddleware missing x-auth-token header.')
-
-        response.status(401).send({
-          error: {
-            tag: 'invalid-auth',
-            message: 'Invalid login credentials.',
-          },
-        })
-
+      if (!this.handleMissingToken(request, response, next)) {
         return
       }
 
@@ -56,4 +41,6 @@ export class ApiGatewayAuthMiddleware extends BaseMiddleware {
       return next(error)
     }
   }
+
+  protected abstract handleMissingToken(request: Request, response: Response, next: NextFunction): boolean
 }

+ 0 - 79
packages/auth/src/Infra/InversifyExpressUtils/Middleware/AuthMiddleware.spec.ts

@@ -1,79 +0,0 @@
-import 'reflect-metadata'
-
-import { AuthMiddleware } from './AuthMiddleware'
-import { NextFunction, Request, Response } from 'express'
-import { User } from '../../../Domain/User/User'
-import { AuthenticateRequest } from '../../../Domain/UseCase/AuthenticateRequest'
-import { Session } from '../../../Domain/Session/Session'
-import { Logger } from 'winston'
-
-describe('AuthMiddleware', () => {
-  let authenticateRequest: AuthenticateRequest
-  let request: Request
-  let response: Response
-  let next: NextFunction
-
-  const logger = {
-    debug: jest.fn(),
-  } as unknown as jest.Mocked<Logger>
-
-  const createMiddleware = () => new AuthMiddleware(authenticateRequest, logger)
-
-  beforeEach(() => {
-    authenticateRequest = {} as jest.Mocked<AuthenticateRequest>
-    authenticateRequest.execute = jest.fn()
-
-    request = {
-      headers: {},
-    } as jest.Mocked<Request>
-    response = {
-      locals: {},
-    } as jest.Mocked<Response>
-    response.status = jest.fn().mockReturnThis()
-    response.send = jest.fn()
-    next = jest.fn()
-  })
-
-  it('should authorize user', async () => {
-    const user = {} as jest.Mocked<User>
-    const session = {} as jest.Mocked<Session>
-    authenticateRequest.execute = jest.fn().mockReturnValue({
-      success: true,
-      user,
-      session,
-    })
-
-    await createMiddleware().handler(request, response, next)
-
-    expect(response.locals.user).toEqual(user)
-    expect(response.locals.session).toEqual(session)
-
-    expect(next).toHaveBeenCalled()
-  })
-
-  it('should not authorize if request authentication fails', async () => {
-    authenticateRequest.execute = jest.fn().mockReturnValue({
-      success: false,
-      responseCode: 401,
-    })
-
-    await createMiddleware().handler(request, response, next)
-
-    expect(response.status).toHaveBeenCalledWith(401)
-    expect(next).not.toHaveBeenCalled()
-  })
-
-  it('should pass the error to next middleware if one occurres', async () => {
-    const error = new Error('Ooops')
-
-    authenticateRequest.execute = jest.fn().mockImplementation(() => {
-      throw error
-    })
-
-    await createMiddleware().handler(request, response, next)
-
-    expect(response.status).not.toHaveBeenCalled()
-
-    expect(next).toHaveBeenCalledWith(error)
-  })
-})

+ 0 - 45
packages/auth/src/Infra/InversifyExpressUtils/Middleware/AuthMiddleware.ts

@@ -1,45 +0,0 @@
-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 { AuthenticateRequest } from '../../../Domain/UseCase/AuthenticateRequest'
-
-@injectable()
-export class AuthMiddleware extends BaseMiddleware {
-  constructor(
-    @inject(TYPES.Auth_AuthenticateRequest) private authenticateRequest: AuthenticateRequest,
-    @inject(TYPES.Auth_Logger) private logger: Logger,
-  ) {
-    super()
-  }
-
-  async handler(request: Request, response: Response, next: NextFunction): Promise<void> {
-    try {
-      const authenticateRequestResponse = await this.authenticateRequest.execute({
-        authorizationHeader: request.headers.authorization,
-      })
-
-      if (!authenticateRequestResponse.success) {
-        this.logger.debug('AuthMiddleware authentication failure.')
-
-        response.status(authenticateRequestResponse.responseCode).send({
-          error: {
-            tag: authenticateRequestResponse.errorTag,
-            message: authenticateRequestResponse.errorMessage,
-          },
-        })
-
-        return
-      }
-
-      response.locals.user = authenticateRequestResponse.user
-      response.locals.session = authenticateRequestResponse.session
-      response.locals.readOnlyAccess = authenticateRequestResponse.session?.readonlyAccess ?? false
-
-      return next()
-    } catch (error) {
-      return next(error)
-    }
-  }
-}

+ 0 - 68
packages/auth/src/Infra/InversifyExpressUtils/Middleware/AuthMiddlewareWithoutResponse.spec.ts

@@ -1,68 +0,0 @@
-import 'reflect-metadata'
-
-import { AuthMiddlewareWithoutResponse } from './AuthMiddlewareWithoutResponse'
-import { NextFunction, Request, Response } from 'express'
-import { User } from '../../../Domain/User/User'
-import { AuthenticateRequest } from '../../../Domain/UseCase/AuthenticateRequest'
-import { Session } from '../../../Domain/Session/Session'
-
-describe('AuthMiddlewareWithoutResponse', () => {
-  let authenticateRequest: AuthenticateRequest
-  let request: Request
-  let response: Response
-  let next: NextFunction
-
-  const createMiddleware = () => new AuthMiddlewareWithoutResponse(authenticateRequest)
-
-  beforeEach(() => {
-    authenticateRequest = {} as jest.Mocked<AuthenticateRequest>
-    authenticateRequest.execute = jest.fn()
-
-    request = {
-      headers: {},
-    } as jest.Mocked<Request>
-    response = {
-      locals: {},
-    } as jest.Mocked<Response>
-    response.status = jest.fn().mockReturnThis()
-    response.send = jest.fn()
-    next = jest.fn()
-  })
-
-  it('should authorize user', async () => {
-    const user = {} as jest.Mocked<User>
-    const session = {} as jest.Mocked<Session>
-    authenticateRequest.execute = jest.fn().mockReturnValue({
-      success: true,
-      user,
-      session,
-    })
-
-    await createMiddleware().handler(request, response, next)
-
-    expect(response.locals.user).toEqual(user)
-    expect(response.locals.session).toEqual(session)
-
-    expect(next).toHaveBeenCalled()
-  })
-
-  it('should skip middleware if authentication fails', async () => {
-    authenticateRequest.execute = jest.fn().mockReturnValue({
-      success: false,
-    })
-
-    await createMiddleware().handler(request, response, next)
-
-    expect(next).toHaveBeenCalled()
-  })
-
-  it('should skip middleware if authentication errors', async () => {
-    authenticateRequest.execute = jest.fn().mockImplementation(() => {
-      throw new Error('Ooops')
-    })
-
-    await createMiddleware().handler(request, response, next)
-
-    expect(next).toHaveBeenCalled()
-  })
-})

+ 0 - 32
packages/auth/src/Infra/InversifyExpressUtils/Middleware/AuthMiddlewareWithoutResponse.ts

@@ -1,32 +0,0 @@
-import { NextFunction, Request, Response } from 'express'
-import { inject, injectable } from 'inversify'
-import { BaseMiddleware } from 'inversify-express-utils'
-import TYPES from '../../../Bootstrap/Types'
-import { AuthenticateRequest } from '../../../Domain/UseCase/AuthenticateRequest'
-
-@injectable()
-export class AuthMiddlewareWithoutResponse extends BaseMiddleware {
-  constructor(@inject(TYPES.Auth_AuthenticateRequest) private authenticateRequest: AuthenticateRequest) {
-    super()
-  }
-
-  async handler(request: Request, response: Response, next: NextFunction): Promise<void> {
-    try {
-      const authenticateRequestResponse = await this.authenticateRequest.execute({
-        authorizationHeader: request.headers.authorization,
-      })
-
-      if (!authenticateRequestResponse.success) {
-        return next()
-      }
-
-      response.locals.user = authenticateRequestResponse.user
-      response.locals.session = authenticateRequestResponse.session
-      response.locals.readOnlyAccess = authenticateRequestResponse.session?.readonlyAccess ?? false
-
-      return next()
-    } catch (error) {
-      return next()
-    }
-  }
-}

+ 27 - 0
packages/auth/src/Infra/InversifyExpressUtils/Middleware/OptionalCrossServiceTokenMiddleware.ts

@@ -0,0 +1,27 @@
+import { CrossServiceTokenData, TokenDecoderInterface } from '@standardnotes/security'
+import { NextFunction, Request, Response } from 'express'
+import { inject, injectable } from 'inversify'
+import { Logger } from 'winston'
+
+import TYPES from '../../../Bootstrap/Types'
+import { ApiGatewayAuthMiddleware } from './ApiGatewayAuthMiddleware'
+
+@injectable()
+export class OptionalCrossServiceTokenMiddleware extends ApiGatewayAuthMiddleware {
+  constructor(
+    @inject(TYPES.Auth_CrossServiceTokenDecoder) tokenDecoder: TokenDecoderInterface<CrossServiceTokenData>,
+    @inject(TYPES.Auth_Logger) logger: Logger,
+  ) {
+    super(tokenDecoder, logger)
+  }
+
+  protected override handleMissingToken(request: Request, _response: Response, next: NextFunction): boolean {
+    if (!request.headers['x-auth-token']) {
+      next()
+
+      return false
+    }
+
+    return true
+  }
+}

+ 32 - 0
packages/auth/src/Infra/InversifyExpressUtils/Middleware/RequiredCrossServiceTokenMiddleware.ts

@@ -0,0 +1,32 @@
+import { CrossServiceTokenData, TokenDecoderInterface } from '@standardnotes/security'
+import { NextFunction, Request, Response } from 'express'
+import { inject, injectable } from 'inversify'
+import { Logger } from 'winston'
+
+import TYPES from '../../../Bootstrap/Types'
+import { ApiGatewayAuthMiddleware } from './ApiGatewayAuthMiddleware'
+
+@injectable()
+export class RequiredCrossServiceTokenMiddleware extends ApiGatewayAuthMiddleware {
+  constructor(
+    @inject(TYPES.Auth_CrossServiceTokenDecoder) tokenDecoder: TokenDecoderInterface<CrossServiceTokenData>,
+    @inject(TYPES.Auth_Logger) logger: Logger,
+  ) {
+    super(tokenDecoder, logger)
+  }
+
+  protected override handleMissingToken(request: Request, response: Response, _next: NextFunction): boolean {
+    if (!request.headers['x-auth-token']) {
+      response.status(401).send({
+        error: {
+          tag: 'invalid-auth',
+          message: 'Invalid login credentials.',
+        },
+      })
+
+      return false
+    }
+
+    return true
+  }
+}