@oondemand/oon-core-back 0.2.0 → 0.3.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.
@@ -1,20 +1,155 @@
1
1
  const express = require("express");
2
+ const axios = require("axios");
2
3
  const auth = require("../core/middlewares/auth");
4
+ const ctx = require("../core/context");
5
+ const GenericError = require("../core/errors/GenericError");
3
6
  const { asyncHandler, sendResponse } = require("../core/utils/helpers");
7
+ const {
8
+ LOGIN_PATH,
9
+ getBaseUrl,
10
+ getAppCode,
11
+ getTimeout,
12
+ } = require("../core/utils/activationProvider");
13
+
14
+ const DEV_ACTIVATION_PATH = "/integracao/dev-local/ativar-central";
4
15
 
5
16
  /**
6
- * Rotas de autenticação do Core. O login propriamente dito é delegado ao
7
- * provedor configurado (Meus Apps por padrão); aqui validamos o token e
8
- * devolvemos o usuário resolvido.
17
+ * Rotas de autenticação do Core. Em aplicações member, o login é delegado para
18
+ * a Central de Ativações com o código do app carregado do ambiente do backend.
9
19
  */
10
20
  const router = express.Router();
11
21
 
22
+ function readBasicAuthorization(req) {
23
+ const authorization = String(req.headers.authorization || "");
24
+ if (!authorization.startsWith("Basic ")) {
25
+ throw new GenericError("Credenciais de autenticação ausentes.", {
26
+ statusCode: 401,
27
+ });
28
+ }
29
+ return authorization;
30
+ }
31
+
32
+ function readOptionalAppCode() {
33
+ return String(
34
+ process.env.APP_CODE ||
35
+ process.env.APP_CODIGO ||
36
+ process.env.OON_APP_CODE ||
37
+ process.env.OON_APP_CODIGO ||
38
+ "",
39
+ )
40
+ .trim()
41
+ .toLowerCase();
42
+ }
43
+
44
+ function translateProviderError(error) {
45
+ if (error instanceof GenericError) return error;
46
+
47
+ const timedOut = error.code === "ECONNABORTED";
48
+ const statusCode = timedOut ? 504 : error.response?.status || 502;
49
+ const message =
50
+ error.response?.data?.message ||
51
+ (timedOut
52
+ ? "A Central de Ativações não respondeu dentro do prazo."
53
+ : "Não foi possível autenticar o usuário na Central de Ativações.");
54
+
55
+ return new GenericError(message, { statusCode });
56
+ }
57
+
58
+ async function authenticateLocal(req, res, authorization) {
59
+ const authenticate = ctx.config.auth?.authenticate;
60
+ if (typeof authenticate !== "function") return false;
61
+
62
+ const result = await authenticate({ authorization, req });
63
+ const statusCode = result?.statusCode || 200;
64
+ const data = result?.data || result;
65
+
66
+ res.set("Cache-Control", "no-store");
67
+ res.status(statusCode).json(data);
68
+ return true;
69
+ }
70
+
71
+ router.post(
72
+ "/autenticar",
73
+ asyncHandler(async (req, res) => {
74
+ const authorization = readBasicAuthorization(req);
75
+
76
+ if (await authenticateLocal(req, res, authorization)) return;
77
+
78
+ const baseUrl = getBaseUrl(req);
79
+ const appCode = getAppCode();
80
+
81
+ try {
82
+ const response = await axios.post(
83
+ `${baseUrl}${LOGIN_PATH}`,
84
+ {
85
+ codigoApp: appCode,
86
+ codigo: appCode,
87
+ },
88
+ {
89
+ timeout: getTimeout(),
90
+ maxRedirects: 0,
91
+ headers: {
92
+ Authorization: authorization,
93
+ "Content-Type": "application/json",
94
+ "x-app-code": appCode,
95
+ },
96
+ },
97
+ );
98
+
99
+ res.set("Cache-Control", "no-store");
100
+ res.status(response.status).json(response.data);
101
+ } catch (error) {
102
+ throw translateProviderError(error);
103
+ }
104
+ }),
105
+ );
106
+
107
+ router.post(
108
+ "/ativar-dev-local",
109
+ asyncHandler(async (req, res) => {
110
+ const authorization = readBasicAuthorization(req);
111
+ const baseUrl = getBaseUrl(req);
112
+ const appCode = readOptionalAppCode();
113
+
114
+ try {
115
+ const response = await axios.post(
116
+ `${baseUrl}${DEV_ACTIVATION_PATH}`,
117
+ {
118
+ appCode: appCode || req.body?.appCode || req.body?.codigoApp,
119
+ nomeCentral: req.body?.nomeCentral || ctx.config.name,
120
+ appName: req.body?.appName || ctx.config.name,
121
+ appSlug: req.body?.appSlug || ctx.config.slug,
122
+ ambiente: req.body?.ambiente || process.env.NODE_ENV || "development",
123
+ urlPublica: req.body?.urlPublica,
124
+ repositorio: req.body?.repositorio,
125
+ branch: req.body?.branch,
126
+ nomeUsuario: req.body?.nomeUsuario,
127
+ nomeTenant: req.body?.nomeTenant,
128
+ },
129
+ {
130
+ timeout: getTimeout(),
131
+ maxRedirects: 0,
132
+ headers: {
133
+ Authorization: authorization,
134
+ "Content-Type": "application/json",
135
+ },
136
+ },
137
+ );
138
+
139
+ res.set("Cache-Control", "no-store");
140
+ res.status(response.status).json(response.data);
141
+ } catch (error) {
142
+ throw translateProviderError(error);
143
+ }
144
+ }),
145
+ );
146
+
12
147
  router.get(
13
148
  "/validar-token",
14
149
  auth,
15
150
  asyncHandler(async (req, res) => {
16
151
  sendResponse({ res, statusCode: 200, usuario: req.usuario });
17
- })
152
+ }),
18
153
  );
19
154
 
20
155
  module.exports = router;
@@ -13,6 +13,8 @@ const { buildModelRouter } = require("../core/factories/modelRouter");
13
13
  const coreRoutes = require("../api/core.routes");
14
14
  const statusRoutes = require("../api/status.routes");
15
15
  const authRoutes = require("../api/auth.routes");
16
+ const activationRoutes = require("../api/activation.routes");
17
+ const activationGuard = require("../core/middlewares/activationGuard");
16
18
 
17
19
  /**
18
20
  * Cria a app Express do Core: middlewares transversais, rotas públicas,
@@ -31,6 +33,8 @@ function createApp() {
31
33
 
32
34
  // Públicas.
33
35
  app.use("/", statusRoutes);
36
+ app.use("/ativacao", activationRoutes);
37
+ app.use(activationGuard);
34
38
  app.use("/auth", authRoutes);
35
39
  app.use("/core", coreRoutes);
36
40
 
@@ -7,7 +7,10 @@
7
7
  */
8
8
  const mongoose = require("mongoose");
9
9
 
10
- const withMeta = (mongooseDef, meta) => ({ ...mongooseDef, __meta: meta });
10
+ const withMeta = (mongooseDef, meta) => ({
11
+ ...mongooseDef,
12
+ __meta: { required: !!mongooseDef.required, ...meta },
13
+ });
11
14
 
12
15
  const string = (opts = {}) =>
13
16
  withMeta(
@@ -0,0 +1,43 @@
1
+ const { ensureInstance } = require("../utils/instanceActivation");
2
+ const Helpers = require("../utils/helpers");
3
+
4
+ const allowed = [
5
+ /^\/ativacao(\/|$)/,
6
+ /^\/health$/,
7
+ /^\/version$/,
8
+ /^\/favicon\.ico$/,
9
+ ];
10
+
11
+ module.exports = async function activationGuard(req, res, next) {
12
+ try {
13
+ if (allowed.some((pattern) => pattern.test(req.path))) return next();
14
+
15
+ const instance = await ensureInstance();
16
+ if (instance.status === "ativa") return next();
17
+
18
+ const code =
19
+ instance.status === "suspensa"
20
+ ? "INSTANCE_SUSPENDED"
21
+ : instance.status === "revogada"
22
+ ? "INSTANCE_REVOKED"
23
+ : "INSTANCE_NOT_ACTIVE";
24
+
25
+ const message =
26
+ instance.status === "suspensa"
27
+ ? instance.motivoSuspensao || "Esta Central está suspensa."
28
+ : instance.status === "revogada"
29
+ ? "Esta Central foi revogada."
30
+ : instance.ecosystemRole === "root"
31
+ ? "O aplicativo root ainda não foi ativado."
32
+ : "Esta Central ainda não foi ativada.";
33
+
34
+ return Helpers.sendErrorResponse({
35
+ res,
36
+ statusCode: 423,
37
+ message,
38
+ code,
39
+ });
40
+ } catch (error) {
41
+ return next(error);
42
+ }
43
+ };
@@ -1,39 +1,50 @@
1
1
  const axios = require("axios");
2
- const Sistema = require("../models/Sistema");
3
2
  const Helpers = require("../utils/helpers");
4
3
  const ctx = require("../context");
4
+ const {
5
+ PERMISSION_PATH,
6
+ getBaseUrl,
7
+ getAppCode,
8
+ getTimeout,
9
+ } = require("../utils/activationProvider");
5
10
 
6
11
  /**
7
- * Verificador padrão: delega ao Meus Apps (comportamento histórico do CST).
12
+ * Verificador padrão: delega para a Central de Ativações e valida o token no
13
+ * contexto do app configurado no backend por APP_CODE/APP_CODIGO.
8
14
  * Pode ser sobrescrito via `central.config.js -> auth.verifyToken`.
9
15
  */
10
- async function defaultVerifyToken(token) {
11
- const sistema = await Sistema.findOne().catch(() => null);
12
- const baseUrl = process.env.MEUS_APPS_BACKEND_URL;
13
- if (!baseUrl) {
14
- const err = new Error("MEUS_APPS_BACKEND_URL não configurada.");
16
+ async function defaultVerifyToken(token, { req } = {}) {
17
+ const baseUrl = getBaseUrl(req);
18
+ const appCode = getAppCode();
19
+
20
+ const response = await axios.get(`${baseUrl}${PERMISSION_PATH}`, {
21
+ timeout: getTimeout(),
22
+ maxRedirects: 0,
23
+ headers: {
24
+ Authorization: `Bearer ${token}`,
25
+ "x-app-code": appCode,
26
+ },
27
+ });
28
+
29
+ const data = response.data.usuario;
30
+ if (!data) {
31
+ const err = new Error("Usuário não retornado pela Central de Ativações.");
15
32
  err.statusCode = 401;
16
33
  throw err;
17
34
  }
18
35
 
19
- const response = await axios.get(
20
- `${baseUrl}/auth/autenticar-aplicativo/`,
21
- { headers: { Authorization: `Bearer ${token}`, origin: sistema?.appKey } }
22
- );
36
+ const tipoAcesso =
37
+ data.aplicativo?.tipoAcesso || data.tipoAcesso || data.perfil || data.tipo;
23
38
 
24
- const data = response.data.usuario;
25
39
  return {
26
- tipo:
27
- data.aplicativo.tipoAcesso === "master" ? "admin" : data.aplicativo.tipoAcesso,
40
+ ...data,
41
+ tipo: tipoAcesso === "master" ? "admin" : tipoAcesso,
42
+ perfil: data.perfil || tipoAcesso,
28
43
  nome: data.nome,
29
44
  email: data.email,
30
45
  };
31
46
  }
32
47
 
33
- /**
34
- * Middleware de autenticação. Sem token => 401 (invariante de segurança).
35
- * Resolve `req.usuario` e segue.
36
- */
37
48
  const authMiddleware = async (req, res, next) => {
38
49
  const token = req.headers.authorization?.split(" ")[1];
39
50
 
@@ -49,14 +60,18 @@ const authMiddleware = async (req, res, next) => {
49
60
 
50
61
  try {
51
62
  const usuario = await verify(token, { req });
52
- if (!usuario) throw new Error("Usuário não resolvido.");
63
+ if (!usuario) {
64
+ const error = new Error("Usuário não resolvido.");
65
+ error.statusCode = 401;
66
+ throw error;
67
+ }
53
68
  req.usuario = usuario;
54
69
  next();
55
70
  } catch (error) {
56
71
  return Helpers.sendErrorResponse({
57
72
  res,
58
- message: "Token inválido ou erro na autenticação.",
59
- statusCode: error.statusCode || 401,
73
+ message: error.message || "Token inválido ou erro na autenticação.",
74
+ statusCode: error.statusCode || error.response?.status || 401,
60
75
  });
61
76
  }
62
77
  };
@@ -0,0 +1,60 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ const statuses = ["nao_ativada", "validando", "configurando", "ativa", "suspensa", "revogada", "erro"];
4
+ const roles = ["root", "member"];
5
+
6
+ const schema = new mongoose.Schema(
7
+ {
8
+ singletonKey: { type: String, default: "default", immutable: true },
9
+ status: { type: String, enum: statuses, default: "nao_ativada", index: true },
10
+ ecosystemRole: { type: String, enum: roles, default: "member", index: true },
11
+ instanceId: { type: String, index: true },
12
+ instanceCode: String,
13
+ appCode: { type: String, index: true },
14
+ appId: String,
15
+ tenantId: String,
16
+ licencaId: String,
17
+ ambiente: { type: String, enum: ["desenvolvimento", "homologacao", "producao"] },
18
+ nomeInstancia: String,
19
+ ecosystemUrl: String,
20
+ publicUrl: String,
21
+ coreVersion: String,
22
+ appVersion: String,
23
+ fingerprint: String,
24
+ credentialEncrypted: { type: String, select: false },
25
+ credentialIv: { type: String, select: false },
26
+ credentialAuthTag: { type: String, select: false },
27
+ credentialVersion: Number,
28
+ configurationVersion: Number,
29
+ parametrosGlobais: { type: mongoose.Schema.Types.Mixed, default: {} },
30
+ parametrosAplicacao: { type: mongoose.Schema.Types.Mixed, default: {} },
31
+ tenant: { type: mongoose.Schema.Types.Mixed, default: {} },
32
+ app: { type: mongoose.Schema.Types.Mixed, default: {} },
33
+ licenca: { type: mongoose.Schema.Types.Mixed, default: {} },
34
+ ativadaEm: Date,
35
+ concluidaEm: Date,
36
+ ultimaValidacaoEm: Date,
37
+ ultimoHeartbeatEm: Date,
38
+ ultimoErro: String,
39
+ motivoSuspensao: String,
40
+ motivoRevogacao: String,
41
+ },
42
+ {
43
+ timestamps: true,
44
+ collection: "oon_instancia_ecossistema",
45
+ toJSON: {
46
+ transform(_doc, ret) {
47
+ delete ret.credentialEncrypted;
48
+ delete ret.credentialIv;
49
+ delete ret.credentialAuthTag;
50
+ return ret;
51
+ },
52
+ },
53
+ },
54
+ );
55
+
56
+ schema.index({ singletonKey: 1 }, { unique: true });
57
+
58
+ module.exports = mongoose.models.InstanciaEcossistema || mongoose.model("InstanciaEcossistema", schema);
59
+ module.exports.STATUSES = statuses;
60
+ module.exports.ROLES = roles;
@@ -0,0 +1,74 @@
1
+ const GenericError = require("../errors/GenericError");
2
+
3
+ const LOGIN_PATH = "/integracao/autenticacao-aplicativos/autenticar";
4
+ const PERMISSION_PATH = "/integracao/autenticacao-aplicativos/verificar-permissao";
5
+
6
+ function getBaseUrl(req) {
7
+ const value = process.env.CENTRAL_ATIVACAO_URL || process.env.MEUS_APPS_BACKEND_URL;
8
+ if (!value) {
9
+ throw new GenericError("CENTRAL_ATIVACAO_URL não configurada.", {
10
+ statusCode: 500,
11
+ });
12
+ }
13
+
14
+ let parsed;
15
+ try {
16
+ parsed = new URL(value);
17
+ } catch {
18
+ throw new GenericError("CENTRAL_ATIVACAO_URL inválida.", {
19
+ statusCode: 500,
20
+ });
21
+ }
22
+
23
+ const currentHost = String(
24
+ req?.headers?.["x-forwarded-host"] || req?.headers?.host || "",
25
+ )
26
+ .split(",")[0]
27
+ .trim()
28
+ .toLowerCase();
29
+
30
+ if (
31
+ currentHost &&
32
+ parsed.host.toLowerCase() === currentHost &&
33
+ process.env.AUTH_ALLOW_SAME_HOST !== "true"
34
+ ) {
35
+ throw new GenericError(
36
+ "CENTRAL_ATIVACAO_URL aponta para o próprio Core. Configure o endereço da Central de Ativações.",
37
+ { statusCode: 500 },
38
+ );
39
+ }
40
+
41
+ return parsed.toString().replace(/\/$/, "");
42
+ }
43
+
44
+ function getAppCode() {
45
+ const value = String(
46
+ process.env.APP_CODE ||
47
+ process.env.APP_CODIGO ||
48
+ process.env.OON_APP_CODE ||
49
+ process.env.OON_APP_CODIGO ||
50
+ "",
51
+ )
52
+ .trim()
53
+ .toLowerCase();
54
+
55
+ if (value) return value;
56
+ if (process.env.NODE_TEST_CONTEXT) return "oon-core-test";
57
+
58
+ throw new GenericError("APP_CODE não configurada no backend.", {
59
+ statusCode: 500,
60
+ });
61
+ }
62
+
63
+ function getTimeout() {
64
+ const value = Number(process.env.AUTH_PROVIDER_TIMEOUT_MS);
65
+ return Number.isFinite(value) && value > 0 ? value : 10000;
66
+ }
67
+
68
+ module.exports = {
69
+ LOGIN_PATH,
70
+ PERMISSION_PATH,
71
+ getBaseUrl,
72
+ getAppCode,
73
+ getTimeout,
74
+ };
@@ -2,10 +2,10 @@ exports.sendResponse = ({ res, statusCode = 200, message = "OK", ...rest }) => {
2
2
  res.status(statusCode).send({ message, ...rest });
3
3
  };
4
4
 
5
- exports.sendErrorResponse = ({ res, statusCode = 500, message, error }) => {
5
+ exports.sendErrorResponse = ({ res, statusCode = 500, message, error, code }) => {
6
6
  res.status(statusCode).json({
7
7
  message,
8
- ...(error ? { error } : {}),
8
+ error: error || (code ? { code, message } : undefined),
9
9
  });
10
10
  };
11
11