Agrego carpeta del source sql de la Base de Datos

This commit is contained in:
Pablo
2026-08-22 19:10:49 -03:00
parent 11e36bd6c2
commit 88d724fbcf
94 changed files with 7820 additions and 0 deletions
@@ -0,0 +1,34 @@
-- ============================================================================
-- fc_eliminar_sesion
-- ============================================================================
-- PROPÓSITO
-- Cierra una sesión activa eliminando su registro de internal.sesiones.
-- Equivalente a "logout".
--
-- PARÁMETROS
-- p_token UUID token de la sesión a cerrar.
--
-- AUTORIZACIÓN
-- No valida permiso (cualquiera con un token válido puede cerrarlo).
-- Si p_token no existe, no hace nada (no rebota).
--
-- ERRORES (RAISE EXCEPTION)
-- Ninguno.
--
-- RETORNA
-- void.
--
-- EFECTOS SECUNDARIOS
-- DELETE en internal.sesiones de la fila con ese token.
-- ============================================================================
CREATE OR REPLACE FUNCTION public.fc_eliminar_sesion(p_token UUID)
RETURNS VOID
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
DELETE FROM internal.sesiones s WHERE s.token = p_token;
END;
$$;
+60
View File
@@ -0,0 +1,60 @@
-- ============================================================================
-- fc_ingresar
-- ============================================================================
-- PROPÓSITO
-- Inicia sesión validando credenciales DNI + contraseña. Si las
-- credenciales son correctas, crea una nueva sesión con vencimiento a
-- 10 días y devuelve el token y el rol del usuario.
--
-- PARÁMETROS
-- dni_input TEXT DNI del usuario.
-- password_plain_input TEXT contraseña en texto plano.
--
-- AUTORIZACIÓN
-- Pública: no requiere token previo (es la entrada al sistema).
--
-- ERRORES (RAISE EXCEPTION)
-- - 'Usuario o contraseña inválidos.' (mismo mensaje para usuario
-- inexistente y para contraseña mal — evita oracles de existencia).
--
-- RETORNA
-- TABLE (token UUID, rol TEXT) con la sesión creada y el rol del usuario.
--
-- EFECTOS SECUNDARIOS
-- - INSERT en internal.sesiones (token + expires_at = now() + 10 días).
-- - El trigger trg_clean_old_sessions borra otras sesiones del mismo
-- usuario al insertar (cada usuario tiene una sola sesión activa).
-- ============================================================================
CREATE OR REPLACE FUNCTION public.fc_ingresar(dni_input TEXT, password_plain_input TEXT)
RETURNS TABLE (token UUID, rol TEXT)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = extensions, public
AS $$
DECLARE
v_user_id UUID;
v_user_rol TEXT;
v_session_token UUID;
v_stored_password_hash TEXT;
BEGIN
SELECT u.id, u.rol, u.password_hash
INTO v_user_id, v_user_rol, v_stored_password_hash
FROM public.usuarios u
WHERE u.dni = dni_input;
IF NOT FOUND THEN
RAISE EXCEPTION 'Usuario o contraseña inválidos.';
END IF;
IF v_stored_password_hash IS NULL OR v_stored_password_hash <> crypt(password_plain_input, v_stored_password_hash) THEN
RAISE EXCEPTION 'Usuario o contraseña inválidos.';
END IF;
v_session_token := gen_random_uuid();
INSERT INTO internal.sesiones (usuario_id, token, expires_at)
VALUES(v_user_id, v_session_token, NOW() + INTERVAL '10 days');
RETURN QUERY SELECT v_session_token, v_user_rol;
END;
$$;
@@ -0,0 +1,55 @@
-- ============================================================================
-- fc_iniciar_sesion_por_token
-- ============================================================================
-- PROPÓSITO
-- Reanuda una sesión existente a partir del token guardado localmente.
-- Si el token es válido (existe y no expiró), extiende su vencimiento
-- otros 10 días desde ahora y devuelve el rol del usuario.
-- Es el equivalente de fc_ingresar pero usando el token persistido en
-- lugar de credenciales — reemplaza a fc_validar_token en el flujo de
-- startup de la app, renovando el TTL en lugar de solo validarlo.
--
-- PARÁMETROS
-- p_token UUID token de sesión guardado localmente.
--
-- AUTORIZACIÓN
-- Pública: recibe el token como única prueba de identidad.
--
-- ERRORES (RAISE EXCEPTION)
-- - 'Sesión inválida o expirada.' si el token no existe o ya venció.
--
-- RETORNA
-- TEXT con el rol del usuario (misma forma que fc_validar_token).
--
-- EFECTOS SECUNDARIOS
-- - UPDATE en internal.sesiones: extends expires_at = now() + 10 días.
-- ============================================================================
CREATE OR REPLACE FUNCTION public.fc_iniciar_sesion_por_token(p_token UUID)
RETURNS TEXT
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_rol TEXT;
BEGIN
-- Validar token, extender el TTL y obtener el rol en una sola sentencia.
-- El UPDATE solo afecta filas cuyo token existe y no venció; el JOIN con
-- usuarios provee el rol vía RETURNING. Si no matchea nada (token
-- inexistente, expirado o sesión sin usuario), NOT FOUND dispara el error.
UPDATE internal.sesiones s
SET expires_at = NOW() + INTERVAL '10 days'
FROM public.usuarios u
WHERE s.token = p_token
AND s.expires_at > NOW()
AND u.id = s.usuario_id
RETURNING u.rol INTO v_rol;
IF NOT FOUND THEN
RAISE EXCEPTION 'Sesión inválida o expirada.';
END IF;
RETURN v_rol;
END;
$$;
@@ -0,0 +1,39 @@
-- ============================================================================
-- internal.clean_expired_sessions
-- ============================================================================
-- PROPÓSITO
-- Elimina todas las sesiones cuya fecha de expiración ya pasó. Pensada
-- para ejecutarse periódicamente por cron.
--
-- El job de cron que la dispara (ten_day_session_cleanup_job, días
-- 1, 11 y 21 de cada mes a las 00:00) se versiona en
-- cron/auth/ten_day_session_cleanup_job.sql.
--
-- PARÁMETROS
-- Ninguno.
--
-- AUTORIZACIÓN
-- No aplica (función interna invocada por cron).
--
-- ERRORES (RAISE EXCEPTION)
-- Ninguno.
--
-- RETORNA
-- void.
--
-- EFECTOS SECUNDARIOS
-- DELETE en internal.sesiones de las filas con expires_at < NOW().
-- ============================================================================
create OR REPLACE function internal.clean_expired_sessions() returns void
security definer
SET search_path = internal, pg_catalog
language plpgsql
as
$$
BEGIN
-- Elimina todas las sesiones cuya fecha de expiración es anterior a la actual.
DELETE FROM internal.sesiones
WHERE expires_at < NOW();
END;
$$;
@@ -0,0 +1,44 @@
-- ============================================================================
-- internal.clean_old_sessions_on_new_login
-- ============================================================================
-- PROPÓSITO
-- Trigger function: al insertar una nueva sesión, elimina cualquier otra
-- sesión previa del mismo usuario. Garantiza que cada usuario tiene a lo
-- sumo una sesión activa.
--
-- El trigger que la invoca (trg_clean_old_sessions, BEFORE INSERT on
-- internal.sesiones) se versiona en
-- triggers/auth/trg_clean_old_sessions.sql.
--
-- PARÁMETROS
-- Implícitos del trigger (NEW.usuario_id, NEW.token).
--
-- AUTORIZACIÓN
-- No aplica (es trigger).
--
-- ERRORES (RAISE EXCEPTION)
-- Ninguno.
--
-- RETORNA
-- trigger (la fila NEW para continuar el INSERT).
--
-- EFECTOS SECUNDARIOS
-- DELETE en internal.sesiones de cualquier sesión del mismo usuario con
-- token distinto al recién insertado.
-- ============================================================================
create OR REPLACE function internal.clean_old_sessions_on_new_login() returns trigger
security definer
set search_path = internal, pg_catalog
language plpgsql
as
$$
BEGIN
-- Esto asegura que solo la sesión actual (la que se está creando) permanezca.
DELETE FROM internal.sesiones
WHERE usuario_id = NEW.usuario_id
AND token IS DISTINCT FROM NEW.token;
RETURN NEW;
END;
$$;
@@ -0,0 +1,39 @@
-- ============================================================================
-- internal.create_password_hash
-- ============================================================================
-- PROPÓSITO
-- Devuelve el hash bcrypt de una contraseña en texto plano. Usa
-- crypt() + gen_salt('bf') del módulo pgcrypto, que vive en el schema
-- extensions.
--
-- Helper interno usado por internal.fc_insertar_usuario (vía
-- public.fc_insertar_usuario) y por funciones futuras de cambio de
-- contraseña.
--
-- PARÁMETROS
-- p_password_plain TEXT contraseña en claro.
--
-- AUTORIZACIÓN
-- Función interna, sin validación propia. Pensada para ser llamada por
-- otras funciones server-side, no por el cliente directamente.
--
-- ERRORES (RAISE EXCEPTION)
-- Ninguno propio.
--
-- RETORNA
-- TEXT con el hash bcrypt.
--
-- EFECTOS SECUNDARIOS
-- Ninguno.
-- ============================================================================
CREATE OR REPLACE FUNCTION internal.create_password_hash(p_password_plain TEXT)
RETURNS TEXT
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = extensions, public
AS $$
BEGIN
RETURN crypt(p_password_plain, gen_salt('bf'::text));
END;
$$;
@@ -0,0 +1,45 @@
-- ============================================================================
-- internal.validar_token
-- ============================================================================
-- PROPÓSITO
-- Dado un token de sesión, devuelve el rol del usuario asociado si la
-- sesión está vigente. Si el token no existe o ya expiró, devuelve NULL.
-- Helper interno usado por internal.validate_permission y por funciones
-- públicas que necesitan saber el rol del actor.
--
-- PARÁMETROS
-- p_token UUID token de sesión.
--
-- AUTORIZACIÓN
-- Función interna, sin validación propia.
--
-- ERRORES (RAISE EXCEPTION)
-- Ninguno.
--
-- RETORNA
-- TEXT con el rol del usuario, o NULL si el token es inválido o expiró.
--
-- EFECTOS SECUNDARIOS
-- Ninguno (STABLE).
-- ============================================================================
CREATE OR REPLACE FUNCTION internal.validar_token(p_token uuid)
RETURNS text
LANGUAGE plpgsql
STABLE SECURITY DEFINER
SET search_path TO 'public'
AS $function$
DECLARE
v_rol TEXT;
BEGIN
SELECT u.rol
FROM public.usuarios u
JOIN internal.sesiones s ON s.usuario_id = u.id
AND s.token = p_token
AND s.expires_at > NOW()
INTO v_rol;
-- Retorna el rol si el token es válido, de lo contrario, retorna NULL
RETURN v_rol;
END;
$function$
;
@@ -0,0 +1,62 @@
-- ============================================================================
-- internal.validate_permission
-- ============================================================================
-- PROPÓSITO
-- Valida que el portador del token tenga permiso para ejecutar una acción
-- dada. Rebota con excepción si el token es inválido, si la acción no
-- está registrada, o si el rol del usuario no está autorizado.
--
-- Es la pieza central del control de acceso: toda función pública (fc_*)
-- la invoca al inicio.
--
-- PARÁMETROS
-- p_token UUID token de sesión del actor.
-- p_accion TEXT nombre de la acción a validar (clave en internal.permisos).
--
-- AUTORIZACIÓN
-- Función interna; ella misma es el validador de autorización.
--
-- ERRORES (RAISE EXCEPTION)
-- - 'Token de sesión inválido o expirado'
-- - 'Error interno: acción no reconocida: X'
-- - 'No tienes internal.permisos para realizar esta acción'
-- (NOTA: el texto del error contiene "internal.permisos" por un error
-- histórico de redacción; se refiere a permisos en general.)
--
-- RETORNA
-- void si el actor tiene permiso. Si no, rebota antes de retornar.
--
-- EFECTOS SECUNDARIOS
-- Ninguno (STABLE).
-- ============================================================================
CREATE OR REPLACE FUNCTION internal.validate_permission(p_token UUID, p_accion TEXT)
RETURNS void
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path TO public
AS $$
DECLARE
v_rol TEXT;
v_roles_permitidos TEXT[];
BEGIN
SELECT internal.validar_token(p_token) INTO v_rol;
IF v_rol IS NULL THEN
RAISE EXCEPTION 'Token de sesión inválido o expirado';
END IF;
SELECT roles_permitidos INTO v_roles_permitidos
FROM internal.permisos
WHERE accion = p_accion;
IF v_roles_permitidos IS NULL THEN
RAISE EXCEPTION 'Error interno: acción no reconocida: %', p_accion;
END IF;
IF v_rol <> ALL(v_roles_permitidos) THEN
RAISE EXCEPTION 'No tienes internal.permisos para realizar esta acción';
END IF;
END;
$$;