diff --git a/backend/models/community/user.js b/backend/models/community/user.js index ecde458..99eeb2e 100644 --- a/backend/models/community/user.js +++ b/backend/models/community/user.js @@ -1,6 +1,7 @@ import { sequelize } from '../../utils/sequelize.js'; import { DataTypes } from 'sequelize'; import bcrypt from 'bcrypt'; +import { encrypt, generateIv } from '../../utils/encryption.js'; const User = sequelize.define('user', { email: { @@ -8,9 +9,17 @@ const User = sequelize.define('user', { allowNull: false, unique: true, set(value) { - this.setDataValue('email', bcrypt.hashSync(value, 10)); + if (value) { + const iv = generateIv(); + this.setDataValue('iv', iv.toString('hex')); + this.setDataValue('email', encrypt(value, iv)); + } } }, + iv: { + type: DataTypes.STRING, + allowNull: false + }, username: { type: DataTypes.STRING, allowNull: false, @@ -19,6 +28,9 @@ const User = sequelize.define('user', { password: { type: DataTypes.STRING, allowNull: false, + set(value) { + this.setDataValue('password', bcrypt.hashSync(value, 10)); + } }, registrationDate: { type: DataTypes.DATE, @@ -29,7 +41,7 @@ const User = sequelize.define('user', { type: DataTypes.BOOLEAN, defaultValue: false }, - resetToken: { + resetToken: { type: DataTypes.UUID, allowNull: true }, @@ -40,7 +52,7 @@ const User = sequelize.define('user', { }, { tableName: 'user', schema: 'community', - underscored: true, + underscored: true }); export default User; diff --git a/backend/models/community/user_param.js b/backend/models/community/user_param.js index c75c0b9..01340fd 100644 --- a/backend/models/community/user_param.js +++ b/backend/models/community/user_param.js @@ -2,6 +2,7 @@ import { sequelize } from '../../utils/sequelize.js'; import { DataTypes } from 'sequelize'; import User from './user.js'; import UserParamType from '../type/user_param.js'; +import { encrypt, decrypt, generateIv } from '../../utils/encryption.js'; const UserParam = sequelize.define('user_param', { userId: { @@ -18,20 +19,52 @@ const UserParam = sequelize.define('user_param', { references: { model: UserParamType, key: 'id' - }, - + } }, value: { + type: DataTypes.STRING, + allowNull: false, + set(value) { + if (value) { + const iv = generateIv(); + this.setDataValue('iv', iv.toString('hex')); + this.setDataValue('value', encrypt(value, iv)); + } + } + }, + iv: { type: DataTypes.STRING, allowNull: false } }, { tableName: 'user_param', schema: 'community', - underscored: true + underscored: true, + hooks: { + beforeSave: (userParam) => { + if (userParam.value && !userParam.iv) { + const iv = generateIv(); + userParam.iv = iv.toString('hex'); + userParam.value = encrypt(userParam.value, iv); + } + }, + afterFind: (userParams) => { + if (userParams) { + if (Array.isArray(userParams)) { + userParams.forEach((userParam) => { + const iv = Buffer.from(userParam.iv, 'hex'); + userParam.value = decrypt(userParam.value, iv); + }); + } else { + const iv = Buffer.from(userParams.iv, 'hex'); + userParams.value = decrypt(userParams.value, iv); + } + } + } + } }); UserParam.belongsTo(User, { foreignKey: 'userId' }); -UserParam.belongsTo(UserParamType, { foreignKey: 'param_type_id' }); +UserParam.belongsTo(UserParamType, { foreignKey: 'paramTypeId' }); export default UserParam; diff --git a/backend/services/authService.js b/backend/services/authService.js index 0d0937b..2aee158 100644 --- a/backend/services/authService.js +++ b/backend/services/authService.js @@ -8,31 +8,40 @@ import { sendAccountActivationEmail, sendPasswordResetEmail } from './emailServi const saltRounds = 10; export const registerUser = async ({ email, username, password, language }) => { + const [results] = await sequelize.query( + 'SELECT * FROM "community"."user" WHERE pgp_sym_decrypt("email", :key) = :email', + { + replacements: { key: process.env.SECRET_KEY, email }, + type: sequelize.QueryTypes.SELECT + } + ); + if (results.length > 0) { + throw new Error('Email already in use'); + } + const iv = generateIv(); + const encryptedEmail = encrypt(email, iv); const hashedPassword = await bcrypt.hash(password, saltRounds); const resetToken = uuidv4(); const user = await User.create({ - email, + email: encryptedEmail, + iv: iv.toString('hex'), username, password: hashedPassword, resetToken: resetToken, active: false, registration_date: new Date() }); - const languageType = await UserParamType.findOne({ where: { description: 'language' } }); if (!languageType) { throw new Error('Language type not found'); } - await UserParam.create({ userId: user.id, paramTypeId: languageType.id, value: language }); - const activationLink = `${process.env.FRONTEND_URL}/activate?token=${resetToken}`; await sendAccountActivationEmail(email, activationLink, username, resetToken, language); - return { id: user.hashedId, username: user.username, active: user.active }; }; diff --git a/backend/utils/encryption.js b/backend/utils/encryption.js new file mode 100644 index 0000000..2f4f17f --- /dev/null +++ b/backend/utils/encryption.js @@ -0,0 +1,32 @@ +import crypto from 'crypto'; +import dotenv from 'dotenv'; + +dotenv.config(); // Laden der Umgebungsvariablen + +const algorithm = 'aes-256-cbc'; +const secretKey = process.env.SECRET_KEY; + +if (!secretKey || secretKey.length !== 32) { + throw new Error('SECRET_KEY length must be 32 bytes'); +} + +const encrypt = (text, iv) => { + const cipher = crypto.createCipheriv(algorithm, Buffer.from(secretKey, 'utf-8'), iv); + let encrypted = cipher.update(text); + encrypted = Buffer.concat([encrypted, cipher.final()]); + return encrypted.toString('hex'); +}; + +const decrypt = (encryptedText, iv) => { + const encryptedBuffer = Buffer.from(encryptedText, 'hex'); + const decipher = crypto.createDecipheriv(algorithm, Buffer.from(secretKey, 'utf-8'), iv); + let decrypted = decipher.update(encryptedBuffer); + decrypted = Buffer.concat([decrypted, decipher.final()]); + return decrypted.toString(); +}; + +const generateIv = () => { + return crypto.randomBytes(16); +}; + +export { encrypt, decrypt, generateIv };