Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,26 @@ NODE_ENV=development
# Postgres Database connection string
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/interrupt_iq?schema=public"

# Redis Distributed Cache & Rate Limiter
REDIS_URL="redis://localhost:6379"

# JWT token signature secret
JWT_SECRET="generate-a-secure-random-token-key-for-production"

# Rate Limiting Configuration
RATE_LIMIT_ENABLED=true
RATE_LIMIT_GLOBAL_MAX=100
RATE_LIMIT_GLOBAL_WINDOW_MS=60000
RATE_LIMIT_AUTH_MAX=10
RATE_LIMIT_EVENTS_MAX=120
RATE_LIMIT_DECISION_MAX=60
RATE_LIMIT_RETRIEVAL_MAX=20
RATE_LIMIT_CRITIC_MAX=10

# Reverse Proxy Trust Configuration (false for direct access; true or CIDR string if behind trusted reverse proxy)
TRUST_PROXY=false

# LLM Providers Configuration (Optional)
OPENAI_API_KEY=""
OLLAMA_ENDPOINT="http://localhost:11434"

2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"dotenv": "^16.4.5",
"fastify": "^4.26.2",
"fastify-plugin": "^4.5.1",
"ioredis": "4.28.5",
"jsonwebtoken": "^9.0.2",
"pino": "^8.19.0",
"zod": "^3.22.4"
Expand All @@ -31,7 +32,6 @@
"@types/ioredis": "4.28.10",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.11.24",
"ioredis": "4.28.5",
"pino-pretty": "^10.3.1",
"prisma": "^4.16.2",
"tsx": "^4.7.1",
Expand Down
18 changes: 14 additions & 4 deletions apps/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import swaggerUi from '@fastify/swagger-ui';

import prismaPlugin from './plugins/prisma';
import observabilityPlugin from './plugins/observability';
import rateLimitPlugin from './plugins/rate-limit';
import rateLimitPlugin, { RateLimitPluginOptions } from './plugins/rate-limit';
import { errorHandler } from './errors/global-handler';
import { env } from './config/env';

Expand All @@ -24,7 +24,11 @@ import { criticRoutes } from './modules/critic/critic.routes';
import { integrationsRoutes } from './modules/integrations/integrations.routes';
import { focusReportRoutes } from './modules/focus-report/focus-report.routes';

export function buildApp(): FastifyInstance {
export interface AppOptions {
rateLimit?: RateLimitPluginOptions;
}

export function buildApp(options?: AppOptions): FastifyInstance {
const loggerConfig = {
development: {
transport: {
Expand All @@ -46,6 +50,7 @@ export function buildApp(): FastifyInstance {

const app = Fastify({
logger: loggerConfig[currentEnv],
trustProxy: env.TRUST_PROXY,
});

// Global Error Handler
Expand All @@ -55,11 +60,16 @@ export function buildApp(): FastifyInstance {
app.register(observabilityPlugin);

// Rate Limiting
app.register(rateLimitPlugin);
app.register(rateLimitPlugin, options?.rateLimit);

// Security & Utility Plugins
app.register(cors, {
origin: env.NODE_ENV === 'production' ? (process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',') : false) : true,
origin:
env.NODE_ENV === 'production'
? process.env.ALLOWED_ORIGINS
? process.env.ALLOWED_ORIGINS.split(',')
: false
: true,
});
app.register(helmet, { contentSecurityPolicy: false }); // Disable CSP for Swagger UI compatibility

Expand Down
44 changes: 43 additions & 1 deletion apps/api/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,56 @@ const envSchema = z.object({
DATABASE_URL: z.string().url().default('postgresql://postgres:postgres@localhost:5432/interrupt_iq?schema=public'),
REDIS_URL: z.string().url().default('redis://localhost:6379'),
JWT_SECRET: z.string().default('supersecret-interrupt-iq-key-change-in-prod'),

// Integrations OAuth credentials
SLACK_CLIENT_ID: z.string().optional(),
SLACK_CLIENT_SECRET: z.string().optional(),
SLACK_SIGNING_SECRET: z.string().optional(),
GOOGLE_CLIENT_ID: z.string().optional(),
GOOGLE_CLIENT_SECRET: z.string().optional(),
APP_BASE_URL: z.string().url().default('http://localhost:3001'),

// Rate Limiting Configuration
RATE_LIMIT_ENABLED: z
.preprocess((val) => {
if (typeof val === 'string') return val.toLowerCase() === 'true' || val === '1';
if (typeof val === 'boolean') return val;
return true;
}, z.boolean())
.default(true),
RATE_LIMIT_GLOBAL_MAX: z
.preprocess((val) => parseInt(val as string, 10), z.number().int().positive())
.default(100),
RATE_LIMIT_GLOBAL_WINDOW_MS: z
.preprocess((val) => parseInt(val as string, 10), z.number().int().positive())
.default(60000),
RATE_LIMIT_AUTH_MAX: z
.preprocess((val) => parseInt(val as string, 10), z.number().int().positive())
.default(10),
RATE_LIMIT_EVENTS_MAX: z
.preprocess((val) => parseInt(val as string, 10), z.number().int().positive())
.default(120),
RATE_LIMIT_DECISION_MAX: z
.preprocess((val) => parseInt(val as string, 10), z.number().int().positive())
.default(60),
RATE_LIMIT_RETRIEVAL_MAX: z
.preprocess((val) => parseInt(val as string, 10), z.number().int().positive())
.default(20),
RATE_LIMIT_CRITIC_MAX: z
.preprocess((val) => parseInt(val as string, 10), z.number().int().positive())
.default(10),

// Reverse proxy trust (false for direct access; true or CIDR string if behind trusted reverse proxy)
TRUST_PROXY: z
.preprocess(
(val) => {
if (val === 'true' || val === true) return true;
if (val === 'false' || val === false || val === undefined) return false;
return val;
},
z.union([z.boolean(), z.string()])
)
.default(false),
});

const parsed = envSchema.safeParse(process.env);
Expand Down
Loading