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:
eximia-gate/v1POST /api/v1/gate/verifylib/gate.ts) + 1 variável de ambiente4 passos para integrar seu app:
Adicione a URL do Gate ao .env.local do seu app:
# .env.local
GATE_URL=http://localhost:3011
NEXT_PUBLIC_GATE_URL=http://localhost:3011Copie 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 }) };
}
}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).
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