trading-platform-database-v2/ddl/schemas/auth/tables/001_sessions.sql
rckrdmrd e520268348 Migración desde trading-platform/apps/database - Estándar multi-repo v2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 08:32:52 -06:00

151 lines
4.3 KiB
PL/PgSQL

-- ============================================================================
-- SCHEMA: auth
-- TABLE: sessions
-- DESCRIPTION: Gestion de sesiones de usuario con tracking de dispositivos
-- VERSION: 1.0.0
-- CREATED: 2026-01-10
-- ============================================================================
-- Crear schema si no existe
CREATE SCHEMA IF NOT EXISTS auth;
-- Enum para estado de sesion
DO $$ BEGIN
CREATE TYPE auth.session_status AS ENUM (
'active', -- Sesion activa
'expired', -- Expirada por tiempo
'revoked' -- Revocada manualmente (logout)
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Enum para tipo de dispositivo
DO $$ BEGIN
CREATE TYPE auth.device_type AS ENUM (
'desktop',
'mobile',
'tablet',
'unknown'
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Tabla de Sesiones
CREATE TABLE IF NOT EXISTS auth.sessions (
-- Identificadores
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE,
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
-- Token hash (para validacion)
token_hash VARCHAR(255) NOT NULL,
-- Informacion del dispositivo
device_type auth.device_type DEFAULT 'unknown',
device_name VARCHAR(255),
browser VARCHAR(100),
browser_version VARCHAR(50),
os VARCHAR(100),
os_version VARCHAR(50),
-- Ubicacion
ip_address INET,
user_agent TEXT,
-- Estado
status auth.session_status NOT NULL DEFAULT 'active',
-- Timestamps
last_active_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE auth.sessions IS
'Sesiones activas de usuarios con informacion de dispositivo para seguridad';
COMMENT ON COLUMN auth.sessions.token_hash IS
'Hash SHA256 del refresh token para validacion sin almacenar el token';
-- Indices
CREATE INDEX IF NOT EXISTS idx_sessions_user
ON auth.sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_sessions_tenant
ON auth.sessions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_sessions_token_hash
ON auth.sessions(token_hash);
CREATE INDEX IF NOT EXISTS idx_sessions_status
ON auth.sessions(status) WHERE status = 'active';
CREATE INDEX IF NOT EXISTS idx_sessions_expires
ON auth.sessions(expires_at) WHERE status = 'active';
CREATE INDEX IF NOT EXISTS idx_sessions_last_active
ON auth.sessions(last_active_at DESC);
-- Funcion para limpiar sesiones expiradas
CREATE OR REPLACE FUNCTION auth.cleanup_expired_sessions()
RETURNS INTEGER AS $$
DECLARE
deleted_count INTEGER;
BEGIN
UPDATE auth.sessions
SET status = 'expired'
WHERE status = 'active'
AND expires_at < NOW();
GET DIAGNOSTICS deleted_count = ROW_COUNT;
-- Eliminar sesiones muy antiguas (> 90 dias)
DELETE FROM auth.sessions
WHERE (status = 'expired' OR status = 'revoked')
AND created_at < NOW() - INTERVAL '90 days';
RETURN deleted_count;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION auth.cleanup_expired_sessions() IS
'Limpia sesiones expiradas y elimina registros muy antiguos. Ejecutar periodicamente.';
-- Funcion para revocar todas las sesiones de un usuario
CREATE OR REPLACE FUNCTION auth.revoke_all_user_sessions(
p_user_id UUID,
p_except_session_id UUID DEFAULT NULL
)
RETURNS INTEGER AS $$
DECLARE
revoked_count INTEGER;
BEGIN
UPDATE auth.sessions
SET status = 'revoked',
revoked_at = NOW()
WHERE user_id = p_user_id
AND status = 'active'
AND (p_except_session_id IS NULL OR id != p_except_session_id);
GET DIAGNOSTICS revoked_count = ROW_COUNT;
RETURN revoked_count;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION auth.revoke_all_user_sessions IS
'Revoca todas las sesiones activas de un usuario, opcionalmente excepto una sesion especifica';
-- RLS Policy para multi-tenancy
ALTER TABLE auth.sessions ENABLE ROW LEVEL SECURITY;
CREATE POLICY sessions_tenant_isolation ON auth.sessions
FOR ALL
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
-- Grants
GRANT SELECT, INSERT, UPDATE, DELETE ON auth.sessions TO trading_app;
GRANT SELECT ON auth.sessions TO trading_readonly;