Skip to content

Row Level Security (RLS) Policies ​

Sport Tech Club - Isolamento Multi-tenant com PostgreSQL RLS

Visão Geral ​

Este documento define as políticas de Row Level Security (RLS) para garantir isolamento completo de dados entre tenants na plataforma Sport Tech Club.


1. Arquitetura de Segurança ​

1.1 Modelo de Multi-tenancy ​

┌─────────────────────────────────────────────────────────────────┐
│                        Application Layer                         │
├─────────────────────────────────────────────────────────────────┤
│                     Connection Pool (PgBouncer)                  │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐  │
│  │   Schema:       │  │   Schema:       │  │   Schema:       │  │
│  │   identity      │  │   arena         │  │   booking       │  │
│  │                 │  │                 │  │                 │  │
│  │  - tenants      │  │  - arenas       │  │  - bookings     │  │
│  │  - users        │  │  - courts       │  │  - payments     │  │
│  │  - roles        │  │  - products     │  │  - check_ins    │  │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘  │
├─────────────────────────────────────────────────────────────────┤
│                    RLS Policies (tenant_id)                      │
├─────────────────────────────────────────────────────────────────┤
│                        PostgreSQL 15+                            │
└─────────────────────────────────────────────────────────────────┘

1.2 Variáveis de Sessão ​

sql
-- Variáveis de contexto para RLS
-- Definidas no início de cada transação

-- Tenant ID do usuário autenticado
SET LOCAL app.current_tenant_id = 'uuid-do-tenant';

-- User ID do usuário autenticado
SET LOCAL app.current_user_id = 'uuid-do-usuario';

-- Roles do usuário (JSON array)
SET LOCAL app.current_user_roles = '["ARENA_OWNER", "PLAYER"]';

-- Arena ID atual (para operações com escopo de arena)
SET LOCAL app.current_arena_id = 'uuid-da-arena';

2. Funções Auxiliares ​

2.1 Funções de Contexto ​

sql
-- Obtém o tenant_id da sessão atual
CREATE OR REPLACE FUNCTION current_tenant_id()
RETURNS UUID AS $$
BEGIN
  RETURN NULLIF(current_setting('app.current_tenant_id', true), '')::UUID;
EXCEPTION
  WHEN OTHERS THEN
    RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;

-- Obtém o user_id da sessão atual
CREATE OR REPLACE FUNCTION current_user_id()
RETURNS UUID AS $$
BEGIN
  RETURN NULLIF(current_setting('app.current_user_id', true), '')::UUID;
EXCEPTION
  WHEN OTHERS THEN
    RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;

-- Obtém as roles do usuário atual
CREATE OR REPLACE FUNCTION current_user_roles()
RETURNS TEXT[] AS $$
DECLARE
  roles_json TEXT;
BEGIN
  roles_json := current_setting('app.current_user_roles', true);
  IF roles_json IS NULL OR roles_json = '' THEN
    RETURN ARRAY[]::TEXT[];
  END IF;
  RETURN ARRAY(SELECT jsonb_array_elements_text(roles_json::JSONB));
EXCEPTION
  WHEN OTHERS THEN
    RETURN ARRAY[]::TEXT[];
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;

-- Obtém o arena_id da sessão atual
CREATE OR REPLACE FUNCTION current_arena_id()
RETURNS UUID AS $$
BEGIN
  RETURN NULLIF(current_setting('app.current_arena_id', true), '')::UUID;
EXCEPTION
  WHEN OTHERS THEN
    RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;

-- Verifica se usuário tem uma role específica
CREATE OR REPLACE FUNCTION has_role(role_name TEXT)
RETURNS BOOLEAN AS $$
BEGIN
  RETURN role_name = ANY(current_user_roles());
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;

-- Verifica se é super admin (acesso irrestrito)
CREATE OR REPLACE FUNCTION is_super_admin()
RETURNS BOOLEAN AS $$
BEGIN
  RETURN has_role('SUPER_ADMIN');
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;

2.2 Funções de Verificação ​

sql
-- Verifica se usuário pertence ao tenant
CREATE OR REPLACE FUNCTION user_belongs_to_tenant(check_tenant_id UUID)
RETURNS BOOLEAN AS $$
BEGIN
  -- Super admin tem acesso a todos os tenants
  IF is_super_admin() THEN
    RETURN TRUE;
  END IF;

  RETURN check_tenant_id = current_tenant_id();
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;

-- Verifica se usuário tem acesso à arena
CREATE OR REPLACE FUNCTION user_has_arena_access(check_arena_id UUID)
RETURNS BOOLEAN AS $$
DECLARE
  arena_tenant_id UUID;
BEGIN
  -- Super admin tem acesso a todas as arenas
  IF is_super_admin() THEN
    RETURN TRUE;
  END IF;

  -- Busca o tenant da arena
  SELECT tenant_id INTO arena_tenant_id
  FROM arena.arenas
  WHERE id = check_arena_id;

  -- Verifica se o tenant da arena é o mesmo do usuário
  RETURN arena_tenant_id = current_tenant_id();
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;

-- Verifica se usuário é dono do recurso
CREATE OR REPLACE FUNCTION is_resource_owner(resource_user_id UUID)
RETURNS BOOLEAN AS $$
BEGIN
  RETURN resource_user_id = current_user_id();
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;

3. Políticas por Tabela ​

3.1 Schema: Identity ​

Tabela: tenants ​

sql
-- Habilita RLS
ALTER TABLE identity.tenants ENABLE ROW LEVEL SECURITY;

-- Força RLS mesmo para o owner da tabela
ALTER TABLE identity.tenants FORCE ROW LEVEL SECURITY;

-- SELECT: Usuário só vê seu próprio tenant (ou todos se super admin)
CREATE POLICY tenants_select_policy ON identity.tenants
  FOR SELECT
  USING (
    is_super_admin() OR id = current_tenant_id()
  );

-- INSERT: Apenas super admin pode criar tenants
CREATE POLICY tenants_insert_policy ON identity.tenants
  FOR INSERT
  WITH CHECK (is_super_admin());

-- UPDATE: Tenant owner ou super admin
CREATE POLICY tenants_update_policy ON identity.tenants
  FOR UPDATE
  USING (
    is_super_admin() OR (
      id = current_tenant_id() AND has_role('ARENA_OWNER')
    )
  );

-- DELETE: Apenas super admin (soft delete na prática)
CREATE POLICY tenants_delete_policy ON identity.tenants
  FOR DELETE
  USING (is_super_admin());

Tabela: users ​

sql
ALTER TABLE identity.users ENABLE ROW LEVEL SECURITY;
ALTER TABLE identity.users FORCE ROW LEVEL SECURITY;

-- SELECT: Usuários do mesmo tenant ou o próprio usuário
CREATE POLICY users_select_policy ON identity.users
  FOR SELECT
  USING (
    is_super_admin()
    OR tenant_id = current_tenant_id()
    OR id = current_user_id()
  );

-- INSERT: Arena owner/manager do tenant ou super admin
CREATE POLICY users_insert_policy ON identity.users
  FOR INSERT
  WITH CHECK (
    is_super_admin()
    OR (
      tenant_id = current_tenant_id()
      AND (has_role('ARENA_OWNER') OR has_role('ARENA_MANAGER'))
    )
  );

-- UPDATE: O próprio usuário ou admin do tenant
CREATE POLICY users_update_policy ON identity.users
  FOR UPDATE
  USING (
    is_super_admin()
    OR id = current_user_id()
    OR (
      tenant_id = current_tenant_id()
      AND (has_role('ARENA_OWNER') OR has_role('ARENA_MANAGER'))
    )
  );

-- DELETE: Admin do tenant ou super admin
CREATE POLICY users_delete_policy ON identity.users
  FOR DELETE
  USING (
    is_super_admin()
    OR (
      tenant_id = current_tenant_id()
      AND has_role('ARENA_OWNER')
      AND id != current_user_id() -- Não pode deletar a si mesmo
    )
  );

Tabela: user_roles ​

sql
ALTER TABLE identity.user_roles ENABLE ROW LEVEL SECURITY;
ALTER TABLE identity.user_roles FORCE ROW LEVEL SECURITY;

-- SELECT: Ver roles de usuários do mesmo tenant
CREATE POLICY user_roles_select_policy ON identity.user_roles
  FOR SELECT
  USING (
    is_super_admin()
    OR EXISTS (
      SELECT 1 FROM identity.users u
      WHERE u.id = user_roles.user_id
      AND u.tenant_id = current_tenant_id()
    )
  );

-- INSERT: Arena owner pode atribuir roles (exceto SUPER_ADMIN)
CREATE POLICY user_roles_insert_policy ON identity.user_roles
  FOR INSERT
  WITH CHECK (
    is_super_admin()
    OR (
      has_role('ARENA_OWNER')
      AND role != 'SUPER_ADMIN'
      AND EXISTS (
        SELECT 1 FROM identity.users u
        WHERE u.id = user_roles.user_id
        AND u.tenant_id = current_tenant_id()
      )
    )
  );

-- DELETE: Arena owner pode remover roles
CREATE POLICY user_roles_delete_policy ON identity.user_roles
  FOR DELETE
  USING (
    is_super_admin()
    OR (
      has_role('ARENA_OWNER')
      AND role != 'SUPER_ADMIN'
      AND EXISTS (
        SELECT 1 FROM identity.users u
        WHERE u.id = user_roles.user_id
        AND u.tenant_id = current_tenant_id()
      )
    )
  );

3.2 Schema: Arena ​

Tabela: arenas ​

sql
ALTER TABLE arena.arenas ENABLE ROW LEVEL SECURITY;
ALTER TABLE arena.arenas FORCE ROW LEVEL SECURITY;

-- SELECT: Arenas do tenant (públicas para busca) ou super admin
CREATE POLICY arenas_select_policy ON arena.arenas
  FOR SELECT
  USING (
    is_super_admin()
    OR tenant_id = current_tenant_id()
    OR status = 'ACTIVE' -- Arenas ativas são públicas para busca
  );

-- INSERT: Arena owner do tenant
CREATE POLICY arenas_insert_policy ON arena.arenas
  FOR INSERT
  WITH CHECK (
    is_super_admin()
    OR (
      tenant_id = current_tenant_id()
      AND has_role('ARENA_OWNER')
    )
  );

-- UPDATE: Arena owner/manager do tenant
CREATE POLICY arenas_update_policy ON arena.arenas
  FOR UPDATE
  USING (
    is_super_admin()
    OR (
      tenant_id = current_tenant_id()
      AND (has_role('ARENA_OWNER') OR has_role('ARENA_MANAGER'))
    )
  );

-- DELETE: Arena owner do tenant
CREATE POLICY arenas_delete_policy ON arena.arenas
  FOR DELETE
  USING (
    is_super_admin()
    OR (
      tenant_id = current_tenant_id()
      AND has_role('ARENA_OWNER')
    )
  );

Tabela: courts ​

sql
ALTER TABLE arena.courts ENABLE ROW LEVEL SECURITY;
ALTER TABLE arena.courts FORCE ROW LEVEL SECURITY;

-- SELECT: Quadras de arenas acessíveis
CREATE POLICY courts_select_policy ON arena.courts
  FOR SELECT
  USING (
    is_super_admin()
    OR EXISTS (
      SELECT 1 FROM arena.arenas a
      WHERE a.id = courts.arena_id
      AND (a.tenant_id = current_tenant_id() OR a.status = 'ACTIVE')
    )
  );

-- INSERT: Manager da arena
CREATE POLICY courts_insert_policy ON arena.courts
  FOR INSERT
  WITH CHECK (
    is_super_admin()
    OR (
      user_has_arena_access(arena_id)
      AND (has_role('ARENA_OWNER') OR has_role('ARENA_MANAGER'))
    )
  );

-- UPDATE: Manager da arena
CREATE POLICY courts_update_policy ON arena.courts
  FOR UPDATE
  USING (
    is_super_admin()
    OR (
      user_has_arena_access(arena_id)
      AND (has_role('ARENA_OWNER') OR has_role('ARENA_MANAGER'))
    )
  );

-- DELETE: Owner da arena
CREATE POLICY courts_delete_policy ON arena.courts
  FOR DELETE
  USING (
    is_super_admin()
    OR (
      user_has_arena_access(arena_id)
      AND has_role('ARENA_OWNER')
    )
  );

Tabela: pricing_rules ​

sql
ALTER TABLE arena.pricing_rules ENABLE ROW LEVEL SECURITY;
ALTER TABLE arena.pricing_rules FORCE ROW LEVEL SECURITY;

-- SELECT: Regras de preço das arenas acessíveis
CREATE POLICY pricing_rules_select_policy ON arena.pricing_rules
  FOR SELECT
  USING (
    is_super_admin()
    OR user_has_arena_access(arena_id)
  );

-- INSERT/UPDATE/DELETE: Manager da arena
CREATE POLICY pricing_rules_modify_policy ON arena.pricing_rules
  FOR ALL
  USING (
    is_super_admin()
    OR (
      user_has_arena_access(arena_id)
      AND (has_role('ARENA_OWNER') OR has_role('ARENA_MANAGER'))
    )
  );

3.3 Schema: Booking ​

Tabela: bookings ​

sql
ALTER TABLE booking.bookings ENABLE ROW LEVEL SECURITY;
ALTER TABLE booking.bookings FORCE ROW LEVEL SECURITY;

-- SELECT: Próprias reservas ou reservas da arena (para staff)
CREATE POLICY bookings_select_policy ON booking.bookings
  FOR SELECT
  USING (
    is_super_admin()
    -- Próprias reservas
    OR user_id = current_user_id()
    -- Staff da arena vê todas as reservas
    OR (
      user_has_arena_access(arena_id)
      AND (
        has_role('ARENA_OWNER')
        OR has_role('ARENA_MANAGER')
        OR has_role('RECEPTIONIST')
      )
    )
  );

-- INSERT: Usuário autenticado pode criar reservas
CREATE POLICY bookings_insert_policy ON booking.bookings
  FOR INSERT
  WITH CHECK (
    is_super_admin()
    OR (
      -- Deve ser para uma arena acessível
      EXISTS (
        SELECT 1 FROM arena.arenas a
        WHERE a.id = bookings.arena_id
        AND a.status = 'ACTIVE'
      )
      -- E o usuário criador deve ser o atual
      AND created_by_id = current_user_id()
    )
  );

-- UPDATE: Própria reserva ou staff da arena
CREATE POLICY bookings_update_policy ON booking.bookings
  FOR UPDATE
  USING (
    is_super_admin()
    -- Própria reserva (apenas antes do horário)
    OR (
      user_id = current_user_id()
      AND start_time > NOW()
      AND status NOT IN ('COMPLETED', 'CANCELLED')
    )
    -- Staff da arena
    OR (
      user_has_arena_access(arena_id)
      AND (
        has_role('ARENA_OWNER')
        OR has_role('ARENA_MANAGER')
        OR has_role('RECEPTIONIST')
      )
    )
  );

-- DELETE: Apenas soft delete via UPDATE (status = CANCELLED)
-- Sem política de DELETE real

Tabela: payments ​

sql
ALTER TABLE booking.payments ENABLE ROW LEVEL SECURITY;
ALTER TABLE booking.payments FORCE ROW LEVEL SECURITY;

-- SELECT: Ver pagamentos de reservas acessíveis
CREATE POLICY payments_select_policy ON booking.payments
  FOR SELECT
  USING (
    is_super_admin()
    OR EXISTS (
      SELECT 1 FROM booking.bookings b
      WHERE b.id = payments.booking_id
      AND (
        b.user_id = current_user_id()
        OR (
          user_has_arena_access(b.arena_id)
          AND (has_role('ARENA_OWNER') OR has_role('ARENA_MANAGER'))
        )
      )
    )
  );

-- INSERT: Sistema ou manager da arena
CREATE POLICY payments_insert_policy ON booking.payments
  FOR INSERT
  WITH CHECK (
    is_super_admin()
    OR EXISTS (
      SELECT 1 FROM booking.bookings b
      WHERE b.id = payments.booking_id
      AND user_has_arena_access(b.arena_id)
      AND (has_role('ARENA_OWNER') OR has_role('ARENA_MANAGER'))
    )
  );

-- UPDATE: Apenas sistema (gateway callbacks)
CREATE POLICY payments_update_policy ON booking.payments
  FOR UPDATE
  USING (is_super_admin());

3.4 Schema: Public ​

Tabela: audit_logs ​

sql
ALTER TABLE public.audit_logs ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.audit_logs FORCE ROW LEVEL SECURITY;

-- SELECT: Ver logs do próprio tenant ou super admin
CREATE POLICY audit_logs_select_policy ON public.audit_logs
  FOR SELECT
  USING (
    is_super_admin()
    OR tenant_id = current_tenant_id()
  );

-- INSERT: Sistema pode inserir logs
CREATE POLICY audit_logs_insert_policy ON public.audit_logs
  FOR INSERT
  WITH CHECK (
    is_super_admin()
    OR tenant_id = current_tenant_id()
  );

-- UPDATE/DELETE: Ninguém pode modificar audit logs
-- (Sem políticas = bloqueia por padrão)

4. Bypass para Serviços ​

4.1 Role de Serviço ​

sql
-- Cria role para serviços internos (workers, migrations)
CREATE ROLE app_service_role NOLOGIN;

-- Concede permissões completas
GRANT ALL ON ALL TABLES IN SCHEMA identity TO app_service_role;
GRANT ALL ON ALL TABLES IN SCHEMA arena TO app_service_role;
GRANT ALL ON ALL TABLES IN SCHEMA booking TO app_service_role;
GRANT ALL ON ALL TABLES IN SCHEMA public TO app_service_role;

-- Bypass RLS para serviços
ALTER ROLE app_service_role BYPASSRLS;

-- Cria usuário de serviço
CREATE USER app_worker WITH PASSWORD 'secure-password';
GRANT app_service_role TO app_worker;

4.2 Função de Contexto para Serviços ​

sql
-- Função para executar como outro tenant (apenas serviços)
CREATE OR REPLACE FUNCTION execute_as_tenant(
  p_tenant_id UUID,
  p_query TEXT
)
RETURNS VOID AS $$
BEGIN
  -- Apenas app_service_role pode usar esta função
  IF NOT pg_has_role(current_user, 'app_service_role', 'MEMBER') THEN
    RAISE EXCEPTION 'Permission denied';
  END IF;

  -- Define o contexto do tenant
  PERFORM set_config('app.current_tenant_id', p_tenant_id::TEXT, true);

  -- Executa a query
  EXECUTE p_query;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

5. Middleware de Aplicação ​

5.1 NestJS Interceptor ​

typescript
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { PrismaService } from './prisma.service';

@Injectable()
export class TenantContextInterceptor implements NestInterceptor {
  constructor(private prisma: PrismaService) {}

  async intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Promise<Observable<any>> {
    const request = context.switchToHttp().getRequest();
    const user = request.user;

    if (user) {
      // Define variáveis de sessão no PostgreSQL
      await this.prisma.$executeRawUnsafe(`
        SET LOCAL app.current_tenant_id = '${user.tenantId}';
        SET LOCAL app.current_user_id = '${user.id}';
        SET LOCAL app.current_user_roles = '${JSON.stringify(user.roles)}';
      `);
    }

    return next.handle();
  }
}

5.2 Prisma Middleware ​

typescript
import { Prisma } from '@prisma/client';

export function tenantMiddleware(): Prisma.Middleware {
  return async (params, next) => {
    // Adiciona tenant_id automaticamente em creates
    if (params.action === 'create' || params.action === 'createMany') {
      const tenantId = getCurrentTenantId();

      if (tenantId && hasTenantIdField(params.model)) {
        if (params.action === 'create') {
          params.args.data.tenantId = tenantId;
        } else {
          params.args.data = params.args.data.map((d: any) => ({
            ...d,
            tenantId,
          }));
        }
      }
    }

    // Adiciona filtro de tenant automaticamente em reads
    if (['findMany', 'findFirst', 'findUnique', 'count'].includes(params.action)) {
      const tenantId = getCurrentTenantId();

      if (tenantId && hasTenantIdField(params.model)) {
        params.args.where = {
          ...params.args.where,
          tenantId,
        };
      }
    }

    return next(params);
  };
}

6. Testes de Segurança ​

6.1 Testes de Isolamento ​

sql
-- Teste: Usuário do Tenant A não pode ver dados do Tenant B
DO $$
DECLARE
  tenant_a_id UUID := 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
  tenant_b_id UUID := 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
  count_result INT;
BEGIN
  -- Setup: Simula usuário do Tenant A
  PERFORM set_config('app.current_tenant_id', tenant_a_id::TEXT, true);
  PERFORM set_config('app.current_user_roles', '["ARENA_OWNER"]', true);

  -- Tenta contar arenas do Tenant B
  SELECT COUNT(*) INTO count_result
  FROM arena.arenas
  WHERE tenant_id = tenant_b_id;

  -- Deve retornar 0 (RLS bloqueia)
  IF count_result > 0 THEN
    RAISE EXCEPTION 'SECURITY VIOLATION: Tenant A can see Tenant B data!';
  END IF;

  RAISE NOTICE 'TEST PASSED: Tenant isolation working correctly';
END $$;

6.2 Testes de Roles ​

sql
-- Teste: Receptionist não pode deletar quadras
DO $$
DECLARE
  test_court_id UUID := 'cccccccc-cccc-cccc-cccc-cccccccccccc';
BEGIN
  -- Setup: Simula receptionist
  PERFORM set_config('app.current_user_roles', '["RECEPTIONIST"]', true);

  -- Tenta deletar quadra
  BEGIN
    DELETE FROM arena.courts WHERE id = test_court_id;
    RAISE EXCEPTION 'SECURITY VIOLATION: Receptionist could delete court!';
  EXCEPTION
    WHEN insufficient_privilege THEN
      RAISE NOTICE 'TEST PASSED: Receptionist correctly blocked from delete';
  END;
END $$;

7. Monitoramento ​

7.1 Query para Auditoria de RLS ​

sql
-- Verifica políticas ativas
SELECT
  schemaname,
  tablename,
  policyname,
  permissive,
  roles,
  cmd,
  qual,
  with_check
FROM pg_policies
WHERE schemaname IN ('identity', 'arena', 'booking', 'public')
ORDER BY schemaname, tablename, policyname;

7.2 Alertas de Violação ​

sql
-- Log de tentativas de acesso bloqueadas
CREATE OR REPLACE FUNCTION log_rls_violation()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO public.security_logs (
    event_type,
    user_id,
    tenant_id,
    table_name,
    attempted_action,
    details,
    created_at
  ) VALUES (
    'RLS_VIOLATION',
    current_user_id(),
    current_tenant_id(),
    TG_TABLE_NAME,
    TG_OP,
    jsonb_build_object(
      'row_id', COALESCE(NEW.id, OLD.id)
    ),
    NOW()
  );

  RETURN NULL; -- Bloqueia a operação
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

8. Manutenção ​

8.1 Script de Verificação ​

bash
#!/bin/bash
# verify-rls-policies.sh

echo "Verificando políticas RLS..."

psql -d sporttechclub -c "
SELECT
  schemaname || '.' || tablename as table,
  CASE WHEN rowsecurity THEN 'ENABLED' ELSE 'DISABLED' END as rls_status,
  COUNT(policyname) as policy_count
FROM pg_tables t
LEFT JOIN pg_policies p ON t.tablename = p.tablename
WHERE t.schemaname IN ('identity', 'arena', 'booking')
GROUP BY schemaname, tablename, rowsecurity
ORDER BY schemaname, tablename;
"

8.2 Rollback de Políticas ​

sql
-- Em caso de emergência: desabilita RLS temporariamente
-- ATENÇÃO: Use apenas em situações críticas!

-- Desabilita RLS em todas as tabelas
DO $$
DECLARE
  r RECORD;
BEGIN
  FOR r IN SELECT schemaname, tablename
           FROM pg_tables
           WHERE schemaname IN ('identity', 'arena', 'booking')
  LOOP
    EXECUTE format('ALTER TABLE %I.%I DISABLE ROW LEVEL SECURITY',
                   r.schemaname, r.tablename);
  END LOOP;
END $$;

-- Para reabilitar:
DO $$
DECLARE
  r RECORD;
BEGIN
  FOR r IN SELECT schemaname, tablename
           FROM pg_tables
           WHERE schemaname IN ('identity', 'arena', 'booking')
  LOOP
    EXECUTE format('ALTER TABLE %I.%I ENABLE ROW LEVEL SECURITY',
                   r.schemaname, r.tablename);
  END LOOP;
END $$;

9. Checklist de Implementação ​

  • [ ] Funções de contexto criadas
  • [ ] RLS habilitado em todas as tabelas
  • [ ] Políticas SELECT implementadas
  • [ ] Políticas INSERT implementadas
  • [ ] Políticas UPDATE implementadas
  • [ ] Políticas DELETE implementadas
  • [ ] Role de serviço configurada
  • [ ] Middleware de aplicação implementado
  • [ ] Testes de isolamento executados
  • [ ] Testes de roles executados
  • [ ] Monitoramento configurado
  • [ ] Documentação atualizada