@oondemand/oon-core-back 0.2.0 → 0.3.0
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 +13 -126
- package/package.json +1 -1
- package/src/api/activation.routes.js +522 -0
- package/src/api/auth.routes.js +105 -6
- package/src/boot/createApp.js +4 -0
- package/src/core/fields.js +4 -1
- package/src/core/middlewares/activationGuard.js +43 -0
- package/src/core/middlewares/auth.js +88 -34
- package/src/core/models/InstanciaEcossistema.js +60 -0
- package/src/core/utils/activationProvider.js +54 -0
- package/src/core/utils/helpers.js +2 -2
- package/src/core/utils/instanceActivation.js +477 -0
- package/src/deploy/render.js +3 -0
- package/src/deploy/templates/configmap.yaml +3 -0
package/README.md
CHANGED
|
@@ -1,132 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OonCore Back - ativação de instâncias
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
O Core suporta `ecosystem.role` em `central.config.js`: `root` para a Central de Ativações e `member` (padrão) para demais Centrais. Aplicações `member` iniciam como `nao_ativada`, expõem apenas `/ativacao/*`, `/health` e `/version`, e só liberam autenticação/CRUD após ativação.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
apenas domínio: você escreve `central.config.js` + `src/models` (e, quando
|
|
7
|
-
preciso, `validations / triggers / pipelines / documents / integrations /
|
|
8
|
-
routes`) e sobe tudo com `oonCore-back start`.
|
|
5
|
+
## Variáveis de ambiente
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
- `CENTRAL_ATIVACAO_URL`: backend real da Central de Ativações; nunca aponte para o próprio Core.
|
|
8
|
+
- `APP_CODE`: código do aplicativo no Ecossistema.
|
|
9
|
+
- `APP_ENVIRONMENT`: `desenvolvimento`, `homologacao` ou `producao`.
|
|
10
|
+
- `PUBLIC_APP_URL`: URL pública confirmada da Central.
|
|
11
|
+
- `INSTANCE_CREDENTIAL_ENCRYPTION_KEY`: chave para AES-256-GCM; obrigatória em produção.
|
|
12
|
+
- `AUTH_PROVIDER_TIMEOUT_MS`: timeout das chamadas à Central de Ativações.
|
|
13
|
+
- `INSTANCE_HEARTBEAT_INTERVAL_MS`: intervalo planejado para sincronização/heartbeat.
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## Fluxo
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
npm install @oondemand/oon-core-back
|
|
18
|
-
```
|
|
17
|
+
`GET /ativacao/status` informa estado sem segredos. `POST /ativacao/validar-codigo` valida sem consumir. `POST /ativacao/concluir` revalida, chama `/ativar`, criptografa imediatamente o token da instância, executa hooks declarativos, chama `/concluir` e marca a instância como `ativa`. `POST /ativacao/tentar-novamente` retoma uma ativação já registrada sem exigir novo código.
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
`central.config.js`:
|
|
23
|
-
|
|
24
|
-
```js
|
|
25
|
-
module.exports = {
|
|
26
|
-
serviceName: "minha-central",
|
|
27
|
-
// opcional: provedor de auth (default = Meus Apps)
|
|
28
|
-
// auth: { verifyToken: async (token) => ({ tipo, nome, email }) },
|
|
29
|
-
};
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
`src/models/Produto.js`:
|
|
33
|
-
|
|
34
|
-
```js
|
|
35
|
-
const { defineModel, fields } = require("@oondemand/oon-core-back");
|
|
36
|
-
|
|
37
|
-
defineModel({
|
|
38
|
-
name: "Produto",
|
|
39
|
-
schema: {
|
|
40
|
-
nome: fields.string({ required: true }),
|
|
41
|
-
preco: fields.currency(),
|
|
42
|
-
moeda: fields.currencyCode(),
|
|
43
|
-
},
|
|
44
|
-
crud: { enabled: true, roles: { write: ["admin"] } },
|
|
45
|
-
});
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Subir:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
npx oonCore-back start # produção
|
|
52
|
-
npx oonCore-back dev # watch/reload
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Isso já expõe, para `Produto`:
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
GET /produtos (list + paginação/filtros/busca/ordenação)
|
|
59
|
-
GET /produtos/:id
|
|
60
|
-
POST /produtos
|
|
61
|
-
PUT /produtos/:id
|
|
62
|
-
PATCH /produtos/:id
|
|
63
|
-
DELETE /produtos/:id
|
|
64
|
-
POST /produtos/import
|
|
65
|
-
GET /produtos/export
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Todas privadas (auth obrigatório), com RBAC e auditoria nas mutações.
|
|
69
|
-
|
|
70
|
-
## API pública
|
|
71
|
-
|
|
72
|
-
```js
|
|
73
|
-
const {
|
|
74
|
-
start, createApp, activate,
|
|
75
|
-
defineCentral, defineModel, fields,
|
|
76
|
-
defineCollection, defineDocument, definePipeline,
|
|
77
|
-
defineOmieMapping, defineRoutes, defineValidation, defineTrigger,
|
|
78
|
-
omie, GenericError, registry,
|
|
79
|
-
} = require("@oondemand/oon-core-back");
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### `defineRoutes` — rotas customizadas com o contrato de segurança
|
|
83
|
-
|
|
84
|
-
Nunca se usa `express.Router()` cru. `router.private.*` aplica auth + RBAC
|
|
85
|
-
(+ auditoria opcional); `router.public.*` é aberto.
|
|
86
|
-
|
|
87
|
-
```js
|
|
88
|
-
const { defineRoutes } = require("@oondemand/oon-core-back");
|
|
89
|
-
|
|
90
|
-
defineRoutes("/relatorios", (router) => {
|
|
91
|
-
router.private.get("/resumo", { roles: ["admin"] }, async (req, res) => {
|
|
92
|
-
res.json({ ok: true, usuario: req.usuario });
|
|
93
|
-
});
|
|
94
|
-
router.public.get("/health", async (_req, res) => res.json({ up: true }));
|
|
95
|
-
});
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Rotas `/core/*` (contrato com o oonCore-front)
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
GET /core/metadata GET /core/models GET /core/models/:name
|
|
102
|
-
GET /core/collections GET /core/documents GET /core/pipelines
|
|
103
|
-
GET /core/integrations GET /core/permissions GET /core/actions
|
|
104
|
-
GET /core/menus GET /core/features
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## CLI
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
oonCore-back start # produção
|
|
111
|
-
oonCore-back dev # watch/reload
|
|
112
|
-
oonCore-back activate # seed/ativação (singleton Sistema + hook)
|
|
113
|
-
oonCore-back build:image # renderiza Dockerfile + docker build
|
|
114
|
-
oonCore-back k8s:render # renderiza manifests a partir de central.manifest.json
|
|
115
|
-
oonCore-back deploy # kubectl apply dos manifests
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
O consumidor não escreve YAML: ajusta `central.manifest.json` e o Core
|
|
119
|
-
renderiza Dockerfile + ConfigMap/Secret/Deployment/Service/Ingress/HPA/Certificate.
|
|
120
|
-
|
|
121
|
-
## Testes
|
|
122
|
-
|
|
123
|
-
```bash
|
|
124
|
-
npm test # invariantes de segurança (offline)
|
|
125
|
-
MONGO_URI=... npm test # inclui CRUD + auditoria (requer Mongo)
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Variáveis de ambiente principais
|
|
129
|
-
|
|
130
|
-
`SERVICE_NAME`, `SERVICE_VERSION`, `PORT`, `NODE_ENV`,
|
|
131
|
-
`DB_SERVER`/`DB_NAME`/`DB_USER`/`DB_PASSWORD`/`DB_AUTH_SOURCE`/`DB_REPLICA_SET`/`DB_TSL`
|
|
132
|
-
(ou `MONGO_URI`), `MEUS_APPS_BACKEND_URL`, `OMIE_APP_KEY`/`OMIE_APP_SECRET`.
|
|
19
|
+
Campos adicionais podem ser declarados em `activation.fields`; `password` e `secret` são sanitizados na configuração retornada ao frontend. Hooks opcionais: `validate`, `beforeComplete`, `afterComplete`.
|
package/package.json
CHANGED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const ctx = require("../core/context");
|
|
3
|
+
const GenericError = require("../core/errors/GenericError");
|
|
4
|
+
const { asyncHandler, sendResponse } = require("../core/utils/helpers");
|
|
5
|
+
const {
|
|
6
|
+
PATHS,
|
|
7
|
+
globalDefaults,
|
|
8
|
+
globalFields,
|
|
9
|
+
rootFields,
|
|
10
|
+
role,
|
|
11
|
+
ambiente,
|
|
12
|
+
publicUrl,
|
|
13
|
+
sanitizeFields,
|
|
14
|
+
activationConfig,
|
|
15
|
+
ensureInstance,
|
|
16
|
+
hasActiveRootInstance,
|
|
17
|
+
claimRootBootstrap,
|
|
18
|
+
validateRootCredentials,
|
|
19
|
+
encrypt,
|
|
20
|
+
instanceHeaders,
|
|
21
|
+
validateParams,
|
|
22
|
+
providerPost,
|
|
23
|
+
providerPayload,
|
|
24
|
+
event,
|
|
25
|
+
} = require("../core/utils/instanceActivation");
|
|
26
|
+
const { getAppCode, getBaseUrl } = require("../core/utils/activationProvider");
|
|
27
|
+
|
|
28
|
+
const router = express.Router();
|
|
29
|
+
const attempts = new Map();
|
|
30
|
+
|
|
31
|
+
function rateLimit(req, res, next) {
|
|
32
|
+
const key = req.ip || "local";
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
const list = (attempts.get(key) || []).filter((time) => now - time < 60000);
|
|
35
|
+
|
|
36
|
+
if (list.length >= 30) {
|
|
37
|
+
return res.status(429).json({
|
|
38
|
+
error: {
|
|
39
|
+
code: "RATE_LIMIT",
|
|
40
|
+
message: "Muitas tentativas de ativação.",
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
list.push(now);
|
|
46
|
+
attempts.set(key, list);
|
|
47
|
+
next();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function assertMember() {
|
|
51
|
+
if (role() === "root") {
|
|
52
|
+
throw new GenericError(
|
|
53
|
+
"Aplicações root usam o bootstrap inicial do ecossistema.",
|
|
54
|
+
{ statusCode: 400 },
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function assertRoot() {
|
|
60
|
+
if (role() !== "root") {
|
|
61
|
+
throw new GenericError(
|
|
62
|
+
"Somente a aplicação root pode executar esta ativação.",
|
|
63
|
+
{ statusCode: 400 },
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function assertApp(data) {
|
|
69
|
+
const returned = data?.app?.codigo || data?.appCode;
|
|
70
|
+
if (returned && String(returned).toLowerCase() !== getAppCode()) {
|
|
71
|
+
throw new GenericError(
|
|
72
|
+
"Código do aplicativo retornado pela Central é incompatível.",
|
|
73
|
+
{ statusCode: 502 },
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function safeContext(data) {
|
|
79
|
+
const { credencial, ...safe } = data || {};
|
|
80
|
+
return safe;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function requireRootBootstrapResult(result) {
|
|
84
|
+
const usuarioId = result?.usuario?.id || result?.usuario?._id;
|
|
85
|
+
const tenantId = result?.tenant?.id || result?.tenant?._id;
|
|
86
|
+
const appId = result?.app?.id || result?.app?._id;
|
|
87
|
+
const licencaId = result?.licenca?.id || result?.licenca?._id;
|
|
88
|
+
|
|
89
|
+
if (!usuarioId || !tenantId || !appId || !licencaId) {
|
|
90
|
+
throw new GenericError(
|
|
91
|
+
"O bootstrap root não retornou a estrutura mínima do ecossistema.",
|
|
92
|
+
{ statusCode: 500 },
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
result?.app?.ecosystemRole &&
|
|
98
|
+
result.app.ecosystemRole !== "root"
|
|
99
|
+
) {
|
|
100
|
+
throw new GenericError(
|
|
101
|
+
"O aplicativo criado pelo bootstrap não foi identificado como root.",
|
|
102
|
+
{ statusCode: 500 },
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
usuarioId: String(usuarioId),
|
|
108
|
+
tenantId: String(tenantId),
|
|
109
|
+
appId: String(appId),
|
|
110
|
+
licencaId: String(licencaId),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
router.use(rateLimit);
|
|
115
|
+
|
|
116
|
+
router.get(
|
|
117
|
+
"/status",
|
|
118
|
+
asyncHandler(async (_req, res) => {
|
|
119
|
+
const instance = await ensureInstance();
|
|
120
|
+
const rootActive =
|
|
121
|
+
instance.ecosystemRole === "root"
|
|
122
|
+
? await hasActiveRootInstance()
|
|
123
|
+
: false;
|
|
124
|
+
|
|
125
|
+
const canActivateRoot =
|
|
126
|
+
instance.ecosystemRole === "root" &&
|
|
127
|
+
!rootActive &&
|
|
128
|
+
["nao_ativada", "erro"].includes(instance.status);
|
|
129
|
+
|
|
130
|
+
const canActivateMember =
|
|
131
|
+
instance.ecosystemRole === "member" &&
|
|
132
|
+
["nao_ativada", "erro"].includes(instance.status) &&
|
|
133
|
+
!instance.instanceId;
|
|
134
|
+
|
|
135
|
+
sendResponse({
|
|
136
|
+
res,
|
|
137
|
+
role: instance.ecosystemRole,
|
|
138
|
+
status: instance.status,
|
|
139
|
+
appCode: getAppCode(),
|
|
140
|
+
ambiente: instance.ambiente || ambiente(),
|
|
141
|
+
podeAtivar: canActivateRoot || canActivateMember,
|
|
142
|
+
podeTentarNovamente:
|
|
143
|
+
instance.ecosystemRole === "member" &&
|
|
144
|
+
Boolean(instance.instanceId) &&
|
|
145
|
+
["configurando", "erro"].includes(instance.status),
|
|
146
|
+
motivoSuspensao: instance.motivoSuspensao,
|
|
147
|
+
motivoRevogacao: instance.motivoRevogacao,
|
|
148
|
+
});
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
router.get(
|
|
153
|
+
"/configuracao",
|
|
154
|
+
asyncHandler(async (req, res) => {
|
|
155
|
+
const instance = await ensureInstance();
|
|
156
|
+
const config = activationConfig();
|
|
157
|
+
|
|
158
|
+
if (instance.ecosystemRole === "root") {
|
|
159
|
+
return sendResponse({
|
|
160
|
+
res,
|
|
161
|
+
mode: "root-bootstrap",
|
|
162
|
+
configurationVersion: config.configurationVersion,
|
|
163
|
+
rootFields: sanitizeFields(rootFields),
|
|
164
|
+
globalFields: [],
|
|
165
|
+
appFields: [],
|
|
166
|
+
defaults: {},
|
|
167
|
+
technical: {
|
|
168
|
+
appCode: getAppCode(),
|
|
169
|
+
ambiente: ambiente(),
|
|
170
|
+
ecosystemRole: "root",
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const host = new URL(getBaseUrl(req)).hostname;
|
|
176
|
+
return sendResponse({
|
|
177
|
+
res,
|
|
178
|
+
mode: "member-activation",
|
|
179
|
+
configurationVersion: config.configurationVersion,
|
|
180
|
+
rootFields: [],
|
|
181
|
+
globalFields,
|
|
182
|
+
appFields: sanitizeFields(config.fields),
|
|
183
|
+
defaults: {
|
|
184
|
+
...globalDefaults,
|
|
185
|
+
ambiente: ambiente(),
|
|
186
|
+
urlPublica: publicUrl(req),
|
|
187
|
+
},
|
|
188
|
+
technical: {
|
|
189
|
+
appCode: getAppCode(),
|
|
190
|
+
ambiente: ambiente(),
|
|
191
|
+
ecosystemRole: "member",
|
|
192
|
+
centralAtivacaoHost: host,
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
router.post(
|
|
199
|
+
"/ativar-root",
|
|
200
|
+
asyncHandler(async (req, res) => {
|
|
201
|
+
assertRoot();
|
|
202
|
+
|
|
203
|
+
const config = activationConfig();
|
|
204
|
+
if (typeof config.rootBootstrap !== "function") {
|
|
205
|
+
throw new GenericError(
|
|
206
|
+
"A aplicação root não configurou o bootstrap inicial.",
|
|
207
|
+
{ statusCode: 500 },
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const { email, password } = validateRootCredentials(
|
|
212
|
+
req.body?.email,
|
|
213
|
+
req.body?.senha,
|
|
214
|
+
);
|
|
215
|
+
const instance = await claimRootBootstrap();
|
|
216
|
+
|
|
217
|
+
event("ROOT_BOOTSTRAP_STARTED", {
|
|
218
|
+
correlationId: req.correlationId,
|
|
219
|
+
appCode: getAppCode(),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const result = await config.rootBootstrap({
|
|
224
|
+
email,
|
|
225
|
+
password,
|
|
226
|
+
appCode: getAppCode(),
|
|
227
|
+
appName: ctx.config.name,
|
|
228
|
+
appSlug: ctx.config.slug,
|
|
229
|
+
environment: ambiente(),
|
|
230
|
+
context: { req },
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const ids = requireRootBootstrapResult(result);
|
|
234
|
+
const now = new Date();
|
|
235
|
+
|
|
236
|
+
Object.assign(instance, {
|
|
237
|
+
ecosystemRole: "root",
|
|
238
|
+
status: "ativa",
|
|
239
|
+
instanceId: result.instanceId || `root:${ids.appId}`,
|
|
240
|
+
instanceCode: result.instanceCode || "root",
|
|
241
|
+
appCode: getAppCode(),
|
|
242
|
+
appId: ids.appId,
|
|
243
|
+
tenantId: ids.tenantId,
|
|
244
|
+
licencaId: ids.licencaId,
|
|
245
|
+
ambiente: ambiente(),
|
|
246
|
+
nomeInstancia: ctx.config.name || ctx.config.slug || getAppCode(),
|
|
247
|
+
publicUrl: publicUrl(req),
|
|
248
|
+
tenant: result.tenant,
|
|
249
|
+
app: result.app,
|
|
250
|
+
licenca: result.licenca,
|
|
251
|
+
parametrosGlobais: {},
|
|
252
|
+
parametrosAplicacao: {},
|
|
253
|
+
configurationVersion: config.configurationVersion,
|
|
254
|
+
ativadaEm: now,
|
|
255
|
+
concluidaEm: now,
|
|
256
|
+
ultimoErro: undefined,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
await instance.save();
|
|
260
|
+
|
|
261
|
+
event("ROOT_BOOTSTRAP_COMPLETED", {
|
|
262
|
+
correlationId: req.correlationId,
|
|
263
|
+
appCode: getAppCode(),
|
|
264
|
+
usuarioId: ids.usuarioId,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return sendResponse({
|
|
268
|
+
res,
|
|
269
|
+
sucesso: true,
|
|
270
|
+
status: "ativa",
|
|
271
|
+
usuario: {
|
|
272
|
+
id: ids.usuarioId,
|
|
273
|
+
email: result.usuario.email,
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
} catch (error) {
|
|
277
|
+
instance.status = "erro";
|
|
278
|
+
instance.ultimoErro = error.message;
|
|
279
|
+
await instance.save();
|
|
280
|
+
|
|
281
|
+
event("ROOT_BOOTSTRAP_FAILED", {
|
|
282
|
+
correlationId: req.correlationId,
|
|
283
|
+
appCode: getAppCode(),
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}),
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
router.post(
|
|
292
|
+
"/validar-codigo",
|
|
293
|
+
asyncHandler(async (req, res) => {
|
|
294
|
+
assertMember();
|
|
295
|
+
const codigoAtivacao = String(req.body?.codigoAtivacao || "").trim();
|
|
296
|
+
if (!codigoAtivacao) {
|
|
297
|
+
throw new GenericError("Código de ativação obrigatório.", {
|
|
298
|
+
statusCode: 400,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const instance = await ensureInstance();
|
|
303
|
+
if (instance.status === "ativa") {
|
|
304
|
+
throw new GenericError("Instância já ativada.", { statusCode: 409 });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
event("INSTANCE_ACTIVATION_VALIDATION_STARTED", {
|
|
308
|
+
correlationId: req.correlationId,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const response = await providerPost(
|
|
313
|
+
req,
|
|
314
|
+
PATHS.validar,
|
|
315
|
+
providerPayload(req, codigoAtivacao),
|
|
316
|
+
);
|
|
317
|
+
assertApp(response.data);
|
|
318
|
+
instance.status = "validando";
|
|
319
|
+
instance.ultimaValidacaoEm = new Date();
|
|
320
|
+
await instance.save();
|
|
321
|
+
event("INSTANCE_ACTIVATION_VALIDATION_SUCCEEDED", {
|
|
322
|
+
correlationId: req.correlationId,
|
|
323
|
+
});
|
|
324
|
+
sendResponse({ res, ...safeContext(response.data) });
|
|
325
|
+
} catch (error) {
|
|
326
|
+
event("INSTANCE_ACTIVATION_VALIDATION_FAILED", {
|
|
327
|
+
correlationId: req.correlationId,
|
|
328
|
+
status: error.response?.status,
|
|
329
|
+
});
|
|
330
|
+
throw error.response
|
|
331
|
+
? new GenericError(
|
|
332
|
+
error.response.data?.message || "Código de ativação inválido.",
|
|
333
|
+
{ statusCode: error.response.status },
|
|
334
|
+
)
|
|
335
|
+
: error;
|
|
336
|
+
}
|
|
337
|
+
}),
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
router.post(
|
|
341
|
+
"/concluir",
|
|
342
|
+
asyncHandler(async (req, res) => {
|
|
343
|
+
assertMember();
|
|
344
|
+
const {
|
|
345
|
+
codigoAtivacao,
|
|
346
|
+
nomeInstancia,
|
|
347
|
+
parametrosGlobais = {},
|
|
348
|
+
parametrosAplicacao = {},
|
|
349
|
+
} = req.body || {};
|
|
350
|
+
|
|
351
|
+
if (!codigoAtivacao) {
|
|
352
|
+
throw new GenericError("Código de ativação obrigatório.", {
|
|
353
|
+
statusCode: 400,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
validateParams(
|
|
358
|
+
{ ...parametrosGlobais, nomeInstancia },
|
|
359
|
+
parametrosAplicacao,
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
const config = activationConfig();
|
|
363
|
+
const instance = await ensureInstance();
|
|
364
|
+
|
|
365
|
+
if (
|
|
366
|
+
instance.instanceId &&
|
|
367
|
+
["configurando", "erro"].includes(instance.status)
|
|
368
|
+
) {
|
|
369
|
+
return res.redirect(307, "/ativacao/tentar-novamente");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (!["nao_ativada", "validando", "erro"].includes(instance.status)) {
|
|
373
|
+
throw new GenericError(
|
|
374
|
+
"Instância não pode ser ativada neste estado.",
|
|
375
|
+
{ statusCode: 409 },
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
event("INSTANCE_ACTIVATION_STARTED", {
|
|
380
|
+
correlationId: req.correlationId,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
if (typeof config.hooks.validate === "function") {
|
|
384
|
+
await config.hooks.validate({
|
|
385
|
+
globalParameters: parametrosGlobais,
|
|
386
|
+
appParameters: parametrosAplicacao,
|
|
387
|
+
context: { req },
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const preview = await providerPost(
|
|
392
|
+
req,
|
|
393
|
+
PATHS.validar,
|
|
394
|
+
providerPayload(req, codigoAtivacao),
|
|
395
|
+
);
|
|
396
|
+
assertApp(preview.data);
|
|
397
|
+
|
|
398
|
+
const activated = await providerPost(
|
|
399
|
+
req,
|
|
400
|
+
PATHS.ativar,
|
|
401
|
+
providerPayload(req, codigoAtivacao, { nomeInstancia, metadata: {} }),
|
|
402
|
+
);
|
|
403
|
+
assertApp(activated.data);
|
|
404
|
+
|
|
405
|
+
const token = activated.data?.credencial?.token;
|
|
406
|
+
if (!activated.data?.instancia?.id || !token) {
|
|
407
|
+
throw new GenericError("Resposta de ativação inválida.", {
|
|
408
|
+
statusCode: 502,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
Object.assign(instance, encrypt(token), {
|
|
413
|
+
status: "configurando",
|
|
414
|
+
instanceId: activated.data.instancia.id,
|
|
415
|
+
instanceCode: activated.data.instancia.codigo,
|
|
416
|
+
credentialVersion: activated.data.credencial.versao,
|
|
417
|
+
appCode: getAppCode(),
|
|
418
|
+
appId: activated.data.app?.id || preview.data?.app?.id,
|
|
419
|
+
tenantId: activated.data.tenant?.id || preview.data?.tenant?.id,
|
|
420
|
+
licencaId: activated.data.licenca?.id || preview.data?.licenca?.id,
|
|
421
|
+
ambiente: ambiente(),
|
|
422
|
+
nomeInstancia,
|
|
423
|
+
publicUrl: publicUrl(req),
|
|
424
|
+
tenant: activated.data.tenant || preview.data?.tenant,
|
|
425
|
+
app: activated.data.app || preview.data?.app,
|
|
426
|
+
licenca: activated.data.licenca || preview.data?.licenca,
|
|
427
|
+
parametrosGlobais,
|
|
428
|
+
parametrosAplicacao,
|
|
429
|
+
configurationVersion: config.configurationVersion,
|
|
430
|
+
ativadaEm: new Date(),
|
|
431
|
+
ultimoErro: undefined,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
await instance.save();
|
|
435
|
+
event("INSTANCE_ACTIVATION_REGISTERED", {
|
|
436
|
+
correlationId: req.correlationId,
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
await complete(req, instance, config);
|
|
440
|
+
sendResponse({
|
|
441
|
+
res,
|
|
442
|
+
sucesso: true,
|
|
443
|
+
status: "ativa",
|
|
444
|
+
instanceId: instance.instanceId,
|
|
445
|
+
});
|
|
446
|
+
}),
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
async function complete(req, instance, config) {
|
|
450
|
+
try {
|
|
451
|
+
if (typeof config.hooks.beforeComplete === "function") {
|
|
452
|
+
await config.hooks.beforeComplete({
|
|
453
|
+
globalParameters: instance.parametrosGlobais,
|
|
454
|
+
appParameters: instance.parametrosAplicacao,
|
|
455
|
+
context: { req },
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
await providerPost(
|
|
460
|
+
req,
|
|
461
|
+
PATHS.concluir,
|
|
462
|
+
{
|
|
463
|
+
configurationVersion: config.configurationVersion,
|
|
464
|
+
parametros: instance.parametrosGlobais,
|
|
465
|
+
},
|
|
466
|
+
instanceHeaders(instance),
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
instance.status = "ativa";
|
|
470
|
+
instance.concluidaEm = new Date();
|
|
471
|
+
instance.ultimoErro = undefined;
|
|
472
|
+
await instance.save();
|
|
473
|
+
|
|
474
|
+
event("INSTANCE_ACTIVATION_COMPLETED", {
|
|
475
|
+
correlationId: req.correlationId,
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
if (typeof config.hooks.afterComplete === "function") {
|
|
479
|
+
await config.hooks.afterComplete({
|
|
480
|
+
instance: instance.toJSON(),
|
|
481
|
+
context: { req },
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
} catch (error) {
|
|
485
|
+
instance.status = "erro";
|
|
486
|
+
instance.ultimoErro = error.message;
|
|
487
|
+
await instance.save();
|
|
488
|
+
event("INSTANCE_ACTIVATION_FAILED", {
|
|
489
|
+
correlationId: req.correlationId,
|
|
490
|
+
});
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
router.post(
|
|
496
|
+
"/tentar-novamente",
|
|
497
|
+
asyncHandler(async (req, res) => {
|
|
498
|
+
assertMember();
|
|
499
|
+
const instance = await ensureInstance();
|
|
500
|
+
const config = activationConfig();
|
|
501
|
+
|
|
502
|
+
if (
|
|
503
|
+
!instance.instanceId ||
|
|
504
|
+
!["configurando", "erro"].includes(instance.status)
|
|
505
|
+
) {
|
|
506
|
+
throw new GenericError(
|
|
507
|
+
"Não há ativação pendente para retomar.",
|
|
508
|
+
{ statusCode: 409 },
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
await complete(req, instance, config);
|
|
513
|
+
sendResponse({
|
|
514
|
+
res,
|
|
515
|
+
sucesso: true,
|
|
516
|
+
status: instance.status,
|
|
517
|
+
instanceId: instance.instanceId,
|
|
518
|
+
});
|
|
519
|
+
}),
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
module.exports = router;
|