Sicherheits-Fixes, toter Code entfernt, Optimierungen

Sicherheit:
- CSRF-Schutz auf allen API-Routes (admin, proposals, files, stats, export)
- authenticateToken vor csrfProtection bei admin/proposals (CSRF-Bypass behoben)
- CORS eingeschränkt auf taskmate.aegis-sight.de
- JWT_SECRET und SESSION_TIMEOUT nicht mehr exportiert
- Tote Auth-Funktionen entfernt (generateCsrfToken, generateToken Legacy)

Toter Code entfernt:
- 6 ungenutzte JS-Dateien (tour, dashboard, 4x contacts-*)
- 2 ungenutzte CSS-Dateien (dashboard, contacts-extended)
- backend/migrations/ Verzeichnis, knowledge.js.backup
- Doppelter bcrypt require in database.js

Optimierung:
- Request-Logging filtert statische Assets (nur /api/ wird geloggt)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Server Deploy
2026-03-19 19:21:40 +01:00
Ursprung 4bd57d653f
Commit 5c87254e97
14 geänderte Dateien mit 1684 neuen und 997 gelöschten Zeilen

Datei anzeigen

@@ -0,0 +1,497 @@
/**
* TASKMATE - Erweiterte Kontakte API
* ===================================
* REST API für Kontakte mit Institutionen
*/
const router = require('express').Router();
const { getDb } = require('../database');
const logger = require('../utils/logger');
// ===================================
// INSTITUTIONEN
// ===================================
// Alle Institutionen abrufen
router.get('/institutions', (req, res) => {
try {
const db = getDb();
const { project_id, search, type } = req.query;
let query = 'SELECT * FROM institutions WHERE 1=1';
const params = [];
if (project_id) {
query += ' AND project_id = ?';
params.push(project_id);
}
if (search) {
query += ' AND (name LIKE ? OR description LIKE ? OR tags LIKE ?)';
const searchPattern = `%${search}%`;
params.push(searchPattern, searchPattern, searchPattern);
}
if (type) {
query += ' AND type = ?';
params.push(type);
}
query += ' ORDER BY name ASC';
const institutions = db.prepare(query).all(...params);
res.json({ success: true, data: institutions });
} catch (error) {
logger.error('Fehler beim Abrufen der Institutionen:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// Institution erstellen
router.post('/institutions', (req, res) => {
// Basis-Validierung
if (!req.body.name || !req.body.project_id) {
return res.status(400).json({ success: false, error: 'Name und Projekt-ID sind erforderlich' });
}
try {
const db = getDb();
const stmt = db.prepare(`
INSERT INTO institutions (
name, type, industry, website, logo_url, description,
main_address, main_postal_code,
main_city, main_state, main_country, trade_register,
notes, tags, created_by, project_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const result = stmt.run(
req.body.name,
req.body.type || null,
req.body.industry || null,
req.body.website || null,
req.body.logo_url || null,
req.body.description || null,
req.body.main_address || null,
req.body.main_postal_code || null,
req.body.main_city || null,
req.body.main_state || null,
req.body.main_country || null,
req.body.trade_register || null,
req.body.notes || null,
req.body.tags || null,
req.userId,
req.body.project_id
);
const institution = db.prepare('SELECT * FROM institutions WHERE id = ?').get(result.lastInsertRowid);
// Socket.io Event
req.app.get('io').to(`project-${req.body.project_id}`).emit('institution:created', institution);
res.status(201).json({ success: true, data: institution });
} catch (error) {
logger.error('Fehler beim Erstellen der Institution:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// Institution aktualisieren
router.put('/institutions/:id', (req, res) => {
try {
const db = getDb();
const stmt = db.prepare(`
UPDATE institutions SET
name = ?, type = ?, industry = ?, website = ?, logo_url = ?,
description = ?,
main_address = ?, main_postal_code = ?, main_city = ?,
main_state = ?, main_country = ?,
trade_register = ?, notes = ?, tags = ?
WHERE id = ?
`);
stmt.run(
req.body.name,
req.body.type,
req.body.industry,
req.body.website,
req.body.logo_url,
req.body.description,
req.body.main_address,
req.body.main_postal_code,
req.body.main_city,
req.body.main_state,
req.body.main_country,
req.body.trade_register,
req.body.notes,
req.body.tags,
req.params.id
);
const institution = db.prepare('SELECT * FROM institutions WHERE id = ?').get(req.params.id);
// Socket.io Event
req.app.get('io').to(`project-${institution.project_id}`).emit('institution:updated', institution);
res.json({ success: true, data: institution });
} catch (error) {
logger.error('Fehler beim Aktualisieren der Institution:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// ===================================
// KONTAKTE (PERSONEN)
// ===================================
// Alle Kontakte abrufen
router.get('/contacts', (req, res) => {
try {
const db = getDb();
const { project_id, institution_id, search, is_active } = req.query;
let query = `
SELECT c.*, i.name as institution_name
FROM contacts_extended c
LEFT JOIN institutions i ON c.institution_id = i.id
WHERE 1=1
`;
const params = [];
if (project_id) {
query += ' AND c.project_id = ?';
params.push(project_id);
}
if (institution_id) {
query += ' AND c.institution_id = ?';
params.push(institution_id);
}
if (search) {
query += ' AND (c.first_name LIKE ? OR c.last_name LIKE ? OR c.display_name LIKE ? OR c.tags LIKE ?)';
const searchPattern = `%${search}%`;
params.push(searchPattern, searchPattern, searchPattern, searchPattern);
}
if (is_active !== undefined) {
query += ' AND c.is_active = ?';
params.push(is_active === 'true' ? 1 : 0);
}
query += ' ORDER BY c.last_name, c.first_name ASC';
const contacts = db.prepare(query).all(...params);
res.json({ success: true, data: contacts });
} catch (error) {
logger.error('Fehler beim Abrufen der Kontakte:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// Kontakt mit allen Details abrufen
router.get('/contacts/:id/full', (req, res) => {
try {
const db = getDb();
// Basis-Kontaktdaten
const contact = db.prepare(`
SELECT c.*, i.name as institution_name
FROM contacts_extended c
LEFT JOIN institutions i ON c.institution_id = i.id
WHERE c.id = ?
`).get(req.params.id);
if (!contact) {
return res.status(404).json({ success: false, error: 'Kontakt nicht gefunden' });
}
// Kontaktdetails
contact.details = db.prepare(`
SELECT * FROM contact_details
WHERE contact_id = ?
ORDER BY is_primary DESC, type ASC
`).all(req.params.id);
// Weitere Institutionen
contact.institutions = db.prepare(`
SELECT i.*, r.position, r.department, r.start_date, r.end_date, r.is_primary
FROM person_institution_relations r
JOIN institutions i ON r.institution_id = i.id
WHERE r.contact_id = ?
ORDER BY r.is_primary DESC, r.start_date DESC
`).all(req.params.id);
// Kategorien
contact.categories = db.prepare(`
SELECT c.*
FROM contact_categories c
JOIN contact_category_assignments a ON c.id = a.category_id
WHERE a.contact_id = ?
`).all(req.params.id);
// Letzte Interaktionen
contact.recent_interactions = db.prepare(`
SELECT * FROM contact_interactions
WHERE contact_id = ?
ORDER BY date DESC
LIMIT 10
`).all(req.params.id);
res.json({ success: true, data: contact });
} catch (error) {
logger.error('Fehler beim Abrufen des Kontakts:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// Kontakt erstellen
router.post('/contacts', (req, res) => {
// Basis-Validierung
if (!req.body.first_name || !req.body.last_name || !req.body.project_id) {
return res.status(400).json({ success: false, error: 'Vor- und Nachname sowie Projekt-ID sind erforderlich' });
}
const db = getDb();
const transaction = db.transaction((data) => {
try {
// Kontakt erstellen
const stmt = db.prepare(`
INSERT INTO contacts_extended (
salutation, title, first_name, last_name, display_name,
position, department, institution_id,
notes, tags, avatar_url, is_active, created_by, project_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const result = stmt.run(
data.salutation || null,
data.title || null,
data.first_name,
data.last_name,
data.display_name || `${data.first_name} ${data.last_name}`,
data.position || null,
data.department || null,
data.institution_id || null,
data.notes || null,
data.tags || null,
data.avatar_url || null,
data.is_active !== false ? 1 : 0,
req.userId,
data.project_id
);
const contactId = result.lastInsertRowid;
// Kontaktdetails hinzufügen
if (data.details && Array.isArray(data.details)) {
const detailStmt = db.prepare(`
INSERT INTO contact_details (
contact_id, type, subtype, value, label, is_primary, is_public
) VALUES (?, ?, ?, ?, ?, ?, ?)
`);
for (const detail of data.details) {
detailStmt.run(
contactId,
detail.type,
detail.subtype || null,
detail.value,
detail.label || null,
detail.is_primary ? 1 : 0,
detail.is_public !== false ? 1 : 0
);
}
}
// Kategorien zuweisen
if (data.categories && Array.isArray(data.categories)) {
const categoryStmt = db.prepare(`
INSERT INTO contact_category_assignments (contact_id, category_id)
VALUES (?, ?)
`);
for (const categoryId of data.categories) {
categoryStmt.run(contactId, categoryId);
}
}
return contactId;
} catch (error) {
throw error;
}
});
try {
const contactId = transaction(req.body);
const contact = db.prepare('SELECT * FROM contacts_extended WHERE id = ?').get(contactId);
// Socket.io Event
req.app.get('io').to(`project-${req.body.project_id}`).emit('contact:created', contact);
res.status(201).json({ success: true, data: contact });
} catch (error) {
logger.error('Fehler beim Erstellen des Kontakts:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// ===================================
// KONTAKTDETAILS
// ===================================
// Kontaktdetail hinzufügen
router.post('/contact-details', (req, res) => {
// Basis-Validierung
if (!req.body.type || !req.body.value) {
return res.status(400).json({ success: false, error: 'Typ und Wert sind erforderlich' });
}
try {
const db = getDb();
const stmt = db.prepare(`
INSERT INTO contact_details (
contact_id, institution_id, type, subtype, value, label,
is_primary, is_public, notes
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const result = stmt.run(
req.body.contact_id || null,
req.body.institution_id || null,
req.body.type,
req.body.subtype || null,
req.body.value,
req.body.label || null,
req.body.is_primary ? 1 : 0,
req.body.is_public !== false ? 1 : 0,
req.body.notes || null
);
const detail = db.prepare('SELECT * FROM contact_details WHERE id = ?').get(result.lastInsertRowid);
// Socket.io Event
const eventType = req.body.contact_id ? 'contact' : 'institution';
req.app.get('io').emit(`${eventType}:detail:added`, detail);
res.status(201).json({ success: true, data: detail });
} catch (error) {
logger.error('Fehler beim Hinzufügen des Kontaktdetails:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// ===================================
// PERSON-INSTITUTION BEZIEHUNGEN
// ===================================
// Beziehung erstellen
router.post('/relations', (req, res) => {
// Basis-Validierung
if (!req.body.contact_id || !req.body.institution_id) {
return res.status(400).json({ success: false, error: 'Kontakt-ID und Institutions-ID sind erforderlich' });
}
try {
const db = getDb();
const stmt = db.prepare(`
INSERT INTO person_institution_relations (
contact_id, institution_id, position, department,
start_date, end_date, is_primary, notes
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`);
const result = stmt.run(
req.body.contact_id,
req.body.institution_id,
req.body.position || null,
req.body.department || null,
req.body.start_date || null,
req.body.end_date || null,
req.body.is_primary ? 1 : 0,
req.body.notes || null
);
const relation = db.prepare('SELECT * FROM person_institution_relations WHERE id = ?')
.get(result.lastInsertRowid);
// Socket.io Event
req.app.get('io').emit('relation:created', relation);
res.status(201).json({ success: true, data: relation });
} catch (error) {
logger.error('Fehler beim Erstellen der Beziehung:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// ===================================
// KATEGORIEN
// ===================================
// Alle Kategorien abrufen
router.get('/categories', (req, res) => {
try {
const db = getDb();
const { project_id } = req.query;
let query = 'SELECT * FROM contact_categories';
const params = [];
if (project_id) {
query += ' WHERE project_id = ?';
params.push(project_id);
}
query += ' ORDER BY name ASC';
const categories = db.prepare(query).all(...params);
res.json({ success: true, data: categories });
} catch (error) {
logger.error('Fehler beim Abrufen der Kategorien:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// ===================================
// INTERAKTIONEN
// ===================================
// Interaktion hinzufügen
router.post('/interactions', (req, res) => {
// Basis-Validierung
if (!req.body.type) {
return res.status(400).json({ success: false, error: 'Typ ist erforderlich' });
}
try {
const db = getDb();
const stmt = db.prepare(`
INSERT INTO contact_interactions (
contact_id, institution_id, type, subject, content,
date, duration_minutes, task_id, created_by
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const result = stmt.run(
req.body.contact_id || null,
req.body.institution_id || null,
req.body.type,
req.body.subject || null,
req.body.content || null,
req.body.date || new Date().toISOString(),
req.body.duration_minutes || null,
req.body.task_id || null,
req.userId
);
const interaction = db.prepare('SELECT * FROM contact_interactions WHERE id = ?')
.get(result.lastInsertRowid);
// Socket.io Event
req.app.get('io').emit('interaction:created', interaction);
res.status(201).json({ success: true, data: interaction });
} catch (error) {
logger.error('Fehler beim Hinzufügen der Interaktion:', error);
res.status(500).json({ success: false, error: error.message });
}
});
module.exports = router;