Reputation: 469
I want to use the getters and setters defined in the models to encrypt and decrypt some data in my database. But during implementation I noticed, that the setters get triggered on update as well as creation but only have an real effect on creation. So creating an entry calls the setter and the value of (see the code) "secret" stands encrypted in the database. But if i want to update this entry, the setter gets called but nothing gets updated.
If I update also values which have no setter function defined for them only this values get updated.
This is my setter:
secret: {
type: DataTypes.STRING,
defaultValue: null,
async get() {
try {
return await crypt.decryptGetter(this, 'secret');
} catch (err) {
throw new Error("Error while decrypting secret: " + err);
}
},
async set(value) {
try {
return await crypt.encryptSetter(this, 'secret', value);
} catch (err) {
throw new Error("Error while encrypting secret: " + err);
}
}
}
I use this setters and getters more often, that's why I outsourced them. I pass the current instance of the model as well as the key and the value if necessary:
async decryptGetter(sequelizeModel, key) {
try {
return await module.exports.decrypt(sequelizeModel.getDataValue(key),config.internal.secret)
} catch (err) {
throw new Error("GETTER: " + err, __filename);
}
},
async encryptSetter(sequelizeModel, key, value) {
try {
let encValue = await module.exports.encrypt(value, config.internal.secret);
return sequelizeModel.setDataValue(key, encValue);
} catch (err) {
throw new Error("SETTER: " + err, __filename);
}
}
Can anybody help me out here? Did I miss something along the way? Thank you in advance!
Upvotes: 3
Views: 1200
Reputation: 469
As r9119 mentioned in his answer, hooks are the right solution for this! I just thought I post a little summary how I did it in the end for others having this problem in the future!
I added beforeCreate and beforeUpdate hooks to my model, as well as a getter function to getterMethods:
const MyModel = sequelize.define(
'MyModel', {
MyModelID: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true
},
name: {
...
},
secret: {
...
}
}, {
timestamps: true,
paranoid: true,
freezeTableName: true,
getterMethods: {
async decrypt() {
try {
return await sequelizeUtils.cryptHookSpecific(this, config.internalSecret, modelCryptConfig.specificProps, projectDecrypt)
} catch(err) {
throw new Error(err);
}
}
},
hooks: {
beforeCreate: async (myModel, options) => {
try {
await sequelizeUtils.cryptHookSpecific(myModel, config.internalSecret, modelCryptConfig.specificProps, projectDecrypt)
} catch(err) {
throw new Error(err);
}
},
beforeUpdate: async (myModel, options) => {
try {
await sequelizeUtils.cryptHookSpecific(myModel, config.internalSecret, modelCryptConfig.specificProps, projectDecrypt)
} catch(err) {
throw new Error(err);
}
},
}
}
);
Per model I define a modelCryptConfig which defines which fields should be decrypted. There I can also define a special key for each field used for de-/encryption:
let modelCryptConfig = {
specificProps: [{field: "apiKey", key:"mySpecialKeyForThisField"},{field: "secret"},{field: "someInfo"},{field: "someOtherInfo"},{field: "somethingElse"},]
}
The sequelizeUtils.cryptHookSpecific function takes in the model, a key to encrypt/decrypt, the fields which should be encrypted / decrypted and at last a project specific function which actually does the encryption/decryption.
async cryptHookSpecific(sequelizeModel, globalKey, specificProps, cryptFunction) {
try {
// loop over all specific properties to encrypt them
for (let propIdx in specificProps) {
if (!sequelizeModel.__proto__.rawAttributes[specificProps[propIdx].field].primaryKey && // is not a primary key
!sequelizeModel.__proto__.rawAttributes[specificProps[propIdx].field].foreignKey && // is not a foreign key
sequelizeModel.dataValues[specificProps[propIdx].field]) { // is not null
// check if there is a specific key provided
if (specificProps[propIdx].hasOwnProperty("key")) {
// if so encrypt the property with the provided specific key
sequelizeModel.dataValues[specificProps[propIdx].field] = await cryptFunction(sequelizeModel.dataValues[specificProps[propIdx].field], specificProps[propIdx].key)
} else {
// if not encrypt it with the globalKey
sequelizeModel.dataValues[specificProps[propIdx].field] = await cryptFunction(sequelizeModel.dataValues[specificProps[propIdx].field], globalKey)
}
}
}
return sequelizeModel;
} catch(err) {
throw new Error(err);
}
},
Besides the cryptHookSpecific function I wrote a cryptHookComplete function as well which encrypts/decrypts all field by default and you can defined fields to exclude in the modelCryptConfig.
Maybe this helps someone out! Thanks again to r9119!
Upvotes: 4
Reputation: 606
have you tried using hooks?
Heres how I use them to encrypt a password before creation and updating:
function hashPassword (user) {
const SALT_FACTOR = 12
if (!user.changed('password')) {
return;
} else {
user.setDataValue('password', bcrypt.hashSync(user.password, SALT_FACTOR))
}
}
module.exports = User = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
password: {
type: DataTypes.STRING
},
{
hooks: {
beforeCreate: hashPassword,
beforeUpdate: hashPassword
}
})
return User
}
Upvotes: 5