Volver al blog

Construyendo mi propio panel de admin: Postgres, Row Level Security y Server Actions

SupabasePostgresNext.jsRow Level Security

Podría haber usado un CMS ya hecho para gestionar los proyectos que se muestran en este sitio. Hay varios buenos. En vez de eso construí el mío propio, a propósito, porque quería entender de verdad las piezas de debajo: una base de datos real, autenticación real, y control de acceso real, no solo un formulario que resulta que guarda datos en algún sitio.

El stack

  • Supabase para Postgres y autenticación
  • Next.js Server Actions para las operaciones de escritura (añadir, editar, borrar, reordenar)
  • @supabase/ssr, el patrón oficial para manejar sesiones de autenticación entre servidor y navegador en un proyecto Next.js con App Router
  • @dnd-kit para el reordenamiento por arrastre en la interfaz de admin

La parte que realmente importa: Row Level Security

Cualquiera puede construir un formulario que escriba en una base de datos. La pregunta de verdad es qué impide que alguien que no soy yo escriba también en ella.

La sección de Trabajo pública de este sitio lee directamente de una tabla projects en Postgres, sin ninguna capa de API separada escondiéndolo. Eso significa que la propia base de datos tiene que ser la que decida quién puede hacer qué, no solo la interfaz. Para esto sirve Row Level Security (RLS).

La política tiene una forma sencilla:

  • Cualquiera puede leer (SELECT), ya que la lista de proyectos es contenido público
  • Solo una sesión autenticada puede escribir (INSERT, UPDATE, DELETE)

Lo importante no es escribir esa política, es demostrar que realmente se sostiene. La probé directamente: envié una petición de escritura usando solo la clave pública sin autenticar, sin ninguna sesión asociada, y confirmé que Postgres la rechazó a nivel de base de datos:

42501: new row violates row-level security policy

Esa es la diferencia entre "el botón está escondido en la interfaz" y "la propia base de datos rechaza la escritura". Lo primero es una decisión de diseño. Lo segundo es seguridad real. Volví a consultar la tabla después para confirmar que nada había cambiado, ninguna fila intrusa, todas las filas intactas.

También añadí una comprobación explícita con getUser() dentro de cada server action que modifica datos, sin depender solo de RLS y del middleware. Eso es defensa en profundidad: si alguna capa tiene un fallo o se configura mal más adelante, todavía hay otra capa capaz de detenerlo antes de que datos malos lleguen cerca de la tabla.

Una decisión deliberada sobre las credenciales

Mientras construía esto, nunca le di mi contraseña de base de datos, la cadena de conexión, ni la clave de servicio a ninguna herramienta de IA para programar, ni siquiera a mitad de sesión, ni siquiera cuando habría sido más rápido. Cuando la migración necesitaba ejecutarse contra la base de datos en vivo, copié el SQL yo mismo y lo ejecuté directamente en el editor SQL propio de Supabase. Es un hábito pequeño, pero es el tipo de límite que importa cada vez más cuanto más toca un proyecto datos reales de usuarios, y pienso mantenerlo en cada proyecto a partir de ahora, no solo en este.

Qué me dio esto en realidad

Un panel de admin funcional donde puedo añadir, editar, borrar y reordenar arrastrando los proyectos que se muestran públicamente, con el cambio reflejado en directo, sin necesidad de volver a desplegar. Pero más allá de la funcionalidad en sí, esto fue un ejercicio real en las partes del desarrollo backend que no aparecen en un tutorial: qué significa que el control de acceso esté aplicado en un sitio donde no se puede saltar, y cómo es verificar de verdad un límite de seguridad en vez de asumir que funciona porque la interfaz se comporta correctamente.

También pasé una auditoría completa sobre el panel terminado, el mismo tipo de revisión que hice sobre el sitio público antes de lanzarlo, buscando confirmaciones que faltaban, fallos silenciosos, entradas sin validar, y huecos de accesibilidad. Salieron algunas cosas (un borrado sin confirmación fue lo más importante), y las arreglé de la misma forma: encontrarlo, entender por qué importa, arreglarlo bien, verificar que se sostiene.