Saltar al contenido
Development

Construyendo blog-manager: un modelo Blog con Active Record Encryption

Por Victor Da Luz
railsblog-managerdev-logactive-record-encryption

Estoy construyendo una app de Rails para gestionar mis blogs. El objetivo: un solo lugar para configurar el repo de GitHub, el token de Medium, y las credenciales de LinkedIn de cada blog, y después automatizar la republicación cruzada. Ahora tengo dos blogs y el flujo de trabajo es un desorden de pestañas guardadas y tokens que se me siguen olvidando. Esto lo arregla.

Esta es la parte 1: el modelo Blog y la UI.

El modelo Blog: las credenciales, no el CRUD

El CRUD es la parte aburrida. La parte interesante son las credenciales. Cada blog tiene su propio token de acceso personal de GitHub, un token de integración de Medium, y un token OAuth de LinkedIn. Ninguno de esos puede quedar en texto plano en SQLite.

El primer instinto fue attr_encrypted, que es a lo que recurre la mayoría de los tutoriales. Después recordé que Rails 7 trajo Active Record Encryption y todavía no lo había usado. La API es tan chica como puede ser:

class Blog < ApplicationRecord
  encrypts :github_token, :medium_token, :linkedin_access_token

  validates :name, presence: true
  validates :github_repo_url, presence: true
end

Eso es todo. Sin gema, sin columnas de texto cifrado en pares, sin columnas IV que manejar a mano. Rails hace todo eso, y las claves viven en config/credentials.yml.enc bajo active_record_encryption, que es donde corresponde.

La trampa de los fixtures que rompió todas las pruebas

Agregué las columnas cifradas a blogs.yml y toda la suite empezó de inmediato a lanzar ActiveRecord::Encryption::Errors::Decryption en cada prueba. El cargador de fixtures evita los callbacks del modelo, así que escribe cadenas crudas en columnas donde Rails espera texto cifrado, y explota en el momento en que algo las vuelve a leer.

La corrección fue sacar las columnas cifradas del archivo de fixtures y usar Blog.create! en las pruebas que en verdad necesitan un token. Dejé un comentario explicando por qué, para no deshacerlo sin darme cuenta dentro de seis meses.

Una columna para una funcionalidad que todavía no existe

El token de LinkedIn expira a los 60 días, así que estoy agregando una columna expires_at ahora aunque el flujo de OAuth todavía no existe. Sale más barato que una migración después. La vista de índice la usa para mostrar “expirado” en rojo y “expira pronto” en ámbar cuando a un token le queda una semana de vida.

Una UI que en verdad usaría

El modelo tomó una tarde. La UI tomó más tiempo, porque los scaffolds de Rails son funcionales pero parecen Bootstrap de 2012, y quería algo que diera gusto abrir todos los días.

Terminé con una barra lateral oscura (#111110), un área principal color crema cálido (#f5f3ee) más o menos del color del papel viejo, y un acento terracota (#c85e32) en el logo. Para la tipografía usé Bricolage Grotesque en los encabezados, que tiene un carácter editorial un poco áspero que se lee como intencional, DM Sans para el texto del cuerpo, y JetBrains Mono para los campos de tokens. Todo de Google Fonts, nada instalado localmente.

Cada integración tiene su propia tarjeta en el formulario con un pequeño ícono: un campo mono para el token de GitHub, una advertencia amarilla en la sección de LinkedIn sobre esa expiración de 60 días. En la tabla, el estado de la credencial es un solo punto de color, verde para configurado, rojo para expirado, ámbar para por expirar, un guion para faltante. La página de inicio de sesión es completamente oscura con una tarjeta crema flotando en el medio y un botón de inicio de sesión terracota. Aunque sea el único usuario, puede verse bien igual.

Dos cosas pequeñas que costaron tiempo

button_to en Rails 8 envuelve el botón en su propio <form>. Entonces una clase CSS en el botón no toca el contenedor del formulario, y el display por defecto del formulario rompía en silencio la alineación de las celdas de la tabla. La corrección es form: { style: "display:inline;" } (o form_class:). Obvio una vez que se sabe, molesto la primera vez.

El otro fue bin/dev. Su shebang es #!/usr/bin/env sh, y ese shell no carga ~/.zshrc, así que los shims de asdf no estaban en $PATH. El viejo bin/dev intentaba hacer gem install foreman contra el Ruby 2.6 del sistema y fallaba. Arreglé el síntoma agregando foreman al Gemfile y llamando a bundle exec foreman, y la causa raíz exportando la ruta de los shims directamente en ~/.zshrc, ya que asdf 0.19 eliminó el viejo asdf.sh.

Qué sigue

La parte 1 es un modelo y una UI. El verdadero punto de la app es la siguiente pieza: traer las publicaciones del repo de GitHub y mostrarlas, que es donde arranca la lógica de republicación. Esa es la parte 2.

Lecturas relacionadas

También te podría ser útil

Proton

Proton Pass

Gestor de contraseñas centrado en la privacidad, del equipo detrás de Proton Mail.

Como socio de Proton, obtengo ingresos por las compras que califican de los servicios de privacidad y seguridad de Proton (Pass, Mail, VPN, Drive).

Más información
RackNerd

RackNerd VPS

Alojamiento VPS económico para servicios ligeros que funcionan de forma continua.

Como afiliado de RackNerd, obtengo ingresos por las compras que califican.

Más información
Proton

Proton Drive

Almacenamiento en la nube cifrado, del equipo detrás de Proton Mail.

Como socio de Proton, obtengo ingresos por las compras que califican de los servicios de privacidad y seguridad de Proton (Pass, Mail, VPN, Drive).

Más información