Encryption of sensitive data - #194
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 |

Description
This PR introduces generic application-layer field-level encryption for sensitive user data using AES-256-GCM. Models declare
encryptedFieldsand encryption/decryption is handled automatically on all reads and writes via hooks onGlobalChangeTrackingPlugin. Key rotation and automatic encryption state sync on startup are also included.New User Features
ENCRYPTION_ENABLEDin.env— the server applies the change automatically on next server startNew Dev Features
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 changesImprovements
The following
usermodel fields are now encrypted at rest:firstName,lastName,email,initialPassword,salttwoFactorOtp,totpSecret,samlNameIdorcidId,ldapUsername,extIdKnown Limitations
ENCRYPTION_ENABLEDis detected at server startup only — changing the flag requires a restartFuture Steps
encryptedFieldsto other tables as needed — no migration or script changes requiredENCRYPTION_ENABLEDthat triggers the sync without a restart