Security fixes N4/H2/H1 + hide template editor for non-admins
This commit is contained in:
49
.env.example
49
.env.example
@@ -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=<z.B. DC=SEATLE,DC=INTRA>
|
||||
LDAP_DOMAIN=SEATLE
|
||||
LDAP_IGNORE_CERT_ERRORS=true
|
||||
LDAP_BIND_USER=<z.B. svc_workflow@seatle.intra>
|
||||
LDAP_BIND_PASSWORD=<LDAP-Service-Account-Passwort>
|
||||
LDAP_SYNC_INTERVAL=300000
|
||||
LDAP_FILTER=
|
||||
LDAP_ATTRIBUTES=mail,displayName,memberOf,distinguishedName,sAMAccountName
|
||||
LDAP_CREATE_OU=<z.B. OU=Users,OU=SEATLE,DC=SEATLE,DC=INTRA>
|
||||
LDAP_UPN_SUFFIX=<z.B. seatle.intra>
|
||||
|
||||
# ============ Admin Account ============
|
||||
ADMIN_EMAIL=admin@workflow.local
|
||||
ADMIN_INIT_PASSWORD=<Admin-Initial-Passwort, min. 8 Zeichen mit Groß-/Kleinbuchstaben + Zahl>
|
||||
|
||||
# ============ Server ============
|
||||
PORT=5000
|
||||
NODE_ENV=production
|
||||
CORS_ORIGIN=http://localhost:3900
|
||||
|
||||
# ============ PostgreSQL Database ============
|
||||
POSTGRES_DB=workflow
|
||||
POSTGRES_USER=workflow
|
||||
POSTGRES_PASSWORD=<Sicheres Datenbank-Passwort>
|
||||
# DATABASE_URL wird automatisch aus den Werten oben generiert:
|
||||
# postgresql://workflow:<POSTGRES_PASSWORD>@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
|
||||
@@ -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',
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backups/workflow_20260825_115300.sql.gz
Normal file
BIN
backups/workflow_20260825_115300.sql.gz
Normal file
Binary file not shown.
BIN
backups/workflow_20260825_175259.sql.gz
Normal file
BIN
backups/workflow_20260825_175259.sql.gz
Normal file
Binary file not shown.
BIN
backups/workflow_20260825_235256.sql.gz
Normal file
BIN
backups/workflow_20260825_235256.sql.gz
Normal file
Binary file not shown.
BIN
backups/workflow_20260826_092234.sql.gz
Normal file
BIN
backups/workflow_20260826_092234.sql.gz
Normal file
Binary file not shown.
BIN
backups/workflow_20260826_152233.sql.gz
Normal file
BIN
backups/workflow_20260826_152233.sql.gz
Normal file
Binary file not shown.
BIN
backups/workflow_20260826_212231.sql.gz
Normal file
BIN
backups/workflow_20260826_212231.sql.gz
Normal file
Binary file not shown.
BIN
backups/workflow_20260827_082653.sql.gz
Normal file
BIN
backups/workflow_20260827_082653.sql.gz
Normal file
Binary file not shown.
BIN
backups/workflow_20260827_142652.sql.gz
Normal file
BIN
backups/workflow_20260827_142652.sql.gz
Normal file
Binary file not shown.
BIN
backups/workflow_20260827_202650.sql.gz
Normal file
BIN
backups/workflow_20260827_202650.sql.gz
Normal file
Binary file not shown.
BIN
backups/workflow_20260828_022649.sql.gz
Normal file
BIN
backups/workflow_20260828_022649.sql.gz
Normal file
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
# Frontend Environment Variables
|
||||
# API base URL for backend (default: http://localhost:5000/api)
|
||||
VITE_API_BASE=http://localhost:5000/api
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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' }] : []),
|
||||
|
||||
Reference in New Issue
Block a user