Files
stechuhr3/backend/src/utils/hashId.js
Torsten Schulz (local) e95bb4cb76 Initial commit: TimeClock v3 - Node.js/Vue.js Zeiterfassung
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
2025-10-17 14:11:28 +02:00

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;