StaticdecryptOptionallegacyOpts: DecryptOptionsStaticencryptStaticgenerateGenerates a base64-encoded random key suitable for the given algorithm.
const newKey = Encrypt.generateKey('aes-256-gcm').current and legacy. For
encrypted columns: dreamApp.set('encryption', { columns: { current: { algorithm: 'aes-256-gcm', key: newKey }, legacy: { algorithm: 'aes-256-gcm', key: oldKey } } }). For cookies, use the equivalent
shape under psychicApp.set('encryption', { cookies: { current, legacy } }).current; existing ciphertext continues
to decrypt via legacy fallback.maxAge so all in-flight
cookies have either expired or been re-issued under the new key. For
@Encrypted columns, re-encrypt every existing row under the new key
by calling reencryptEncryptedColumns() on each model that has
encrypted properties (await User.reencryptEncryptedColumns()), in
each environment, from a console or a script rather than a migration.
Run it only once every server in that environment runs with the new
current: a server still on the old current keeps writing old-key
values that its single pass over the table can miss. Writing old-key
ciphertext straight into a backing column (setAttribute,
setAttributes or updateAttributes given the backing column) after
it has run also puts an old-key value back. For any other value
encrypted with these keys, Encrypt.reencrypt rewrites it.legacy from config and deploy again.To see which values still need the old key while legacy is
configured, register dreamApp.on('encryption:legacy-key-used', ...),
which fires each time reading an @Encrypted property needed legacy
to open the value, or pass onLegacyKeyUsed to Encrypt.decrypt
for values you decrypt yourself. A value nobody reads never fires
either, so silence alone does not show that legacy is safe to drop.
legacymaxAge, so in-flight sessions are
not forced to re-authenticate.@Encrypted columns: until every existing row has been
re-encrypted under the new key. Once legacy is dropped, reading the
@Encrypted property of a row that is still under the old key throws
DecryptionError, the same error a tampered or corrupted value
throws, so the error class alone cannot show that legacy was
dropped too early. DecryptionRotationError is thrown only while
legacy is configured and neither key opens the value. Neither class
extends the other, so code that catches DecryptionRotationError
during a rotation must also catch DecryptionError to handle such a
read once legacy is dropped.StaticreencryptOpens encrypted as Encrypt.decrypt does, with currentOpts and,
when they do not open it, legacyOpts, and encrypts the value again with
currentOpts, so that reading it no longer needs the legacy key. Use it
during a key rotation for a value stored outside an @deco.Encrypted
column, such as a cookie; Model.reencryptEncryptedColumns() does this
for every value of a model's encrypted properties.
const rewritten = Encrypt.reencrypt(
storedValue,
{ algorithm: 'aes-256-gcm', key: newKey },
{ algorithm: 'aes-256-gcm', key: oldKey }
)
OptionallegacyOpts: DecryptOptionsStaticvalidate
Decrypts a value previously produced by Encrypt.encrypt.
Behavior depends on whether
legacyOptsis provided:Two-arg form (no rotation):
null/undefinedinput returnsnull.DecryptionError.DecryptionParseError.Three-arg form (rotation): tries the current key first; on
DecryptionErrorfalls back to the legacy key. If both fail, throwsDecryptionRotationErrorcarrying both per-key errors. ADecryptionParseErrorfrom the current key is not retried — the cipher already matched, so a parse failure means the encrypted format is wrong (an app bug), not a wrong key.MissingEncryptionKeypropagates from either form when a key is missing.onLegacyKeyUsed(optional fourth argument): during a key rotation, tells you which values still need the legacy key. It is called with no arguments, synchronously, before a three-arg call returns a value that the legacy key opened. It is not called when the current key opens the value, when decryption throws, or in the two-arg form. An error it throws propagates fromdecryptas is.