@omnizap-system/omnizap 2.6.1 → 2.6.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/.env.example +78 -9
- package/.github/workflows/ci.yml +3 -3
- package/.github/workflows/security-runner-hardening.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +1 -0
- package/app/config/index.js +6 -0
- package/app/configParts/adminIdentity.js +36 -7
- package/app/configParts/baileysConfig.js +343 -56
- package/app/configParts/groupUtils.js +226 -0
- package/app/configParts/loggerConfig.js +185 -0
- package/app/configParts/messagePersistenceService.js +307 -5
- package/app/configParts/sessionConfig.js +242 -0
- package/app/connection/baileysCompatibility.test.js +10 -1
- package/app/connection/baileysDbAuthState.js +205 -9
- package/app/connection/baileysLibsignalPatch.js +210 -0
- package/app/connection/groupOwnerWriteStateResolver.js +141 -0
- package/app/connection/socketController.js +694 -123
- package/app/connection/socketController.multiSession.test.js +128 -0
- package/app/controllers/messageController.js +1 -1
- package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
- package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +96 -4
- package/app/controllers/messageProcessingPipeline.js +90 -9
- package/app/controllers/messageProcessingPipeline.test.js +202 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +856 -14
- package/app/modules/adminModule/groupCommandHandlers.test.js +375 -9
- package/app/modules/adminModule/groupWarningRepository.js +152 -0
- package/app/modules/aiModule/AGENT.md +47 -30
- package/app/modules/aiModule/aiConfigRuntime.js +1 -0
- package/app/modules/aiModule/catCommand.js +132 -25
- package/app/modules/aiModule/commandConfig.json +114 -28
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
- package/app/modules/gameModule/AGENT.md +1 -1
- package/app/modules/gameModule/commandConfig.json +29 -0
- package/app/modules/menuModule/AGENT.md +1 -1
- package/app/modules/menuModule/commandConfig.json +45 -10
- package/app/modules/menuModule/menuCatalogService.js +190 -0
- package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
- package/app/modules/menuModule/menuDynamicService.js +511 -0
- package/app/modules/menuModule/menuDynamicService.test.js +141 -0
- package/app/modules/menuModule/menus.js +36 -5
- package/app/modules/playModule/AGENT.md +10 -5
- package/app/modules/playModule/commandConfig.json +74 -16
- package/app/modules/playModule/playCommandConstants.js +13 -7
- package/app/modules/playModule/playCommandCore.js +4 -6
- package/app/modules/playModule/{playCommandYtDlpClient.js → playCommandMediaClient.js} +684 -332
- package/app/modules/playModule/playConfigRuntime.js +5 -6
- package/app/modules/playModule/playModuleCriticalFlows.test.js +44 -59
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerPackModule/AGENT.md +1 -1
- package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
- package/app/modules/stickerPackModule/commandConfig.json +29 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +1 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +78 -57
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/geminiService.js +131 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/moduleAiHelpCoreService.js +33 -4
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +51 -21
- package/app/services/messaging/newsBroadcastService.js +843 -27
- package/app/services/multiSession/assignmentBalancerService.js +452 -0
- package/app/services/multiSession/groupOwnershipRepository.js +346 -0
- package/app/services/multiSession/groupOwnershipService.js +809 -0
- package/app/services/multiSession/groupOwnershipService.test.js +317 -0
- package/app/services/multiSession/sessionRegistryService.js +239 -0
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +391 -25
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/database/index.js +6 -0
- package/database/migrations/20260307_d0_hardening_down.sql +1 -1
- package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
- package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
- package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
- package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
- package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
- package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
- package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
- package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
- package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
- package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
- package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
- package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
- package/database/schema.sql +102 -1
- package/docker-compose.yml +4 -1
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +2 -2
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
- package/docs/security/omnizap-static-security-headers.conf +25 -0
- package/ecosystem.prod.config.cjs +31 -11
- package/index.js +52 -18
- package/observability/alert-rules.yml +20 -0
- package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
- package/observability/mysql-setup.sql +4 -4
- package/observability/system-admin-observability.md +26 -0
- package/package.json +14 -6
- package/public/comandos/commands-catalog.json +2253 -78
- package/public/css/payments-react.css +478 -0
- package/public/js/apps/commandsReactApp.js +267 -87
- package/public/js/apps/createPackApp.js +3 -3
- package/public/js/apps/homeReactApp.js +2 -2
- package/public/js/apps/paymentsCancelReactApp.js +45 -0
- package/public/js/apps/paymentsReactApp.js +399 -0
- package/public/js/apps/paymentsSuccessReactApp.js +148 -0
- package/public/js/apps/stickersApp.js +255 -103
- package/public/js/apps/termsReactApp.js +57 -8
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +96 -47
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/pagamentos-cancelado.html +21 -0
- package/public/pages/pagamentos-sucesso.html +21 -0
- package/public/pages/pagamentos.html +30 -0
- package/public/pages/politica-de-privacidade.html +1 -1
- package/public/pages/stickers.html +5 -5
- package/public/pages/termos-de-uso-texto-integral.html +1 -1
- package/public/pages/termos-de-uso.html +1 -1
- package/public/pages/user-password-reset.html +3 -4
- package/public/pages/user-systemadm.html +8 -462
- package/public/pages/user.html +1 -1
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +13 -0
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +155 -0
- package/scripts/new-whatsapp-session.sh +564 -0
- package/scripts/security-web-surface-check.mjs +218 -0
- package/server/controllers/admin/adminPanelHandlers.js +253 -3
- package/server/controllers/admin/systemAdminController.js +254 -0
- package/server/controllers/payments/paymentsController.js +731 -0
- package/server/controllers/sticker/stickerCatalogController.js +9 -23
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/stickerCatalogSystemContext.js +27 -6
- package/server/controllers/system/systemController.js +228 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailAutomationRuntime.js +36 -1
- package/server/email/emailAutomationService.js +42 -1
- package/server/email/emailTemplateService.js +140 -33
- package/server/http/httpRequestUtils.js +18 -14
- package/server/http/httpServer.js +8 -4
- package/server/middleware/securityHeaders.js +35 -3
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +50 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/payments/paymentsRouter.js +47 -0
- package/server/routes/static/staticPageRouter.js +30 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +5 -1
- package/app/modules/playModule/local/installYtDlp.js +0 -25
- package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
2
|
+
import logger from '#logger';
|
|
3
|
+
import { resolveAdminPhoneFromEnv, resolveBotPhoneFromEnv, resolveSupportPhoneFromEnv } from '../../utils/whatsapp/contactEnv.js';
|
|
2
4
|
const DEFAULT_SITE_ORIGIN = 'https://omnizap.shop';
|
|
3
5
|
const DEFAULT_BRAND_NAME = 'OmniZap';
|
|
6
|
+
const DEFAULT_BRAND_LOGO_PATH = '/assets/images/brand-logo-128.webp';
|
|
4
7
|
|
|
5
8
|
const resolveSiteOrigin = () =>
|
|
6
9
|
String(process.env.SITE_ORIGIN || process.env.WHATSAPP_LOGIN_BASE_URL || DEFAULT_SITE_ORIGIN)
|
|
@@ -27,6 +30,24 @@ const escapeHtml = (value) =>
|
|
|
27
30
|
.replace(/"/g, '"')
|
|
28
31
|
.replace(/'/g, ''');
|
|
29
32
|
|
|
33
|
+
const summarizePayloadKeys = (value, maxItems = 24) => {
|
|
34
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return [];
|
|
35
|
+
return Object.keys(value)
|
|
36
|
+
.map((key) =>
|
|
37
|
+
String(key || '')
|
|
38
|
+
.trim()
|
|
39
|
+
.slice(0, 64),
|
|
40
|
+
)
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.slice(0, maxItems);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const clipTemplatePreview = (value, maxLength = 140) =>
|
|
46
|
+
String(value || '')
|
|
47
|
+
.replace(/\s+/g, ' ')
|
|
48
|
+
.trim()
|
|
49
|
+
.slice(0, maxLength);
|
|
50
|
+
|
|
30
51
|
const normalizeHttpUrl = (value, fallback = '') => {
|
|
31
52
|
const normalized = String(value || '')
|
|
32
53
|
.trim()
|
|
@@ -47,6 +68,19 @@ const normalizeEmailAddress = (value) => {
|
|
|
47
68
|
return candidate.slice(0, 255);
|
|
48
69
|
};
|
|
49
70
|
|
|
71
|
+
const resolveBrandLogoUrl = ({ siteOrigin = '', payloadLogoUrl = '' } = {}) => {
|
|
72
|
+
const fallbackLogoUrl = normalizeHttpUrl(`${String(siteOrigin || DEFAULT_SITE_ORIGIN).replace(/\/+$/, '')}${DEFAULT_BRAND_LOGO_PATH}`, `${DEFAULT_SITE_ORIGIN}${DEFAULT_BRAND_LOGO_PATH}`);
|
|
73
|
+
|
|
74
|
+
const explicitLogoUrl = normalizeHttpUrl(payloadLogoUrl || process.env.EMAIL_BRAND_LOGO_URL || '', '');
|
|
75
|
+
if (!explicitLogoUrl) return fallbackLogoUrl;
|
|
76
|
+
|
|
77
|
+
const explicitLower = explicitLogoUrl.toLowerCase();
|
|
78
|
+
const looksLikeFavicon = explicitLower.endsWith('/favicon.ico') || explicitLower.includes('/favicon-');
|
|
79
|
+
if (looksLikeFavicon) return fallbackLogoUrl;
|
|
80
|
+
|
|
81
|
+
return explicitLogoUrl;
|
|
82
|
+
};
|
|
83
|
+
|
|
50
84
|
const normalizePhoneDigits = (value, maxLength = 20) =>
|
|
51
85
|
String(value || '')
|
|
52
86
|
.replace(/\D+/g, '')
|
|
@@ -77,7 +111,7 @@ const resolveBrandConfig = (payload = {}) => {
|
|
|
77
111
|
const supportFallback = `${siteOrigin}/termos-de-uso/`;
|
|
78
112
|
const replyToAddress = normalizeEmailAddress(payload?.replyTo || process.env.SMTP_REPLY_TO || process.env.EMAIL_REPLY_TO || process.env.MAIL_REPLY_TO || '');
|
|
79
113
|
const fromAddress = normalizeEmailAddress(process.env.SMTP_FROM || process.env.EMAIL_FROM || process.env.MAIL_FROM || process.env.SMTP_USER || process.env.EMAIL_USER || process.env.MAIL_USER || '');
|
|
80
|
-
const supportPhoneCandidate = normalizePhoneDigits(payload?.supportPhone ||
|
|
114
|
+
const supportPhoneCandidate = normalizePhoneDigits(payload?.supportPhone || resolveSupportPhoneFromEnv({ fallback: resolveAdminPhoneFromEnv({ fallback: '' }) }) || '', 20);
|
|
81
115
|
const supportPhoneDigits = isLikelyPhoneDigits(supportPhoneCandidate) ? supportPhoneCandidate : '';
|
|
82
116
|
const supportPhonePn = formatPhonePn(supportPhoneDigits);
|
|
83
117
|
const supportWhatsappUrl = supportPhoneDigits ? `https://wa.me/${supportPhoneDigits}` : '';
|
|
@@ -88,7 +122,10 @@ const resolveBrandConfig = (payload = {}) => {
|
|
|
88
122
|
siteOrigin,
|
|
89
123
|
brandName: normalizeText(payload?.brandName || process.env.EMAIL_BRAND_NAME || DEFAULT_BRAND_NAME, 80) || DEFAULT_BRAND_NAME,
|
|
90
124
|
brandTagline: normalizeText(payload?.brandTagline || process.env.EMAIL_BRAND_TAGLINE || 'Automação profissional para WhatsApp.', 120) || null,
|
|
91
|
-
brandLogoUrl:
|
|
125
|
+
brandLogoUrl: resolveBrandLogoUrl({
|
|
126
|
+
siteOrigin,
|
|
127
|
+
payloadLogoUrl: payload?.brandLogoUrl || payload?.logoUrl || '',
|
|
128
|
+
}),
|
|
92
129
|
supportUrl: resolvedSupportUrl,
|
|
93
130
|
supportLabel: resolvedSupportLabel || 'Central de suporte',
|
|
94
131
|
supportEmail: replyToAddress || fromAddress || '',
|
|
@@ -127,53 +164,75 @@ const renderEmailLayout = ({ payload = {}, preheader = '', heading = '', greetin
|
|
|
127
164
|
const safeSecurityNote = normalizeText(securityNote, 220);
|
|
128
165
|
const safeFooterMessage = normalizeText(footerMessage, 220);
|
|
129
166
|
const year = __timeNow().getUTCFullYear();
|
|
167
|
+
const generatedAt = __timeNowIso();
|
|
130
168
|
|
|
131
|
-
const logoBlock = brand.brandLogoUrl ? `<img src="${escapeHtml(brand.brandLogoUrl)}" alt="${escapeHtml(brand.brandName)}" width="
|
|
169
|
+
const logoBlock = brand.brandLogoUrl ? `<img src="${escapeHtml(brand.brandLogoUrl)}" alt="${escapeHtml(brand.brandName)}" width="138" style="display:block;border:0;outline:none;text-decoration:none;height:auto;" />` : `<div style="display:inline-block;font-size:24px;font-weight:800;line-height:1.1;color:#ffffff;letter-spacing:0.3px;">${escapeHtml(brand.brandName)}</div>`;
|
|
132
170
|
|
|
133
|
-
const
|
|
134
|
-
const
|
|
171
|
+
const headingBlock = safeHeading ? `<h1 style="margin:0;color:#0f172a;font-size:27px;line-height:1.2;font-weight:800;letter-spacing:0.1px;">${escapeHtml(safeHeading)}</h1>` : '';
|
|
172
|
+
const greetingBlock = safeGreeting ? `<p style="margin:0 0 10px;color:#0f172a;font-size:16px;font-weight:700;line-height:1.6;">${escapeHtml(safeGreeting)}</p>` : '';
|
|
173
|
+
const introBlock = safeIntro ? `<p style="margin:0 0 14px;color:#334155;font-size:15px;line-height:1.7;">${escapeHtml(safeIntro)}</p>` : '';
|
|
135
174
|
const bodyBlock = renderParagraphsHtml(body);
|
|
136
175
|
|
|
137
176
|
const ctaBlock =
|
|
138
177
|
safeCtaUrl && safeCtaLabel
|
|
139
178
|
? `
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
179
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:14px 0 8px;">
|
|
180
|
+
<tr>
|
|
181
|
+
<td align="center" bgcolor="#1d4ed8" style="border-radius:10px;">
|
|
182
|
+
<a href="${escapeHtml(safeCtaUrl)}" style="display:inline-block;padding:13px 22px;font-size:15px;line-height:1.2;font-weight:700;color:#ffffff;text-decoration:none;letter-spacing:0.2px;">${escapeHtml(safeCtaLabel)}</a>
|
|
183
|
+
</td>
|
|
184
|
+
</tr>
|
|
185
|
+
</table>
|
|
186
|
+
`.trim()
|
|
148
187
|
: '';
|
|
149
188
|
|
|
150
|
-
const ctaHintBlock = safeCtaHint ? `<p style="margin:
|
|
151
|
-
const secondaryCtaBlock =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
189
|
+
const ctaHintBlock = safeCtaHint ? `<p style="margin:6px 0 0;color:#64748b;font-size:13px;line-height:1.6;">${escapeHtml(safeCtaHint)}</p>` : '';
|
|
190
|
+
const secondaryCtaBlock =
|
|
191
|
+
safeSecondaryCtaLabel && safeSecondaryCtaUrl
|
|
192
|
+
? `
|
|
193
|
+
<p style="margin:12px 0 0;color:#334155;font-size:13px;line-height:1.65;">
|
|
194
|
+
${escapeHtml(safeSecondaryCtaLabel)}:
|
|
195
|
+
<a href="${escapeHtml(safeSecondaryCtaUrl)}" style="color:#1d4ed8;text-decoration:none;font-weight:600;">${escapeHtml(safeSecondaryCtaUrl)}</a>
|
|
196
|
+
</p>
|
|
197
|
+
`.trim()
|
|
198
|
+
: '';
|
|
199
|
+
const fallbackLinkBlock = safeCtaUrl ? `<p style="margin:14px 0 0;color:#64748b;font-size:12px;line-height:1.7;word-break:break-all;">Se o botão não funcionar, copie e cole este link no navegador: ${escapeHtml(safeCtaUrl)}</p>` : '';
|
|
200
|
+
const securityNoteBlock = safeSecurityNote ? `<p style="margin:16px 0 0;padding:12px 13px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;color:#475569;font-size:12px;line-height:1.65;">${escapeHtml(safeSecurityNote)}</p>` : '';
|
|
201
|
+
const brandTaglineBlock = brand.brandTagline ? `<p style="margin:8px 0 0;color:#cbd5e1;font-size:13px;line-height:1.6;">${escapeHtml(brand.brandTagline)}</p>` : '';
|
|
155
202
|
const supportEmailLine = brand.supportEmail ? `<span style="display:block;margin-top:6px;">E-mail: <a href="mailto:${escapeHtml(brand.supportEmail)}" style="color:#2563eb;text-decoration:none;">${escapeHtml(brand.supportEmail)}</a></span>` : '';
|
|
156
|
-
const footerMessageBlock = safeFooterMessage ? `<span style="display:block;margin-top:
|
|
157
|
-
const taglineBlock = brand.brandTagline ? `<p style="margin:8px 0 0;color:#64748b;font-size:13px;line-height:1.6;">${escapeHtml(brand.brandTagline)}</p>` : '';
|
|
203
|
+
const footerMessageBlock = safeFooterMessage ? `<span style="display:block;margin-top:7px;color:#64748b;">${escapeHtml(safeFooterMessage)}</span>` : '';
|
|
158
204
|
|
|
159
205
|
return `
|
|
160
206
|
<!doctype html>
|
|
161
207
|
<html lang="pt-BR">
|
|
162
|
-
<body style="margin:0;padding:0;background:#
|
|
208
|
+
<body style="margin:0;padding:0;background:#eef2f8;font-family:'Segoe UI',Roboto,Helvetica,Arial,sans-serif;">
|
|
163
209
|
<div style="display:none;max-height:0;overflow:hidden;opacity:0;color:transparent;">${escapeHtml(safePreheader)}</div>
|
|
164
|
-
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background:#
|
|
210
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background:#eef2f8;padding:26px 10px;">
|
|
165
211
|
<tr>
|
|
166
212
|
<td align="center">
|
|
167
213
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="max-width:640px;">
|
|
168
214
|
<tr>
|
|
169
|
-
<td
|
|
170
|
-
|
|
171
|
-
|
|
215
|
+
<td style="background:#0f172a;border-radius:16px 16px 0 0;padding:22px 24px 20px;">
|
|
216
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
|
217
|
+
<tr>
|
|
218
|
+
<td align="left" valign="middle" style="padding-right:12px;">
|
|
219
|
+
${logoBlock}
|
|
220
|
+
${brandTaglineBlock}
|
|
221
|
+
</td>
|
|
222
|
+
<td align="right" valign="middle" style="white-space:nowrap;">
|
|
223
|
+
<span style="display:inline-block;padding:6px 10px;border:1px solid rgba(148,163,184,0.45);border-radius:999px;font-size:11px;font-weight:700;letter-spacing:0.35px;text-transform:uppercase;color:#e2e8f0;">Comunicado oficial</span>
|
|
224
|
+
</td>
|
|
225
|
+
</tr>
|
|
226
|
+
</table>
|
|
227
|
+
</td>
|
|
228
|
+
</tr>
|
|
229
|
+
<tr>
|
|
230
|
+
<td style="background:#ffffff;border-left:1px solid #d9e2ef;border-right:1px solid #d9e2ef;padding:26px 24px 10px;">
|
|
231
|
+
${headingBlock}
|
|
172
232
|
</td>
|
|
173
233
|
</tr>
|
|
174
234
|
<tr>
|
|
175
|
-
<td style="background:#ffffff;border:1px solid #
|
|
176
|
-
<h1 style="margin:0 0 14px;color:#0f172a;font-size:25px;line-height:1.25;">${escapeHtml(safeHeading)}</h1>
|
|
235
|
+
<td style="background:#ffffff;border-left:1px solid #d9e2ef;border-right:1px solid #d9e2ef;padding:0 24px 22px;">
|
|
177
236
|
${greetingBlock}
|
|
178
237
|
${introBlock}
|
|
179
238
|
${bodyBlock}
|
|
@@ -185,11 +244,12 @@ const renderEmailLayout = ({ payload = {}, preheader = '', heading = '', greetin
|
|
|
185
244
|
</td>
|
|
186
245
|
</tr>
|
|
187
246
|
<tr>
|
|
188
|
-
<td style="padding:14px
|
|
247
|
+
<td style="background:#ffffff;border:1px solid #d9e2ef;border-top:none;border-radius:0 0 16px 16px;padding:14px 24px 18px;color:#64748b;font-size:12px;line-height:1.7;">
|
|
189
248
|
<span style="display:block;">${escapeHtml(brand.brandName)} © ${year}. Todos os direitos reservados.</span>
|
|
190
249
|
<span style="display:block;margin-top:6px;">Central de suporte: <a href="${escapeHtml(brand.supportUrl)}" style="color:#2563eb;text-decoration:none;">${escapeHtml(brand.supportLabel)}</a></span>
|
|
191
250
|
${supportEmailLine}
|
|
192
251
|
${footerMessageBlock}
|
|
252
|
+
<span style="display:block;margin-top:7px;color:#94a3b8;font-size:11px;">Gerado em ${escapeHtml(generatedAt)} UTC.</span>
|
|
193
253
|
</td>
|
|
194
254
|
</tr>
|
|
195
255
|
</table>
|
|
@@ -216,7 +276,7 @@ const resolveNavigationLinks = (payload = {}) => {
|
|
|
216
276
|
};
|
|
217
277
|
|
|
218
278
|
const resolveWelcomeBotWhatsApp = (payload = {}) => {
|
|
219
|
-
const botPhoneCandidate = normalizePhoneDigits(payload?.botPhone || payload?.botNumber || process.env.EMAIL_WELCOME_BOT_PHONE ||
|
|
279
|
+
const botPhoneCandidate = normalizePhoneDigits(payload?.botPhone || payload?.botNumber || process.env.EMAIL_WELCOME_BOT_PHONE || resolveBotPhoneFromEnv({ fallback: resolveSupportPhoneFromEnv({ fallback: '' }) }) || '', 20);
|
|
220
280
|
const botPhoneDigits = isLikelyPhoneDigits(botPhoneCandidate) ? botPhoneCandidate : '';
|
|
221
281
|
const botPhonePn = formatPhonePn(botPhoneDigits);
|
|
222
282
|
const botWhatsAppUrl = botPhoneDigits ? `https://wa.me/${botPhoneDigits}` : '';
|
|
@@ -456,19 +516,66 @@ const TEMPLATE_BUILDERS = {
|
|
|
456
516
|
};
|
|
457
517
|
|
|
458
518
|
export const renderEmailTemplate = (templateKey, payload = {}) => {
|
|
519
|
+
const renderStartedAtMs = __timeNowMs();
|
|
459
520
|
const normalizedTemplateKey = normalizeTemplateKey(templateKey);
|
|
460
|
-
|
|
521
|
+
const payloadKeys = summarizePayloadKeys(payload);
|
|
522
|
+
|
|
523
|
+
if (!normalizedTemplateKey) {
|
|
524
|
+
logger.warn('Render de template de e-mail ignorado por chave inválida.', {
|
|
525
|
+
action: 'email_template_render_invalid_key',
|
|
526
|
+
template_key_raw: clipTemplatePreview(templateKey, 64),
|
|
527
|
+
payload_keys: payloadKeys,
|
|
528
|
+
});
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
461
531
|
const builder = TEMPLATE_BUILDERS[normalizedTemplateKey];
|
|
462
|
-
if (typeof builder !== 'function')
|
|
532
|
+
if (typeof builder !== 'function') {
|
|
533
|
+
logger.warn('Template de e-mail não encontrado.', {
|
|
534
|
+
action: 'email_template_builder_not_found',
|
|
535
|
+
template_key: normalizedTemplateKey,
|
|
536
|
+
payload_keys: payloadKeys,
|
|
537
|
+
});
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
463
540
|
|
|
464
541
|
const rendered = builder(payload || {});
|
|
465
|
-
if (!rendered)
|
|
542
|
+
if (!rendered) {
|
|
543
|
+
logger.warn('Template de e-mail retornou conteúdo vazio.', {
|
|
544
|
+
action: 'email_template_builder_empty',
|
|
545
|
+
template_key: normalizedTemplateKey,
|
|
546
|
+
payload_keys: payloadKeys,
|
|
547
|
+
});
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
466
550
|
|
|
467
551
|
const subject = normalizeText(rendered.subject, 180);
|
|
468
552
|
const text = normalizeText(rendered.text, 120_000);
|
|
469
553
|
const html = normalizeText(rendered.html, 500_000);
|
|
470
554
|
|
|
471
|
-
if (!subject || (!text && !html))
|
|
555
|
+
if (!subject || (!text && !html)) {
|
|
556
|
+
logger.warn('Template de e-mail inválido após normalização.', {
|
|
557
|
+
action: 'email_template_render_invalid_output',
|
|
558
|
+
template_key: normalizedTemplateKey,
|
|
559
|
+
subject_length: subject.length,
|
|
560
|
+
text_length: text.length,
|
|
561
|
+
html_length: html.length,
|
|
562
|
+
payload_keys: payloadKeys,
|
|
563
|
+
});
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
logger.debug('Template de e-mail renderizado com sucesso.', {
|
|
568
|
+
action: 'email_template_rendered',
|
|
569
|
+
template_key: normalizedTemplateKey,
|
|
570
|
+
subject_preview: clipTemplatePreview(subject),
|
|
571
|
+
subject_length: subject.length,
|
|
572
|
+
text_length: text.length,
|
|
573
|
+
html_length: html.length,
|
|
574
|
+
has_text: Boolean(text),
|
|
575
|
+
has_html: Boolean(html),
|
|
576
|
+
payload_keys: payloadKeys,
|
|
577
|
+
render_duration_ms: Math.max(0, __timeNowMs() - renderStartedAtMs),
|
|
578
|
+
});
|
|
472
579
|
|
|
473
580
|
return {
|
|
474
581
|
subject,
|
|
@@ -225,7 +225,7 @@ export const buildCookieString = (name, value, req, options = {}) => {
|
|
|
225
225
|
return parts.join('; ');
|
|
226
226
|
};
|
|
227
227
|
|
|
228
|
-
export const
|
|
228
|
+
export const readRawBody = async (req, { maxBytes = 64 * 1024 } = {}) =>
|
|
229
229
|
new Promise((resolve, reject) => {
|
|
230
230
|
const chunks = [];
|
|
231
231
|
let total = 0;
|
|
@@ -243,20 +243,24 @@ export const readJsonBody = async (req, { maxBytes = 64 * 1024 } = {}) =>
|
|
|
243
243
|
});
|
|
244
244
|
|
|
245
245
|
req.on('end', () => {
|
|
246
|
-
|
|
247
|
-
if (!raw) {
|
|
248
|
-
resolve({});
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
try {
|
|
253
|
-
resolve(JSON.parse(raw));
|
|
254
|
-
} catch {
|
|
255
|
-
const error = new Error('JSON invalido.');
|
|
256
|
-
error.statusCode = 400;
|
|
257
|
-
reject(error);
|
|
258
|
-
}
|
|
246
|
+
resolve(Buffer.concat(chunks));
|
|
259
247
|
});
|
|
260
248
|
|
|
261
249
|
req.on('error', (error) => reject(error));
|
|
262
250
|
});
|
|
251
|
+
|
|
252
|
+
export const readJsonBody = async (req, { maxBytes = 64 * 1024 } = {}) => {
|
|
253
|
+
const rawBuffer = await readRawBody(req, { maxBytes });
|
|
254
|
+
const raw = rawBuffer.toString('utf8').trim();
|
|
255
|
+
if (!raw) {
|
|
256
|
+
return {};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
return JSON.parse(raw);
|
|
261
|
+
} catch {
|
|
262
|
+
const error = new Error('JSON invalido.');
|
|
263
|
+
error.statusCode = 400;
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
@@ -6,6 +6,7 @@ import { getMetricsServerConfig, isMetricsEnabled, recordHttpRequest, resolveRou
|
|
|
6
6
|
import { applyCachePolicy } from '../middleware/cachePolicy.js';
|
|
7
7
|
import { applySensitiveRouteRateLimit } from '../middleware/endpointRateLimit.js';
|
|
8
8
|
import { applySecurityHeaders } from '../middleware/securityHeaders.js';
|
|
9
|
+
import { shouldHandleGrafanaProxyPath } from '../routes/observability/grafanaProxyRouter.js';
|
|
9
10
|
import { getIndexRouteConfigs, routeRequest } from '../routes/indexRouter.js';
|
|
10
11
|
import { parseRequestUrl, normalizeRequestId } from './requestContext.js';
|
|
11
12
|
|
|
@@ -65,6 +66,7 @@ export const startHttpServer = () => {
|
|
|
65
66
|
userConfig: routeConfigs?.userConfig || null,
|
|
66
67
|
systemAdminConfig: routeConfigs?.systemAdminConfig || null,
|
|
67
68
|
});
|
|
69
|
+
const isGrafanaProxyRequest = shouldHandleGrafanaProxyPath(pathname, routeConfigs?.grafanaProxyConfig || null);
|
|
68
70
|
|
|
69
71
|
res.once('finish', () => {
|
|
70
72
|
recordHttpRequest({
|
|
@@ -76,10 +78,12 @@ export const startHttpServer = () => {
|
|
|
76
78
|
});
|
|
77
79
|
|
|
78
80
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
if (!isGrafanaProxyRequest) {
|
|
82
|
+
applySecurityHeaders(req, res);
|
|
83
|
+
applyCachePolicy(req, res, { pathname });
|
|
84
|
+
const allowedByRateLimit = await applySensitiveRouteRateLimit(req, res, { pathname });
|
|
85
|
+
if (!allowedByRateLimit) return;
|
|
86
|
+
}
|
|
83
87
|
|
|
84
88
|
await routeRequest(req, res, {
|
|
85
89
|
pathname,
|
|
@@ -10,23 +10,44 @@ const parseEnvBool = (value, fallback = false) => {
|
|
|
10
10
|
return fallback;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
const parseEnvList = (value) =>
|
|
14
|
+
String(value || '')
|
|
15
|
+
.split(',')
|
|
16
|
+
.map((item) => String(item || '').trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
|
|
19
|
+
const toHttpOrigin = (value) => {
|
|
20
|
+
const raw = String(value || '').trim();
|
|
21
|
+
if (!raw) return '';
|
|
22
|
+
try {
|
|
23
|
+
const parsed = new URL(raw);
|
|
24
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) return '';
|
|
25
|
+
return parsed.origin;
|
|
26
|
+
} catch {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
13
31
|
const HELMET_CSP_ENFORCE = parseEnvBool(process.env.HELMET_CONTENT_SECURITY_POLICY_ENABLED, true);
|
|
14
32
|
const BACKEND_BUILD_ID = String(process.env.OMNIZAP_BUILD_ID || '')
|
|
15
33
|
.trim()
|
|
16
34
|
.slice(0, 80);
|
|
35
|
+
const FRAME_SRC_EXTRA = Array.from(new Set([...parseEnvList(process.env.HELMET_CSP_FRAME_SRC_EXTRA), process.env.SYSTEM_ADMIN_GRAFANA_URL, process.env.GRAFANA_PUBLIC_URL].map((item) => toHttpOrigin(item)).filter(Boolean)));
|
|
17
36
|
|
|
18
37
|
const HELMET_CSP_DIRECTIVES = {
|
|
19
38
|
defaultSrc: ["'self'"],
|
|
20
39
|
baseUri: ["'self'"],
|
|
21
40
|
objectSrc: ["'none'"],
|
|
22
41
|
frameAncestors: ["'self'"],
|
|
23
|
-
|
|
42
|
+
// Google Identity Services usa submit interno para accounts.google.com/gsi/transform.
|
|
43
|
+
// Sem essa origem no form-action o login pode travar na etapa de transform.
|
|
44
|
+
formAction: ["'self'", 'https://accounts.google.com'],
|
|
24
45
|
scriptSrc: ["'self'", "'unsafe-inline'", 'https://accounts.google.com', 'https://cdn.tailwindcss.com'],
|
|
25
46
|
styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com', 'https://cdnjs.cloudflare.com'],
|
|
26
47
|
imgSrc: ["'self'", 'data:', 'blob:', 'https:'],
|
|
27
48
|
fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com', 'https://cdnjs.cloudflare.com'],
|
|
28
49
|
connectSrc: ["'self'", 'https://accounts.google.com', 'https://oauth2.googleapis.com', 'https://api.github.com'],
|
|
29
|
-
frameSrc: ["'self'", 'https://accounts.google.com'],
|
|
50
|
+
frameSrc: ["'self'", 'https://accounts.google.com', ...FRAME_SRC_EXTRA],
|
|
30
51
|
workerSrc: ["'self'", 'blob:'],
|
|
31
52
|
manifestSrc: ["'self'"],
|
|
32
53
|
};
|
|
@@ -52,6 +73,11 @@ const helmetMiddleware = helmet({
|
|
|
52
73
|
directives: HELMET_CSP_DIRECTIVES,
|
|
53
74
|
reportOnly: !HELMET_CSP_ENFORCE,
|
|
54
75
|
},
|
|
76
|
+
// Fluxos OAuth/FedCM em popup (Google GIS) podem quebrar com COOP strict.
|
|
77
|
+
// Permitimos opener em popups mantendo isolamento para navegação principal.
|
|
78
|
+
crossOriginOpenerPolicy: {
|
|
79
|
+
policy: 'same-origin-allow-popups',
|
|
80
|
+
},
|
|
55
81
|
crossOriginEmbedderPolicy: false,
|
|
56
82
|
// Mantemos permissões explícitas para browser APIs sensíveis.
|
|
57
83
|
permissionsPolicy: {
|
|
@@ -59,6 +85,7 @@ const helmetMiddleware = helmet({
|
|
|
59
85
|
geolocation: [],
|
|
60
86
|
microphone: [],
|
|
61
87
|
camera: [],
|
|
88
|
+
'identity-credentials-get': ['self'],
|
|
62
89
|
},
|
|
63
90
|
},
|
|
64
91
|
});
|
|
@@ -67,7 +94,12 @@ const applyFallbackHeaders = (res) => {
|
|
|
67
94
|
if (!res.getHeader('X-Content-Type-Options')) res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
68
95
|
if (!res.getHeader('X-Frame-Options')) res.setHeader('X-Frame-Options', 'DENY');
|
|
69
96
|
if (!res.getHeader('Referrer-Policy')) res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
70
|
-
if (!res.getHeader('Permissions-Policy'))
|
|
97
|
+
if (!res.getHeader('Permissions-Policy')) {
|
|
98
|
+
res.setHeader(
|
|
99
|
+
'Permissions-Policy',
|
|
100
|
+
'geolocation=(), microphone=(), camera=(), identity-credentials-get=(self)',
|
|
101
|
+
);
|
|
102
|
+
}
|
|
71
103
|
if (FALLBACK_CSP_HEADER && !res.getHeader('Content-Security-Policy') && !res.getHeader('Content-Security-Policy-Report-Only')) {
|
|
72
104
|
const cspHeaderName = HELMET_CSP_ENFORCE ? 'Content-Security-Policy' : 'Content-Security-Policy-Report-Only';
|
|
73
105
|
res.setHeader(cspHeaderName, FALLBACK_CSP_HEADER);
|
|
@@ -24,8 +24,10 @@ const DEFAULT_USER_SYSTEM_ADMIN_WEB_PATH = '/user/systemadm';
|
|
|
24
24
|
const DEFAULT_LEGACY_STICKER_ADMIN_WEB_PATH = '/stickers/admin';
|
|
25
25
|
const DEFAULT_SYSTEM_ADMIN_API_BASE_PATH = '/api/admin';
|
|
26
26
|
const DEFAULT_SYSTEM_ADMIN_API_SESSION_PATH = '/api/admin/session';
|
|
27
|
+
const DEFAULT_SYSTEM_ADMIN_API_MULTI_SESSION_PATH = '/api/admin/multi-session';
|
|
27
28
|
const DEFAULT_LEGACY_STICKER_ADMIN_API_BASE_PATH = '/api/sticker-packs/admin';
|
|
28
29
|
const DEFAULT_LEGACY_STICKER_ADMIN_API_SESSION_PATH = '/api/sticker-packs/admin/session';
|
|
30
|
+
const DEFAULT_LEGACY_STICKER_ADMIN_API_MULTI_SESSION_PATH = '/api/sticker-packs/admin/multi-session';
|
|
29
31
|
|
|
30
32
|
export const getSystemAdminRouterConfig = async () => {
|
|
31
33
|
const controller = await loadSystemAdminController();
|
|
@@ -35,8 +37,10 @@ export const getSystemAdminRouterConfig = async () => {
|
|
|
35
37
|
legacyWebPath: normalizeBasePath(legacyConfig.legacyWebPath, DEFAULT_LEGACY_STICKER_ADMIN_WEB_PATH),
|
|
36
38
|
apiAdminBasePath: normalizeBasePath(legacyConfig.apiAdminBasePath, DEFAULT_SYSTEM_ADMIN_API_BASE_PATH),
|
|
37
39
|
apiAdminSessionPath: normalizeBasePath(legacyConfig.apiAdminSessionPath, DEFAULT_SYSTEM_ADMIN_API_SESSION_PATH),
|
|
40
|
+
apiAdminMultiSessionPath: normalizeBasePath(legacyConfig.apiAdminMultiSessionPath, DEFAULT_SYSTEM_ADMIN_API_MULTI_SESSION_PATH),
|
|
38
41
|
legacyApiAdminBasePath: normalizeBasePath(legacyConfig.legacyApiAdminBasePath, DEFAULT_LEGACY_STICKER_ADMIN_API_BASE_PATH),
|
|
39
42
|
legacyApiAdminSessionPath: normalizeBasePath(legacyConfig.legacyApiAdminSessionPath, DEFAULT_LEGACY_STICKER_ADMIN_API_SESSION_PATH),
|
|
43
|
+
legacyApiAdminMultiSessionPath: normalizeBasePath(legacyConfig.legacyApiAdminMultiSessionPath, DEFAULT_LEGACY_STICKER_ADMIN_API_MULTI_SESSION_PATH),
|
|
40
44
|
};
|
|
41
45
|
};
|
|
42
46
|
|
|
@@ -46,8 +50,10 @@ export const shouldHandleSystemAdminPath = (pathname, systemAdminConfig = null)
|
|
|
46
50
|
legacyWebPath: DEFAULT_LEGACY_STICKER_ADMIN_WEB_PATH,
|
|
47
51
|
apiAdminBasePath: DEFAULT_SYSTEM_ADMIN_API_BASE_PATH,
|
|
48
52
|
apiAdminSessionPath: DEFAULT_SYSTEM_ADMIN_API_SESSION_PATH,
|
|
53
|
+
apiAdminMultiSessionPath: DEFAULT_SYSTEM_ADMIN_API_MULTI_SESSION_PATH,
|
|
49
54
|
legacyApiAdminBasePath: DEFAULT_LEGACY_STICKER_ADMIN_API_BASE_PATH,
|
|
50
55
|
legacyApiAdminSessionPath: DEFAULT_LEGACY_STICKER_ADMIN_API_SESSION_PATH,
|
|
56
|
+
legacyApiAdminMultiSessionPath: DEFAULT_LEGACY_STICKER_ADMIN_API_MULTI_SESSION_PATH,
|
|
51
57
|
};
|
|
52
58
|
|
|
53
59
|
if (startsWithPath(pathname, resolvedConfig.webPath)) return true;
|
|
@@ -3,12 +3,14 @@ import path from 'node:path';
|
|
|
3
3
|
import { maybeHandleMetricsRequest } from './metrics/metricsRouter.js';
|
|
4
4
|
import { maybeHandleHealthRequest, shouldHandleHealthPath } from './health/healthRouter.js';
|
|
5
5
|
import { getEmailAutomationRouterConfig, maybeHandleEmailAutomationRequest, shouldHandleEmailAutomationPath } from './email/emailAutomationRouter.js';
|
|
6
|
+
import { getPaymentsRouterConfig, maybeHandlePaymentsRequest, shouldHandlePaymentsPath } from './payments/paymentsRouter.js';
|
|
6
7
|
import { buildUserApiPaths, getUserRouterConfig, maybeHandleUserRequest, shouldHandleUserPath } from './user/userRouter.js';
|
|
7
8
|
import { getSystemAdminRouterConfig, maybeHandleSystemAdminRequest, shouldHandleSystemAdminPath } from './admin/systemAdminRouter.js';
|
|
8
9
|
import { getStickerSiteRouterConfig, maybeHandleStickerSiteRequest, shouldHandleStickerSitePath } from './sticker/stickerSiteRouter.js';
|
|
9
10
|
import { getStickerDataRouterConfig, maybeHandleStickerDataRequest, shouldHandleStickerDataPath } from './sticker/stickerDataRouter.js';
|
|
10
11
|
import { getStickerApiRouterConfig, maybeHandleStickerApiRequest, shouldHandleStickerApiPath } from './sticker/stickerApiRouter.js';
|
|
11
12
|
import { maybeHandleStaticPageRequest, shouldHandleStaticPagePath } from './static/staticPageRouter.js';
|
|
13
|
+
import { getGrafanaProxyRouterConfig, maybeHandleGrafanaProxyRequest, shouldHandleGrafanaProxyPath } from './observability/grafanaProxyRouter.js';
|
|
12
14
|
|
|
13
15
|
const startsWithPath = (pathname, prefix) => {
|
|
14
16
|
if (!pathname || !prefix) return false;
|
|
@@ -75,6 +77,16 @@ const loadEmailAutomationConfigSafe = async () => {
|
|
|
75
77
|
}
|
|
76
78
|
};
|
|
77
79
|
|
|
80
|
+
const loadPaymentsConfigSafe = async () => {
|
|
81
|
+
try {
|
|
82
|
+
return await getPaymentsRouterConfig();
|
|
83
|
+
} catch {
|
|
84
|
+
return {
|
|
85
|
+
apiBasePath: '/api/payments',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
78
90
|
const loadStickerSiteConfigSafe = async () => {
|
|
79
91
|
try {
|
|
80
92
|
return await getStickerSiteRouterConfig();
|
|
@@ -112,12 +124,28 @@ const loadStickerApiConfigSafe = async () => {
|
|
|
112
124
|
}
|
|
113
125
|
};
|
|
114
126
|
|
|
127
|
+
const loadGrafanaProxyConfigSafe = async () => {
|
|
128
|
+
try {
|
|
129
|
+
return getGrafanaProxyRouterConfig();
|
|
130
|
+
} catch {
|
|
131
|
+
return {
|
|
132
|
+
enabled: false,
|
|
133
|
+
basePath: '/api/grafana',
|
|
134
|
+
legacyBasePath: '/grafana',
|
|
135
|
+
timeoutMs: 15000,
|
|
136
|
+
target: null,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
115
141
|
export const getIndexRouteConfigs = async () => {
|
|
116
142
|
if (!indexRouteConfigsPromise) {
|
|
117
|
-
indexRouteConfigsPromise = Promise.all([loadUserConfigSafe(), loadSystemAdminConfigSafe(), loadEmailAutomationConfigSafe(), loadStickerSiteConfigSafe(), loadStickerDataConfigSafe(), loadStickerApiConfigSafe()]).then(([userConfig, systemAdminConfig, emailAutomationConfig, stickerSiteConfig, stickerDataConfig, stickerApiConfig]) => ({
|
|
143
|
+
indexRouteConfigsPromise = Promise.all([loadUserConfigSafe(), loadSystemAdminConfigSafe(), loadEmailAutomationConfigSafe(), loadPaymentsConfigSafe(), loadStickerSiteConfigSafe(), loadStickerDataConfigSafe(), loadStickerApiConfigSafe(), loadGrafanaProxyConfigSafe()]).then(([userConfig, systemAdminConfig, emailAutomationConfig, paymentsConfig, stickerSiteConfig, stickerDataConfig, stickerApiConfig, grafanaProxyConfig]) => ({
|
|
118
144
|
userConfig,
|
|
119
145
|
systemAdminConfig,
|
|
120
146
|
emailAutomationConfig,
|
|
147
|
+
paymentsConfig,
|
|
148
|
+
grafanaProxyConfig,
|
|
121
149
|
stickerConfig: {
|
|
122
150
|
...stickerSiteConfig,
|
|
123
151
|
...stickerDataConfig,
|
|
@@ -140,6 +168,8 @@ export const routeRequest = async (req, res, { pathname, url, metricsPath = '/me
|
|
|
140
168
|
const userConfig = resolvedConfigs?.userConfig || null;
|
|
141
169
|
const systemAdminConfig = resolvedConfigs?.systemAdminConfig || null;
|
|
142
170
|
const emailAutomationConfig = resolvedConfigs?.emailAutomationConfig || null;
|
|
171
|
+
const paymentsConfig = resolvedConfigs?.paymentsConfig || null;
|
|
172
|
+
const grafanaProxyConfig = resolvedConfigs?.grafanaProxyConfig || null;
|
|
143
173
|
const stickerConfig = resolvedConfigs?.stickerConfig || null;
|
|
144
174
|
|
|
145
175
|
// 1) Metrics
|
|
@@ -163,7 +193,21 @@ export const routeRequest = async (req, res, { pathname, url, metricsPath = '/me
|
|
|
163
193
|
return sendNotFound(req, res);
|
|
164
194
|
}
|
|
165
195
|
|
|
166
|
-
// 4)
|
|
196
|
+
// 4) Payments API
|
|
197
|
+
if (shouldHandlePaymentsPath(pathname, paymentsConfig)) {
|
|
198
|
+
const handled = await maybeHandlePaymentsRequest(req, res, { pathname, url });
|
|
199
|
+
if (handled) return true;
|
|
200
|
+
return sendNotFound(req, res);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 5) Grafana proxy (/api/grafana e alias /grafana)
|
|
204
|
+
if (shouldHandleGrafanaProxyPath(pathname, grafanaProxyConfig)) {
|
|
205
|
+
const handled = await maybeHandleGrafanaProxyRequest(req, res, { pathname, url, config: grafanaProxyConfig });
|
|
206
|
+
if (handled) return true;
|
|
207
|
+
return sendNotFound(req, res);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 6) User
|
|
167
211
|
const systemAdminCandidate = shouldHandleSystemAdminStep(pathname, systemAdminConfig);
|
|
168
212
|
if (shouldHandleUserStep(pathname, userConfig)) {
|
|
169
213
|
const handled = await maybeHandleUserRequest(req, res, { pathname, url });
|
|
@@ -173,14 +217,14 @@ export const routeRequest = async (req, res, { pathname, url, metricsPath = '/me
|
|
|
173
217
|
if (!systemAdminCandidate) return sendNotFound(req, res);
|
|
174
218
|
}
|
|
175
219
|
|
|
176
|
-
//
|
|
220
|
+
// 7) System admin + legacy /stickers/admin
|
|
177
221
|
if (systemAdminCandidate) {
|
|
178
222
|
const handled = await maybeHandleSystemAdminRequest(req, res, { pathname, url });
|
|
179
223
|
if (handled) return true;
|
|
180
224
|
return sendNotFound(req, res);
|
|
181
225
|
}
|
|
182
226
|
|
|
183
|
-
//
|
|
227
|
+
// 8) Sticker catalog apenas nos prefixes permitidos
|
|
184
228
|
if (shouldHandleStickerSitePath(pathname, stickerConfig)) {
|
|
185
229
|
const handled = await maybeHandleStickerSiteRequest(req, res, { pathname, url });
|
|
186
230
|
if (handled) return true;
|
|
@@ -212,14 +256,14 @@ export const routeRequest = async (req, res, { pathname, url, metricsPath = '/me
|
|
|
212
256
|
return sendNotFound(req, res);
|
|
213
257
|
}
|
|
214
258
|
|
|
215
|
-
//
|
|
259
|
+
// 9) Paginas estaticas (templates em public/pages)
|
|
216
260
|
if (shouldHandleStaticPagePath(pathname)) {
|
|
217
261
|
const handled = await maybeHandleStaticPageRequest(req, res, { pathname });
|
|
218
262
|
if (handled) return true;
|
|
219
263
|
return sendNotFound(req, res);
|
|
220
264
|
}
|
|
221
265
|
|
|
222
|
-
//
|
|
266
|
+
// 10) 404 global
|
|
223
267
|
return sendNotFound(req, res);
|
|
224
268
|
};
|
|
225
269
|
|