@hed-hog/core 0.0.397 → 0.0.399
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/dist/auth/auth.controller.d.ts +8 -8
- package/dist/auth/auth.service.d.ts +18 -18
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +18 -1
- package/dist/file/file.service.js.map +1 -1
- package/dist/integration-profile/integration-profile.service.d.ts +13 -0
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.service.js +40 -5
- package/dist/integration-profile/integration-profile.service.js.map +1 -1
- package/dist/mail-sent/mail-sent.controller.d.ts +1 -1
- package/dist/mail-sent/mail-sent.service.d.ts +1 -1
- package/dist/notification/notification.controller.d.ts +20 -20
- package/dist/notification/notification.service.d.ts +20 -20
- package/dist/pairing/pairing.service.d.ts +2 -2
- package/dist/profile/profile.controller.d.ts +1 -1
- package/dist/profile/profile.service.d.ts +1 -1
- package/dist/session/session.controller.d.ts +2 -2
- package/dist/session/session.service.d.ts +8 -8
- package/dist/user/user.service.d.ts +4 -4
- package/hedhog/data/menu.yaml +5 -1
- package/hedhog/query/device_pairing_request.sql +46 -0
- package/hedhog/query/impersonation_grant.sql +71 -0
- package/hedhog/query/oauth_authorize_pkce.sql +58 -0
- package/hedhog/query/oauth_mobile_state_token.sql +30 -0
- package/hedhog/query/oauth_web_ref_token.sql +25 -0
- package/package.json +5 -5
- package/src/file/file.service.ts +23 -1
- package/src/integration-profile/integration-profile.service.ts +64 -7
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
-- Simulacao de acesso ("simular login"): um operador abre uma sessao curta,
|
|
2
|
+
-- auditada e nao renovavel como outro usuario.
|
|
3
|
+
--
|
|
4
|
+
-- Tabela de infra de auth: vive so aqui, sem YAML em hedhog/table (ver
|
|
5
|
+
-- oauth_mobile_state_token.sql).
|
|
6
|
+
--
|
|
7
|
+
-- Usada por libraries/core/src/impersonation/impersonation.service.ts.
|
|
8
|
+
--
|
|
9
|
+
-- POR QUE impersonator_user_id E target_user_id SAO INTEGER SEM FOREIGN KEY:
|
|
10
|
+
-- as tabelas marcadas mais abaixo ja tem uma FK para "user". Uma segunda FK faz
|
|
11
|
+
-- o `prisma db pull` desambiguar os nomes das relacoes NOS DOIS LADOS, o que
|
|
12
|
+
-- renomearia `user.user_session`, `user.access_log` e `user.user_activity` e
|
|
13
|
+
-- quebraria UserService, packages/api-types e a tela de sessoes do admin. O nome
|
|
14
|
+
-- do operador e resolvido por uma segunda consulta.
|
|
15
|
+
CREATE TABLE IF NOT EXISTS impersonation_grant (
|
|
16
|
+
id SERIAL PRIMARY KEY,
|
|
17
|
+
code_hash VARCHAR(255) NOT NULL UNIQUE,
|
|
18
|
+
-- Sem FK de proposito — ver a nota no topo do arquivo.
|
|
19
|
+
impersonator_user_id INTEGER NOT NULL,
|
|
20
|
+
target_user_id INTEGER NOT NULL,
|
|
21
|
+
-- Chave do app de destino ('admin', 'class', 'partners'), resolvida contra a
|
|
22
|
+
-- setting app-urls.
|
|
23
|
+
app VARCHAR(50) NOT NULL,
|
|
24
|
+
reason VARCHAR(255) NULL,
|
|
25
|
+
-- Preenchido no resgate: aponta para a user_session simulada que nasceu daqui.
|
|
26
|
+
session_id INTEGER NULL,
|
|
27
|
+
issued_ip VARCHAR(64) NULL,
|
|
28
|
+
issued_user_agent TEXT NULL,
|
|
29
|
+
redeemed_ip VARCHAR(64) NULL,
|
|
30
|
+
redeemed_user_agent TEXT NULL,
|
|
31
|
+
consumed_at TIMESTAMPTZ NULL,
|
|
32
|
+
ended_at TIMESTAMPTZ NULL,
|
|
33
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
34
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE INDEX IF NOT EXISTS idx_impersonation_grant_impersonator
|
|
38
|
+
ON impersonation_grant (impersonator_user_id, created_at DESC);
|
|
39
|
+
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_impersonation_grant_target
|
|
41
|
+
ON impersonation_grant (target_user_id, created_at DESC);
|
|
42
|
+
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_impersonation_grant_expires
|
|
44
|
+
ON impersonation_grant (expires_at);
|
|
45
|
+
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_impersonation_grant_session
|
|
47
|
+
ON impersonation_grant (session_id)
|
|
48
|
+
WHERE session_id IS NOT NULL;
|
|
49
|
+
|
|
50
|
+
-- ---------------------------------------------------------------------------
|
|
51
|
+
-- Indices das colunas de simulacao nas tabelas que TEM YAML.
|
|
52
|
+
--
|
|
53
|
+
-- As colunas em si vem de hedhog/table/{user_session,access_log,user_activity}.yaml
|
|
54
|
+
-- e o CLI as cria; os indices NAO — o gerador nao emite indice para elas, e a
|
|
55
|
+
-- semantica parcial (WHERE ... IS NOT NULL) nao e exprimivel em YAML.
|
|
56
|
+
--
|
|
57
|
+
-- Parciais de proposito: a esmagadora maioria das linhas nao tem simulacao
|
|
58
|
+
-- nenhuma, entao um indice cheio numa coluna quase toda NULL seria bem maior sem
|
|
59
|
+
-- servir a nenhuma consulta.
|
|
60
|
+
-- ---------------------------------------------------------------------------
|
|
61
|
+
CREATE INDEX IF NOT EXISTS "user_session_impersonator_user_id_idx"
|
|
62
|
+
ON "user_session" ("impersonator_user_id")
|
|
63
|
+
WHERE "impersonator_user_id" IS NOT NULL;
|
|
64
|
+
|
|
65
|
+
CREATE INDEX IF NOT EXISTS "access_log_impersonator_user_id_idx"
|
|
66
|
+
ON "access_log" ("impersonator_user_id")
|
|
67
|
+
WHERE "impersonator_user_id" IS NOT NULL;
|
|
68
|
+
|
|
69
|
+
CREATE INDEX IF NOT EXISTS "user_activity_actor_user_id_idx"
|
|
70
|
+
ON "user_activity" ("actor_user_id")
|
|
71
|
+
WHERE "actor_user_id" IS NOT NULL;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
-- OAuth authorization code + PKCE: a API como PROVEDORA de identidade.
|
|
2
|
+
--
|
|
3
|
+
-- Tabelas de infra de auth: vivem so aqui, sem YAML em hedhog/table (ver
|
|
4
|
+
-- oauth_mobile_state_token.sql).
|
|
5
|
+
--
|
|
6
|
+
-- Usadas por libraries/core/src/authorize/authorize.service.ts e
|
|
7
|
+
-- libraries/core/src/pairing/pairing.service.ts.
|
|
8
|
+
--
|
|
9
|
+
-- A ordem interna importa: oauth_authorization_code.client_id aponta para
|
|
10
|
+
-- oauth_app_client.client_id. A FK para "user" resolve porque os arquivos de
|
|
11
|
+
-- hedhog/query sao anexados DEPOIS de todos os CREATE TABLE (ver docs/YAML-SPEC.md
|
|
12
|
+
-- secao 7).
|
|
13
|
+
CREATE TABLE IF NOT EXISTS oauth_app_client (
|
|
14
|
+
id SERIAL PRIMARY KEY,
|
|
15
|
+
client_id VARCHAR(100) NOT NULL UNIQUE,
|
|
16
|
+
name VARCHAR(255) NOT NULL,
|
|
17
|
+
-- 'public' = sem client_secret, PKCE obrigatorio (o caso de uma extensao, que
|
|
18
|
+
-- nao consegue guardar segredo).
|
|
19
|
+
client_type VARCHAR(20) NOT NULL DEFAULT 'public',
|
|
20
|
+
-- Allowlist de URIs exatas, uma por linha. A comparacao e igualdade de string:
|
|
21
|
+
-- parsing de origin quebraria para esquemas nao-http.
|
|
22
|
+
redirect_uris TEXT NOT NULL,
|
|
23
|
+
scopes TEXT NOT NULL DEFAULT 'vaults',
|
|
24
|
+
-- Reservado: hoje NAO pula a tela de consent. Pular economizaria um clique e
|
|
25
|
+
-- custaria um authorize silencioso disparavel por qualquer pagina.
|
|
26
|
+
is_first_party BOOLEAN NOT NULL DEFAULT TRUE,
|
|
27
|
+
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
28
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
29
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
-- Authorization codes de vida curta (60s), consumidos atomicamente uma unica vez.
|
|
33
|
+
CREATE TABLE IF NOT EXISTS oauth_authorization_code (
|
|
34
|
+
id BIGSERIAL PRIMARY KEY,
|
|
35
|
+
-- Guardamos so o hash: um dump do banco nao permite trocar codes por tokens.
|
|
36
|
+
code_hash VARCHAR(255) NOT NULL UNIQUE,
|
|
37
|
+
client_id VARCHAR(100) NOT NULL,
|
|
38
|
+
user_id INTEGER NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
|
|
39
|
+
redirect_uri TEXT NOT NULL,
|
|
40
|
+
scope TEXT NOT NULL,
|
|
41
|
+
code_challenge VARCHAR(255) NOT NULL,
|
|
42
|
+
code_challenge_method VARCHAR(10) NOT NULL DEFAULT 'S256',
|
|
43
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
44
|
+
consumed_at TIMESTAMPTZ NULL,
|
|
45
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_oauth_authorization_code_expires_at
|
|
49
|
+
ON oauth_authorization_code (expires_at);
|
|
50
|
+
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_oauth_authorization_code_client
|
|
52
|
+
ON oauth_authorization_code (client_id);
|
|
53
|
+
|
|
54
|
+
-- NOTA: o repositorio de referencia semeia aqui um oauth_app_client para a
|
|
55
|
+
-- extensao "HedHog Vaults" (client_id 'hedhog-extension', scope 'vaults'). Esse
|
|
56
|
+
-- seed NAO foi trazido de proposito: e dado de aplicacao, nao estrutura; depende
|
|
57
|
+
-- da library `vaults`; e o proprio redirect_uri de la ja mudou depois daquela
|
|
58
|
+
-- migration. Cada instalacao cadastra os proprios clients.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
-- Estado do fluxo OAuth mobile, duravel entre replicas da API.
|
|
2
|
+
--
|
|
3
|
+
-- Tabela de infra de auth: vive so aqui, sem YAML em hedhog/table, porque nao e
|
|
4
|
+
-- entidade de negocio e nao tem CRUD administravel. hedhog/table e para o que
|
|
5
|
+
-- ganha scaffolding.
|
|
6
|
+
--
|
|
7
|
+
-- O `state` opaco nao pode viver em memoria de processo: com mais de uma replica,
|
|
8
|
+
-- o callback do provedor cai em qualquer pod. O token e gravado com hash, amarrado
|
|
9
|
+
-- a provider + redirect_uri + flow_type, e consumido uma unica vez (consumed_at)
|
|
10
|
+
-- antes de expirar (expires_at).
|
|
11
|
+
--
|
|
12
|
+
-- Usada por libraries/core/src/oauth/oauth.service.ts.
|
|
13
|
+
CREATE TABLE IF NOT EXISTS oauth_mobile_state_token (
|
|
14
|
+
id BIGSERIAL PRIMARY KEY,
|
|
15
|
+
token_hash VARCHAR(255) NOT NULL UNIQUE,
|
|
16
|
+
provider VARCHAR(100) NOT NULL,
|
|
17
|
+
redirect_uri TEXT NOT NULL,
|
|
18
|
+
flow_type VARCHAR(20) NOT NULL,
|
|
19
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
20
|
+
consumed_at TIMESTAMPTZ NULL,
|
|
21
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
-- Lookup do callback: casa o state com o par provider + redirect_uri.
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_oauth_mobile_state_token_provider_redirect
|
|
26
|
+
ON oauth_mobile_state_token (provider, redirect_uri);
|
|
27
|
+
|
|
28
|
+
-- Varredura de expirados.
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_oauth_mobile_state_token_expires_at
|
|
30
|
+
ON oauth_mobile_state_token (expires_at);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-- Referencia curta de uso unico para o retorno do OAuth web (Microsoft Entra ID).
|
|
2
|
+
--
|
|
3
|
+
-- Tabela de infra de auth: vive so aqui, sem YAML em hedhog/table (ver
|
|
4
|
+
-- oauth_mobile_state_token.sql).
|
|
5
|
+
--
|
|
6
|
+
-- A API troca o authorization code (~2KB) do provedor no servidor, guarda o perfil
|
|
7
|
+
-- resultante cifrado sob um id curto e opaco, e devolve so esse id ao front-end,
|
|
8
|
+
-- que o resgata. Modelada a partir de oauth_mobile_state_token, pelo mesmo motivo:
|
|
9
|
+
-- ser duravel entre replicas.
|
|
10
|
+
--
|
|
11
|
+
-- Usada por libraries/core/src/oauth/oauth.service.ts.
|
|
12
|
+
CREATE TABLE IF NOT EXISTS oauth_web_ref_token (
|
|
13
|
+
id BIGSERIAL PRIMARY KEY,
|
|
14
|
+
token_hash VARCHAR(255) NOT NULL UNIQUE,
|
|
15
|
+
provider VARCHAR(100) NOT NULL,
|
|
16
|
+
flow_type VARCHAR(20) NOT NULL,
|
|
17
|
+
app_origin TEXT NULL,
|
|
18
|
+
profile_enc TEXT NOT NULL,
|
|
19
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
20
|
+
consumed_at TIMESTAMPTZ NULL,
|
|
21
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_oauth_web_ref_token_expires_at
|
|
25
|
+
ON oauth_web_ref_token (expires_at);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hed-hog/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.399",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -45,13 +45,13 @@
|
|
|
45
45
|
"speakeasy": "^2.0.0",
|
|
46
46
|
"ua-parser-js": "^2.0.10",
|
|
47
47
|
"uuid": "^11.1.0",
|
|
48
|
+
"@hed-hog/vault-crypto": "0.0.1",
|
|
48
49
|
"@hed-hog/api-prisma": "0.0.7",
|
|
49
|
-
"@hed-hog/api-
|
|
50
|
+
"@hed-hog/api-types": "0.0.2",
|
|
50
51
|
"@hed-hog/api-mail": "0.0.11",
|
|
51
|
-
"@hed-hog/
|
|
52
|
-
"@hed-hog/api": "0.0.9",
|
|
52
|
+
"@hed-hog/api-locale": "0.0.15",
|
|
53
53
|
"@hed-hog/api-pagination": "0.0.8",
|
|
54
|
-
"@hed-hog/api
|
|
54
|
+
"@hed-hog/api": "0.0.9"
|
|
55
55
|
},
|
|
56
56
|
"exports": {
|
|
57
57
|
".": {
|
package/src/file/file.service.ts
CHANGED
|
@@ -1197,7 +1197,29 @@ export class FileService implements OnModuleInit {
|
|
|
1197
1197
|
name: string,
|
|
1198
1198
|
buffer?: Buffer,
|
|
1199
1199
|
): Promise<string> {
|
|
1200
|
-
if (mimetype && mimetype.startsWith('image/'))
|
|
1200
|
+
if (mimetype && mimetype.startsWith('image/')) {
|
|
1201
|
+
// O mimetype que chega do navegador vem da EXTENSÃO do arquivo, não do
|
|
1202
|
+
// conteúdo: um PNG renomeado para `.svg` é anunciado como `image/svg+xml`.
|
|
1203
|
+
// Gravar isso faz o endpoint de imagem servir bytes PNG sob Content-Type
|
|
1204
|
+
// de SVG e, com o `nosniff` que a API envia, o navegador não corrige
|
|
1205
|
+
// sozinho — a imagem simplesmente não renderiza, sem erro nenhum.
|
|
1206
|
+
// Com os bytes em mãos, quem decide é a assinatura do arquivo.
|
|
1207
|
+
if (buffer) {
|
|
1208
|
+
const sniffed =
|
|
1209
|
+
await this.imageOptimizationService.detectImageMimetype(buffer);
|
|
1210
|
+
// Só sobrepõe quando o sniffing reconheceu o formato: um `null` significa
|
|
1211
|
+
// que o sharp não entendeu o buffer, e aí o declarado ainda é o melhor
|
|
1212
|
+
// palpite disponível.
|
|
1213
|
+
if (sniffed && sniffed !== mimetype) {
|
|
1214
|
+
this.logger.warn(
|
|
1215
|
+
`Upload "${name}" foi anunciado como ${mimetype}, mas os bytes são ${sniffed}. Persistindo o tipo real.`,
|
|
1216
|
+
);
|
|
1217
|
+
return sniffed;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
return mimetype;
|
|
1222
|
+
}
|
|
1201
1223
|
// Tipos concretos não-imagem (video, pdf, ...) são mantidos como estão.
|
|
1202
1224
|
if (mimetype && mimetype !== 'application/octet-stream') return mimetype;
|
|
1203
1225
|
|
|
@@ -165,18 +165,30 @@ export class IntegrationProfileService {
|
|
|
165
165
|
const items = Array.isArray(result.data) ? result.data : [];
|
|
166
166
|
if (items.length > 0) {
|
|
167
167
|
const ids = items.map((p: any) => p.id as number);
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
const usageTables = await this.getExistingUsageTables();
|
|
169
|
+
|
|
170
|
+
const counts =
|
|
171
|
+
usageTables.length === 0
|
|
172
|
+
? []
|
|
173
|
+
: await this.prismaService.$queryRawUnsafe<
|
|
174
|
+
{ id: number; uses_count: bigint }[]
|
|
175
|
+
>(
|
|
176
|
+
`
|
|
171
177
|
SELECT
|
|
172
178
|
ip.id,
|
|
173
179
|
(
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
${usageTables
|
|
181
|
+
.map(
|
|
182
|
+
(table) =>
|
|
183
|
+
`COALESCE((SELECT COUNT(*)::int FROM ${table} WHERE integration_profile_id = ip.id), 0)`,
|
|
184
|
+
)
|
|
185
|
+
.join(' + ')}
|
|
176
186
|
) AS uses_count
|
|
177
187
|
FROM integration_profile ip
|
|
178
|
-
WHERE ip.id = ANY($
|
|
179
|
-
|
|
188
|
+
WHERE ip.id = ANY($1::int[])
|
|
189
|
+
`,
|
|
190
|
+
ids,
|
|
191
|
+
);
|
|
180
192
|
|
|
181
193
|
const countMap = new Map(
|
|
182
194
|
counts.map((r) => [r.id, Number(r.uses_count)]),
|
|
@@ -196,6 +208,51 @@ export class IntegrationProfileService {
|
|
|
196
208
|
return result;
|
|
197
209
|
}
|
|
198
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Tabelas que referenciam um perfil de integração, para o contador `uses_count`.
|
|
213
|
+
*
|
|
214
|
+
* Nem toda instalação tem todas: `campaign` vem de uma library de marketing que
|
|
215
|
+
* pode não estar instalada, enquanto `webhook_action` é do core. O Postgres
|
|
216
|
+
* valida os nomes de relação ao analisar a query, então citar uma tabela ausente
|
|
217
|
+
* não devolve zero — derruba o `$queryRaw` inteiro com 42P01 e, com ele, toda a
|
|
218
|
+
* listagem de perfis (500, sem nenhum perfil visível na tela).
|
|
219
|
+
*/
|
|
220
|
+
private static readonly USAGE_TABLES = ['campaign', 'webhook_action'];
|
|
221
|
+
|
|
222
|
+
/** Cacheado: o conjunto de tabelas só muda com migration, não entre requests. */
|
|
223
|
+
private existingUsageTables: string[] | null = null;
|
|
224
|
+
|
|
225
|
+
private async getExistingUsageTables(): Promise<string[]> {
|
|
226
|
+
if (this.existingUsageTables) return this.existingUsageTables;
|
|
227
|
+
|
|
228
|
+
const rows = await this.prismaService.$queryRaw<{ table_name: string }[]>`
|
|
229
|
+
SELECT table_name
|
|
230
|
+
FROM information_schema.tables
|
|
231
|
+
WHERE table_schema = 'public'
|
|
232
|
+
AND table_name = ANY(${IntegrationProfileService.USAGE_TABLES}::text[])
|
|
233
|
+
`;
|
|
234
|
+
|
|
235
|
+
const found = new Set(rows.map((r) => r.table_name));
|
|
236
|
+
|
|
237
|
+
// Filtrar a constante (em vez de usar o retorno do banco direto) mantém os
|
|
238
|
+
// nomes interpolados na query restritos a esta lista fixa.
|
|
239
|
+
this.existingUsageTables = IntegrationProfileService.USAGE_TABLES.filter(
|
|
240
|
+
(table) => found.has(table),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const missing = IntegrationProfileService.USAGE_TABLES.filter(
|
|
244
|
+
(table) => !found.has(table),
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
if (missing.length > 0) {
|
|
248
|
+
this.logger.log(
|
|
249
|
+
`uses_count ignora tabela(s) ausente(s) nesta instalação: ${missing.join(', ')}.`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return this.existingUsageTables;
|
|
254
|
+
}
|
|
255
|
+
|
|
199
256
|
/**
|
|
200
257
|
* Resolve o config do perfil de integração ATIVO para um par tipo/provider — uso
|
|
201
258
|
* server-side por serviços que precisam das credenciais (ex.: InfraScaler do
|