Rotas privadas

defineRoutes registra endpoints próprios autenticados, com permissão declarada, auditoria opcional e omissão de corpo sensível nos logs.

Crie rotas customizadas somente quando a operação não puder ser representada por CRUD, ação declarativa ou processo. Arquivos em backend/src/routes são autocarregados.

const { defineRoutes, GenericError } = require("@oondemand/oon-core-back");
const pedido = require("../models/Pedido");
const { aprovarPedido } = require("../services/aprovacao");

defineRoutes("/pedidos-acoes", (router) => {
  router.private.post("/:id/aprovar", {
    permission: "pedidos.approve",
    audit: { entidade: "Pedido", acao: "aprovado" },
  }, async (req, res) => {
    const resultado = await aprovarPedido(pedido, req.params.id, req.accessContext);
    if (!resultado) throw new GenericError("Pedido não encontrado.", { statusCode: 404, code: "PEDIDO_NAO_ENCONTRADO" });
    res.json(resultado);
  });
});

Assinatura

router.private[get|post|put|patch|delete](path, options, handler)
router.private[...](path, handler)       // forma curta — sem permissão de negócio
Opção Descrição
permission Recomendada. Permissão única dominio.acao.
permissions Lista — aceita qualquer uma (não exige todas). Evite declarar junto com permission.
roles Compatibilidade legada; não substitui a política RBAC.
audit { entidade, acao } — registra auditoria a partir da resposta. Não coloque segredos.
sensitiveBody true em post/put/patch/delete: omite entrada e resposta no requestLog, inclusive em erro.

router.public existe para rotas realmente públicas — nunca o use para evitar autenticação de uma operação privada.

Prefixos

Camada Caminho
Registro defineRoutes("/pedidos-acoes") + post("/:id/aprovar")
Backend direto POST /pedidos-acoes/:id/aprovar
Navegador / proxy POST /api/pedidos-acoes/:id/aprovar
useOonApi (baseUrl /api) http.post("/pedidos-acoes/123/aprovar")

O proxy remove /api. Não registre esse prefixo nem o duplique na chamada.

Declare a permissão

Em central.app.json → rbac.permissions e conceda em rbac.roles[].permissions. O admin com "*" não dispensa a declaração.

sensitiveBody

Omite entrada e resposta no requestLog para aquele método/caminho. Ele não:

  • cifra o banco;
  • mascara campos seletivamente;
  • cobre CRUD, GET, console, URL, query string, cabeçalhos, logs de integração, auditoria ou mensagens de erro.

Credenciais pertencem a core.secrets ou a armazenamento protegido do App — nunca ao frontend, ao manifesto ou à URL.

Exemplo: integração externa

Não há "integration runtime" genérico nesta release. Uma consulta externa usa rota privada + serviço no App + fetch do Node:

const { defineRoutes, GenericError } = require("@oondemand/oon-core-back");

defineRoutes("/integracao", (router) => {
  router.private.post("/testar", { permission: "integracao.test", sensitiveBody: true }, async (req, res) => {
    const url = process.env.INTEGRACAO_URL;
    const token = process.env.INTEGRACAO_TOKEN;
    if (!url || !token) throw new GenericError("Configure a integração.", {
      statusCode: 409, code: "INTEGRATION_NOT_CONFIGURED",
    });
    let target;
    try { target = new URL(url); } catch { /* tratado abaixo */ }
    if (!target || target.protocol !== "https:" || target.username || target.password) {
      throw new GenericError("Configure um endpoint HTTPS sem credenciais na URL.", {
        statusCode: 422, code: "INTEGRATION_URL_INVALID",
      });
    }
    let response;
    try {
      response = await fetch(target, {
        headers: { Authorization: `Bearer ${token}` },
        signal: AbortSignal.timeout(10000), redirect: "error",
      });
    } catch {
      throw new GenericError("O serviço externo não respondeu no prazo ou está indisponível.", {
        statusCode: 502, code: "INTEGRATION_TRANSPORT_ERROR",
      });
    }
    await response.body?.cancel();
    if (!response.ok) throw new GenericError("O serviço externo recusou a consulta.", {
      statusCode: 502, code: "INTEGRATION_REMOTE_REJECTED", details: { remoteStatus: response.status },
    });
    res.json({ message: "Consulta externa concluída." });
  });
});

Boas práticas do exemplo: HTTPS obrigatório, sem credencial na URL, timeout, redirect: "error", erros normalizados com código e sem repassar a resposta remota crua.

Checklist de toda rota

  • autenticação (router.private);
  • permissão de negócio declarada;
  • validação de entrada;
  • dados filtrados por req.accessContext e helpers de escopo;
  • erros com GenericError e code;
  • nenhum segredo hardcoded;
  • rastreabilidade (audit) sem dados sensíveis.