import express, { Application, Request, Response } from 'express'; import cors from 'cors'; import helmet from 'helmet'; import rateLimit from 'express-rate-limit'; import path from 'path'; import environment from './config/environment'; import logger from './utils/logger'; import { errorHandler, notFoundHandler } from './middleware/error.middleware'; import { requestTimeout } from './middleware/request-timeout.middleware'; import { authenticate } from './middleware/auth.middleware'; const app: Application = express(); // Trust proxy (required for correct IP detection behind Traefik/Nginx) app.set('trust proxy', 1); // Security middleware app.use(helmet()); // CORS configuration app.use(cors({ origin: environment.cors.origin, credentials: true, })); // Rate limiting - general API routes (applied below, after auth limiter) // Rate limiting - auth routes (generous to avoid blocking logins during // normal use; each OAuth flow = 1 callback + token exchange) const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 60, // 60 auth attempts per window (allows ~20 full login cycles) message: 'Zu viele Anmeldeversuche. Bitte versuchen Sie es später erneut.', standardHeaders: true, legacyHeaders: false, }); app.use('/api/auth', authLimiter); // General rate limiter — skip auth routes (own limiter above) and authenticated // requests (Bearer token present). Auth middleware validates the token downstream; // rate-limiting authenticated dashboard polling would cause 429 floods. app.use('/api', rateLimit({ windowMs: environment.rateLimit.windowMs, max: environment.rateLimit.max, message: 'Too many requests from this IP, please try again later.', standardHeaders: true, legacyHeaders: false, skip: (req) => { if (req.path.startsWith('/auth')) return true; const auth = req.headers.authorization; return typeof auth === 'string' && auth.startsWith('Bearer '); }, })); // Body parsing middleware app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); // Request timeout middleware app.use(requestTimeout); // Request logging middleware app.use((req: Request, _res: Response, next) => { logger.info('Incoming request', { method: req.method, path: req.path, ip: req.ip, }); next(); }); // Health check endpoint app.get('/health', (_req: Request, res: Response) => { res.status(200).json({ status: 'ok', timestamp: new Date().toISOString(), uptime: process.uptime(), environment: environment.nodeEnv, }); }); // API routes import authRoutes from './routes/auth.routes'; import userRoutes from './routes/user.routes'; import memberRoutes from './routes/member.routes'; import adminRoutes from './routes/admin.routes'; import trainingRoutes from './routes/training.routes'; import vehicleRoutes from './routes/vehicle.routes'; import incidentRoutes from './routes/incident.routes'; import equipmentRoutes from './routes/equipment.routes'; import nextcloudRoutes from './routes/nextcloud.routes'; import atemschutzRoutes from './routes/atemschutz.routes'; import eventsRoutes from './routes/events.routes'; import bookingRoutes from './routes/booking.routes'; import notificationRoutes from './routes/notification.routes'; import bookstackRoutes from './routes/bookstack.routes'; import vikunjaRoutes from './routes/vikunja.routes'; import bestellungRoutes from './routes/bestellung.routes'; import configRoutes from './routes/config.routes'; import serviceMonitorRoutes from './routes/serviceMonitor.routes'; import settingsRoutes from './routes/settings.routes'; import bannerRoutes from './routes/banner.routes'; import permissionRoutes from './routes/permission.routes'; import ausruestungsanfrageRoutes from './routes/ausruestungsanfrage.routes'; import issueRoutes from './routes/issue.routes'; import buchungskategorieRoutes from './routes/buchungskategorie.routes'; import checklistRoutes from './routes/checklist.routes'; import fahrzeugTypRoutes from './routes/fahrzeugTyp.routes'; import ausruestungTypRoutes from './routes/ausruestungTyp.routes'; import buchhaltungRoutes from './routes/buchhaltung.routes'; import personalEquipmentRoutes from './routes/personalEquipment.routes'; import toolConfigRoutes from './routes/toolConfig.routes'; import scheduledMessagesRoutes from './routes/scheduledMessages.routes'; app.use('/api/auth', authRoutes); app.use('/api/user', userRoutes); app.use('/api/members', memberRoutes); app.use('/api/admin', adminRoutes); app.use('/api/training', trainingRoutes); app.use('/api/vehicles', vehicleRoutes); app.use('/api/incidents', incidentRoutes); app.use('/api/equipment', equipmentRoutes); app.use('/api/atemschutz', atemschutzRoutes); app.use('/api/nextcloud/talk', nextcloudRoutes); app.use('/api/events', eventsRoutes); app.use('/api/bookings', bookingRoutes); app.use('/api/notifications', notificationRoutes); app.use('/api/bookstack', bookstackRoutes); app.use('/api/vikunja', vikunjaRoutes); app.use('/api/bestellungen', bestellungRoutes); app.use('/api/config', configRoutes); app.use('/api/admin', serviceMonitorRoutes); app.use('/api/admin/settings', settingsRoutes); app.use('/api/settings', settingsRoutes); app.use('/api/banners', bannerRoutes); app.use('/api/permissions', permissionRoutes); app.use('/api/ausruestungsanfragen', ausruestungsanfrageRoutes); app.use('/api/issues', issueRoutes); app.use('/api/buchungskategorien', buchungskategorieRoutes); app.use('/api/checklisten', checklistRoutes); app.use('/api/fahrzeug-typen', fahrzeugTypRoutes); app.use('/api/ausruestung-typen', ausruestungTypRoutes); app.use('/api/buchhaltung', buchhaltungRoutes); app.use('/api/persoenliche-ausruestung', personalEquipmentRoutes); app.use('/api/admin/tools', toolConfigRoutes); app.use('/api/scheduled-messages', scheduledMessagesRoutes); // Static file serving for uploads (authenticated) const uploadsDir = process.env.NODE_ENV === 'production' ? '/app/uploads' : path.resolve(__dirname, '../../uploads'); app.use('/uploads', authenticate, express.static(uploadsDir)); // 404 handler app.use(notFoundHandler); // Error handling middleware (must be last) app.use(errorHandler); export default app;