Features: - Backend: Node.js/Express mit MySQL/MariaDB - Frontend: Vue.js 3 mit Composition API - UTC-Zeithandling für korrekte Zeiterfassung - Timewish-basierte Überstundenberechnung - Wochenübersicht mit Urlaubs-/Krankheits-/Feiertagshandling - Bereinigtes Arbeitsende (Generell/Woche) - Überstunden-Offset für historische Daten - Fixed Layout mit scrollbarem Content - Kompakte UI mit grünem Theme
144 lines
3.6 KiB
JavaScript
144 lines
3.6 KiB
JavaScript
const crypto = require('crypto');
|
|
|
|
/**
|
|
* Utility für ID-Hashing
|
|
* Konvertiert numerische IDs in Hashes und zurück
|
|
*/
|
|
class HashId {
|
|
constructor() {
|
|
// Secret aus Umgebungsvariable oder Fallback
|
|
this.secret = process.env.HASH_ID_SECRET || 'timeclock-hash-secret-change-in-production';
|
|
}
|
|
|
|
/**
|
|
* Konvertiert eine numerische ID in einen Hash
|
|
* @param {number} id - Numerische ID
|
|
* @returns {string} Hash-String
|
|
*/
|
|
encode(id) {
|
|
if (!id) return null;
|
|
|
|
// Erstelle einen deterministischen Hash aus ID + Secret
|
|
const hmac = crypto.createHmac('sha256', this.secret);
|
|
hmac.update(id.toString());
|
|
const hash = hmac.digest('base64url'); // base64url ist URL-sicher
|
|
|
|
// Füge die ID in verschlüsselter Form hinzu für Dekodierung
|
|
const encrypted = this.encryptId(id);
|
|
|
|
// Format: {verschlüsselte-id}.{hash-prefix}
|
|
return `${encrypted}.${hash.substring(0, 12)}`;
|
|
}
|
|
|
|
/**
|
|
* Konvertiert einen Hash zurück in eine numerische ID
|
|
* @param {string} hash - Hash-String
|
|
* @returns {number|null} Numerische ID oder null bei Fehler
|
|
*/
|
|
decode(hash) {
|
|
if (!hash || typeof hash !== 'string') return null;
|
|
|
|
try {
|
|
// Extrahiere verschlüsselte ID
|
|
const parts = hash.split('.');
|
|
if (parts.length !== 2) return null;
|
|
|
|
const encrypted = parts[0];
|
|
const hashPart = parts[1];
|
|
|
|
// Entschlüssele ID
|
|
const id = this.decryptId(encrypted);
|
|
if (!id) return null;
|
|
|
|
// Verifiziere Hash
|
|
const expectedHash = this.encode(id);
|
|
if (!expectedHash || !expectedHash.endsWith(hashPart)) {
|
|
return null;
|
|
}
|
|
|
|
return id;
|
|
} catch (error) {
|
|
console.error('Fehler beim Dekodieren der Hash-ID:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verschlüsselt eine ID
|
|
* @private
|
|
*/
|
|
encryptId(id) {
|
|
const cipher = crypto.createCipheriv(
|
|
'aes-256-cbc',
|
|
crypto.scryptSync(this.secret, 'salt', 32),
|
|
Buffer.alloc(16, 0) // IV
|
|
);
|
|
|
|
let encrypted = cipher.update(id.toString(), 'utf8', 'base64url');
|
|
encrypted += cipher.final('base64url');
|
|
|
|
return encrypted;
|
|
}
|
|
|
|
/**
|
|
* Entschlüsselt eine ID
|
|
* @private
|
|
*/
|
|
decryptId(encrypted) {
|
|
try {
|
|
const decipher = crypto.createDecipheriv(
|
|
'aes-256-cbc',
|
|
crypto.scryptSync(this.secret, 'salt', 32),
|
|
Buffer.alloc(16, 0) // IV
|
|
);
|
|
|
|
let decrypted = decipher.update(encrypted, 'base64url', 'utf8');
|
|
decrypted += decipher.final('utf8');
|
|
|
|
return parseInt(decrypted, 10);
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Konvertiert ein Objekt mit IDs in eines mit Hashes
|
|
* @param {Object} obj - Objekt mit ID-Feldern
|
|
* @param {Array<string>} idFields - Array von Feldnamen, die IDs enthalten
|
|
* @returns {Object} Objekt mit gehashten IDs
|
|
*/
|
|
encodeObject(obj, idFields = ['id', 'user_id', 'auth_info_id']) {
|
|
if (!obj) return obj;
|
|
|
|
const result = { ...obj };
|
|
|
|
for (const field of idFields) {
|
|
if (result[field] !== null && result[field] !== undefined) {
|
|
result[field] = this.encode(result[field]);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Konvertiert ein Array von Objekten
|
|
* @param {Array} array - Array von Objekten
|
|
* @param {Array<string>} idFields - Array von Feldnamen, die IDs enthalten
|
|
* @returns {Array} Array mit gehashten IDs
|
|
*/
|
|
encodeArray(array, idFields = ['id', 'user_id', 'auth_info_id']) {
|
|
if (!Array.isArray(array)) return array;
|
|
|
|
return array.map(obj => this.encodeObject(obj, idFields));
|
|
}
|
|
}
|
|
|
|
// Singleton-Instanz
|
|
const hashId = new HashId();
|
|
|
|
module.exports = hashId;
|
|
|
|
|
|
|