🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇧🇦 bs · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇱 he · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇮🇹 it · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇳🇴 no · 🇮🇳 or · 🇮🇳 pa · 🇵🇭 phi · 🇵🇱 pl · 🇧🇷 pt-BR · 🇷🇴 ro · 🇷🇺 ru · 🇱🇰 si · 🇸🇰 sk · 🇸🇮 sl · 🇷🇸 sr · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇭 th · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇺🇿 uz · 🇻🇳 vi · 🇳🇬 yo · 🇨🇳 zh-CN · 🇹🇼 zh-TW
Fonte da verdade:
src/server/authz/,src/shared/constants/publicApiRoutes.ts,src/lib/api/requireManagementAuth.ts,src/shared/utils/apiAuth.tsÚltima atualização: 2026-09-22 — os espaços de nomes de âmbito apontam para MCP-SERVER.md
OmniRoute possui um pipeline de autorização sensível a rotas que protege cada pedido de API. A classificação é determinística e fail-closed — qualquer coisa que não possa ser classificada acaba como MANAGEMENT e exige uma sessão ou um token de nível de gestão. Esta página explica o modelo para engenheiros que mantêm rotas ou que desenham novos endpoints.
Fonte: diagrams/authz-pipeline.mmd
Utilizada nas APIs de cliente compatíveis com OpenAI/Anthropic/Gemini e em algumas rotas de gestão quando a chave tem o âmbito manage.
Authorization: Bearer <api-key>
Validada por isValidApiKey() / extractApiKey() em src/sse/services/auth.ts e reexportada através de src/shared/utils/apiAuth.ts. O validador também aceita as variáveis de ambiente OMNIROUTE_API_KEY / ROUTER_API_KEY como chaves persistentes de passagem direta (incidência #1350).
Para páginas do dashboard e operações administrativas.
Cookie: auth_token=<JWT assinado com JWT_SECRET>
Um cookie só é uma sessão quando o JWT é validado e contém authenticated: true
(src/shared/utils/dashboardSessionToken.ts → verifyDashboardSessionToken). Todos os
consumidores do cookie (proteção de rotas, atualização do pipeline de AuthZ, handshake
WebSocket, servidor em direto, /api/settings/require-login, /api/auth/status) passam
por esse auxiliar. Existem outros JWT assinados com JWT_SECRET — a passagem direta da
CLI do Cursor emite tokens iss "omniroute" / aud "cursor-cli" para detentores de
chaves — e estes nunca são sessões (#13298).
Validada por isDashboardSessionAuthenticated() em src/shared/utils/apiAuth.ts. O pipeline atualiza automaticamente o JWT quando faltam menos de 7 dias para o fim do seu período de validade de 30 dias.
Algumas rotas de gestão aceitam qualquer um dos modos: cookie OU Bearer <key> quando a chave de API tem o âmbito manage (ou admin). É isto que permite o fluxo de trabalho «configurável através de chamadas à API» adicionado na v3.8.
O início de sessão administrativo do dashboard também suporta um fluxo OIDC (OpenID Connect) facultativo, em conjunto com o início de sessão predefinido por palavra-passe — o início de sessão por palavra-passe nunca é removido, sendo apenas complementado:
- Está desativado, exceto se
settings.oidcEnabled === trueeoidcIssuer/oidcClientId/oidcClientSecretestiverem todos configurados (Definições → Autenticação). Caso contrário,GET /api/auth/oidc/logindevolve400. GET /api/auth/oidc/loginobtém oauthorization_endpointa partir do/.well-known/openid-configurationdo emissor (recorrendo a<issuer>/authorizecomo alternativa), constrói o URI de redirecionamento a partir do pedido recebido (tendo em contax-forwarded-proto) e redireciona para o IdP com umstatealeatório armazenado num cookieoidc_statehttpOnly.GET /api/auth/oidc/callbackvalida ostate, troca o código de autorização e verifica a assinatura do token de ID através do JWKS do emissor (createRemoteJWKSetdejose, mantido em cache por URI de JWKS), com verificações deissuer/audience. Uma lista de permissões opcionaloidcAllowedSubjectscompara a declaraçãosubdo token ou a respetiva declaraçãoemail— a declaração de email só é aceite quandoemail_verified === true, pelo que um email não verificado no IdP nunca pode passar o controlo.- Em caso de sucesso, emite exatamente o mesmo JWT
auth_tokende 30 dias emitido pelo início de sessão por palavra-passe (src/app/api/auth/login/route.ts), pelo que o restante pipeline da sessão do dashboard (atualização automática, atributos do cookie) permanece inalterado — o OIDC apenas substitui a forma como o cookie é emitido, não as permissões que este concede.
src/server/authz/types.ts define três classes; qualquer rota que não possa ser classificada de forma determinística recorre a MANAGEMENT.
| Classe | Descrição | Autenticação necessária |
|---|---|---|
PUBLIC |
Rotas explicitamente seguras — início de sessão, fim de sessão, estado, inicialização, integridade, arranque da integração inicial. | Nenhuma |
CLIENT_API |
Endpoints de disponibilização de modelos — /api/v1/*, /api/v1beta/*, e ainda os aliases /v1/*, /v1beta/*, /chat/completions, /responses, /models, /codex/*. |
Chave Bearer quando o feature flag efetivo REQUIRE_API_KEY está ativado |
MANAGEMENT |
Páginas do painel, definições, fornecedores, chaves, endpoints de administração e diagnóstico. | Sessão do painel OU Bearer com o âmbito manage |
Pedido recebido → src/proxy.ts
→ runAuthzPipeline() em src/server/authz/pipeline.ts
1. Remover cabeçalhos internos fidedignos (x-omniroute-auth-*, x-omniroute-route-class)
2. Gerar o ID do pedido, classificar a rota através de classifyRoute()
3. Se pathname == "/" → redirecionar para /dashboard
4. Se estiver em esvaziamento (encerramento gracioso) e /api/* → 503
5. Se não for GET em /api/* → proteção checkBodySize()
6. Se for OPTIONS → preflight CORS 204
7. Se options.enforce == false → deixar passar com os cabeçalhos da classe de rota
8. Caso contrário: POLICIES[routeClass].evaluate(ctx)
- permitir → aplicar x-omniroute-auth-{kind,id,label,scopes} → NextResponse.next()
- rejeitar → erro JSON com correlation_id (páginas do painel → 302 /login)
Os cabeçalhos internos fidedignos (definidos em src/server/authz/headers.ts) são removidos dos pedidos recebidos antes da classificação — os clientes não podem preencher previamente x-omniroute-auth-* para se fazerem passar por um sujeito.
Cada classe de rota tem uma política em src/server/authz/policies/:
publicPolicy(policies/public.ts) — devolve sempreallow({ kind: "anonymous", id: "anonymous" }).clientApiPolicy(policies/clientApi.ts) — extrai o Bearer e valida-o através devalidateApiKey(). Só recorre ao acesso anónimo quando o feature flag efetivoREQUIRE_API_KEYestá desativado. O valor efetivo do flag é determinado através deisRequireApiKeyEnabled()(substituição do feature flag da BD > process.env.REQUIRE_API_KEY > predefinição), para que os Feature Flags do Painel e as variáveis de ambiente controlem/api/v1/*,/api/v1beta/*e os aliases de forma consistente; as falhas do resolvedor resultam em negação por predefinição. Permite pedidos com sessão do painel nas rotas da API de cliente (incluindo/api/v1/models, utilizada pelo catálogo de modelos do painel).managementPolicy(policies/management.ts) — aceita uma sessão do painel, pedidos internos de sincronização de modelos (correspondentes a/api/providers/[name]/(sync-models|models)) ou ignora totalmente a autenticação seisAuthRequired()devolver false. Devolve 403 (AUTH_001) quando está presente um token Bearer inválido e 401 nos restantes casos. Também aplica os níveis de proteção de rotas (LOCAL_ONLY / ALWAYS_PROTECTED) antes de qualquer ramificação de autenticação — consulte Níveis de Proteção de Rotas. Os caminhos LOCAL_ONLY emLOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES(atualmente:/api/mcp/) podem ser acedidos a partir de um endereço que não seja loopback quando a chave Bearer inclui o âmbitomanage; todos os restantes caminhos LOCAL_ONLY continuam estritamente limitados a loopback, independentemente do âmbito.
Uma política bem-sucedida devolve AuthSubject com kind ∈ { client_api_key, dashboard_session, management_key, anonymous }. Os handlers a jusante podem lê-lo através de assertAuth(request, "CLIENT_API") em src/server/authz/assertAuth.ts, em vez de voltarem a executar a lógica de autenticação.
src/shared/constants/publicApiRoutes.ts é a lista de permissões explícita:
A lista está dividida por formato, e esta divisão é essencial (GHSA-74g9-q8f6-793h): um prefixo é
comparado com startsWith(), pelo que também corresponde a todos os caminhos adjacentes que partilhem os respetivos caracteres iniciais.
Usar /api/usage/om-usage como prefixo tornou /api/usage/om-usage<anything> PÚBLICO, e o Next resolve
isso como /api/usage/[connectionId] — um handler sem autenticação própria.
// Subárvores genuínas. Cada entrada DEVE terminar em "/" (verificado por um teste unitário).
PUBLIC_API_ROUTE_PREFIXES = [
"/api/auth/oidc/",
"/api/v1/", // tratado como CLIENT_API em classify, não como público "sem autenticação"
"/api/oauth/",
"/api/codex/connect/",
"/api/telegram/",
"/api/cursor-cli/",
];
// Rotas individuais, comparadas EXATAMENTE (com ou sem uma barra final).
PUBLIC_API_ROUTES_EXACT = new Set([
"/api/auth/login",
"/api/auth/logout",
"/api/auth/status",
"/api/init",
"/api/sync/bundle",
"/api/cli/connect",
"/api/usage/om-usage",
"/api/skills/collect/chaos",
]);
// Rotas individuais só de leitura que também recebem a flexibilização da origem CORS.
PUBLIC_READONLY_CORS_API_ROUTES = [
"/api/health/ping",
"/api/monitoring/health",
"/api/settings/require-login",
];
// Rota individual só de leitura SEM a flexibilização de CORS.
PUBLIC_READONLY_API_ROUTES_EXACT = new Set(["/api/health"]);
PUBLIC_READONLY_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);As rotas só de leitura são públicas apenas para métodos seguros. Nota: classifyRoute() exclui /api/v1/* e /api/v1beta/* do caso geral PUBLIC — estas são sempre CLIENT_API, para que a política de chave Bearer continue a ser aplicada.
As rotas em /api/v1/ e /api/v1beta/ são classificadas automaticamente como CLIENT_API. O middleware aplica a verificação Bearer; os handlers das rotas não precisam de a repetir, mas podem ler o sujeito se tal for útil.
// src/app/api/v1/your-route/route.ts
import { NextRequest, NextResponse } from "next/server";
import { assertAuth } from "@/server/authz/assertAuth";
export async function POST(req: NextRequest) {
const subject = assertAuth(req, "CLIENT_API");
// subject.kind === "client_api_key" | "anonymous" | "dashboard_session"
// ... lógica do handler
}Utilize requireManagementAuth() de src/lib/api/requireManagementAuth.ts:
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
export async function POST(request: Request) {
const rejection = await requireManagementAuth(request);
if (rejection) return rejection;
// ... lógica do handler
}requireManagementAuth() devolve null em caso de sucesso ou uma Response de erro JSON:
- 401
AUTH_001"Autenticação necessária" — não existem quaisquer credenciais - 403 — Bearer inválido ou Bearer presente, mas a chave não tem o âmbito
manage/admin
hasManageScope(scopes) devolve true para "manage" ou "admin".
Escolha o conjunto com base no formato, não na conveniência. Uma rota deve ser adicionada a PUBLIC_API_ROUTES_EXACT (ou a PUBLIC_READONLY_CORS_API_ROUTES se for apenas GET); apenas uma subárvore genuína deve ser adicionada a PUBLIC_API_ROUTE_PREFIXES, e esta deve terminar em /. Colocar uma única rota na lista de prefixos também torna público qualquer caminho adjacente que partilhe os respetivos caracteres iniciais — incluindo caminhos irmãos com segmentos dinâmicos adicionados posteriormente (GHSA-74g9-q8f6-793h). Atualize os testes unitários em tests/unit/public-api-routes.test.ts, tests/unit/authz/public-route-exact-match.test.ts e tests/unit/authz/classify.test.ts.
Três espaços de nomes. Cada verificador lê apenas as suas próprias strings. A comparação lado a lado,
incluindo por que razão manage falha scopeMatches para read:compression e por que razão um
token de acesso read não pode PATCH /api/keys/{id}, está em
Três espaços de nomes de âmbito.
As chaves de API contêm um array scopes (armazenado como JSON em api_keys.scopes, veja src/lib/db/apiKeys.ts).
manage/admin—hasManageScope. Acesso de portador a rotas da API de gestão.mcp:connect,self:usage,self:account-quotaepolicy:bypass-provider-quotasão âmbitos aditivos de correspondência exata. Eles situam-se fora deMANAGEMENT_API_KEY_SCOPES.mcp:connectabre apenas o recorte não-loopback/api/mcp/.
Catálogo e regras de correspondência (string idêntica, ou um âmbito concedido que termina em *):
Âmbitos da ferramenta MCP.
MCP_SCOPE_LIST em src/shared/constants/mcpScopes.ts é o subconjunto tipado
original, não o catálogo completo. A aplicação é executada em
open-sse/mcp-server/scopeEnforcement.ts depois de resolveCallerScopeContext()
resolver os âmbitos a partir das informações de autenticação do MCP, metadados do pedido ou OMNIROUTE_MCP_SCOPES.
Permanece desativado a menos que OMNIROUTE_MCP_ENFORCE_SCOPES=true.
read / write / admin em tokens oma_live_…, classificados por scopeSatisfies
(src/lib/accessTokens/scopes.ts). Esta classificação aplica-se apenas à credencial
do token de acesso. Veja Autenticação de Gestão.
isAuthRequired() em src/shared/utils/apiAuth.ts determina se alguma autenticação é aplicada a um pedido:
settings.requireLogin === false→ a autenticação é desativada globalmente.- Nenhuma palavra-passe configurada e nenhuma variável de ambiente
INITIAL_PASSWORD→ o modo de inicialização permite o assistente de configuração inicial e pedidos de loopback, mas os pedidos de rede expostos continuam a exigir credenciais. - Qualquer erro da BD → bloqueia o acesso (seguro por predefinição).
A aplicação de chaves de API de cliente utiliza isRequireApiKeyEnabled() em src/shared/utils/featureFlags.ts, e não uma leitura direta de process.env.REQUIRE_API_KEY. Isto é importante para instâncias implementadas: alternar REQUIRE_API_KEY em Dashboard → Feature Flags armazena uma substituição na BD e afeta imediatamente /v1/*, /v1beta/*, /models, /responses, /chat/completions, /codex/* e outras verificações de autenticação da API de cliente que partilham esta função auxiliar. Se não for possível ler o armazenamento de indicadores de funcionalidades, a autenticação da API de cliente bloqueia o acesso e exige uma chave.
Os endpoints /api/v1/agents/tasks/* e /api/resilience/model-cooldowns agora requerem autenticação de gestão (commit 588a0333). Os clientes que anteriormente enviavam uma chave de API normal sem o âmbito manage recebem 403. Migração: atribua à chave o âmbito manage no painel API Keys ou utilize uma sessão autenticada no painel.
/api/mcp/* (o servidor MCP remoto) continua a ser LOCAL_ONLY por predefinição, mas agora aceita pedidos que não sejam de loopback quando o cabeçalho Authorization: Bearer <api-key> inclui o âmbito manage. A exceção é explicitamente controlada por caminho através de LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES em src/server/authz/routeGuard.ts; o prefixo LOCAL_ONLY relacionado /api/cli-tools/runtime/* não pode, intencionalmente, contornar esta restrição, pois pode iniciar subprocessos arbitrários. Os pedidos anónimos para /api/mcp/* que não sejam de loopback continuam a devolver 403 LOCAL_ONLY — a predefinição para qualquer novo caminho LOCAL_ONLY continua a exigir estritamente loopback. Consulte Níveis de proteção de rotas.
- Testes unitários:
tests/unit/authz/—classify.test.ts,pipeline.test.ts,client-api-policy.test.ts,management-policy.test.ts,public-policy.test.ts. - Lista de permissões pública:
tests/unit/public-api-routes.test.ts. - Execução focada:
node --import tsx/esm --test tests/unit/authz/classify.test.ts.
O pipeline adiciona sempre os seguintes cabeçalhos às respostas:
x-request-id: <id de correlação, repetido nos corpos dos erros>
x-omniroute-route-class: PUBLIC | CLIENT_API | MANAGEMENT
Para pedidos autenticados, os cabeçalhos do pedido a montante (do lado do handler) também incluem:
x-omniroute-auth-kind: client_api_key | dashboard_session | management_key | anonymous
x-omniroute-auth-id: key_<últimos-4> | "dashboard" | "anonymous"
x-omniroute-auth-label: (opcional)
x-omniroute-auth-scopes: lista separada por vírgulas
Utilize assertAuth(req, expectedClass) nos handlers — lança AuthzAssertionError com o código AUTHZ_NOT_INITIALIZED caso o middleware tenha sido ignorado (útil para detetar regressões de configuração nos testes).
- API_REFERENCE.md — marcador de autenticação por endpoint
- COMPLIANCE.md — registo de auditoria para eventos de autenticação
- MCP-SERVER.md — três namespaces de escopo e catálogo de escopo de ferramenta MCP
- Fonte:
src/server/authz/,src/lib/api/requireManagementAuth.ts