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:
@@ -763,8 +763,7 @@ async function createDefaultUsers() {
|
||||
const adminExists = db.prepare('SELECT id, password_hash FROM users WHERE email = ? AND role = ?').get('admin@taskmate.local', 'admin');
|
||||
if (adminExists) {
|
||||
const correctAdminPassword = process.env.ADMIN_PASSWORD || 'admin123';
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
|
||||
// Prüfen ob das Passwort bereits korrekt ist
|
||||
const isCorrect = await bcrypt.compare(correctAdminPassword, adminExists.password_hash);
|
||||
if (!isCorrect) {
|
||||
|
||||
@@ -15,8 +15,6 @@ if (!JWT_SECRET || JWT_SECRET.length < 32) {
|
||||
}
|
||||
const ACCESS_TOKEN_EXPIRY = 60; // Minuten (kürzer für mehr Sicherheit)
|
||||
const REFRESH_TOKEN_EXPIRY = 7 * 24 * 60; // 7 Tage in Minuten
|
||||
const SESSION_TIMEOUT = parseInt(process.env.SESSION_TIMEOUT) || 30; // Minuten
|
||||
|
||||
/**
|
||||
* JWT Access-Token generieren (kurze Lebensdauer)
|
||||
*/
|
||||
@@ -63,13 +61,6 @@ function generateRefreshToken(userId, ipAddress, userAgent) {
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy generateToken für Rückwärtskompatibilität
|
||||
*/
|
||||
function generateToken(user) {
|
||||
return generateAccessToken(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT-Token verifizieren
|
||||
*/
|
||||
@@ -120,7 +111,7 @@ function authenticateToken(req, res, next) {
|
||||
const refreshThreshold = 5 * 60 * 1000; // 5 Minuten vor Ablauf
|
||||
|
||||
if (tokenExp - now < refreshThreshold) {
|
||||
const newToken = generateToken({
|
||||
const newToken = generateAccessToken({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
display_name: user.displayName,
|
||||
@@ -203,14 +194,6 @@ function authenticateSocket(socket, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* CSRF-Token generieren (für Forms)
|
||||
*/
|
||||
function generateCsrfToken() {
|
||||
const { randomBytes } = require('crypto');
|
||||
return randomBytes(32).toString('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh-Token validieren und neuen Access-Token generieren
|
||||
*/
|
||||
@@ -272,7 +255,6 @@ function cleanupExpiredTokens() {
|
||||
setInterval(cleanupExpiredTokens, 6 * 60 * 60 * 1000);
|
||||
|
||||
module.exports = {
|
||||
generateToken,
|
||||
generateAccessToken,
|
||||
generateRefreshToken,
|
||||
refreshAccessToken,
|
||||
@@ -280,10 +262,7 @@ module.exports = {
|
||||
verifyToken,
|
||||
authenticateToken,
|
||||
authenticateSocket,
|
||||
generateCsrfToken,
|
||||
requireAdmin,
|
||||
requireRegularUser,
|
||||
checkPermission,
|
||||
JWT_SECRET,
|
||||
SESSION_TIMEOUT
|
||||
checkPermission
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { getDb } = require('../database');
|
||||
const { generateToken, generateRefreshToken, refreshAccessToken, revokeAllRefreshTokens, authenticateToken } = require('../middleware/auth');
|
||||
const { generateAccessToken, generateRefreshToken, refreshAccessToken, revokeAllRefreshTokens, authenticateToken } = require('../middleware/auth');
|
||||
const { getTokenForUser } = require('../middleware/csrf');
|
||||
const { validatePassword } = require('../middleware/validation');
|
||||
const logger = require('../utils/logger');
|
||||
@@ -112,7 +112,7 @@ router.post('/login', async (req, res) => {
|
||||
logAttempt(user.id, true);
|
||||
|
||||
// JWT Access-Token generieren (kurze Lebensdauer)
|
||||
const accessToken = generateToken(user);
|
||||
const accessToken = generateAccessToken(user);
|
||||
|
||||
// Refresh-Token generieren (lange Lebensdauer)
|
||||
const refreshToken = generateRefreshToken(user.id, ip, userAgent);
|
||||
@@ -267,7 +267,7 @@ function legacyRefresh(req, res) {
|
||||
return res.status(404).json({ error: 'Benutzer nicht gefunden' });
|
||||
}
|
||||
|
||||
const newToken = generateToken(dbUser);
|
||||
const newToken = generateAccessToken(dbUser);
|
||||
const csrfToken = getTokenForUser(dbUser.id);
|
||||
|
||||
res.json({ token: newToken, csrfToken });
|
||||
|
||||
497
backend/routes/contacts-extended.js
Normale Datei
497
backend/routes/contacts-extended.js
Normale Datei
@@ -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;
|
||||
@@ -82,7 +82,7 @@ app.use(helmet({
|
||||
|
||||
// CORS
|
||||
app.use(cors({
|
||||
origin: true,
|
||||
origin: process.env.CORS_ORIGIN || 'https://taskmate.aegis-sight.de',
|
||||
credentials: true
|
||||
}));
|
||||
|
||||
@@ -97,14 +97,15 @@ app.use(cookieParser());
|
||||
const { sanitizeMiddleware } = require('./middleware/validation');
|
||||
app.use(sanitizeMiddleware);
|
||||
|
||||
// Request Logging
|
||||
// Request Logging (nur API-Requests, keine statischen Assets)
|
||||
app.use((req, res, next) => {
|
||||
const start = Date.now();
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start;
|
||||
// Use originalUrl to see the full path including /api prefix
|
||||
logger.info(`${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`);
|
||||
});
|
||||
if (req.originalUrl.startsWith('/api/')) {
|
||||
const start = Date.now();
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start;
|
||||
logger.info(`${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`);
|
||||
});
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
@@ -140,18 +141,18 @@ app.use('/api/tasks', authenticateToken, csrfProtection, taskRoutes);
|
||||
app.use('/api/subtasks', authenticateToken, csrfProtection, subtaskRoutes);
|
||||
app.use('/api/comments', authenticateToken, csrfProtection, commentRoutes);
|
||||
app.use('/api/labels', authenticateToken, csrfProtection, labelRoutes);
|
||||
app.use('/api/files', authenticateToken, fileRoutes);
|
||||
app.use('/api/files', authenticateToken, csrfProtection, fileRoutes);
|
||||
app.use('/api/links', authenticateToken, csrfProtection, linkRoutes);
|
||||
app.use('/api/templates', authenticateToken, csrfProtection, templateRoutes);
|
||||
app.use('/api/stats', authenticateToken, statsRoutes);
|
||||
app.use('/api/export', authenticateToken, exportRoutes);
|
||||
app.use('/api/stats', authenticateToken, csrfProtection, statsRoutes);
|
||||
app.use('/api/export', authenticateToken, csrfProtection, exportRoutes);
|
||||
app.use('/api/import', authenticateToken, csrfProtection, importRoutes);
|
||||
|
||||
// Admin-Routes (eigene Auth-Middleware)
|
||||
app.use('/api/admin', csrfProtection, adminRoutes);
|
||||
// Admin-Routes
|
||||
app.use('/api/admin', authenticateToken, csrfProtection, adminRoutes);
|
||||
|
||||
// Proposals-Routes (eigene Auth-Middleware)
|
||||
app.use('/api/proposals', csrfProtection, proposalRoutes);
|
||||
// Proposals-Routes
|
||||
app.use('/api/proposals', authenticateToken, csrfProtection, proposalRoutes);
|
||||
|
||||
// Notifications-Routes
|
||||
app.use('/api/notifications', authenticateToken, csrfProtection, notificationRoutes);
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren