Agrego carpeta del source sql de la Base de Datos
This commit is contained in:
@@ -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;
|
||||
$$;
|
||||
@@ -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;
|
||||
$$;
|
||||
Reference in New Issue
Block a user