mirror of
https://github.com/domfelipe/mika-agent-assist.git
synced 2026-08-07 08:36:44 +00:00
152 lines
4.8 KiB
TypeScript
152 lines
4.8 KiB
TypeScript
// oauth-start (autenticada via JWT)
|
|
// Recebe { mcp_slug }, valida plano e retorna { auth_url } para o frontend redirecionar.
|
|
|
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
|
import { corsHeaders } from "../_shared/cors.ts";
|
|
import {
|
|
buildAuthorizeUrl,
|
|
getProviderEnv,
|
|
type ProviderSlug,
|
|
} from "../_shared/oauth-providers.ts";
|
|
|
|
function jsonResponse(body: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(body), {
|
|
status,
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
function generateStateToken(): string {
|
|
const bytes = new Uint8Array(32);
|
|
crypto.getRandomValues(bytes);
|
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
}
|
|
|
|
Deno.serve(async (req) => {
|
|
if (req.method === "OPTIONS") {
|
|
return new Response(null, { headers: corsHeaders });
|
|
}
|
|
|
|
try {
|
|
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
|
|
const serviceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
|
|
const anonKey = Deno.env.get("SUPABASE_ANON_KEY")!;
|
|
|
|
const authHeader = req.headers.get("Authorization") ?? "";
|
|
const userClient = createClient(supabaseUrl, anonKey, {
|
|
global: { headers: { Authorization: authHeader } },
|
|
});
|
|
const { data: userData, error: userErr } = await userClient.auth.getUser();
|
|
if (userErr || !userData.user) {
|
|
return jsonResponse({ error: "Não autenticado" }, 401);
|
|
}
|
|
const userId = userData.user.id;
|
|
|
|
const { mcp_slug } = await req.json() as { mcp_slug?: string };
|
|
if (!mcp_slug) {
|
|
return jsonResponse({ error: "mcp_slug é obrigatório" }, 400);
|
|
}
|
|
|
|
const admin = createClient(supabaseUrl, serviceKey);
|
|
|
|
// Busca MCP
|
|
const { data: mcp, error: mcpErr } = await admin
|
|
.from("available_mcps")
|
|
.select("id, slug, oauth_authorize_url, required_scopes, available_in_plans, is_active")
|
|
.eq("slug", mcp_slug)
|
|
.maybeSingle();
|
|
|
|
if (mcpErr || !mcp || !mcp.is_active) {
|
|
return jsonResponse({ error: "Integração não encontrada" }, 404);
|
|
}
|
|
|
|
// Valida agente ativo
|
|
const { data: agent } = await admin
|
|
.from("agent_instances")
|
|
.select("status")
|
|
.eq("user_id", userId)
|
|
.maybeSingle();
|
|
|
|
if (!agent || agent.status !== "active") {
|
|
return jsonResponse(
|
|
{ error: "agent_inactive", message: "Seu agente ainda não está ativo. Complete o onboarding." },
|
|
403,
|
|
);
|
|
}
|
|
|
|
// Valida plano
|
|
const { data: limits } = await admin
|
|
.from("user_integration_limits")
|
|
.select("plan_slug, max_integrations, current_integrations_count")
|
|
.eq("user_id", userId)
|
|
.maybeSingle();
|
|
|
|
if (!limits || !limits.plan_slug || !limits.max_integrations) {
|
|
return jsonResponse(
|
|
{ error: "no_subscription", message: "Você precisa de uma assinatura ativa." },
|
|
403,
|
|
);
|
|
}
|
|
|
|
const availableIn = (mcp.available_in_plans as string[]) ?? [];
|
|
if (!availableIn.includes(limits.plan_slug)) {
|
|
return jsonResponse(
|
|
{
|
|
error: "plan_not_allowed",
|
|
message: `A integração ${mcp.slug} não está disponível no plano ${limits.plan_slug}.`,
|
|
},
|
|
403,
|
|
);
|
|
}
|
|
|
|
// Verifica se já não atingiu limite (evita iniciar fluxo que vai falhar)
|
|
if (
|
|
(limits.current_integrations_count ?? 0) >= limits.max_integrations
|
|
) {
|
|
// Permite reconectar uma existente; mas se for nova vai falhar.
|
|
const { data: existing } = await admin
|
|
.from("user_integrations")
|
|
.select("id")
|
|
.eq("user_id", userId)
|
|
.eq("mcp_id", mcp.id)
|
|
.maybeSingle();
|
|
if (!existing) {
|
|
return jsonResponse(
|
|
{ error: "limit_reached", message: "Você atingiu o limite de integrações do seu plano." },
|
|
403,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Gera state token
|
|
const stateToken = generateStateToken();
|
|
const { error: stErr } = await admin.from("oauth_state_tokens").insert({
|
|
state_token: stateToken,
|
|
user_id: userId,
|
|
mcp_id: mcp.id,
|
|
});
|
|
if (stErr) {
|
|
console.error("oauth_state insert error", stErr);
|
|
return jsonResponse({ error: "Falha ao iniciar OAuth" }, 500);
|
|
}
|
|
|
|
// Monta URL de autorização
|
|
const redirectUri = `${supabaseUrl}/functions/v1/oauth-callback`;
|
|
const env = getProviderEnv(mcp.slug as ProviderSlug, redirectUri);
|
|
const authUrl = buildAuthorizeUrl(
|
|
mcp.slug as ProviderSlug,
|
|
mcp.oauth_authorize_url,
|
|
(mcp.required_scopes as string[]) ?? [],
|
|
stateToken,
|
|
env,
|
|
);
|
|
|
|
return jsonResponse({ auth_url: authUrl });
|
|
} catch (err) {
|
|
console.error("oauth-start fatal", err instanceof Error ? err.message : "unknown");
|
|
return jsonResponse(
|
|
{ error: err instanceof Error ? err.message : "Erro inesperado" },
|
|
500,
|
|
);
|
|
}
|
|
});
|