@oondemand/oon-core-back 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,3 +73,21 @@ defineModel({
73
73
  ```
74
74
 
75
75
  Use `populateRefs: true` para todas as referências da model ou informe uma lista explícita. O CRUD devolve somente `_id` e campos usuais de identificação, como nome, razão social, descrição, código, e-mail e campos pesquisáveis da model referenciada. Exportações mantêm os identificadores originais.
76
+
77
+ ## Requisições com corpo sensível
78
+
79
+ Rotas privadas declaradas com `defineRoutes` que recebem credenciais ou outros segredos devem marcar a operação mutante com `sensitiveBody: true`:
80
+
81
+ ```js
82
+ defineRoutes("/integracao", (router) => {
83
+ router.private.put("/credencial", {
84
+ permission: "integracao.config.write",
85
+ sensitiveBody: true,
86
+ }, async (req, res) => {
87
+ // Validar e persistir com segurança; devolver somente metadados sanitizados.
88
+ res.json({ configured: true });
89
+ });
90
+ });
91
+ ```
92
+
93
+ O Core omite todo o corpo de entrada e resposta no `requestLog` para esse método e caminho, inclusive se a rota responder erro. Método, URL, status, usuário e correlationId continuam registrados. A opção não cifra o banco nem substitui autorização, validação e respostas sanitizadas. Nunca retorne o segredo pela rota, pelo erro ou pela auditoria da mutação. A opção não se aplica automaticamente a endpoints de CRUD gerados.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oondemand/oon-core-back",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/oondemand/oon-platform.git",
@@ -161,6 +161,9 @@ function defineRoutes(basePath, builder) {
161
161
  const required = opts.permission || opts.permissions;
162
162
  if (required) chain.push(requirePermission(required));
163
163
  else chain.push(rbac(opts.roles || []));
164
+ if (["post", "put", "patch", "delete"].includes(method) && opts.sensitiveBody === true) {
165
+ registry.registerSensitiveRequest(method, basePath, path);
166
+ }
164
167
  if (["post", "put", "patch", "delete"].includes(method) && opts.audit) {
165
168
  chain.push(mutationAudit(opts.audit));
166
169
  }
@@ -1,4 +1,5 @@
1
1
  const Log = require("../models/Log");
2
+ const registry = require("../registry");
2
3
 
3
4
  /**
4
5
  * Registra requisições mutantes (não-GET) na model interna Log.
@@ -7,18 +8,19 @@ const Log = require("../models/Log");
7
8
  const requestLog = async (req, res, next) => {
8
9
  if (req.method === "GET") return next();
9
10
 
11
+ const sensitiveBody = await registry.isSensitiveRequest(req.method, req.url || req.originalUrl);
10
12
  const log = new Log({
11
13
  usuario: req.usuario || null,
12
14
  endpoint: req.originalUrl,
13
15
  metodo: req.method,
14
16
  ip: req.ip || req.headers["x-forwarded-for"] || req.connection?.remoteAddress,
15
17
  correlationId: req.correlationId,
16
- dadosRequisicao: req.body,
18
+ dadosRequisicao: sensitiveBody ? { omitted: true } : req.body,
17
19
  });
18
20
 
19
21
  res.on("finish", () => {
20
22
  log.statusResposta = res.statusCode;
21
- log.dadosResposta = res.locals.body || null;
23
+ log.dadosResposta = sensitiveBody ? null : (res.locals.body || null);
22
24
  log.save().catch(() => {});
23
25
  });
24
26
 
@@ -1,3 +1,5 @@
1
+ const express = require("express");
2
+
1
3
  /**
2
4
  * Model Registry — coração técnico do Core (Seção 17).
3
5
  *
@@ -17,6 +19,7 @@ const store = {
17
19
  pipelines: new Map(),
18
20
  menus: [],
19
21
  features: new Map(),
22
+ sensitiveRequests: [], // Express routers used only to classify sensitive paths
20
23
  };
21
24
 
22
25
  function registerModel(entry) {
@@ -68,6 +71,37 @@ const simpleRegister = (mapName) => (key, value) => {
68
71
  };
69
72
  const simpleList = (mapName) => () => [...store[mapName].values()];
70
73
 
74
+ function registerSensitiveRequest(method, basePath, path) {
75
+ // Use the same mount and route semantics as defineRoutes/createApp.
76
+ const route = express.Router();
77
+ route[method.toLowerCase()](path, (req) => req.sensitiveMatch());
78
+ const mounted = express.Router();
79
+ mounted.use(basePath, route);
80
+ store.sensitiveRequests.push(mounted);
81
+ }
82
+
83
+ async function isSensitiveRequest(method, url) {
84
+ for (const router of store.sensitiveRequests) {
85
+ const matched = await new Promise((resolve) => {
86
+ // A separate request prevents classification from modifying the real request.
87
+ const request = {
88
+ method: String(method).toUpperCase(),
89
+ url: String(url || '/'),
90
+ headers: {},
91
+ sensitiveMatch: () => resolve(true),
92
+ };
93
+ // Routing errors fail closed: omit bodies instead of risking disclosure.
94
+ try {
95
+ router.handle(request, {}, (error) => resolve(Boolean(error)));
96
+ } catch {
97
+ resolve(true);
98
+ }
99
+ });
100
+ if (matched) return true;
101
+ }
102
+ return false;
103
+ }
104
+
71
105
  function registerMenu(item) {
72
106
  store.menus.push(item);
73
107
  }
@@ -82,6 +116,7 @@ function reset() {
82
116
  store.pipelines.clear();
83
117
  store.menus.length = 0;
84
118
  store.features.clear();
119
+ store.sensitiveRequests.length = 0;
85
120
  }
86
121
 
87
122
  module.exports = {
@@ -104,6 +139,8 @@ module.exports = {
104
139
  registerFeature: simpleRegister("features"),
105
140
  listFeatures: simpleList("features"),
106
141
  registerMenu,
142
+ registerSensitiveRequest,
143
+ isSensitiveRequest,
107
144
  listMenus: () => store.menus,
108
145
  reset,
109
146
  };