From 175e495b2afb82d5db983185838e40a933457052 Mon Sep 17 00:00:00 2001 From: Szymon Szafoni Date: Thu, 9 Nov 2023 18:18:41 +0100 Subject: [PATCH] auth startegy impl --- .../authentication/authentication.module.ts | 2 ++ .../authentication/config/x-api-key.config.ts | 3 +++ .../strategy/x-api-key.strategy.ts | 27 +++++++++++++++++++ ...r.ts => deletion-executions.controller.ts} | 8 +++--- ...ler.ts => deletion-requests.controller.ts} | 6 ++--- .../src/modules/deletion/deletion.module.ts | 10 +++++-- .../src/modules/server/server.config.ts | 7 ++++- config/default.schema.json | 20 ++++++++++++++ package-lock.json | 19 +++++++++++++ package.json | 1 + 10 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 apps/server/src/modules/authentication/config/x-api-key.config.ts create mode 100644 apps/server/src/modules/authentication/strategy/x-api-key.strategy.ts rename apps/server/src/modules/deletion/controller/{deletionExecutions.controller.ts => deletion-executions.controller.ts} (82%) rename apps/server/src/modules/deletion/controller/{deletionRequests.controller.ts => deletion-requests.controller.ts} (93%) diff --git a/apps/server/src/modules/authentication/authentication.module.ts b/apps/server/src/modules/authentication/authentication.module.ts index 8f2bdcd3b0d..61092ff5e16 100644 --- a/apps/server/src/modules/authentication/authentication.module.ts +++ b/apps/server/src/modules/authentication/authentication.module.ts @@ -18,6 +18,7 @@ import { JwtStrategy } from './strategy/jwt.strategy'; import { LdapStrategy } from './strategy/ldap.strategy'; import { LocalStrategy } from './strategy/local.strategy'; import { Oauth2Strategy } from './strategy/oauth2.strategy'; +import { XApiKeyStrategy } from './strategy/x-api-key.strategy'; // values copied from Algorithm definition. Type does not exist at runtime and can't be checked anymore otherwise const algorithms = [ @@ -76,6 +77,7 @@ const jwtModuleOptions: JwtModuleOptions = { LdapService, LdapStrategy, Oauth2Strategy, + XApiKeyStrategy, ], exports: [AuthenticationService], }) diff --git a/apps/server/src/modules/authentication/config/x-api-key.config.ts b/apps/server/src/modules/authentication/config/x-api-key.config.ts new file mode 100644 index 00000000000..79288c0dd82 --- /dev/null +++ b/apps/server/src/modules/authentication/config/x-api-key.config.ts @@ -0,0 +1,3 @@ +export interface IXApiKeyConfig { + ADMIN_API__ALLOWED_API_KEYS: string[]; +} diff --git a/apps/server/src/modules/authentication/strategy/x-api-key.strategy.ts b/apps/server/src/modules/authentication/strategy/x-api-key.strategy.ts new file mode 100644 index 00000000000..b3dff6c9533 --- /dev/null +++ b/apps/server/src/modules/authentication/strategy/x-api-key.strategy.ts @@ -0,0 +1,27 @@ +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { ConfigService } from '@nestjs/config'; +import Strategy from 'passport-headerapikey'; +import { IXApiKeyConfig } from '../config/x-api-key.config'; + +@Injectable() +export class XApiKeyStrategy extends PassportStrategy(Strategy, 'api-key') { + private readonly allowedApiKeys: string[]; + + constructor(private readonly configService: ConfigService) { + super( + { header: 'X-API-KEY', prefix: '' }, + true, + // eslint-disable-next-line @typescript-eslint/require-await + async (apiKey: string, done: (error: Error | null, data: boolean | null) => void) => this.validate(apiKey, done) + ); + this.allowedApiKeys = this.configService.get('ADMIN_API__ALLOWED_API_KEYS'); + } + + public validate = (apiKey: string, done: (error: Error | null, data: boolean | null) => void) => { + if (this.allowedApiKeys.includes(apiKey)) { + done(null, true); + } + done(new UnauthorizedException(), null); + }; +} diff --git a/apps/server/src/modules/deletion/controller/deletionExecutions.controller.ts b/apps/server/src/modules/deletion/controller/deletion-executions.controller.ts similarity index 82% rename from apps/server/src/modules/deletion/controller/deletionExecutions.controller.ts rename to apps/server/src/modules/deletion/controller/deletion-executions.controller.ts index 4a6898dc1ea..f6d1754690d 100644 --- a/apps/server/src/modules/deletion/controller/deletionExecutions.controller.ts +++ b/apps/server/src/modules/deletion/controller/deletion-executions.controller.ts @@ -1,14 +1,14 @@ -import { Controller, Post, Query } from '@nestjs/common'; +import { Controller, Post, Query, UseGuards } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { Authenticate } from '@src/modules/authentication/decorator/auth.decorator'; import { ForbiddenOperationError, ValidationError } from '@shared/common'; +import { AuthGuard } from '@nestjs/passport'; import { DeletionRequestUc } from '../uc/deletion-request.uc'; import { DeletionExecutionParams } from './dto'; @ApiTags('DeletionExecutions') -@Authenticate('jwt') +@UseGuards(AuthGuard('api-key')) @Controller('deletionExecutions') -export class AccountController { +export class DeletionExecutionsController { constructor(private readonly deletionRequestUc: DeletionRequestUc) {} @Post() diff --git a/apps/server/src/modules/deletion/controller/deletionRequests.controller.ts b/apps/server/src/modules/deletion/controller/deletion-requests.controller.ts similarity index 93% rename from apps/server/src/modules/deletion/controller/deletionRequests.controller.ts rename to apps/server/src/modules/deletion/controller/deletion-requests.controller.ts index a32eca60aac..6f6823acbc2 100644 --- a/apps/server/src/modules/deletion/controller/deletionRequests.controller.ts +++ b/apps/server/src/modules/deletion/controller/deletion-requests.controller.ts @@ -1,14 +1,14 @@ -import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { Authenticate } from '@src/modules/authentication/decorator/auth.decorator'; import { ForbiddenOperationError, ValidationError } from '@shared/common'; +import { AuthGuard } from '@nestjs/passport'; import { DeletionRequestUc } from '../uc/deletion-request.uc'; import { DeletionRequestResponse } from './dto/deletion-request.response'; import { DeletionRequestBodyProps } from './dto/deletion-request.body.params'; import { DeletionRequestLogResponse } from './dto'; @ApiTags('DeletionRequests') -@Authenticate('jwt') +@UseGuards(AuthGuard('api-key')) @Controller('deletionRequests') export class DeletionRequestsController { constructor(private readonly deletionRequestUc: DeletionRequestUc) {} diff --git a/apps/server/src/modules/deletion/deletion.module.ts b/apps/server/src/modules/deletion/deletion.module.ts index 440a9418d70..5c8143e61d0 100644 --- a/apps/server/src/modules/deletion/deletion.module.ts +++ b/apps/server/src/modules/deletion/deletion.module.ts @@ -1,11 +1,17 @@ import { Module } from '@nestjs/common'; import { LoggerModule } from '@src/core/logger'; +import { ConfigService } from '@nestjs/config'; import { DeletionRequestService } from './services/deletion-request.service'; import { DeletionRequestRepo } from './repo/deletion-request.repo'; +import { AuthenticationModule } from '../authentication'; +import { DeletionRequestsController } from './controller/deletion-requests.controller'; +import { DeletionExecutionsController } from './controller/deletion-executions.controller'; +import { IXApiKeyConfig } from '../authentication/config/x-api-key.config'; @Module({ - imports: [LoggerModule], - providers: [DeletionRequestService, DeletionRequestRepo], + imports: [LoggerModule, AuthenticationModule], + controllers: [DeletionRequestsController, DeletionExecutionsController], + providers: [DeletionRequestService, DeletionRequestRepo, ConfigService], exports: [DeletionRequestService], }) export class DeletionModule {} diff --git a/apps/server/src/modules/server/server.config.ts b/apps/server/src/modules/server/server.config.ts index 5d1ea95cc3b..4d1784da4ca 100644 --- a/apps/server/src/modules/server/server.config.ts +++ b/apps/server/src/modules/server/server.config.ts @@ -6,6 +6,7 @@ import type { IFilesStorageClientConfig } from '@modules/files-storage-client'; import type { IUserConfig } from '@modules/user'; import type { ICommonCartridgeConfig } from '@modules/learnroom/common-cartridge'; import { IMailConfig } from '@src/infra/mail/interfaces/mail-config'; +import { IXApiKeyConfig } from '../authentication/config/x-api-key.config'; export enum NodeEnvType { TEST = 'test', @@ -21,7 +22,8 @@ export interface IServerConfig IAccountConfig, IIdentityManagementConfig, ICommonCartridgeConfig, - IMailConfig { + IMailConfig, + IXApiKeyConfig { NODE_ENV: string; SC_DOMAIN: string; } @@ -44,6 +46,9 @@ const config: IServerConfig = { ADDITIONAL_BLACKLISTED_EMAIL_DOMAINS: (Configuration.get('ADDITIONAL_BLACKLISTED_EMAIL_DOMAINS') as string) .split(',') .map((domain) => domain.trim()), + ADMIN_API__ALLOWED_API_KEYS: (Configuration.get('ADMIN_API__ALLOWED_API_KEYS') as string) + .split(',') + .map((apiKey) => apiKey.trim()), }; export const serverConfig = () => config; diff --git a/config/default.schema.json b/config/default.schema.json index bd637b719e9..dcbdf9dff1e 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -1410,6 +1410,26 @@ "SECRET_ES_MERLIN_COUNTIES_CREDENTIALS" ] } + }, + "ADMIN_API": { + "type": "object", + "description": "Configuration of the schulcloud-server's admin API.", + "properties": { + "ENABLED": { + "type": "boolean", + "default": true, + "description": "Flag to turn on/off the Admin API." + }, + "PORT": { + "type": "number", + "default": 4030, + "description": "Port of the exposed Admin API server." + }, + "ALLOWED_API_KEYS": { + "type": "string", + "description": "Allowed Admin API keys (for accessing the Admin API)." + } + } } } } diff --git a/package-lock.json b/package-lock.json index 5f895871520..c78e284fc4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -107,6 +107,7 @@ "papaparse": "^5.1.1", "passport": "^0.6.0", "passport-custom": "^1.1.1", + "passport-headerapikey": "^1.2.2", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "prom-client": "^13.1.0", @@ -19179,6 +19180,15 @@ "node": ">= 0.10.0" } }, + "node_modules/passport-headerapikey": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/passport-headerapikey/-/passport-headerapikey-1.2.2.tgz", + "integrity": "sha512-4BvVJRrWsNJPrd3UoZfcnnl4zvUWYKEtfYkoDsaOKBsrWHYmzTApCjs7qUbncOLexE9ul0IRiYBFfBG0y9IVQA==", + "dependencies": { + "lodash": "^4.17.15", + "passport-strategy": "^1.0.0" + } + }, "node_modules/passport-jwt": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", @@ -39366,6 +39376,15 @@ "passport-strategy": "1.x.x" } }, + "passport-headerapikey": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/passport-headerapikey/-/passport-headerapikey-1.2.2.tgz", + "integrity": "sha512-4BvVJRrWsNJPrd3UoZfcnnl4zvUWYKEtfYkoDsaOKBsrWHYmzTApCjs7qUbncOLexE9ul0IRiYBFfBG0y9IVQA==", + "requires": { + "lodash": "^4.17.15", + "passport-strategy": "^1.0.0" + } + }, "passport-jwt": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", diff --git a/package.json b/package.json index 6afd927c17c..4426340c022 100644 --- a/package.json +++ b/package.json @@ -190,6 +190,7 @@ "papaparse": "^5.1.1", "passport": "^0.6.0", "passport-custom": "^1.1.1", + "passport-headerapikey": "^1.2.2", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "prom-client": "^13.1.0",