-
Notifications
You must be signed in to change notification settings - Fork 1
Encryption of sensitive data #194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
c49c087
7ea39b5
698a7f4
1d7c03e
0228f6c
fbad854
e62f502
07dc3f2
7674fa8
873ef11
168a0c9
11f43e5
a261817
95c46ac
b119ead
980387a
bc4902d
67d7251
b2d778b
770bc09
12d5eb2
8355e11
444aadc
8250438
b4e5a7e
67d5eaa
8cf9586
681110b
f693c3e
0c94ad0
1029133
bc1772c
ea05cc7
199ec00
73f5397
afb47ac
8c5a123
faf1de9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| 'use strict'; | ||
|
|
||
| /** | ||
| * Encrypt existing plaintext values in user fields: firstName, lastName, email, initialPassword. | ||
| * Also populates emailHash from the newly encrypted email value. | ||
| * | ||
| * Requires DB_ENCRYPTION_KEY to be set in the environment. | ||
| * Skips rows where the field already appears encrypted (safe to re-run). | ||
| */ | ||
|
|
||
| const { encrypt, getKey, initializeEncryptionKey, decrypt } = require('../../utils/helper/encryption'); | ||
|
|
||
| module.exports = { | ||
| async up(queryInterface) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| const isEncryptionEnabled = process.env.ENCRYPTION_ENABLED === 'true'; | ||
| if (!isEncryptionEnabled) { | ||
| return; | ||
| } | ||
| initializeEncryptionKey(); | ||
| const encryptionKey = getKey(); | ||
| if (!encryptionKey) { | ||
| throw new Error( | ||
| 'DB_ENCRYPTION_KEY must be set before running the user encryption data migration' | ||
| ); | ||
| } | ||
|
|
||
| const transaction = await queryInterface.sequelize.transaction(); | ||
| try { | ||
| const users = await queryInterface.sequelize.query( | ||
| `SELECT id, "firstName", "lastName", email, "initialPassword", "twoFactorOtp", "totpSecret", "orcidId", "ldapUsername", "samlNameId", "salt" FROM "user"`, | ||
| { type: queryInterface.sequelize.QueryTypes.SELECT, transaction } | ||
| ); | ||
|
|
||
| for (const user of users) { | ||
| const updates = {}; | ||
|
|
||
| if (user.firstName) { | ||
| updates.firstName = encrypt(user.firstName); | ||
| } | ||
| if (user.lastName) { | ||
| updates.lastName = encrypt(user.lastName); | ||
| } | ||
| if (user.email) { | ||
| const encryptedEmail = encrypt(user.email); | ||
| updates.email = encryptedEmail; | ||
| } | ||
| if (user.initialPassword) { | ||
| updates.initialPassword = encrypt(user.initialPassword); | ||
| } | ||
| if (user.twoFactorOtp) { | ||
| updates.twoFactorOtp = encrypt(user.twoFactorOtp); | ||
| } | ||
| if (user.totpSecret) { | ||
| updates.totpSecret = encrypt(user.totpSecret); | ||
| } | ||
| if (user.orcidId) { | ||
| updates.orcidId = encrypt(user.orcidId); | ||
| } | ||
| if (user.ldapUsername) { | ||
| updates.ldapUsername = encrypt(user.ldapUsername); | ||
| } | ||
| if (user.samlNameId) { | ||
| updates.samlNameId = encrypt(user.samlNameId); | ||
| } | ||
| if (user.salt) { | ||
| updates.salt = encrypt(user.salt); | ||
| } | ||
|
|
||
| if (Object.keys(updates).length > 0) { | ||
| const setClauses = Object.keys(updates) | ||
| .map(col => `"${col}" = :${col}`) | ||
| .join(', '); | ||
|
|
||
| await queryInterface.sequelize.query( | ||
| `UPDATE "user" SET ${setClauses} WHERE id = :id`, | ||
| { | ||
| replacements: { ...updates, id: user.id }, | ||
| transaction, | ||
| } | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| await transaction.commit(); | ||
| } catch (err) { | ||
| await transaction.rollback(); | ||
| throw err; | ||
| } | ||
| }, | ||
|
|
||
| async down(queryInterface) { | ||
| const isEncryptionEnabled = process.env.ENCRYPTION_ENABLED === 'true'; | ||
| if (!isEncryptionEnabled) { | ||
| return; | ||
| } | ||
| initializeEncryptionKey(); | ||
| const encryptionKey = getKey(); | ||
| if (!encryptionKey) { | ||
| throw new Error( | ||
| 'DB_ENCRYPTION_KEY must be set before running the user encryption data migration' | ||
| ); | ||
| } | ||
|
|
||
| const transaction = await queryInterface.sequelize.transaction(); | ||
| try { | ||
| const users = await queryInterface.sequelize.query( | ||
| `SELECT id, "firstName", "lastName", email, "initialPassword", "twoFactorOtp", "totpSecret", "orcidId", "ldapUsername", "samlNameId", "salt" FROM "user"`, | ||
| { type: queryInterface.sequelize.QueryTypes.SELECT, transaction } | ||
| ); | ||
|
|
||
| for (const user of users) { | ||
| const updates = {}; | ||
|
|
||
| if (user.firstName) { | ||
| updates.firstName = decrypt(user.firstName); | ||
| } | ||
| if (user.lastName) { | ||
| updates.lastName = decrypt(user.lastName); | ||
| } | ||
| if (user.email) { | ||
| const decryptedEmail = decrypt(user.email); | ||
| updates.email = decryptedEmail; | ||
| } | ||
| if (user.initialPassword) { | ||
| updates.initialPassword = decrypt(user.initialPassword); | ||
| } | ||
| if (user.twoFactorOtp) { | ||
| updates.twoFactorOtp = decrypt(user.twoFactorOtp); | ||
| } | ||
| if (user.totpSecret) { | ||
| updates.totpSecret = decrypt(user.totpSecret); | ||
| } | ||
| if (user.orcidId) { | ||
| updates.orcidId = decrypt(user.orcidId); | ||
| } | ||
| if (user.ldapUsername) { | ||
| updates.ldapUsername = decrypt(user.ldapUsername); | ||
| } | ||
| if (user.samlNameId) { | ||
| updates.samlNameId = decrypt(user.samlNameId); | ||
| } | ||
| if (user.salt) { | ||
| updates.salt = decrypt(user.salt); | ||
| } | ||
|
|
||
| if (Object.keys(updates).length > 0) { | ||
| const setClauses = Object.keys(updates) | ||
| .map(col => `"${col}" = :${col}`) | ||
| .join(', '); | ||
|
|
||
| await queryInterface.sequelize.query( | ||
| `UPDATE "user" SET ${setClauses} WHERE id = :id`, | ||
| { | ||
| replacements: { ...updates, id: user.id }, | ||
| transaction, | ||
| } | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| await transaction.commit(); | ||
| } catch (err) { | ||
| await transaction.rollback(); | ||
| throw err; | ||
| } | ||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -602,6 +602,8 @@ module.exports = (sequelize, DataTypes) => { | |
| sequelize, | ||
| modelName: "user", | ||
| tableName: "user", | ||
| //Keys that require encryption unique set to false by default | ||
| encryptedFields: ['firstName', 'lastName', { name: 'email', unique: true }, 'salt', 'initialPassword', 'twoFactorOtp', 'totpSecret', 'orcidId', 'ldapUsername', 'samlNameId'], | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we also need to encrypt |
||
| hooks: { | ||
| afterCreate: async (user, options) => { | ||
| const {context, transaction} = options; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.