diff --git a/.env.example b/.env.example deleted file mode 100644 index 575fdfb..0000000 --- a/.env.example +++ /dev/null @@ -1,49 +0,0 @@ -# ============================================================ -# Workflow Portal - Environment Configuration -# ============================================================ -# Kopiere diese Datei zu .env und passe die Werte an. -# Alle Werte in <> müssen ausgefüllt werden. -# Werte mit Defaults können auskommentiert oder belassen werden. -# ============================================================ - -# ============ LDAP / Active Directory ============ -LDAP_SERVER=PIDC02.seatle.intra -LDAP_PORT=636 -LDAP_SEARCH_BASE= -LDAP_DOMAIN=SEATLE -LDAP_IGNORE_CERT_ERRORS=true -LDAP_BIND_USER= -LDAP_BIND_PASSWORD= -LDAP_SYNC_INTERVAL=300000 -LDAP_FILTER= -LDAP_ATTRIBUTES=mail,displayName,memberOf,distinguishedName,sAMAccountName -LDAP_CREATE_OU= -LDAP_UPN_SUFFIX= - -# ============ Admin Account ============ -ADMIN_EMAIL=admin@workflow.local -ADMIN_INIT_PASSWORD= - -# ============ Server ============ -PORT=5000 -NODE_ENV=production -CORS_ORIGIN=http://localhost:3900 - -# ============ PostgreSQL Database ============ -POSTGRES_DB=workflow -POSTGRES_USER=workflow -POSTGRES_PASSWORD= -# DATABASE_URL wird automatisch aus den Werten oben generiert: -# postgresql://workflow:@db:5432/workflow - -# ============ Security ============ -SESSION_MAX_PER_USER=5 -SESSION_TTL_HOURS=168 -LOGIN_MAX_ATTEMPTS=5 -LOGIN_LOCKOUT_MINUTES=15 -BODY_LIMIT=1mb -UPLOAD_MAX_MB=10 - -# ============ DB Backup ============ -BACKUP_INTERVAL_HOURS=6 -BACKUP_RETENTION_DAYS=30 \ No newline at end of file diff --git a/backend/ldapOperations.js b/backend/ldapOperations.js index 5599d62..a7faac3 100644 --- a/backend/ldapOperations.js +++ b/backend/ldapOperations.js @@ -77,8 +77,18 @@ async function browseOUTree(searchBase) { throw new Error('LDAP nicht konfiguriert.'); } - const client = await createClient(); + // H1: Validate searchBase - must be a DN under the configured LDAP_SEARCH_BASE + // (prevents arbitrary LDAP tree browsing outside the allowed scope) const base = searchBase || LDAP_SEARCH_BASE; + if (base !== LDAP_SEARCH_BASE) { + const escapeDNRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const basePattern = new RegExp(',' + escapeDNRegex(LDAP_SEARCH_BASE) + '$', 'i'); + if (!basePattern.test(base)) { + throw new Error('Ungültige Suchbasis: muss unterhalb von ' + LDAP_SEARCH_BASE + ' liegen.'); + } + } + + const client = await createClient(); try { const { searchEntries } = await client.search(base, { @@ -342,10 +352,19 @@ async function searchADGroups(query) { throw new Error('LDAP nicht konfiguriert.'); } + // H2: Validate query - length limit + only safe characters (prevents LDAP injection & DoS) + const safeQuery = String(query || '').trim(); + if (!safeQuery || safeQuery.length < 2 || safeQuery.length > 100) { + return []; + } + if (!/^[a-zA-Z0-9äöüÄÖÜß._\- ]+$/.test(safeQuery)) { + return []; + } + const client = await createClient(); try { - const escapedQuery = query.replace(/[()*\\]/g, '\\$&'); + const escapedQuery = safeQuery.replace(/[()*\\]/g, '\\$&'); const { searchEntries } = await client.search(LDAP_SEARCH_BASE, { filter: `(&(objectClass=group)(|(cn=*${escapedQuery}*)(displayName=*${escapedQuery}*)(sAMAccountName=*${escapedQuery}*)))`, scope: 'sub', diff --git a/backend/server.js b/backend/server.js index f527eff..d3d62b7 100644 --- a/backend/server.js +++ b/backend/server.js @@ -87,7 +87,8 @@ const allowedOrigins = validCorsOrigins.length > 0 : ['http://localhost:5000', 'http://localhost:5173', 'http://127.0.0.1:5000', 'http://127.0.0.1:5173']; app.use(cors({ origin: allowedOrigins, credentials: true })); // Punkt 14: Body-Size-Limit to prevent DoS via large payloads -app.use(express.json({ limit: process.env.BODY_LIMIT || '1mb' })); +// N4: 10MB to accommodate file uploads (matches UPLOAD_MAX_MB) +app.use(express.json({ limit: process.env.BODY_LIMIT || '10mb' })); // Punkt 8: Cookie-Parser für HttpOnly-Cookie Auth app.use(cookieParser()); diff --git a/backups/workflow_20260727_123700.sql.gz b/backups/workflow_20260727_123700.sql.gz deleted file mode 100644 index cc982cc..0000000 Binary files a/backups/workflow_20260727_123700.sql.gz and /dev/null differ diff --git a/backups/workflow_20260727_123743.sql.gz b/backups/workflow_20260727_123743.sql.gz deleted file mode 100644 index 12a0774..0000000 Binary files a/backups/workflow_20260727_123743.sql.gz and /dev/null differ diff --git a/backups/workflow_20260727_123848.sql.gz b/backups/workflow_20260727_123848.sql.gz deleted file mode 100644 index 050624a..0000000 Binary files a/backups/workflow_20260727_123848.sql.gz and /dev/null differ diff --git a/backups/workflow_20260825_115300.sql.gz b/backups/workflow_20260825_115300.sql.gz new file mode 100644 index 0000000..f083437 Binary files /dev/null and b/backups/workflow_20260825_115300.sql.gz differ diff --git a/backups/workflow_20260825_175259.sql.gz b/backups/workflow_20260825_175259.sql.gz new file mode 100644 index 0000000..79f0d82 Binary files /dev/null and b/backups/workflow_20260825_175259.sql.gz differ diff --git a/backups/workflow_20260825_235256.sql.gz b/backups/workflow_20260825_235256.sql.gz new file mode 100644 index 0000000..cc5295c Binary files /dev/null and b/backups/workflow_20260825_235256.sql.gz differ diff --git a/backups/workflow_20260826_092234.sql.gz b/backups/workflow_20260826_092234.sql.gz new file mode 100644 index 0000000..b476849 Binary files /dev/null and b/backups/workflow_20260826_092234.sql.gz differ diff --git a/backups/workflow_20260826_152233.sql.gz b/backups/workflow_20260826_152233.sql.gz new file mode 100644 index 0000000..e88ad04 Binary files /dev/null and b/backups/workflow_20260826_152233.sql.gz differ diff --git a/backups/workflow_20260826_212231.sql.gz b/backups/workflow_20260826_212231.sql.gz new file mode 100644 index 0000000..58f99c0 Binary files /dev/null and b/backups/workflow_20260826_212231.sql.gz differ diff --git a/backups/workflow_20260827_082653.sql.gz b/backups/workflow_20260827_082653.sql.gz new file mode 100644 index 0000000..622fab4 Binary files /dev/null and b/backups/workflow_20260827_082653.sql.gz differ diff --git a/backups/workflow_20260827_142652.sql.gz b/backups/workflow_20260827_142652.sql.gz new file mode 100644 index 0000000..a4e5965 Binary files /dev/null and b/backups/workflow_20260827_142652.sql.gz differ diff --git a/backups/workflow_20260827_202650.sql.gz b/backups/workflow_20260827_202650.sql.gz new file mode 100644 index 0000000..d42833f Binary files /dev/null and b/backups/workflow_20260827_202650.sql.gz differ diff --git a/backups/workflow_20260828_022649.sql.gz b/backups/workflow_20260828_022649.sql.gz new file mode 100644 index 0000000..91a03dd Binary files /dev/null and b/backups/workflow_20260828_022649.sql.gz differ diff --git a/frontend/.env.example b/frontend/.env.example deleted file mode 100644 index 686b06e..0000000 --- a/frontend/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -# Frontend Environment Variables -# API base URL for backend (default: http://localhost:5000/api) -VITE_API_BASE=http://localhost:5000/api \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index bc2732d..114dca8 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -50,8 +50,14 @@ function AppContent() { }; // Punkt 1: Immediate tab switch - no skeleton/transition delay to avoid bounce + // Admin-only tabs: non-admins are redirected to dashboard (defense in depth) + const ADMIN_TABS = ['templates', 'tasks', 'users', 'auditlog']; const handleTabChange = (newTab) => { if (newTab === activeTab) return; + if (user?.role !== 'admin' && ADMIN_TABS.includes(newTab)) { + setActiveTab('dashboard'); + return; + } setActiveTab(newTab); }; diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index 14ff9ba..f01b174 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -27,7 +27,8 @@ export default function Sidebar({ activeTab, onTabChange }) { const tabs = [ { id: 'dashboard', label: 'Vorlagen' }, - { id: 'templates', label: 'Vorlageneditor' }, + // Vorlageneditor nur für Admins sichtbar + ...(user?.role === 'admin' ? [{ id: 'templates', label: 'Vorlageneditor' }] : []), ...(user?.role === 'admin' ? [{ id: 'tasks', label: 'Aufgaben' }] : []), ...(user?.role === 'admin' ? [{ id: 'users', label: 'Nutzerverwaltung' }] : []), ...(user?.role === 'admin' ? [{ id: 'auditlog', label: 'Audit-Log' }] : []),