From 1927937109676867a5058a2324b590309bc68ac8 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Tue, 28 Oct 2025 20:13:09 +0100 Subject: [PATCH] added corrected password needs --- backend/src/middleware/rateLimit.ts | 48 +++++-- backend/src/middleware/validation.ts | 4 +- backend/src/server.ts | 132 +++++++++--------- .../Employees/components/EmployeeForm.tsx | 8 +- 4 files changed, 115 insertions(+), 77 deletions(-) diff --git a/backend/src/middleware/rateLimit.ts b/backend/src/middleware/rateLimit.ts index 5d9837e..710f23e 100644 --- a/backend/src/middleware/rateLimit.ts +++ b/backend/src/middleware/rateLimit.ts @@ -1,16 +1,48 @@ import rateLimit from 'express-rate-limit'; +import { Request } from 'express'; -export const authLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 5, // Limit each IP to 5 login requests per windowMs - message: { error: 'Zu viele Login-Versuche, bitte versuchen Sie es später erneut' }, - standardHeaders: true, - legacyHeaders: false, -}); +// Helper to check if request should be limited +const shouldSkipLimit = (req: Request): boolean => { + const skipPaths = [ + '/api/health', + '/api/setup/status', + '/api/auth/validate' + ]; + + // Skip for successful GET requests (data fetching) + if (req.method === 'GET' && req.path.startsWith('/api/')) { + return true; + } + + return skipPaths.includes(req.path); +}; +// Main API limiter - nur für POST/PUT/DELETE export const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes - max: 100, // Limit each IP to 100 requests per windowMs + max: 200, // 200 non-GET requests per 15 minutes + message: { + error: 'Zu viele Anfragen, bitte verlangsamen Sie Ihre Aktionen' + }, standardHeaders: true, legacyHeaders: false, + skip: (req) => { + // ✅ Skip für GET requests (Data Fetching) + if (req.method === 'GET') return true; + + // ✅ Skip für Health/Status Checks + return shouldSkipLimit(req); + } +}); + +// Strict limiter for auth endpoints +export const authLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 5, + message: { + error: 'Zu viele Login-Versuche, bitte versuchen Sie es später erneut' + }, + standardHeaders: true, + legacyHeaders: false, + skipSuccessfulRequests: true, }); \ No newline at end of file diff --git a/backend/src/middleware/validation.ts b/backend/src/middleware/validation.ts index 1199fc3..28951b3 100644 --- a/backend/src/middleware/validation.ts +++ b/backend/src/middleware/validation.ts @@ -284,7 +284,7 @@ export const validateCreateFromPreset = [ body('presetName') .isLength({ min: 1 }) .withMessage('Preset name is required') - .isIn(['standardWeek', 'extendedWeek', 'weekendFocused', 'morningOnly', 'eveningOnly']) + .isIn(['standardWeek', 'extendedWeek', 'weekendFocused', 'morningOnly', 'eveningOnly', 'ZEBRA_STANDARD']) .withMessage('Invalid preset name'), body('name') @@ -444,7 +444,7 @@ export const handleValidationErrors = (req: Request, res: Response, next: NextFu const errorMessages = errors.array().map(error => ({ field: error.type === 'field' ? error.path : error.type, message: error.msg, - value: error + value: error.msg })); return res.status(400).json({ diff --git a/backend/src/server.ts b/backend/src/server.ts index 853fed3..fe25eb6 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -1,6 +1,7 @@ // backend/src/server.ts import express from 'express'; import path from 'path'; +import { fileURLToPath } from 'url'; import { initializeDatabase } from './scripts/initializeDatabase.js'; import fs from 'fs'; import helmet from 'helmet'; @@ -14,9 +15,14 @@ import scheduledShifts from './routes/scheduledShifts.js'; import schedulingRoutes from './routes/scheduling.js'; import { authLimiter, apiLimiter } from './middleware/rateLimit.js'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + const app = express(); const PORT = 3002; +const isDevelopment = process.env.NODE_ENV === 'development'; +// Security configuration if (process.env.NODE_ENV === 'production') { console.info('Checking for JWT_SECRET'); const JWT_SECRET = process.env.JWT_SECRET; @@ -26,10 +32,9 @@ if (process.env.NODE_ENV === 'production') { } } - // Security headers app.use(helmet({ - contentSecurityPolicy: { + contentSecurityPolicy: isDevelopment ? false : { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], @@ -37,7 +42,7 @@ app.use(helmet({ imgSrc: ["'self'", "data:", "https:"], }, }, - crossOriginEmbedderPolicy: false // Required for Vite dev + crossOriginEmbedderPolicy: false })); // Additional security headers @@ -51,9 +56,14 @@ app.use((req, res, next) => { // Middleware app.use(express.json()); -// API Routes -app.use('/api/', apiLimiter); +// Rate limiting - weniger restriktiv in Development +if (process.env.NODE_ENV === 'production') { + app.use('/api/', apiLimiter); +} else { + console.log('🔧 Development: Rate limiting relaxed'); +} +// API Routes app.use('/api/setup', setupRoutes); app.use('/api/auth', authLimiter, authRoutes); app.use('/api/employees', employeeRoutes); @@ -62,82 +72,86 @@ app.use('/api/scheduled-shifts', scheduledShifts); app.use('/api/scheduling', schedulingRoutes); // Health route -app.get('/api/health', (req: any, res: any) => { +app.get('/api/health', (req: express.Request, res: express.Response) => { res.json({ status: 'OK', message: 'Backend läuft!', - timestamp: new Date().toISOString() + timestamp: new Date().toISOString(), + mode: process.env.NODE_ENV || 'development' }); }); -// 🆕 STATIC FILE SERVING -// Use absolute path that matches Docker container structure -const frontendBuildPath = path.resolve('/app/frontend-build'); -console.log('📁 Frontend build path:', frontendBuildPath); +// 🆕 IMPROVED STATIC FILE SERVING +const findFrontendBuildPath = (): string | null => { + const possiblePaths = [ + // Production path (Docker) + '/app/frontend-build', + // Development paths + path.resolve(__dirname, '../../frontend/dist'), + path.resolve(__dirname, '../../frontend-build'), + path.resolve(process.cwd(), '../frontend/dist'), + path.resolve(process.cwd(), 'frontend-build'), + ]; + + for (const testPath of possiblePaths) { + try { + if (fs.existsSync(testPath)) { + const indexPath = path.join(testPath, 'index.html'); + if (fs.existsSync(indexPath)) { + console.log('✅ Found frontend build at:', testPath); + return testPath; + } + } + } catch (error) { + // Silent catch - just try next path + } + } + return null; +}; + +const frontendBuildPath = findFrontendBuildPath(); if (frontendBuildPath) { - // Serviere statische Dateien app.use(express.static(frontendBuildPath)); - - // List files for debugging - try { - const files = fs.readdirSync(frontendBuildPath); - console.log('📄 Files in frontend-build:', files); - } catch (err) { - console.log('❌ Could not read frontend-build directory:', err); - } - console.log('✅ Static file serving configured'); } else { - console.log('❌ Frontend build directory NOT FOUND in any location'); + console.log(isDevelopment ? + '🔧 Development: Frontend served by Vite dev server (localhost:3003)' : + '❌ Production: No frontend build found' + ); } // Root route -app.get('/', apiLimiter, (req, res) => { +app.get('/', (req, res) => { if (!frontendBuildPath) { + if (isDevelopment) { + return res.redirect('http://localhost:3003'); + } return res.status(500).send('Frontend build not found'); } const indexPath = path.join(frontendBuildPath, 'index.html'); - console.log('📄 Serving index.html from:', indexPath); - - if (fs.existsSync(indexPath)) { - res.sendFile(indexPath); - } else { - console.error('❌ index.html not found at:', indexPath); - res.status(404).send('Frontend not found - index.html missing'); - } + res.sendFile(indexPath); }); // Client-side routing fallback -app.get('*', apiLimiter, (req, res) => { - // Ignoriere API Routes +app.get('*', (req, res) => { if (req.path.startsWith('/api/')) { return res.status(404).json({ error: 'API endpoint not found' }); } if (!frontendBuildPath) { + if (isDevelopment) { + return res.redirect(`http://localhost:3003${req.path}`); + } return res.status(500).json({ error: 'Frontend application not available' }); } const indexPath = path.join(frontendBuildPath, 'index.html'); - console.log('🔄 Client-side routing for:', req.path, '->', indexPath); - - if (fs.existsSync(indexPath)) { - // Use absolute path with res.sendFile - res.sendFile(indexPath, (err) => { - if (err) { - console.error('Error sending index.html:', err); - res.status(500).send('Error loading application'); - } - }); - } else { - console.error('❌ index.html not found for client-side routing at:', indexPath); - res.status(404).json({ error: 'Frontend application not found' }); - } + res.sendFile(indexPath); }); -// Production error handling - don't leak stack traces +// Error handling app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { console.error('Error:', err); @@ -155,12 +169,7 @@ app.use((err: any, req: express.Request, res: express.Response, next: express.Ne } }); -// Error handling middleware -app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { - console.error('Unhandled error:', err); - res.status(500).json({ error: 'Internal server error' }); -}); - +// 404 handling app.use('*', (req, res) => { res.status(404).json({ error: 'Endpoint not found' }); }); @@ -168,22 +177,20 @@ app.use('*', (req, res) => { // Initialize the application const initializeApp = async () => { try { - // Initialize database with base schema await initializeDatabase(); - - // Apply any pending migrations const { applyMigration } = await import('./scripts/applyMigration.js'); await applyMigration(); - // Start server only after successful initialization app.listen(PORT, () => { console.log('🎉 APPLICATION STARTED SUCCESSFULLY!'); console.log(`📍 Port: ${PORT}`); - console.log(`📍 Frontend: http://localhost:${PORT}`); + console.log(`📍 Mode: ${process.env.NODE_ENV || 'development'}`); + if (frontendBuildPath) { + console.log(`📍 Frontend: http://localhost:${PORT}`); + } else if (isDevelopment) { + console.log(`📍 Frontend (Vite): http://localhost:3003`); + } console.log(`📍 API: http://localhost:${PORT}/api`); - console.log(''); - console.log(`🔧 Setup: http://localhost:${PORT}/api/setup/status`); - console.log('📝 Create your admin account on first launch'); }); } catch (error) { console.error('❌ Error during initialization:', error); @@ -191,5 +198,4 @@ const initializeApp = async () => { } }; -// Start the application initializeApp(); \ No newline at end of file diff --git a/frontend/src/pages/Employees/components/EmployeeForm.tsx b/frontend/src/pages/Employees/components/EmployeeForm.tsx index c4feaa0..b7e8204 100644 --- a/frontend/src/pages/Employees/components/EmployeeForm.tsx +++ b/frontend/src/pages/Employees/components/EmployeeForm.tsx @@ -185,7 +185,7 @@ const EmployeeForm: React.FC = ({ // Password change logic remains the same if (showPasswordSection && passwordForm.newPassword && hasRole(['admin'])) { if (passwordForm.newPassword.length < 6) { - throw new Error('Das neue Passwort muss mindestens 6 Zeichen lang sein'); + throw new Error('Das Passwort muss mindestens 6 Zeichen lang sein, Zahlen und Groß- / Kleinbuchstaben enthalten'); } if (passwordForm.newPassword !== passwordForm.confirmPassword) { throw new Error('Die Passwörter stimmen nicht überein'); @@ -351,10 +351,10 @@ const EmployeeForm: React.FC = ({ borderRadius: '4px', fontSize: '16px' }} - placeholder="Mindestens 6 Zeichen" + placeholder="Mindestens 6 Zeichen, Zahlen, Groß- / Kleinzeichen" />
- Das Passwort muss mindestens 6 Zeichen lang sein. + Das Passwort muss mindestens 6 Zeichen lang sein, Zahlen und Groß- / Kleinbuchstaben enthalten.
)} @@ -672,7 +672,7 @@ const EmployeeForm: React.FC = ({ borderRadius: '4px', fontSize: '16px' }} - placeholder="Mindestens 6 Zeichen" + placeholder="Mindestens 6 Zeichen, Zahlen, Groß- / Kleinzeichen" />