Encryption of sensitive data#194
Encryption of sensitive data#194junaidferoz wants to merge 38 commits intodevUKPLab/CARE:devfrom feat-156-pgcrypto_encryptionUKPLab/CARE:feat-156-pgcrypto_encryptionCopy head branch name to clipboard
Conversation
Add DB-only migration to encrypt user table fields with pgcrypto and wire encryption helpers in db/index.js.
44d84ec to
c49c087
Compare
dennis-zyska
left a comment
There was a problem hiding this comment.
@bingobongomann the code looks good, could you test it locally, if it works, we can merge
Looks good. The User Table of a newly set up server is encrypted. New encryption key was generated during the migration. |
bingobongomann
left a comment
There was a problem hiding this comment.
Implementation looks good, encryption and decryption work well and are correctly updating the DB when the env is changed.
Left one comment about make change_encryption_key to add optional generation of a new key if none is provided
bingobongomann
left a comment
There was a problem hiding this comment.
Looks Good now! Encryption can be updated via command line.
The only thing missing is the decrypted database backup command and then we can merge this
…Lab/CARE into feat-156-pgcrypto_encryption
…unique attribute that is served by the db
bingobongomann
left a comment
There was a problem hiding this comment.
sidecar backup looks good, adding constraints and hooks for BlindIndexes looks good too.
Just need Documentation and some clarification about existing unique fields.
|
this is ready @bingobongomann |

Overview
This PR introduces generic application-layer field-level encryption for sensitive user data using AES-256-GCM.
Changes
Core encryption
encryption.jsutility with AES-256-GCM encrypt/decrypt helpers and key managementGlobalChangeTrackingPlugin— models declareencryptedFieldsand encryption/decryption happens automatically on all writes and readsENCRYPTION_ENABLEDchanges on startup and encrypts or decrypts all affected fields automaticallyEncrypted user fields
The following
usermodel fields are now encrypted at rest:firstName,lastName,email,initialPassword,salttwoFactorOtp,totpSecret,samlNameIdorcidId,ldapUsername,extIdNew user-facing behaviour
ENCRYPTION_ENABLEDin.env— the server applies the change automatically on next startupNew developer API
backend/utils/encryption.jsencrypt(plaintext, key?)— AES-256-GCM encryption with Base64 serialisation; accepts an optional explicit keydecrypt(value)— always uses the stored keyModel-level
encryptedFieldsoption on model definitions for declarative field-level encryption — any model that declares it is automatically covered by all bulk operationsBulk operations
encryptAllModels(db)/decryptAllModels(db)— bulk encrypt/decrypt all fields across all models that declareencryptedFieldsKey rotation
reEncryptAllModels(db, newKey)/reEncryptValue(value, newKey)— old key read fromencryption.key, new key passed explicitlybackend/scripts/changeEncryptionKey.js+make change_encryption_key NEW_KEY=<hex>— CLI for key rotation without server downtimeStartup sync
syncEncryptionState(db)— comparesENCRYPTION_ENABLEDagainstencryption.stateon startup and triggers encrypt/decrypt automatically if the flag changedMigration
20260612100001-encrypt-user-fieldssimplified to useencryptAllModels/decryptAllModels— new models withencryptedFieldsare automatically covered without migration changesKnown limitations
ENCRYPTION_ENABLEDis detected at server startup only — changing the flag requires a restartencryption.statetracks the last known flag value; on first startup after the migration the state file is created without re-encrypting (migration handles initial encryption)Future steps
encryptedFieldsto other tables as needed — no migration or script changes required