Agenda, Agendador, Configurações
This commit is contained in:
235
migrations/support_sessions.sql
Normal file
235
migrations/support_sessions.sql
Normal file
@@ -0,0 +1,235 @@
|
||||
-- ═══════════════════════════════════════════════════════════════════════════
|
||||
-- Support Sessions — Sessões de suporte técnico SaaS
|
||||
-- ═══════════════════════════════════════════════════════════════════════════
|
||||
-- Permite que admins SaaS gerem tokens de acesso temporário para debug
|
||||
-- de agendas de terapeutas, sem expor debug para usuários comuns.
|
||||
--
|
||||
-- SEGURANÇA:
|
||||
-- - RLS: só saas_admin pode criar/listar sessões
|
||||
-- - Token é opaco (gen_random_uuid) — não adivinhável
|
||||
-- - expires_at com TTL máximo de 60 minutos
|
||||
-- - validate_support_session() retorna apenas true/false + tenant_id
|
||||
-- (não expõe dados do admin)
|
||||
-- ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- ── Tabela ──────────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "public"."support_sessions" (
|
||||
"id" uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
"tenant_id" uuid NOT NULL,
|
||||
"admin_id" uuid NOT NULL,
|
||||
"token" text NOT NULL DEFAULT (replace(gen_random_uuid()::text, '-', '') || replace(gen_random_uuid()::text, '-', '')),
|
||||
"expires_at" timestamp with time zone NOT NULL
|
||||
DEFAULT (now() + interval '60 minutes'),
|
||||
"created_at" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
|
||||
CONSTRAINT "support_sessions_pkey" PRIMARY KEY ("id"),
|
||||
|
||||
CONSTRAINT "support_sessions_tenant_fk"
|
||||
FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT "support_sessions_admin_fk"
|
||||
FOREIGN KEY ("admin_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT "support_sessions_token_unique" UNIQUE ("token")
|
||||
);
|
||||
|
||||
-- ── Índices ──────────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "support_sessions_token_idx"
|
||||
ON "public"."support_sessions" ("token");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "support_sessions_tenant_idx"
|
||||
ON "public"."support_sessions" ("tenant_id");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "support_sessions_expires_idx"
|
||||
ON "public"."support_sessions" ("expires_at");
|
||||
|
||||
-- ── RLS ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
ALTER TABLE "public"."support_sessions" ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Somente saas_admin pode ver suas próprias sessões de suporte
|
||||
DROP POLICY IF EXISTS "support_sessions_saas_select" ON "public"."support_sessions";
|
||||
CREATE POLICY "support_sessions_saas_select"
|
||||
ON "public"."support_sessions"
|
||||
FOR SELECT
|
||||
USING (
|
||||
auth.uid() = admin_id
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM public.profiles
|
||||
WHERE id = auth.uid()
|
||||
AND role = 'saas_admin'
|
||||
)
|
||||
);
|
||||
|
||||
-- Somente saas_admin pode criar sessões de suporte
|
||||
DROP POLICY IF EXISTS "support_sessions_saas_insert" ON "public"."support_sessions";
|
||||
CREATE POLICY "support_sessions_saas_insert"
|
||||
ON "public"."support_sessions"
|
||||
FOR INSERT
|
||||
WITH CHECK (
|
||||
auth.uid() = admin_id
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM public.profiles
|
||||
WHERE id = auth.uid()
|
||||
AND role = 'saas_admin'
|
||||
)
|
||||
);
|
||||
|
||||
-- Somente saas_admin pode deletar suas próprias sessões
|
||||
DROP POLICY IF EXISTS "support_sessions_saas_delete" ON "public"."support_sessions";
|
||||
CREATE POLICY "support_sessions_saas_delete"
|
||||
ON "public"."support_sessions"
|
||||
FOR DELETE
|
||||
USING (
|
||||
auth.uid() = admin_id
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM public.profiles
|
||||
WHERE id = auth.uid()
|
||||
AND role = 'saas_admin'
|
||||
)
|
||||
);
|
||||
|
||||
-- ── RPC: create_support_session ───────────────────────────────────────────────
|
||||
-- Cria uma sessão de suporte para um tenant.
|
||||
-- Apenas saas_admin pode chamar. TTL: 60 minutos (configurável via p_ttl_minutes).
|
||||
-- Retorna: token, expires_at
|
||||
|
||||
DROP FUNCTION IF EXISTS public.create_support_session(uuid, integer);
|
||||
CREATE OR REPLACE FUNCTION public.create_support_session(
|
||||
p_tenant_id uuid,
|
||||
p_ttl_minutes integer DEFAULT 60
|
||||
)
|
||||
RETURNS json
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_admin_id uuid;
|
||||
v_role text;
|
||||
v_token text;
|
||||
v_expires timestamp with time zone;
|
||||
v_session support_sessions;
|
||||
BEGIN
|
||||
-- Verifica autenticação
|
||||
v_admin_id := auth.uid();
|
||||
IF v_admin_id IS NULL THEN
|
||||
RAISE EXCEPTION 'Não autenticado.' USING ERRCODE = 'P0001';
|
||||
END IF;
|
||||
|
||||
-- Verifica role saas_admin
|
||||
SELECT role INTO v_role
|
||||
FROM public.profiles
|
||||
WHERE id = v_admin_id;
|
||||
|
||||
IF v_role <> 'saas_admin' THEN
|
||||
RAISE EXCEPTION 'Acesso negado. Somente saas_admin pode criar sessões de suporte.'
|
||||
USING ERRCODE = 'P0002';
|
||||
END IF;
|
||||
|
||||
-- Valida TTL (1 a 120 minutos)
|
||||
IF p_ttl_minutes < 1 OR p_ttl_minutes > 120 THEN
|
||||
RAISE EXCEPTION 'TTL inválido. Use entre 1 e 120 minutos.'
|
||||
USING ERRCODE = 'P0003';
|
||||
END IF;
|
||||
|
||||
-- Valida tenant
|
||||
IF NOT EXISTS (SELECT 1 FROM public.tenants WHERE id = p_tenant_id) THEN
|
||||
RAISE EXCEPTION 'Tenant não encontrado.'
|
||||
USING ERRCODE = 'P0004';
|
||||
END IF;
|
||||
|
||||
-- Gera token único (64 chars hex, sem pgcrypto)
|
||||
v_token := replace(gen_random_uuid()::text, '-', '') || replace(gen_random_uuid()::text, '-', '');
|
||||
v_expires := now() + (p_ttl_minutes || ' minutes')::interval;
|
||||
|
||||
-- Insere sessão
|
||||
INSERT INTO public.support_sessions (tenant_id, admin_id, token, expires_at)
|
||||
VALUES (p_tenant_id, v_admin_id, v_token, v_expires)
|
||||
RETURNING * INTO v_session;
|
||||
|
||||
RETURN json_build_object(
|
||||
'token', v_session.token,
|
||||
'expires_at', v_session.expires_at,
|
||||
'session_id', v_session.id
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- ── RPC: validate_support_session ────────────────────────────────────────────
|
||||
-- Valida um token de suporte. Não requer autenticação (chamada pública).
|
||||
-- Retorna: { valid: bool, tenant_id: uuid|null }
|
||||
-- NUNCA retorna admin_id ou dados internos.
|
||||
|
||||
DROP FUNCTION IF EXISTS public.validate_support_session(text);
|
||||
CREATE OR REPLACE FUNCTION public.validate_support_session(
|
||||
p_token text
|
||||
)
|
||||
RETURNS json
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_session support_sessions;
|
||||
BEGIN
|
||||
IF p_token IS NULL OR length(trim(p_token)) < 32 THEN
|
||||
RETURN json_build_object('valid', false, 'tenant_id', null);
|
||||
END IF;
|
||||
|
||||
SELECT * INTO v_session
|
||||
FROM public.support_sessions
|
||||
WHERE token = p_token
|
||||
AND expires_at > now()
|
||||
LIMIT 1;
|
||||
|
||||
IF NOT FOUND THEN
|
||||
RETURN json_build_object('valid', false, 'tenant_id', null);
|
||||
END IF;
|
||||
|
||||
RETURN json_build_object(
|
||||
'valid', true,
|
||||
'tenant_id', v_session.tenant_id
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- ── RPC: revoke_support_session ───────────────────────────────────────────────
|
||||
-- Revoga um token manualmente. Apenas o admin que criou pode revogar.
|
||||
|
||||
DROP FUNCTION IF EXISTS public.revoke_support_session(text);
|
||||
CREATE OR REPLACE FUNCTION public.revoke_support_session(
|
||||
p_token text
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_admin_id uuid;
|
||||
v_role text;
|
||||
BEGIN
|
||||
v_admin_id := auth.uid();
|
||||
IF v_admin_id IS NULL THEN
|
||||
RAISE EXCEPTION 'Não autenticado.' USING ERRCODE = 'P0001';
|
||||
END IF;
|
||||
|
||||
SELECT role INTO v_role FROM public.profiles WHERE id = v_admin_id;
|
||||
IF v_role <> 'saas_admin' THEN
|
||||
RAISE EXCEPTION 'Acesso negado.' USING ERRCODE = 'P0002';
|
||||
END IF;
|
||||
|
||||
DELETE FROM public.support_sessions
|
||||
WHERE token = p_token
|
||||
AND admin_id = v_admin_id;
|
||||
|
||||
RETURN FOUND;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- ── Cleanup automático (opcional) ────────────────────────────────────────────
|
||||
-- Sessões expiradas podem ser limpas periodicamente via pg_cron ou edge function.
|
||||
-- DELETE FROM public.support_sessions WHERE expires_at < now();
|
||||
Reference in New Issue
Block a user