Files
stack/apps/api/src/federation/federation.module.ts
Jason Woltje d2003a7b03
All checks were successful
ci/woodpecker/push/api Pipeline was successful
fix(api): make federation config validation non-fatal at startup
Federation is optional and should not prevent the app from starting
when DEFAULT_WORKSPACE_ID is not set. Changed from throwing (crash)
to logging a warning. The endpoint-level validation in the controller
still rejects requests when federation is unconfigured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 01:08:09 -06:00

108 lines
3.4 KiB
TypeScript

/**
* Federation Module
*
* Provides instance identity and federation management with DoS protection via rate limiting.
* Issue #272: Rate limiting added to prevent DoS attacks on federation endpoints
* Issue #338: Validate DEFAULT_WORKSPACE_ID at startup
*/
import { Module, Logger, OnModuleInit } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { HttpModule } from "@nestjs/axios";
import { ThrottlerModule } from "@nestjs/throttler";
import { FederationController } from "./federation.controller";
import { FederationAuthController } from "./federation-auth.controller";
import { FederationService } from "./federation.service";
import { CryptoService } from "./crypto.service";
import { FederationAuditService } from "./audit.service";
import { SignatureService } from "./signature.service";
import { ConnectionService } from "./connection.service";
import { OIDCService } from "./oidc.service";
import { CommandService } from "./command.service";
import { QueryService } from "./query.service";
import { FederationAgentService } from "./federation-agent.service";
import { validateFederationConfig } from "./federation.config";
import { PrismaModule } from "../prisma/prisma.module";
import { AuthModule } from "../auth/auth.module";
import { TasksModule } from "../tasks/tasks.module";
import { EventsModule } from "../events/events.module";
import { ProjectsModule } from "../projects/projects.module";
import { RedisProvider } from "../common/providers/redis.provider";
@Module({
imports: [
ConfigModule,
PrismaModule,
AuthModule,
TasksModule,
EventsModule,
ProjectsModule,
HttpModule.register({
timeout: 10000,
maxRedirects: 5,
}),
// Rate limiting for DoS protection (Issue #272)
// Uses in-memory storage by default (suitable for single-instance deployments)
// For multi-instance deployments, configure Redis storage via ThrottlerStorageRedisService
ThrottlerModule.forRoot([
{
name: "short",
ttl: 1000, // 1 second
limit: 3, // 3 requests per second (very strict for public endpoints)
},
{
name: "medium",
ttl: 60000, // 1 minute
limit: 20, // 20 requests per minute (for authenticated endpoints)
},
{
name: "long",
ttl: 3600000, // 1 hour
limit: 200, // 200 requests per hour (for read operations)
},
]),
],
controllers: [FederationController, FederationAuthController],
providers: [
RedisProvider,
FederationService,
CryptoService,
FederationAuditService,
SignatureService,
ConnectionService,
OIDCService,
CommandService,
QueryService,
FederationAgentService,
],
exports: [
FederationService,
CryptoService,
FederationAuditService,
SignatureService,
ConnectionService,
OIDCService,
CommandService,
QueryService,
FederationAgentService,
],
})
export class FederationModule implements OnModuleInit {
private readonly logger = new Logger(FederationModule.name);
/**
* Validate federation configuration at module initialization.
* Issue #338: Fail fast if DEFAULT_WORKSPACE_ID is not a valid UUID.
*/
onModuleInit(): void {
try {
validateFederationConfig();
this.logger.log("Federation configuration validated successfully");
} catch (error) {
this.logger.warn(
`Federation disabled: ${error instanceof Error ? error.message : String(error)}`
);
}
}
}