72 lines
2.5 KiB
Ruby
72 lines
2.5 KiB
Ruby
# app/models/concerns/atributo_cifrado.rb
|
|
#
|
|
# Cifra atributos sensíveis (senha SMTP, token do Twilio) em colunas
|
|
# `<nome>_cifrado` usando ActiveSupport::MessageEncryptor (AES-256-GCM).
|
|
#
|
|
# POR QUE NÃO ActiveRecord Encryption (`encrypts :campo`): este projeto não usa
|
|
# `config/credentials.yml.enc` nem `config/master.key` — tudo vem do .env. Ligar
|
|
# o AR Encryption exigiria configurar 3 chaves novas e depender da ordem de
|
|
# execução dos initializers do framework. Aqui a chave é DERIVADA do
|
|
# secret_key_base, sem nada novo para o operador gerar.
|
|
#
|
|
# ⚠️ ROTAÇÃO: se o SECRET_KEY_BASE do servidor for trocado, os valores já
|
|
# gravados viram ilegíveis. O reader devolve nil (nunca estoura 500), o app
|
|
# cai no fallback do .env e a tela mostra um aviso pedindo para redigitar.
|
|
# Para ficar imune a isso, defina NOTIFICACAO_SECRET no .env (qualquer string
|
|
# longa e fixa) — a derivação passa a usar ela em vez do secret_key_base.
|
|
module AtributoCifrado
|
|
extend ActiveSupport::Concern
|
|
|
|
SALT = 'reem-notas/notificacao/v1'
|
|
|
|
class_methods do
|
|
def atributo_cifrado(*nomes)
|
|
nomes.each do |nome|
|
|
coluna = :"#{nome}_cifrado"
|
|
|
|
define_method(nome) { AtributoCifrado.decifrar(self[coluna]) }
|
|
|
|
define_method(:"#{nome}=") do |valor|
|
|
texto = valor.to_s
|
|
# Sem isso, salvar a tela sem mexer no campo geraria um criptograma
|
|
# novo (o IV é aleatório) e o dirty tracking acusaria mudança à toa.
|
|
next if texto == send(nome)
|
|
|
|
self[coluna] = texto.blank? ? nil : AtributoCifrado.cifrar(texto)
|
|
end
|
|
|
|
define_method(:"#{nome}?") { send(nome).present? }
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.cifrar(texto)
|
|
cofre.encrypt_and_sign(texto)
|
|
end
|
|
|
|
def self.decifrar(blob)
|
|
return nil if blob.blank?
|
|
|
|
cofre.decrypt_and_verify(blob)
|
|
rescue ActiveSupport::MessageEncryptor::InvalidMessage,
|
|
ActiveSupport::MessageVerifier::InvalidSignature => e
|
|
Rails.logger.error("[AtributoCifrado] valor ilegível (SECRET_KEY_BASE mudou?): #{e.class}")
|
|
nil
|
|
end
|
|
|
|
# O default do MessageEncryptor no Rails 7.1 é aes-256-gcm → chave de 32 bytes.
|
|
def self.cofre
|
|
@cofre ||= ActiveSupport::MessageEncryptor.new(chave)
|
|
end
|
|
|
|
def self.chave
|
|
if (segredo = ENV['NOTIFICACAO_SECRET'].presence)
|
|
ActiveSupport::KeyGenerator
|
|
.new(segredo, hash_digest_class: OpenSSL::Digest::SHA256)
|
|
.generate_key(SALT, 32)
|
|
else
|
|
Rails.application.key_generator.generate_key(SALT, 32)
|
|
end
|
|
end
|
|
end
|