Module: Familia::Features::EncryptedFields::ModelClassMethods

Defined in:
lib/familia/features/encrypted_fields.rb

Instance Method Summary collapse

Instance Method Details

#encrypted_field(name, aad_fields: [], algorithm: nil) ⇒ Object

Define an encrypted field that transparently encrypts/decrypts values

Encrypted fields are stored as JSON objects containing the encrypted ciphertext along with cryptographic metadata. Values are automatically encrypted on assignment and decrypted on access.

Examples:

Basic encrypted field

class Vault < Familia::Horreum
  feature :encrypted_fields
  encrypted_field :secret_key
end

Encrypted field with additional authentication

class Document < Familia::Horreum
  feature :encrypted_fields
  field :doc_id, :owner_id
  encrypted_field :content, aad_fields: [:doc_id, :owner_id]
end

Encrypted field bound to dynamic entropy

class Secret < Familia::Horreum
  feature :encrypted_fields
  encrypted_field :payload, key_material: ->(rec) { rec.passphrase }
end

Field pinned to a specific write algorithm

class Ledger < Familia::Horreum
  feature :encrypted_fields
  # Keep writing AES-256-GCM even after rbnacl makes XChaCha20 the
  # default, so older nodes can still decrypt during a fleet rollout.
  encrypted_field :entry, algorithm: 'aes-256-gcm'
end

Parameters:

  • name (Symbol)

    Field name

  • aad_fields (Array<Symbol>) (defaults to: [])

    Additional fields to include in authentication

  • key_material (Proc, nil)

    Optional proc mixed into key derivation as extra entropy. It is called with the record instance and must return the entropy as a String (a RedactedString is unwrapped automatically); return nil to contribute no entropy for that record. Because the returned value participates in key derivation rather than authentication, supplying the wrong material yields a derivation mismatch (garbage/failed decrypt), not an auth-tag error. The value is not persisted, so it must be reproducible at decrypt time.

  • algorithm (String, nil) (defaults to: nil)

    Optional per-field write-algorithm pin. When nil (default), writes use the registry's default provider -- the highest priority available, i.e. XChaCha20-Poly1305 whenever rbnacl is loaded, else AES-256-GCM. When set to a registered algorithm identifier ('aes-256-gcm' or 'xchacha20poly1305'), every write from this field is encrypted with that algorithm regardless of the default. Reads are always driven by the stored envelope's own algorithm, so pinning is safe to add, change, or remove without breaking ciphertext already at rest. This is the supported lever for a reader-before-writer format migration: deploy rbnacl fleet-wide so every node can read XChaCha20, while keeping writes pinned to 'aes-256-gcm' until all readers are confirmed capable, then remove the pin. An unregistered algorithm raises Familia::EncryptionError on first write.

  • kwargs (Hash)

    Additional field options



335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/familia/features/encrypted_fields.rb', line 335

def encrypted_field(name, aad_fields: [], algorithm: nil, **)
  @encrypted_fields ||= []
  @encrypted_fields << name unless @encrypted_fields.include?(name)

  # Add to field_groups if the group exists
  if field_groups&.key?(:encrypted_fields)
    field_groups[:encrypted_fields] << name
  end

  field_type = EncryptedFieldType.new(name, aad_fields: aad_fields, algorithm: algorithm, **)
  register_field_type(field_type)
end

#encrypted_field?(field_name) ⇒ Boolean

Check if a field is encrypted

Parameters:

  • field_name (Symbol)

    The field name to check

Returns:

  • (Boolean)

    true if field is encrypted, false otherwise



361
362
363
# File 'lib/familia/features/encrypted_fields.rb', line 361

def encrypted_field?(field_name)
  encrypted_fields.include?(field_name.to_sym)
end

#encrypted_fieldsArray<Symbol>

Returns list of encrypted field names defined on this class

Returns:

  • (Array<Symbol>)

    Array of encrypted field names



352
353
354
# File 'lib/familia/features/encrypted_fields.rb', line 352

def encrypted_fields
  @encrypted_fields || []
end

#encryption_infoHash

Get encryption algorithm information

Returns:

  • (Hash)

    Hash containing encryption algorithm details



369
370
371
372
373
374
375
376
377
# File 'lib/familia/features/encrypted_fields.rb', line 369

def encryption_info
  provider = Familia::Encryption.current_provider
  {
    algorithm: provider.algorithm_name,
    key_size: provider.key_size,
    nonce_size: provider.nonce_size,
    tag_size: provider.tag_size,
  }
end