import { sequelize } from '../../utils/sequelize.js'; import { DataTypes } from 'sequelize'; import { encrypt, decrypt } from '../../utils/encryption.js'; import crypto from 'crypto'; function encodeEncryptedValueToBlob(value) { const encrypted = encrypt(value); return Buffer.from(encrypted, 'utf8'); } /** Nur echte Adressen zurückgeben — verhindert Anzeige von Base64-/Key-artigem Müll bei fehlender Entschlüsselung. */ function looksLikePlausibleEmail(s) { if (typeof s !== 'string') { return false; } const t = s.trim(); if (!t || t.length > 254) { return false; } return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i.test(t); } function normalizeEmailCandidate(s) { if (!s || typeof s !== 'string') { return null; } const t = s.trim(); return looksLikePlausibleEmail(t) ? t : null; } function decodeEncryptedBlob(value) { if (!value) { return null; } try { const encryptedUtf8 = value.toString('utf8'); const decryptedUtf8 = decrypt(encryptedUtf8); const fromUtf8 = normalizeEmailCandidate(decryptedUtf8); if (fromUtf8) { return fromUtf8; } } catch (error) { console.warn('Email utf8 decryption failed, trying legacy hex format:', error.message); } try { const encryptedHex = value.toString('hex'); const decryptedHex = decrypt(encryptedHex); const fromHex = normalizeEmailCandidate(decryptedHex); if (fromHex) { return fromHex; } } catch (error) { console.warn('Email legacy hex decryption failed:', error.message); } try { return normalizeEmailCandidate(value.toString('utf8')); } catch (error) { console.warn('Email could not be read as plain text:', error.message); return null; } } const User = sequelize.define('user', { email: { type: DataTypes.BLOB, allowNull: false, unique: true, set(value) { if (value) { this.setDataValue('email', encodeEncryptedValueToBlob(value)); } }, get() { const encrypted = this.getDataValue('email'); return decodeEncryptedBlob(encrypted); } }, salt: { type: DataTypes.STRING, allowNull: false, defaultValue: () => crypto.randomBytes(16).toString('hex') }, username: { type: DataTypes.STRING, allowNull: false, unique: true }, password: { type: DataTypes.STRING, allowNull: false, }, registrationDate: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW }, active: { type: DataTypes.BOOLEAN, defaultValue: false }, resetToken: { type: DataTypes.UUID, allowNull: true }, hashedId: { type: DataTypes.STRING, allowNull: true }, searchable: { type: DataTypes.BOOLEAN, defaultValue: true }, authCode: { type: DataTypes.STRING, allowNull: true } }, { tableName: 'user', schema: 'community', underscored: true, hooks: { afterCreate: async (user, options) => { const hashedId = crypto.createHash('sha256').update(user.id.toString()).digest('hex'); user.hashedId = hashedId; await user.save(); }, } }); export default User;