diff --git a/supabase/functions/keep-alive-agents/index.ts b/supabase/functions/keep-alive-agents/index.ts index 90aa1db..8541d8b 100644 --- a/supabase/functions/keep-alive-agents/index.ts +++ b/supabase/functions/keep-alive-agents/index.ts @@ -13,6 +13,7 @@ import { createClient } from "https://esm.sh/@supabase/supabase-js@2.45.4"; import { corsHeaders } from "../_shared/cors.ts"; +import { authorizeInternalRequest } from "../_shared/internal-auth.ts"; import { pullAgentCronjobsRuntimeState } from "../_shared/runtime-sync.ts"; const SUPABASE_URL = Deno.env.get("SUPABASE_URL")!; @@ -31,6 +32,13 @@ interface AgentRow { Deno.serve(async (req) => { if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders }); + // Auth: aceita X-Internal-Secret (pg_cron) OU JWT de admin. + const auth = await authorizeInternalRequest(req, { allowOwner: false }); + if (!auth.ok) { + console.warn(`keep-alive: auth rejected (${auth.reason})`); + return jsonResponse(401, { error: "unauthorized", reason: auth.reason }); + } + const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, { auth: { persistSession: false, autoRefreshToken: false }, }); diff --git a/supabase/functions/provision-agent/index.ts b/supabase/functions/provision-agent/index.ts index 8c92087..d41983b 100644 --- a/supabase/functions/provision-agent/index.ts +++ b/supabase/functions/provision-agent/index.ts @@ -5,6 +5,7 @@ import { createClient } from "https://esm.sh/@supabase/supabase-js@2.45.4"; import { corsHeaders } from "../_shared/cors.ts"; +import { authorizeInternalRequest } from "../_shared/internal-auth.ts"; import { HERMES_START_COMMAND, createRailwayService, @@ -60,6 +61,13 @@ async function notifyAdmin(message: string): Promise { Deno.serve(async (req) => { if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders }); + // Auth: aceita X-Internal-Secret (trigger pg_net / chamadas internas) OU JWT de admin. + const auth = await authorizeInternalRequest(req, { allowOwner: false }); + if (!auth.ok) { + console.warn(`[provision-agent] auth rejected: ${auth.reason}`); + return jsonResponse(401, { error: "unauthorized", reason: auth.reason }); + } + if (!RAILWAY_API_TOKEN) { return jsonResponse(500, { error: "RAILWAY_API_TOKEN not configured" }); } diff --git a/supabase/functions/resume-agent/index.ts b/supabase/functions/resume-agent/index.ts index 0af0053..017fd26 100644 --- a/supabase/functions/resume-agent/index.ts +++ b/supabase/functions/resume-agent/index.ts @@ -5,6 +5,7 @@ import { createClient } from "https://esm.sh/@supabase/supabase-js@2.45.4"; import { corsHeaders } from "../_shared/cors.ts"; +import { authorizeInternalRequest } from "../_shared/internal-auth.ts"; import { setHermesSuspended, getServiceContext } from "../_shared/railway.ts"; const SUPABASE_URL = Deno.env.get("SUPABASE_URL")!; @@ -39,12 +40,20 @@ Deno.serve(async (req) => { const { data: agent } = await supabase .from("agent_instances") - .select("id, status, railway_service_id, vps_pool_id") + .select("id, status, railway_service_id, vps_pool_id, user_id") .eq("id", body.agent_instance_id) .maybeSingle(); if (!agent) return jsonResponse(404, { error: "agent_instance not found" }); + // Auth: X-Internal-Secret (trigger), JWT de admin, OU JWT do dono do agente. + const auth = await authorizeInternalRequest(req, { ownerUserId: agent.user_id }); + if (!auth.ok) { + console.warn(`resume-agent: auth rejected (${auth.reason})`); + return jsonResponse(401, { error: "unauthorized", reason: auth.reason }); + } + + if (!agent.railway_service_id) { return jsonResponse(409, { error: "agent has no container — needs full provisioning instead", diff --git a/supabase/functions/suspend-agent/index.ts b/supabase/functions/suspend-agent/index.ts index 8bf9910..464b856 100644 --- a/supabase/functions/suspend-agent/index.ts +++ b/supabase/functions/suspend-agent/index.ts @@ -6,6 +6,7 @@ import { createClient } from "https://esm.sh/@supabase/supabase-js@2.45.4"; import { corsHeaders } from "../_shared/cors.ts"; +import { authorizeInternalRequest } from "../_shared/internal-auth.ts"; import { setHermesSuspended, getServiceContext } from "../_shared/railway.ts"; const SUPABASE_URL = Deno.env.get("SUPABASE_URL")!; @@ -40,11 +41,19 @@ Deno.serve(async (req) => { const { data: agent } = await supabase .from("agent_instances") - .select("id, status, railway_service_id, vps_pool_id") + .select("id, status, railway_service_id, vps_pool_id, user_id") .eq("id", body.agent_instance_id) .maybeSingle(); if (!agent) return jsonResponse(404, { error: "agent_instance not found" }); + + // Auth: X-Internal-Secret (trigger), JWT de admin, OU JWT do dono do agente. + const auth = await authorizeInternalRequest(req, { ownerUserId: agent.user_id }); + if (!auth.ok) { + console.warn(`suspend-agent: auth rejected (${auth.reason})`); + return jsonResponse(401, { error: "unauthorized", reason: auth.reason }); + } + if (agent.status === "suspended") { return jsonResponse(200, { ok: true, already_suspended: true }); }