Implement 301 redirects for www to non-www and enhance canonical tag handling
This commit adds 301 redirects in the Apache configuration to redirect traffic from www.tt-tagebuch.de to tt-tagebuch.de for both HTTP and HTTPS. Additionally, it introduces middleware in the backend to dynamically set canonical tags based on the request URL, ensuring proper SEO practices. The request logging middleware has been disabled, and sensitive data handling has been improved in the MyTischtennis model and API logging service, ensuring compliance with data protection regulations. Frontend updates include enhanced descriptions and features in the application, improving user experience and clarity.
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import ApiLog from '../models/ApiLog.js';
|
||||
import { Op } from 'sequelize';
|
||||
import { sanitizeLogData, truncateString, sanitizeIpAddress, sanitizeUserAgent } from '../utils/logDataSanitizer.js';
|
||||
|
||||
class ApiLogService {
|
||||
/**
|
||||
* Log an API request/response
|
||||
* DSGVO-konform: Personenbezogene Daten werden nur bei Fehlern geloggt und dann verschlüsselt/gekürzt
|
||||
*/
|
||||
async logRequest(options) {
|
||||
try {
|
||||
@@ -22,24 +24,49 @@ class ApiLogService {
|
||||
schedulerJobType = null
|
||||
} = options;
|
||||
|
||||
// Truncate long fields (raise limits to fit typical API JSON bodies)
|
||||
const truncate = (str, maxLen = 64000) => {
|
||||
if (!str) return null;
|
||||
const strVal = typeof str === 'string' ? str : JSON.stringify(str);
|
||||
return strVal.length > maxLen ? strVal.substring(0, maxLen) + '... (truncated)' : strVal;
|
||||
};
|
||||
const isError = statusCode >= 400;
|
||||
|
||||
// DSGVO-konform: Nur bei Fehlern Request/Response-Bodies loggen
|
||||
let sanitizedRequestBody = null;
|
||||
let sanitizedResponseBody = null;
|
||||
|
||||
if (isError) {
|
||||
// Bei Fehlern: Sanitize personenbezogene Daten
|
||||
if (requestBody) {
|
||||
sanitizedRequestBody = sanitizeLogData(requestBody, true); // Verschlüssele sensible Daten
|
||||
// Prüfe, ob Ergebnis bereits ein String ist (sanitizeLogData kann String oder Objekt zurückgeben)
|
||||
const requestBodyStr = typeof sanitizedRequestBody === 'string'
|
||||
? sanitizedRequestBody
|
||||
: JSON.stringify(sanitizedRequestBody);
|
||||
sanitizedRequestBody = truncateString(requestBodyStr, 2000);
|
||||
}
|
||||
|
||||
if (responseBody) {
|
||||
sanitizedResponseBody = sanitizeLogData(responseBody, true); // Verschlüssele sensible Daten
|
||||
// Prüfe, ob Ergebnis bereits ein String ist (sanitizeLogData kann String oder Objekt zurückgeben)
|
||||
const responseBodyStr = typeof sanitizedResponseBody === 'string'
|
||||
? sanitizedResponseBody
|
||||
: JSON.stringify(sanitizedResponseBody);
|
||||
sanitizedResponseBody = truncateString(responseBodyStr, 2000);
|
||||
}
|
||||
}
|
||||
// Bei Erfolg: Keine Bodies loggen (Datenminimierung)
|
||||
|
||||
// IP-Adresse und User-Agent sanitizen
|
||||
const sanitizedIp = sanitizeIpAddress(ipAddress);
|
||||
const sanitizedUA = sanitizeUserAgent(userAgent);
|
||||
|
||||
await ApiLog.create({
|
||||
userId,
|
||||
method,
|
||||
path,
|
||||
statusCode,
|
||||
requestBody: truncate(requestBody, 64000),
|
||||
responseBody: truncate(responseBody, 64000),
|
||||
requestBody: sanitizedRequestBody,
|
||||
responseBody: sanitizedResponseBody,
|
||||
executionTime,
|
||||
errorMessage: truncate(errorMessage, 5000),
|
||||
ipAddress,
|
||||
userAgent,
|
||||
errorMessage: errorMessage ? truncateString(errorMessage, 5000) : null,
|
||||
ipAddress: sanitizedIp,
|
||||
userAgent: sanitizedUA,
|
||||
logType,
|
||||
schedulerJobType
|
||||
});
|
||||
@@ -157,6 +184,8 @@ class ApiLogService {
|
||||
'id', 'userId', 'method', 'path', 'statusCode',
|
||||
'executionTime', 'errorMessage', 'ipAddress', 'logType',
|
||||
'schedulerJobType', 'createdAt'
|
||||
// requestBody und responseBody werden NICHT zurückgegeben (DSGVO: Datenminimierung)
|
||||
// Nur bei expliziter Anfrage über getLogById verfügbar
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
@@ -11,10 +11,30 @@ class MyTischtennisService {
|
||||
*/
|
||||
async getAccount(userId) {
|
||||
const account = await MyTischtennis.findOne({
|
||||
where: { userId },
|
||||
attributes: ['id', 'userId', 'email', 'savePassword', 'autoUpdateRatings', 'lastLoginAttempt', 'lastLoginSuccess', 'lastUpdateRatings', 'expiresAt', 'userData', 'clubId', 'clubName', 'fedNickname', 'createdAt', 'updatedAt']
|
||||
where: { userId }
|
||||
// Keine attributes-Limitierung, damit getter-Methoden für Verschlüsselung funktionieren
|
||||
});
|
||||
return account;
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
// Rückgabe mit automatischer Entschlüsselung durch Model-Getters
|
||||
return {
|
||||
id: account.id,
|
||||
userId: account.userId,
|
||||
email: account.email, // Automatisch entschlüsselt
|
||||
savePassword: account.savePassword,
|
||||
autoUpdateRatings: account.autoUpdateRatings,
|
||||
lastLoginAttempt: account.lastLoginAttempt,
|
||||
lastLoginSuccess: account.lastLoginSuccess,
|
||||
lastUpdateRatings: account.lastUpdateRatings,
|
||||
expiresAt: account.expiresAt,
|
||||
userData: account.userData, // Automatisch entschlüsselt
|
||||
clubId: account.clubId, // Automatisch entschlüsselt
|
||||
clubName: account.clubName, // Automatisch entschlüsselt
|
||||
fedNickname: account.fedNickname, // Automatisch entschlüsselt
|
||||
createdAt: account.createdAt,
|
||||
updatedAt: account.updatedAt
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user