INSIGHT-MVP/packages/core-service/src/config/env.validation.ts
Thomas Reitz 45cf644f81 feat: add Microsoft Entra ID (Azure AD) SSO integration
Backend-driven Authorization Code Flow with @azure/msal-node:
- EntraIdService: MSAL ConfidentialClientApplication, auth URL generation, token exchange
- SsoController: /auth/sso/microsoft (initiate) + /auth/sso/microsoft/callback (callback)
- AuthService.loginViaSso(): User provisioning (find by OID, auto-link by email, or create new)
- CSRF protection via state parameter stored in Redis
- SSO status endpoint for frontend feature detection

Frontend:
- "Mit Microsoft anmelden" button on login page (shown only when SSO is configured)
- SsoCallbackPage: handles redirect from backend, sets token, loads user profile
- AuthContext.loginWithToken(): new method for SSO token handling

Configuration:
- AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_REDIRECT_URI env vars
- docker-compose.yml updated to pass Azure vars to core service

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:31:34 +01:00

120 lines
2.1 KiB
TypeScript

import { plainToInstance, Type } from 'class-transformer';
import {
IsEnum,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
Min,
Max,
validateSync,
} from 'class-validator';
enum Environment {
Development = 'development',
Production = 'production',
Test = 'test',
}
class EnvironmentVariables {
@IsEnum(Environment)
NODE_ENV: Environment = Environment.Development;
@Type(() => Number)
@IsNumber()
@Min(1)
@Max(65535)
APP_PORT = 3000;
@IsString()
@IsNotEmpty()
APP_URL = 'http://172.20.10.59';
// Datenbank
@IsString()
@IsNotEmpty()
DATABASE_URL!: string;
@IsString()
@IsOptional()
DATABASE_URL_DIRECT?: string;
// Redis
@IsString()
REDIS_HOST = 'redis';
@Type(() => Number)
@IsNumber()
REDIS_PORT = 6379;
@IsString()
@IsOptional()
REDIS_PASSWORD?: string;
// JWT
@IsString()
@IsNotEmpty()
JWT_PRIVATE_KEY_PATH = '/app/keys/jwt-private.pem';
@IsString()
@IsNotEmpty()
JWT_PUBLIC_KEY_PATH = '/app/keys/jwt-public.pem';
@IsString()
JWT_ACCESS_TOKEN_EXPIRY = '15m';
@IsString()
JWT_REFRESH_TOKEN_EXPIRY = '7d';
@IsString()
JWT_ISSUER = 'insight-platform';
// Bcrypt
@Type(() => Number)
@IsNumber()
@Min(10)
@Max(14)
BCRYPT_COST = 12;
// Rate Limiting
@Type(() => Number)
@IsNumber()
THROTTLE_TTL = 60000;
@Type(() => Number)
@IsNumber()
THROTTLE_LIMIT = 200;
// Microsoft Entra ID (Azure AD) SSO - optional
@IsOptional()
@IsString()
AZURE_TENANT_ID?: string;
@IsOptional()
@IsString()
AZURE_CLIENT_ID?: string;
@IsOptional()
@IsString()
AZURE_CLIENT_SECRET?: string;
@IsOptional()
@IsString()
AZURE_REDIRECT_URI?: string;
}
export function validateConfig(
config: Record<string, unknown>,
): EnvironmentVariables {
const validatedConfig = plainToInstance(EnvironmentVariables, config, {
enableImplicitConversion: true,
});
const errors = validateSync(validatedConfig, {
skipMissingProperties: false,
});
if (errors.length > 0) {
throw new Error(`Config validation error: ${errors.toString()}`);
}
return validatedConfig;
}