@omnizap-system/omnizap 2.6.1 → 2.6.2

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.
Files changed (156) hide show
  1. package/.env.example +54 -9
  2. package/.github/workflows/ci.yml +3 -3
  3. package/.github/workflows/security-runner-hardening.yml +1 -1
  4. package/.github/workflows/security-zap-full-scan.yml +1 -0
  5. package/app/config/index.js +2 -0
  6. package/app/configParts/adminIdentity.js +5 -5
  7. package/app/configParts/baileysConfig.js +226 -55
  8. package/app/configParts/groupUtils.js +5 -0
  9. package/app/configParts/messagePersistenceService.js +143 -3
  10. package/app/configParts/sessionConfig.js +157 -0
  11. package/app/connection/baileysCompatibility.test.js +1 -1
  12. package/app/connection/groupOwnerWriteStateResolver.js +109 -0
  13. package/app/connection/socketController.js +625 -124
  14. package/app/connection/socketController.multiSession.test.js +108 -0
  15. package/app/controllers/messageController.js +1 -1
  16. package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
  17. package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
  18. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
  19. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
  20. package/app/controllers/messageProcessingPipeline.js +88 -9
  21. package/app/controllers/messageProcessingPipeline.test.js +200 -0
  22. package/app/modules/adminModule/AGENT.md +1 -1
  23. package/app/modules/adminModule/commandConfig.json +3318 -1347
  24. package/app/modules/adminModule/groupCommandHandlers.js +856 -14
  25. package/app/modules/adminModule/groupCommandHandlers.test.js +375 -9
  26. package/app/modules/adminModule/groupWarningRepository.js +152 -0
  27. package/app/modules/aiModule/AGENT.md +47 -30
  28. package/app/modules/aiModule/aiConfigRuntime.js +1 -0
  29. package/app/modules/aiModule/catCommand.js +132 -25
  30. package/app/modules/aiModule/commandConfig.json +114 -28
  31. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
  32. package/app/modules/gameModule/AGENT.md +1 -1
  33. package/app/modules/gameModule/commandConfig.json +29 -0
  34. package/app/modules/menuModule/AGENT.md +1 -1
  35. package/app/modules/menuModule/commandConfig.json +45 -10
  36. package/app/modules/menuModule/menuCatalogService.js +190 -0
  37. package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
  38. package/app/modules/menuModule/menuDynamicService.js +511 -0
  39. package/app/modules/menuModule/menuDynamicService.test.js +141 -0
  40. package/app/modules/menuModule/menus.js +36 -5
  41. package/app/modules/playModule/AGENT.md +10 -5
  42. package/app/modules/playModule/commandConfig.json +74 -16
  43. package/app/modules/playModule/playCommandConstants.js +13 -7
  44. package/app/modules/playModule/playCommandCore.js +4 -6
  45. package/app/modules/playModule/{playCommandYtDlpClient.js → playCommandMediaClient.js} +684 -332
  46. package/app/modules/playModule/playConfigRuntime.js +5 -6
  47. package/app/modules/playModule/playModuleCriticalFlows.test.js +44 -59
  48. package/app/modules/quoteModule/AGENT.md +1 -1
  49. package/app/modules/quoteModule/commandConfig.json +29 -0
  50. package/app/modules/rpgPokemonModule/AGENT.md +1 -1
  51. package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
  52. package/app/modules/statsModule/AGENT.md +1 -1
  53. package/app/modules/statsModule/commandConfig.json +58 -0
  54. package/app/modules/stickerModule/AGENT.md +1 -1
  55. package/app/modules/stickerModule/commandConfig.json +145 -0
  56. package/app/modules/stickerPackModule/AGENT.md +1 -1
  57. package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
  58. package/app/modules/stickerPackModule/commandConfig.json +29 -0
  59. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +1 -1
  60. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +78 -57
  61. package/app/modules/stickerPackModule/stickerPackService.js +13 -6
  62. package/app/modules/systemMetricsModule/AGENT.md +1 -1
  63. package/app/modules/systemMetricsModule/commandConfig.json +29 -0
  64. package/app/modules/tiktokModule/AGENT.md +1 -1
  65. package/app/modules/tiktokModule/commandConfig.json +29 -0
  66. package/app/modules/userModule/AGENT.md +1 -1
  67. package/app/modules/userModule/commandConfig.json +29 -0
  68. package/app/modules/waifuPicsModule/AGENT.md +57 -27
  69. package/app/modules/waifuPicsModule/commandConfig.json +87 -0
  70. package/app/observability/metrics.js +136 -0
  71. package/app/services/ai/commandConfigEnrichmentService.js +229 -47
  72. package/app/services/ai/geminiService.js +131 -7
  73. package/app/services/ai/geminiService.test.js +59 -2
  74. package/app/services/ai/moduleAiHelpCoreService.js +33 -4
  75. package/app/services/group/groupMetadataService.js +24 -1
  76. package/app/services/infra/dbWriteQueue.js +51 -21
  77. package/app/services/messaging/newsBroadcastService.js +843 -27
  78. package/app/services/multiSession/assignmentBalancerService.js +457 -0
  79. package/app/services/multiSession/groupOwnershipRepository.js +381 -0
  80. package/app/services/multiSession/groupOwnershipService.js +890 -0
  81. package/app/services/multiSession/groupOwnershipService.test.js +309 -0
  82. package/app/services/multiSession/sessionRegistryService.js +293 -0
  83. package/app/store/aiPromptStore.js +36 -19
  84. package/app/store/groupConfigStore.js +41 -5
  85. package/app/store/premiumUserStore.js +21 -7
  86. package/app/utils/antiLink/antiLinkModule.js +352 -16
  87. package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
  88. package/database/index.js +6 -0
  89. package/database/migrations/20260307_d0_hardening_down.sql +1 -1
  90. package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
  91. package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
  92. package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
  93. package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
  94. package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
  95. package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
  96. package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
  97. package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
  98. package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
  99. package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
  100. package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
  101. package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
  102. package/database/schema.sql +102 -1
  103. package/docker-compose.yml +4 -1
  104. package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
  105. package/docs/compliance/privacy-policy-2026-03-07.md +2 -2
  106. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
  107. package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
  108. package/docs/security/omnizap-static-security-headers.conf +25 -0
  109. package/ecosystem.prod.config.cjs +31 -11
  110. package/index.js +52 -18
  111. package/observability/alert-rules.yml +20 -0
  112. package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
  113. package/observability/mysql-setup.sql +4 -4
  114. package/observability/system-admin-observability.md +26 -0
  115. package/package.json +12 -5
  116. package/public/comandos/commands-catalog.json +2253 -78
  117. package/public/js/apps/commandsReactApp.js +267 -87
  118. package/public/js/apps/createPackApp.js +3 -3
  119. package/public/js/apps/stickersApp.js +255 -103
  120. package/public/js/apps/termsReactApp.js +57 -8
  121. package/public/js/apps/userPasswordResetReactApp.js +406 -0
  122. package/public/js/apps/userReactApp.js +96 -47
  123. package/public/js/apps/userSystemAdmReactApp.js +1506 -0
  124. package/public/pages/politica-de-privacidade.html +1 -1
  125. package/public/pages/stickers.html +5 -5
  126. package/public/pages/termos-de-uso-texto-integral.html +1 -1
  127. package/public/pages/termos-de-uso.html +1 -1
  128. package/public/pages/user-password-reset.html +3 -4
  129. package/public/pages/user-systemadm.html +8 -462
  130. package/public/pages/user.html +1 -1
  131. package/scripts/clear-whatsapp-session.sh +123 -0
  132. package/scripts/core-ai-mode.mjs +163 -0
  133. package/scripts/deploy.sh +10 -0
  134. package/scripts/enrich-command-config-ux-openai.mjs +492 -0
  135. package/scripts/generate-commands-catalog.mjs +155 -0
  136. package/scripts/new-whatsapp-session.sh +317 -0
  137. package/scripts/security-web-surface-check.mjs +218 -0
  138. package/server/controllers/admin/adminPanelHandlers.js +253 -3
  139. package/server/controllers/admin/systemAdminController.js +267 -0
  140. package/server/controllers/sticker/stickerCatalogController.js +9 -23
  141. package/server/controllers/system/contactController.js +9 -17
  142. package/server/controllers/system/stickerCatalogSystemContext.js +27 -6
  143. package/server/controllers/system/systemController.js +254 -1
  144. package/server/controllers/userController.js +6 -0
  145. package/server/email/emailTemplateService.js +3 -2
  146. package/server/http/httpServer.js +8 -4
  147. package/server/middleware/securityHeaders.js +20 -1
  148. package/server/routes/admin/systemAdminRouter.js +6 -0
  149. package/server/routes/indexRouter.js +30 -6
  150. package/server/routes/observability/grafanaProxyRouter.js +254 -0
  151. package/server/routes/static/staticPageRouter.js +27 -1
  152. package/server/utils/publicContact.js +31 -0
  153. package/utils/whatsapp/contactEnv.js +39 -0
  154. package/vite.config.mjs +2 -1
  155. package/app/modules/playModule/local/installYtDlp.js +0 -25
  156. package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
@@ -138,15 +138,50 @@ CREATE TABLE IF NOT EXISTS `group_configs` (
138
138
  PRIMARY KEY (`id`)
139
139
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
140
140
 
141
+ CREATE TABLE IF NOT EXISTS `group_user_warnings` (
142
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
143
+ `group_id` varchar(255) NOT NULL,
144
+ `participant_jid` varchar(255) NOT NULL,
145
+ `warned_by_jid` varchar(255) DEFAULT NULL,
146
+ `reason` text DEFAULT NULL,
147
+ `created_at` timestamp NULL DEFAULT current_timestamp(),
148
+ PRIMARY KEY (`id`),
149
+ KEY `idx_group_user_warnings_lookup` (`group_id`,`participant_jid`,`created_at`),
150
+ KEY `idx_group_user_warnings_prune` (`group_id`,`participant_jid`,`id`)
151
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
152
+
153
+ CREATE TABLE IF NOT EXISTS `system_premium_users` (
154
+ `id` varchar(255) NOT NULL,
155
+ `created_at` timestamp NULL DEFAULT current_timestamp(),
156
+ `updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
157
+ PRIMARY KEY (`id`)
158
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
159
+
160
+ CREATE TABLE IF NOT EXISTS `system_ai_prompts` (
161
+ `id` varchar(255) NOT NULL,
162
+ `prompt` longtext NOT NULL,
163
+ `created_at` timestamp NULL DEFAULT current_timestamp(),
164
+ `updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
165
+ PRIMARY KEY (`id`)
166
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
167
+
141
168
  CREATE TABLE IF NOT EXISTS `groups_metadata` (
142
169
  `id` varchar(255) NOT NULL,
143
170
  `subject` varchar(255) DEFAULT NULL,
144
171
  `description` text DEFAULT NULL,
145
172
  `owner_jid` varchar(255) DEFAULT NULL,
146
173
  `creation` bigint(20) DEFAULT NULL,
174
+ `linked_parent_jid` varchar(255) DEFAULT NULL,
175
+ `is_community` tinyint(1) DEFAULT NULL,
176
+ `is_community_announce` tinyint(1) DEFAULT NULL,
177
+ `member_add_mode` tinyint(1) DEFAULT NULL,
178
+ `join_approval_mode` tinyint(1) DEFAULT NULL,
179
+ `addressing_mode` varchar(8) DEFAULT NULL,
147
180
  `participants` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`participants`)),
148
181
  `updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
149
- PRIMARY KEY (`id`)
182
+ PRIMARY KEY (`id`),
183
+ KEY `idx_groups_metadata_linked_parent_jid` (`linked_parent_jid`),
184
+ KEY `idx_groups_metadata_is_community_parent` (`is_community`,`linked_parent_jid`)
150
185
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
151
186
 
152
187
  CREATE TABLE IF NOT EXISTS `lid_map` (
@@ -162,6 +197,7 @@ CREATE TABLE IF NOT EXISTS `lid_map` (
162
197
 
163
198
  CREATE TABLE IF NOT EXISTS `message_analysis_event` (
164
199
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
200
+ `session_id` varchar(64) NOT NULL DEFAULT 'default',
165
201
  `message_id` varchar(255) DEFAULT NULL,
166
202
  `chat_id` varchar(255) DEFAULT NULL,
167
203
  `sender_id` varchar(255) DEFAULT NULL,
@@ -185,6 +221,10 @@ CREATE TABLE IF NOT EXISTS `message_analysis_event` (
185
221
  `created_at` timestamp NULL DEFAULT current_timestamp(),
186
222
  PRIMARY KEY (`id`),
187
223
  KEY `idx_message_analysis_created` (`created_at`),
224
+ KEY `idx_message_analysis_session_created` (`session_id`,`created_at`),
225
+ KEY `idx_message_analysis_session_chat_created` (`session_id`,`chat_id`,`created_at`),
226
+ KEY `idx_message_analysis_session_sender_created` (`session_id`,`sender_id`,`created_at`),
227
+ KEY `idx_message_analysis_session_command_created` (`session_id`,`command_name`,`created_at`),
188
228
  KEY `idx_message_analysis_chat_created` (`chat_id`,`created_at`),
189
229
  KEY `idx_message_analysis_sender_created` (`sender_id`,`created_at`),
190
230
  KEY `idx_message_analysis_command_created` (`command_name`,`created_at`),
@@ -195,6 +235,7 @@ CREATE TABLE IF NOT EXISTS `message_analysis_event` (
195
235
 
196
236
  CREATE TABLE IF NOT EXISTS `baileys_event_journal` (
197
237
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
238
+ `session_id` varchar(64) NOT NULL DEFAULT 'default',
198
239
  `event_name` varchar(64) NOT NULL,
199
240
  `socket_generation` int(10) unsigned DEFAULT NULL,
200
241
  `chat_id` varchar(255) DEFAULT NULL,
@@ -205,6 +246,10 @@ CREATE TABLE IF NOT EXISTS `baileys_event_journal` (
205
246
  `created_at` timestamp NULL DEFAULT current_timestamp(),
206
247
  PRIMARY KEY (`id`),
207
248
  KEY `idx_baileys_event_created` (`created_at`),
249
+ KEY `idx_baileys_event_session_created` (`session_id`,`created_at`),
250
+ KEY `idx_baileys_event_session_name_created` (`session_id`,`event_name`,`created_at`),
251
+ KEY `idx_baileys_event_session_chat_created` (`session_id`,`chat_id`,`created_at`),
252
+ KEY `idx_baileys_event_session_message_created` (`session_id`,`message_id`,`created_at`),
208
253
  KEY `idx_baileys_event_name_created` (`event_name`,`created_at`),
209
254
  KEY `idx_baileys_event_chat_created` (`chat_id`,`created_at`),
210
255
  KEY `idx_baileys_event_message_created` (`message_id`,`created_at`),
@@ -222,9 +267,61 @@ CREATE TABLE IF NOT EXISTS `baileys_auth_state` (
222
267
  KEY `idx_baileys_auth_state_category_updated` (`category`,`updated_at`)
223
268
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
224
269
 
270
+ CREATE TABLE IF NOT EXISTS `wa_session_registry` (
271
+ `session_id` varchar(64) NOT NULL,
272
+ `bot_jid` varchar(255) DEFAULT NULL,
273
+ `status` varchar(24) NOT NULL DEFAULT 'offline',
274
+ `capacity_weight` int(10) unsigned NOT NULL DEFAULT 1,
275
+ `current_score` decimal(12,4) NOT NULL DEFAULT 0.0000,
276
+ `last_heartbeat_at` datetime DEFAULT NULL,
277
+ `last_connected_at` datetime DEFAULT NULL,
278
+ `last_disconnected_at` datetime DEFAULT NULL,
279
+ `metadata` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),
280
+ `created_at` timestamp NOT NULL DEFAULT current_timestamp(),
281
+ `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
282
+ PRIMARY KEY (`session_id`),
283
+ KEY `idx_wa_session_registry_status_updated` (`status`,`updated_at`),
284
+ KEY `idx_wa_session_registry_heartbeat` (`last_heartbeat_at`)
285
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
286
+
287
+ CREATE TABLE IF NOT EXISTS `group_assignment` (
288
+ `group_jid` varchar(255) NOT NULL,
289
+ `owner_session_id` varchar(64) NOT NULL,
290
+ `lease_expires_at` datetime NOT NULL,
291
+ `cooldown_until` datetime DEFAULT NULL,
292
+ `assignment_version` bigint(20) unsigned NOT NULL DEFAULT 1,
293
+ `pinned` tinyint(1) NOT NULL DEFAULT 0,
294
+ `last_reason` varchar(64) DEFAULT NULL,
295
+ `created_at` timestamp NOT NULL DEFAULT current_timestamp(),
296
+ `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
297
+ PRIMARY KEY (`group_jid`),
298
+ KEY `idx_group_assignment_owner_lease` (`owner_session_id`,`lease_expires_at`),
299
+ KEY `idx_group_assignment_lease` (`lease_expires_at`),
300
+ KEY `idx_group_assignment_cooldown` (`cooldown_until`),
301
+ KEY `idx_group_assignment_pinned_updated` (`pinned`,`updated_at`),
302
+ CONSTRAINT `fk_group_assignment_owner_session` FOREIGN KEY (`owner_session_id`) REFERENCES `wa_session_registry` (`session_id`) ON UPDATE CASCADE
303
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
304
+
305
+ CREATE TABLE IF NOT EXISTS `group_assignment_history` (
306
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
307
+ `group_jid` varchar(255) NOT NULL,
308
+ `previous_session_id` varchar(64) DEFAULT NULL,
309
+ `new_session_id` varchar(64) NOT NULL,
310
+ `change_reason` varchar(64) DEFAULT NULL,
311
+ `changed_by` varchar(64) NOT NULL DEFAULT 'system',
312
+ `assignment_version` bigint(20) unsigned NOT NULL,
313
+ `metadata` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),
314
+ `created_at` timestamp NOT NULL DEFAULT current_timestamp(),
315
+ PRIMARY KEY (`id`),
316
+ KEY `idx_group_assignment_history_group_created` (`group_jid`,`created_at`),
317
+ KEY `idx_group_assignment_history_new_session_created` (`new_session_id`,`created_at`),
318
+ KEY `idx_group_assignment_history_prev_session_created` (`previous_session_id`,`created_at`)
319
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
320
+
225
321
  CREATE TABLE IF NOT EXISTS `messages` (
226
322
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
227
323
  `message_id` varchar(255) NOT NULL,
324
+ `session_id` varchar(64) NOT NULL DEFAULT 'default',
228
325
  `chat_id` varchar(255) NOT NULL,
229
326
  `sender_id` varchar(255) DEFAULT NULL,
230
327
  `canonical_sender_id` varchar(255) DEFAULT NULL,
@@ -234,6 +331,10 @@ CREATE TABLE IF NOT EXISTS `messages` (
234
331
  `created_at` timestamp NULL DEFAULT current_timestamp(),
235
332
  PRIMARY KEY (`id`),
236
333
  UNIQUE KEY `message_id` (`message_id`),
334
+ KEY `idx_messages_session_message_id` (`session_id`,`message_id`),
335
+ KEY `idx_messages_session_chat_timestamp` (`session_id`,`chat_id`,`timestamp`),
336
+ KEY `idx_messages_session_sender_timestamp` (`session_id`,`sender_id`,`timestamp`),
337
+ KEY `idx_messages_session_canonical_sender_timestamp` (`session_id`,`canonical_sender_id`,`timestamp`),
237
338
  KEY `idx_chat_timestamp` (`chat_id`,`timestamp`),
238
339
  KEY `idx_sender` (`sender_id`),
239
340
  KEY `idx_timestamp` (`timestamp`),
@@ -22,8 +22,10 @@ services:
22
22
  environment:
23
23
  - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER:-admin}
24
24
  - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin}
25
+ - GF_SECURITY_ALLOW_EMBEDDING=${GRAFANA_ALLOW_EMBEDDING:-true}
25
26
  - GF_USERS_ALLOW_SIGN_UP=false
26
27
  - GF_SERVER_ROOT_URL=${GRAFANA_ROOT_URL:-%(protocol)s://%(domain)s:%(http_port)s/}
28
+ - GF_SERVER_SERVE_FROM_SUB_PATH=${GRAFANA_SERVE_FROM_SUB_PATH:-true}
27
29
  # Opcional: se quiser setar timezone do Grafana
28
30
  - GF_DATE_FORMATS_DEFAULT_TIMEZONE=${GRAFANA_TIMEZONE:-America/Boa_Vista}
29
31
  volumes:
@@ -65,7 +67,7 @@ services:
65
67
  image: prom/mysqld-exporter:${MYSQL_EXPORTER_IMAGE_TAG:-v0.15.1}
66
68
  restart: unless-stopped
67
69
  environment:
68
- - DATA_SOURCE_NAME=${MYSQL_EXPORTER_DSN:-exporter:exporter@(host.docker.internal:3306)/}
70
+ - DATA_SOURCE_NAME=${MYSQL_EXPORTER_DSN:-exporter:exporter@unix(/run/mysqld/mysqld.sock)/}
69
71
  command:
70
72
  - "--config.my-cnf=/etc/mysql-exporter/my.cnf"
71
73
  - "--collect.global_status"
@@ -79,6 +81,7 @@ services:
79
81
  - "--collect.perf_schema.tablelocks"
80
82
  volumes:
81
83
  - ${MYSQL_EXPORTER_CNF_PATH:-./observability/mysql-exporter.cnf}:/etc/mysql-exporter/my.cnf:ro
84
+ - /run/mysqld:/run/mysqld
82
85
  ports:
83
86
  - "${MYSQL_EXPORTER_BIND_HOST:-127.0.0.1}:${MYSQL_EXPORTER_PORT:-9104}:9104"
84
87
  extra_hosts:
@@ -32,4 +32,4 @@ Definir usos permitidos e proibidos na plataforma OmniZap para reduzir risco jur
32
32
  ## 5) Canal de denúncia
33
33
 
34
34
  - Página formal: `/notice-and-takedown/`.
35
- - Contato oficial: https://wa.me/559591122954.
35
+ - Contato oficial: `https://wa.me/<WHATSAPP_SUPPORT_NUMBER>`.
@@ -12,14 +12,14 @@ Escopo: site, login web, painel, API e operação de automação do Omnizap.
12
12
  ## 1) Controlador e contato
13
13
 
14
14
  - Controlador: 59.034.123 KAIKY BRITO RIBEIRO (CNPJ 59.034.123/0001-96).
15
- - Contato de privacidade: https://wa.me/559591122954
15
+ - Contato de privacidade: `https://wa.me/<WHATSAPP_SUPPORT_NUMBER>`
16
16
  - Canal para titulares (LGPD): mensagem com assunto "DSAR LGPD" no contato oficial.
17
17
  - Para serviços de terceiros baseados em fork/self-host, o contato de privacidade deve ser solicitado diretamente ao operador da respectiva instância.
18
18
 
19
19
  ## 1.1) Encarregado pelo tratamento (LGPD art. 41)
20
20
 
21
21
  - Encarregado (DPO): Kaiky Brito Ribeiro (59.034.123 KAIKY BRITO RIBEIRO, CNPJ 59.034.123/0001-96).
22
- - Contato do encarregado: https://wa.me/559591122954 (assunto recomendado: "ENCARREGADO LGPD").
22
+ - Contato do encarregado: `https://wa.me/<WHATSAPP_SUPPORT_NUMBER>` (assunto recomendado: "ENCARREGADO LGPD").
23
23
  - Divulgação pública mantida para transparência regulatória.
24
24
 
25
25
  ## 2) Tabela de tratamento de dados
@@ -8,7 +8,7 @@ Padronizar resposta para direitos do titular: acesso, correção, exclusão, opo
8
8
 
9
9
  ## 2) Canais de entrada
10
10
 
11
- - WhatsApp oficial: https://wa.me/559591122954
11
+ - WhatsApp oficial: `https://wa.me/<WHATSAPP_SUPPORT_NUMBER>`
12
12
  - Canal interno de compliance (registro em ticket obrigatório)
13
13
 
14
14
  ## 3) Papéis e responsáveis
@@ -129,6 +129,59 @@ Resultado esperado:
129
129
  - `22`, `80`, `443` abertos;
130
130
  - `3001` e `8007` filtrados/fechados externamente.
131
131
 
132
+ ## 8) Hardening da superficie web estatica (ZAP issue #103)
133
+
134
+ Objetivo: eliminar soft-404 e alinhar headers de seguranca em rotas estaticas servidas direto pelo Nginx (`/`, `/comandos/`, `/login/`, etc.).
135
+
136
+ 1. Publicar snippet de headers estaticos:
137
+
138
+ ```bash
139
+ sudo install -D -m 0644 docs/security/omnizap-static-security-headers.conf /etc/nginx/snippets/omnizap-static-security-headers.conf
140
+ ```
141
+
142
+ 2. No `server { ... }` de producao, garantir que rotas proxy (`/api/`, `/stickers/`, `/data/`) venham antes do fallback estatico e aplicar:
143
+
144
+ ```nginx
145
+ # Nao permitir dotfiles sensiveis
146
+ location ~* (^|/)\.(?!well-known/) {
147
+ return 404;
148
+ }
149
+
150
+ # Fallback estatico sem soft-404 (nao retorna home para caminho inexistente)
151
+ location / {
152
+ try_files $uri $uri/ =404;
153
+ include /etc/nginx/snippets/omnizap-static-security-headers.conf;
154
+ }
155
+ ```
156
+
157
+ 3. Validar e recarregar:
158
+
159
+ ```bash
160
+ sudo nginx -t
161
+ sudo systemctl reload nginx
162
+ ```
163
+
164
+ 4. Validacao rapida manual:
165
+
166
+ ```bash
167
+ curl -I https://omnizap.shop/
168
+ curl -I https://omnizap.shop/notice-and-takedown/
169
+ curl -I https://omnizap.shop/.env
170
+ curl -I https://omnizap.shop/__security_probe_nonexistent_omnizap__.txt
171
+ ```
172
+
173
+ 5. Validacao automatizada (repositorio):
174
+
175
+ ```bash
176
+ npm run security:web-surface
177
+ ```
178
+
179
+ Resultado esperado:
180
+
181
+ - rotas estaticas com `Content-Security-Policy`, `X-Frame-Options`, `Strict-Transport-Security`, `X-Content-Type-Options`, `Permissions-Policy`;
182
+ - `/.env` sem `200`;
183
+ - caminho inexistente sem fallback `200` para a home.
184
+
132
185
  ## Referências
133
186
 
134
187
  - Nginx admin guide: https://nginx.org/en/docs/
@@ -0,0 +1,25 @@
1
+ # OmniZap static web hardening snippet (Nginx)
2
+ #
3
+ # Uso recomendado:
4
+ # 1) Salvar em /etc/nginx/snippets/omnizap-static-security-headers.conf
5
+ # 2) Incluir SOMENTE em blocos de location que servem conteudo estatico.
6
+ # 3) Nao incluir em locations proxy de API/catalogo que ja recebem headers do backend.
7
+ #
8
+ # Exemplo:
9
+ # location / {
10
+ # try_files $uri $uri/ =404;
11
+ # include /etc/nginx/snippets/omnizap-static-security-headers.conf;
12
+ # }
13
+
14
+ add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; form-action 'self'; script-src 'self' 'unsafe-inline' https://accounts.google.com https://cdn.tailwindcss.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdnjs.cloudflare.com; img-src 'self' data: blob: https:; font-src 'self' data: https://fonts.gstatic.com https://cdnjs.cloudflare.com; connect-src 'self' https://accounts.google.com https://oauth2.googleapis.com https://api.github.com; frame-src 'self' https://accounts.google.com https://omnizap.shop; worker-src 'self' blob:; manifest-src 'self'" always;
15
+ add_header Cross-Origin-Opener-Policy "same-origin" always;
16
+ add_header Cross-Origin-Resource-Policy "same-origin" always;
17
+ add_header Referrer-Policy "no-referrer" always;
18
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
19
+ add_header X-Content-Type-Options "nosniff" always;
20
+ add_header X-DNS-Prefetch-Control "off" always;
21
+ add_header X-Download-Options "noopen" always;
22
+ add_header X-Frame-Options "SAMEORIGIN" always;
23
+ add_header X-Permitted-Cross-Domain-Policies "none" always;
24
+ add_header X-XSS-Protection "0" always;
25
+ add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
@@ -29,15 +29,31 @@ const baseEnv = {
29
29
  STICKER_WORKER_CURATION_CADENCE_MS: '21600000',
30
30
  STICKER_WORKER_REBUILD_CADENCE_MS: '43200000',
31
31
  STICKER_DEDICATED_WORKERS_ENABLED: 'false',
32
- STICKER_DEDICATED_WORKER_POLL_INTERVAL_MS: '30000',
33
- STICKER_DEDICATED_WORKER_IDLE_BACKOFF_MULTIPLIER: '2.5',
34
- STICKER_DEDICATED_WORKER_IDLE_MAX_POLL_INTERVAL_MS: '180000',
35
- STICKER_DEDICATED_WORKER_IDLE_JITTER_PERCENT: '15',
32
+ STICKER_DEDICATED_WORKER_POLL_INTERVAL_MS: '45000',
33
+ STICKER_DEDICATED_WORKER_IDLE_BACKOFF_MULTIPLIER: '3',
34
+ STICKER_DEDICATED_WORKER_IDLE_MAX_POLL_INTERVAL_MS: '300000',
35
+ STICKER_DEDICATED_WORKER_IDLE_JITTER_PERCENT: '12',
36
36
  STICKER_DOMAIN_EVENT_CONSUMER_ENABLED: 'false',
37
37
  STICKER_DOMAIN_EVENT_CONSUMER_POLLER_INTERVAL_MS: '15000',
38
38
  STICKER_SCORE_SNAPSHOT_ENABLED: 'false',
39
39
  STICKER_SCORE_SNAPSHOT_REFRESH_INTERVAL_MS: '1500000',
40
40
  STICKER_SCORE_SNAPSHOT_BATCH_SIZE: '20',
41
+ // Core IA defaults locked in PM2 env to avoid drift between restarts.
42
+ AI_HELP_LLM_PROVIDER: process.env.AI_HELP_LLM_PROVIDER || 'gemini',
43
+ GEMINI_AUTH_MODE: process.env.GEMINI_AUTH_MODE || 'cli',
44
+ GEMINI_MODEL: process.env.GEMINI_MODEL || 'gemini-2.5-flash',
45
+ GEMINI_CLI_COMMAND: process.env.GEMINI_CLI_COMMAND || 'gemini',
46
+ COMMAND_CONFIG_ENRICHMENT_WORKER_PROVIDER: process.env.COMMAND_CONFIG_ENRICHMENT_WORKER_PROVIDER || 'gemini',
47
+ COMMAND_CONFIG_ENRICHMENT_WORKER_MODEL: process.env.COMMAND_CONFIG_ENRICHMENT_WORKER_MODEL || 'gemini-2.5-flash',
48
+ COMMAND_CONFIG_ENRICHMENT_WORKER_GEMINI_AUTH_MODE: process.env.COMMAND_CONFIG_ENRICHMENT_WORKER_GEMINI_AUTH_MODE || 'cli',
49
+ COMMAND_CONFIG_ENRICHMENT_WORKER_TIMEOUT_MS: process.env.COMMAND_CONFIG_ENRICHMENT_WORKER_TIMEOUT_MS || '90000',
50
+ COMMAND_CONFIG_ENRICHMENT_PROVIDER_FAILURE_COOLDOWN_MS: process.env.COMMAND_CONFIG_ENRICHMENT_PROVIDER_FAILURE_COOLDOWN_MS || '900000',
51
+ AI_HELP_CONTINUOUS_LEARNING_ENABLED: process.env.AI_HELP_CONTINUOUS_LEARNING_ENABLED || 'true',
52
+ AI_HELP_CONTINUOUS_LEARNING_INTERVAL_MS: process.env.AI_HELP_CONTINUOUS_LEARNING_INTERVAL_MS || '120000',
53
+ AI_HELP_CONTINUOUS_LEARNING_BATCH_SIZE: process.env.AI_HELP_CONTINUOUS_LEARNING_BATCH_SIZE || '18',
54
+ AI_HELP_CONTINUOUS_LEARNING_MIN_AUTO_APPLY_CONFIDENCE: process.env.AI_HELP_CONTINUOUS_LEARNING_MIN_AUTO_APPLY_CONFIDENCE || '0.72',
55
+ AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_QUESTIONS_PER_COMMAND: process.env.AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_QUESTIONS_PER_COMMAND || '4',
56
+ AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_CALLS_PER_CYCLE: process.env.AI_HELP_CONTINUOUS_LEARNING_MAX_HELP_CALLS_PER_CYCLE || '90',
41
57
  METRICS_HOST: process.env.METRICS_HOST || '127.0.0.1',
42
58
  };
43
59
 
@@ -78,9 +94,9 @@ module.exports = {
78
94
  env: {
79
95
  ...baseEnv,
80
96
  STICKER_DEDICATED_WORKERS_ENABLED: 'true',
81
- STICKER_DEDICATED_WORKER_POLL_INTERVAL_MS: '25000',
82
- STICKER_DEDICATED_WORKER_IDLE_BACKOFF_MULTIPLIER: '2.4',
83
- STICKER_DEDICATED_WORKER_IDLE_MAX_POLL_INTERVAL_MS: '180000',
97
+ STICKER_DEDICATED_WORKER_POLL_INTERVAL_MS: '45000',
98
+ STICKER_DEDICATED_WORKER_IDLE_BACKOFF_MULTIPLIER: '3',
99
+ STICKER_DEDICATED_WORKER_IDLE_MAX_POLL_INTERVAL_MS: '300000',
84
100
  STICKER_CLASSIFICATION_BACKGROUND_BATCH_SIZE: '3',
85
101
  STICKER_CLASSIFICATION_BACKGROUND_CONCURRENCY: '1',
86
102
  STICKER_REPROCESS_QUEUE_ENABLED: 'false',
@@ -104,9 +120,11 @@ module.exports = {
104
120
  error_file: `logs/${appName}-worker-curation-error.log`,
105
121
  env: {
106
122
  ...baseEnv,
107
- STICKER_DEDICATED_WORKERS_ENABLED: 'false',
123
+ STICKER_DEDICATED_WORKERS_ENABLED: 'true',
108
124
  STICKER_DEDICATED_WORKER_POLL_INTERVAL_MS: '60000',
109
- STICKER_AUTO_PACK_BY_TAGS_ENABLED: 'false',
125
+ STICKER_AUTO_PACK_BY_TAGS_ENABLED: 'true',
126
+ STICKER_AUTO_PACK_BY_TAGS_MAX_SCAN_ASSETS: '1200',
127
+ STICKER_AUTO_PACK_BY_TAGS_SCAN_PASSES: '1',
110
128
  },
111
129
  kill_timeout: 5000,
112
130
  },
@@ -125,9 +143,11 @@ module.exports = {
125
143
  error_file: `logs/${appName}-worker-rebuild-error.log`,
126
144
  env: {
127
145
  ...baseEnv,
128
- STICKER_DEDICATED_WORKERS_ENABLED: 'false',
146
+ STICKER_DEDICATED_WORKERS_ENABLED: 'true',
129
147
  STICKER_DEDICATED_WORKER_POLL_INTERVAL_MS: '60000',
130
- STICKER_AUTO_PACK_BY_TAGS_ENABLED: 'false',
148
+ STICKER_AUTO_PACK_BY_TAGS_ENABLED: 'true',
149
+ STICKER_AUTO_PACK_BY_TAGS_MAX_SCAN_ASSETS: '1200',
150
+ STICKER_AUTO_PACK_BY_TAGS_SCAN_PASSES: '1',
131
151
  },
132
152
  kill_timeout: 5000,
133
153
  },
package/index.js CHANGED
@@ -18,8 +18,8 @@
18
18
  import 'dotenv/config';
19
19
 
20
20
  import logger from '#logger';
21
- import { connectToWhatsApp, getActiveSocket } from './app/connection/socketController.js';
22
- import { backfillLidMapFromMessagesOnce } from './app/config/index.js';
21
+ import { connectAllWhatsAppSessions, disconnectAllWhatsAppSessions } from './app/connection/socketController.js';
22
+ import { backfillLidMapFromMessagesOnce, getMultiSessionRuntimeConfig } from './app/config/index.js';
23
23
  import { initializeNewsBroadcastService, stopNewsBroadcastService } from './app/services/messaging/newsBroadcastService.js';
24
24
  import initializeDatabase from './database/init.js';
25
25
  import { startHttpServer, stopHttpServer } from './server/index.js';
@@ -31,7 +31,9 @@ import { startStickerPackScoreSnapshotRuntime, stopStickerPackScoreSnapshotRunti
31
31
  import { startStickerDomainEventConsumer, stopStickerDomainEventConsumer } from './app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js';
32
32
  import { startAiLearningWorker, stopAiLearningWorker } from './app/workers/aiLearningWorker.js';
33
33
  import { startCommandConfigEnrichmentWorker, stopCommandConfigEnrichmentWorker } from './app/workers/commandConfigEnrichmentWorker.js';
34
+ import { startAiHelperContinuousLearningWorker, stopAiHelperContinuousLearningWorker } from './app/workers/aiHelperContinuousLearningWorker.js';
34
35
  import { formatCommandConfigValidationReport, validateAllCommandConfigs } from './app/services/ai/commandConfigValidationService.js';
36
+ import { startGroupAssignmentBalancer, stopGroupAssignmentBalancer } from './app/services/multiSession/assignmentBalancerService.js';
35
37
 
36
38
  /**
37
39
  * Timeout máximo para inicialização do banco (criar/verificar DB + tabelas).
@@ -187,6 +189,21 @@ async function closeDatabasePool() {
187
189
  async function startApp() {
188
190
  try {
189
191
  logger.info('Iniciando Omnizap...');
192
+ const multiSessionConfig = getMultiSessionRuntimeConfig();
193
+ logger.info('Configuracao de sessoes WhatsApp carregada.', {
194
+ sessionIds: multiSessionConfig.sessionIds,
195
+ primarySessionId: multiSessionConfig.primarySessionId,
196
+ sessionWeights: multiSessionConfig.sessionWeights,
197
+ ownerEnforcementMode: multiSessionConfig.ownerEnforcementMode,
198
+ ownerLeaseMs: multiSessionConfig.ownerLeaseMs,
199
+ ownerHeartbeatMs: multiSessionConfig.ownerHeartbeatMs,
200
+ balancerEnabled: multiSessionConfig.balancerEnabled,
201
+ });
202
+ if (multiSessionConfig.warnings.length > 0) {
203
+ logger.warn('Ajustes aplicados na configuracao multi-sessao do WhatsApp.', {
204
+ warnings: multiSessionConfig.warnings,
205
+ });
206
+ }
190
207
 
191
208
  const shouldValidateCommandConfigs = process.env.COMMAND_CONFIG_VALIDATE_ON_BOOT !== 'false';
192
209
  if (shouldValidateCommandConfigs) {
@@ -224,6 +241,7 @@ async function startApp() {
224
241
  startStickerDomainEventConsumer();
225
242
  startAiLearningWorker();
226
243
  startCommandConfigEnrichmentWorker();
244
+ startAiHelperContinuousLearningWorker();
227
245
 
228
246
  // Backfill é opcional, rodando em background.
229
247
  const shouldBackfill = process.env.LID_BACKFILL_ON_START !== 'false';
@@ -242,9 +260,12 @@ async function startApp() {
242
260
  });
243
261
  }
244
262
 
245
- logger.info('Conectando ao WhatsApp...');
246
- await withTimeout(connectToWhatsApp(), WHATSAPP_CONNECT_TIMEOUT_MS, 'Conexao WhatsApp');
247
- logger.info('WhatsApp conectado.');
263
+ logger.info('Conectando sessoes do WhatsApp...');
264
+ const sessionConnectTimeoutMs = Math.max(WHATSAPP_CONNECT_TIMEOUT_MS, multiSessionConfig.sessionIds.length * WHATSAPP_CONNECT_TIMEOUT_MS);
265
+ await withTimeout(connectAllWhatsAppSessions(), sessionConnectTimeoutMs, 'Conexao WhatsApp (multi-session)');
266
+ logger.info('Sessoes do WhatsApp conectadas.');
267
+
268
+ startGroupAssignmentBalancer();
248
269
 
249
270
  logger.info('Inicializando servico de noticias...');
250
271
  await initializeNewsBroadcastService();
@@ -309,6 +330,14 @@ async function shutdown(signal, error) {
309
330
  logger.warn('Falha ao encerrar servico de noticias.', { error: stopError.message });
310
331
  }
311
332
 
333
+ try {
334
+ stopGroupAssignmentBalancer();
335
+ } catch (balancerStopError) {
336
+ logger.warn('Falha ao encerrar balanceador de grupos.', {
337
+ error: balancerStopError?.message,
338
+ });
339
+ }
340
+
312
341
  // 2) Esperar backfill (best-effort) com timeout
313
342
  if (backfillPromise) {
314
343
  try {
@@ -322,19 +351,16 @@ async function shutdown(signal, error) {
322
351
  }
323
352
  }
324
353
 
325
- // 3) Encerrar conexão WhatsApp
326
- const sock = getActiveSocket();
327
- if (sock) {
328
- try {
329
- logger.info('Encerrando conexão do WhatsApp...');
330
- await withTimeout(sock.end(), 8000, 'Encerramento WhatsApp');
331
- logger.info('Conexao do WhatsApp encerrada.');
332
- } catch (sockError) {
333
- logger.error('Erro ao encerrar a conexão do WhatsApp:', {
334
- error: sockError.message,
335
- stack: sockError.stack,
336
- });
337
- }
354
+ // 3) Encerrar conexões WhatsApp
355
+ try {
356
+ logger.info('Encerrando sessoes do WhatsApp...');
357
+ await withTimeout(disconnectAllWhatsAppSessions(), 12000, 'Encerramento WhatsApp multi-session');
358
+ logger.info('Sessoes do WhatsApp encerradas.');
359
+ } catch (sockError) {
360
+ logger.error('Erro ao encerrar conexões do WhatsApp:', {
361
+ error: sockError.message,
362
+ stack: sockError.stack,
363
+ });
338
364
  }
339
365
 
340
366
  // 4) Encerrar servidor HTTP
@@ -403,6 +429,14 @@ async function shutdown(signal, error) {
403
429
  });
404
430
  }
405
431
 
432
+ try {
433
+ stopAiHelperContinuousLearningWorker();
434
+ } catch (continuousLearningWorkerError) {
435
+ logger.warn('Falha ao encerrar worker de aprendizado contínuo de IA.', {
436
+ error: continuousLearningWorkerError?.message,
437
+ });
438
+ }
439
+
406
440
  try {
407
441
  stopEmailAutomationRuntime();
408
442
  } catch (emailRuntimeError) {
@@ -58,3 +58,23 @@ groups:
58
58
  annotations:
59
59
  summary: "Memória acima de 80%"
60
60
  description: "Uso de memória acima de 80% por 10 minutos."
61
+
62
+ - name: omnizap-admin
63
+ rules:
64
+ - alert: OmniZapAdminSnapshotStale
65
+ expr: (time() - omnizap_admin_overview_updated_at_seconds) > 300
66
+ for: 15m
67
+ labels:
68
+ severity: warning
69
+ annotations:
70
+ summary: "Snapshot admin sem atualizacao recente"
71
+ description: "As métricas do painel admin estão sem atualização há mais de 5 minutos."
72
+
73
+ - alert: OmniZapAdminCriticalAlerts
74
+ expr: omnizap_admin_alerts_total{severity="critical"} > 0
75
+ for: 2m
76
+ labels:
77
+ severity: critical
78
+ annotations:
79
+ summary: "Alertas críticos ativos no painel admin"
80
+ description: "Existe ao menos um alerta crítico no snapshot de observabilidade do System Admin."