Documentação

Tudo que você precisa para integrar seu app ao ecossistema eximIA via Gate.

eximIA Gate é o serviço central de autenticação e identidade do ecossistema eximIA. Usuários criam uma única conta e acessam qualquer app do ecossistema — Academy, Forms, Profiler, Maps e todos os demais.

O administrador controla quais apps cada usuário pode acessar. A autenticação é via JWT e cada app valida o token chamando um único endpoint do Gate.

Resumo do contrato:

  • Contrato: eximia-gate/v1
  • Auth: JWT (HS256) — access token 1h, refresh token 30d
  • Verificação: POST /api/v1/gate/verify
  • Integração: 1 arquivo (lib/gate.ts) + 1 variável de ambiente

4 passos para integrar seu app:

1Variável de ambiente
2Criar lib/gate.ts
3Login/Register UI
4Proteger rotas
01

Adicionar variável de ambiente

Adicione a URL do Gate ao .env.local do seu app:

# .env.local
GATE_URL=http://localhost:3011
NEXT_PUBLIC_GATE_URL=http://localhost:3011
02

Criar lib/gate.ts

Copie este arquivo na raiz do seu projeto. Ele contém todas as funções necessárias — login, register, token management, proteção de rotas.

Troque YOUR_APP_SLUG pelo slug do seu app (ex: academy, forms, profiler).

// lib/gate.ts
// eximIA Gate integration — contract v1
// Troque APP_SLUG pelo slug do seu app registrado no Gate

const GATE_URL = process.env.GATE_URL || process.env.NEXT_PUBLIC_GATE_URL || "http://localhost:3011";
const APP_SLUG = "YOUR_APP_SLUG"; // ← TROCAR: academy, forms, profiler, etc.

// ─── Types ───

export interface GateUser {
  id: string;
  email: string;
  name: string;
  role: "user" | "admin";
  avatar_url: string | null;
  apps: string[];
  created_at: string;
}

export interface AuthResponse {
  token: string;
  refresh_token: string;
  user: GateUser;
}

export interface VerifyResponse {
  user: GateUser;
  scopes: string[];
  apps_allowed: string[];
}

// ─── Client-side: Auth actions ───

export async function gateLogin(email: string, password: string): Promise<AuthResponse> {
  const res = await fetch(`${GATE_URL}/api/v1/gate/login`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email, password }),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error || "Login failed");
  return data;
}

export async function gateRegister(name: string, email: string, password: string): Promise<AuthResponse> {
  const res = await fetch(`${GATE_URL}/api/v1/gate/register`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name, email, password }),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error || "Registration failed");
  return data;
}

export async function gateRefresh(refresh_token: string): Promise<{ token: string; refresh_token: string }> {
  const res = await fetch(`${GATE_URL}/api/v1/gate/refresh`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ refresh_token }),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error || "Refresh failed");
  return data;
}

// ─── Client-side: Token management (localStorage) ───

export function saveAuth(data: AuthResponse): void {
  localStorage.setItem("gate_token", data.token);
  localStorage.setItem("gate_refresh", data.refresh_token);
  localStorage.setItem("gate_user", JSON.stringify(data.user));
}

export function getToken(): string | null {
  if (typeof window === "undefined") return null;
  return localStorage.getItem("gate_token");
}

export function getUser(): GateUser | null {
  if (typeof window === "undefined") return null;
  const raw = localStorage.getItem("gate_user");
  return raw ? JSON.parse(raw) : null;
}

export function isAuthenticated(): boolean {
  return !!getToken();
}

export function logout(): void {
  localStorage.removeItem("gate_token");
  localStorage.removeItem("gate_refresh");
  localStorage.removeItem("gate_user");
}

export async function getValidToken(): Promise<string | null> {
  const token = getToken();
  if (!token) return null;
  try {
    const payload = JSON.parse(atob(token.split(".")[1]));
    const expiresIn = payload.exp * 1000 - Date.now();
    if (expiresIn < 5 * 60 * 1000) {
      const refresh = localStorage.getItem("gate_refresh");
      if (!refresh) return null;
      try {
        const data = await gateRefresh(refresh);
        localStorage.setItem("gate_token", data.token);
        localStorage.setItem("gate_refresh", data.refresh_token);
        return data.token;
      } catch {
        logout();
        return null;
      }
    }
    return token;
  } catch {
    return token;
  }
}

// ─── Server-side: Protect API routes ───

export async function withGateAuth(req: Request): Promise<
  { user: VerifyResponse["user"]; scopes: string[]; error?: never } |
  { user?: never; scopes?: never; error: Response }
> {
  const authHeader = req.headers.get("authorization");
  if (!authHeader?.startsWith("Bearer ")) {
    return { error: Response.json({ error: "Missing authorization", code: "UNAUTHORIZED" }, { status: 401 }) };
  }
  try {
    const res = await fetch(`${GATE_URL}/api/v1/gate/verify`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ token: authHeader.slice(7) }),
      signal: AbortSignal.timeout(5000),
    });
    if (!res.ok) {
      return { error: Response.json({ error: "Invalid token", code: "INVALID_TOKEN" }, { status: 401 }) };
    }
    const data: VerifyResponse = await res.json();
    if (!data.apps_allowed.includes(APP_SLUG)) {
      return { error: Response.json({ error: "No access to this app", code: "APP_ACCESS_DENIED" }, { status: 403 }) };
    }
    return { user: data.user, scopes: data.scopes };
  } catch {
    return { error: Response.json({ error: "Gate unavailable", code: "GATE_UNAVAILABLE" }, { status: 503 }) };
  }
}
03

Login e Register

Opção A: App novo (criar página de login)

Se seu app ainda não tem login, crie a página:

// app/login/page.tsx
"use client";

import { useState, FormEvent } from "react";
import { useRouter } from "next/navigation";
import { gateLogin, saveAuth } from "@/lib/gate";

export default function LoginPage() {
  const router = useRouter();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(false);

  async function handleSubmit(e: FormEvent) {
    e.preventDefault();
    setError("");
    setLoading(true);
    try {
      const data = await gateLogin(email, password);
      saveAuth(data);
      router.push("/dashboard");
    } catch (err) {
      setError(err instanceof Error ? err.message : "Erro ao fazer login");
    } finally {
      setLoading(false);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" value={email} onChange={e => setEmail(e.target.value)}
        placeholder="Email" required />
      <input type="password" value={password} onChange={e => setPassword(e.target.value)}
        placeholder="Senha" required />
      {error && <p className="text-red-500 text-sm">{error}</p>}
      <button type="submit" disabled={loading}>
        {loading ? "Entrando..." : "Entrar"}
      </button>
    </form>
  );
}

Opção B: App existente (trocar handler)

Se seu app já tem um form de login, troque apenas a chamada:

// Se seu app JÁ tem um form de login, troque apenas o handler:

// ❌ Antes (auth local):
const res = await fetch("/api/auth/login", { method: "POST", body: ... });

// ✅ Depois (Gate):
import { gateLogin, saveAuth } from "@/lib/gate";

const data = await gateLogin(email, password);
saveAuth(data);
router.push("/dashboard");

O Register segue o mesmo padrão — use gateRegister(name, email, password) seguido de saveAuth(data).

04

Proteger rotas

API Routes (server-side)

Use withGateAuth(req) em qualquer route handler:

// app/api/minha-rota-protegida/route.ts
import { withGateAuth } from "@/lib/gate";

export async function GET(req: Request) {
  const auth = await withGateAuth(req);
  if (auth.error) return auth.error;

  // auth.user disponível: { id, email, name, role, apps }
  // auth.scopes disponível: ["read", "write"]
  return Response.json({
    message: `Olá ${auth.user.name}`,
    role: auth.user.role,
  });
}

Páginas (client-side)

Use isAuthenticated() e getUser() em componentes client:

// Em qualquer página "use client" protegida
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { isAuthenticated, getUser } from "@/lib/gate";

export default function ProtectedPage() {
  const router = useRouter();

  useEffect(() => {
    if (!isAuthenticated()) router.push("/login");
  }, [router]);

  const user = getUser();
  if (!user) return null;

  return <div>Bem-vindo, {user.name}</div>;
}

Chamadas autenticadas

Use getValidToken() para chamadas que precisam de auth — ele renova o token automaticamente:

// Chamada autenticada a partir do client
import { getValidToken, logout } from "@/lib/gate";

async function fetchData() {
  const token = await getValidToken(); // auto-refresh se expirado
  if (!token) {
    logout();
    window.location.href = "/login";
    return;
  }

  const res = await fetch("/api/minha-rota-protegida", {
    headers: { Authorization: `Bearer ${token}` },
  });
  return res.json();
}

Pronto!

Seu app agora usa Gate para autenticação. Usuários fazem login com as mesmas credenciais em qualquer app do ecossistema. O admin controla acesso pelo painel do Gate.

SSO ativoControle de acessoAuto-refresh de tokenPerfil unificado