@omnizap-system/omnizap 2.6.0 → 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.
- package/.env.example +58 -13
- package/.github/workflows/ci.yml +5 -5
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/db-migration-check.yml +2 -2
- package/.github/workflows/dependency-review.yml +1 -1
- package/.github/workflows/deploy.yml +2 -2
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/security-attest-provenance.yml +2 -2
- package/.github/workflows/security-gitleaks.yml +13 -4
- package/.github/workflows/security-runner-hardening.yml +2 -2
- package/.github/workflows/security-scorecard.yml +1 -1
- package/.github/workflows/security-zap-baseline.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +2 -1
- package/.github/workflows/security-zizmor.yml +1 -1
- package/.github/workflows/wiki-sync.yml +1 -1
- package/.gitleaksignore +9 -0
- package/CODE_OF_CONDUCT.md +2 -2
- package/GEMINI.md +64 -0
- package/README.md +52 -82
- package/SECURITY.md +1 -1
- package/app/config/index.js +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +230 -58
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +145 -4
- package/app/configParts/sessionConfig.js +157 -0
- package/app/connection/baileysCompatibility.test.js +1 -1
- package/app/connection/groupOwnerWriteStateResolver.js +109 -0
- package/app/connection/socketController.js +660 -158
- package/app/connection/socketController.multiSession.test.js +108 -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 +80 -2
- package/app/controllers/messageProcessingPipeline.js +93 -13
- package/app/controllers/messageProcessingPipeline.test.js +200 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +858 -15
- package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
- 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 +135 -27
- 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 +140 -12
- package/app/modules/playModule/playCommand.js +1 -1417
- package/app/modules/playModule/playCommandConstants.js +80 -0
- package/app/modules/playModule/playCommandCore.js +361 -0
- package/app/modules/playModule/playCommandHandlers.js +41 -0
- package/app/modules/playModule/playCommandMediaClient.js +1872 -0
- package/app/modules/playModule/playConfigRuntime.js +245 -4
- package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/quoteModule/quoteCommand.js +3 -2
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/statsModule/rankingCommon.js +5 -4
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/addStickerMetadata.js +4 -3
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerModule/stickerCommand.js +1 -1
- 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/semanticThemeClusterService.js +7 -6
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/systemMetricsModule/pingCommand.js +6 -5
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/tiktokCommand.js +2 -1
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/userModule/userCommand.js +72 -23
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/conversationRouterService.js +4 -3
- package/app/services/ai/geminiService.js +132 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/globalModuleAiHelpService.js +3 -2
- package/app/services/ai/messageCommandExecutionService.js +2 -1
- package/app/services/ai/moduleAiHelpCoreService.js +45 -14
- package/app/services/ai/moduleToolExecutorService.js +3 -2
- package/app/services/ai/moduleToolRegistryService.js +2 -1
- package/app/services/ai/toolCandidateSelectorService.js +6 -5
- package/app/services/auth/googleWebLinkService.js +3 -2
- package/app/services/auth/whatsappLoginLinkService.js +3 -2
- package/app/services/external/pokeApiService.js +4 -3
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +57 -26
- package/app/services/infra/featureFlagService.js +2 -1
- package/app/services/messaging/captchaService.js +3 -2
- package/app/services/messaging/newsBroadcastService.js +846 -29
- package/app/services/multiSession/assignmentBalancerService.js +457 -0
- package/app/services/multiSession/groupOwnershipRepository.js +381 -0
- package/app/services/multiSession/groupOwnershipService.js +890 -0
- package/app/services/multiSession/groupOwnershipService.test.js +309 -0
- package/app/services/multiSession/sessionRegistryService.js +293 -0
- package/app/services/sticker/stickerFocusService.js +11 -10
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/conversationSessionStore.js +7 -6
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +352 -16
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/app/workers/aiLearningWorker.js +6 -5
- package/app/workers/commandConfigEnrichmentWorker.js +4 -3
- package/database/index.js +14 -8
- 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/dpa-b2b-standard-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/incident-response-lgpd-anpd-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/docs/wiki/Home.md +1 -1
- package/ecosystem.prod.config.cjs +32 -12
- package/index.js +57 -23
- 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 +20 -6
- package/public/apple-touch-icon.png +0 -0
- package/public/comandos/commands-catalog.json +2853 -3326
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/js/apps/apiDocsApp.js +3 -2
- package/public/js/apps/commandsReactApp.js +280 -99
- package/public/js/apps/createPackApp.js +11 -10
- package/public/js/apps/homeReactApp.js +181 -130
- package/public/js/apps/loginReactApp.js +1 -1
- package/public/js/apps/stickersApp.js +263 -110
- package/public/js/apps/termsReactApp.js +73 -24
- package/public/js/apps/userApp.js +4 -3
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +355 -280
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/api-docs.html +1 -1
- package/public/pages/aup.html +2 -2
- package/public/pages/dpa.html +3 -3
- package/public/pages/licenca.html +4 -4
- package/public/pages/login.html +1 -1
- package/public/pages/notice-and-takedown.html +2 -2
- package/public/pages/politica-de-privacidade.html +6 -6
- package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
- package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
- package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
- package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
- package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
- package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
- package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
- package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
- package/public/pages/stickers-admin.html +1 -1
- package/public/pages/stickers-create.html +1 -1
- package/public/pages/stickers.html +6 -6
- package/public/pages/suboperadores.html +2 -2
- package/public/pages/termos-de-uso-texto-integral.html +6 -6
- package/public/pages/termos-de-uso.html +3 -3
- package/public/pages/user-password-reset.html +4 -5
- package/public/pages/user-systemadm.html +9 -463
- package/public/pages/user.html +2 -2
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +11 -1
- package/scripts/email-broadcast-terms-update.mjs +2 -1
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +166 -2
- package/scripts/generate-module-agents.mjs +2 -1
- package/scripts/generate-seo-satellite-pages.mjs +5 -4
- package/scripts/github-deploy-notify.mjs +2 -1
- package/scripts/github-release-notify.mjs +25 -10
- package/scripts/new-whatsapp-session.sh +317 -0
- package/scripts/release.sh +2 -19
- package/scripts/security-smoketest.mjs +6 -5
- package/scripts/security-web-surface-check.mjs +218 -0
- package/scripts/sticker-catalog-loadtest.mjs +5 -4
- package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
- package/server/auth/jwt/webJwtService.js +1 -1
- package/server/auth/stickerCatalogAuthContext.js +2 -1
- package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
- package/server/auth/userPassword/userPasswordAuthService.js +2 -1
- package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
- package/server/auth/webAccount/webAccountHandlers.js +9 -10
- package/server/controllers/admin/adminPanelHandlers.js +267 -16
- package/server/controllers/admin/systemAdminController.js +267 -0
- package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
- package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
- package/server/controllers/sticker/stickerCatalogController.js +23 -36
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/githubController.js +3 -2
- package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
- package/server/controllers/system/systemController.js +254 -1
- package/server/controllers/system/systemMetricsController.js +2 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +5 -3
- package/server/http/httpServer.js +11 -6
- package/server/middleware/rateLimit.js +2 -1
- package/server/middleware/securityHeaders.js +20 -1
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +30 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/static/staticPageRouter.js +27 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/time/timeModule.js +135 -0
- package/utils/time/timeModule.test.js +65 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +7 -1
- package/public/assets/images/brand-icon-192.png +0 -0
- package/scripts/sync-readme-snapshot.mjs +0 -133
package/scripts/release.sh
CHANGED
|
@@ -29,9 +29,6 @@ RELEASE_GITHUB_RELEASE_INCLUDE_CHANGED_FILES="${RELEASE_GITHUB_RELEASE_INCLUDE_C
|
|
|
29
29
|
RELEASE_GITHUB_RELEASE_MAX_FILES="${RELEASE_GITHUB_RELEASE_MAX_FILES:-300}"
|
|
30
30
|
RELEASE_REQUIRE_DUAL_PUBLISH="${RELEASE_REQUIRE_DUAL_PUBLISH:-1}"
|
|
31
31
|
RELEASE_VERIFY_UNIFIED_VERSION="${RELEASE_VERIFY_UNIFIED_VERSION:-1}"
|
|
32
|
-
RELEASE_README_SYNC="${RELEASE_README_SYNC:-1}"
|
|
33
|
-
RELEASE_README_SYNC_REQUIRED="${RELEASE_README_SYNC_REQUIRED:-0}"
|
|
34
|
-
RELEASE_README_SYNC_COMMAND="${RELEASE_README_SYNC_COMMAND:-npm run readme:sync-snapshot}"
|
|
35
32
|
RELEASE_WIKI_SYNC="${RELEASE_WIKI_SYNC:-1}"
|
|
36
33
|
RELEASE_WIKI_SYNC_REQUIRED="${RELEASE_WIKI_SYNC_REQUIRED:-0}"
|
|
37
34
|
RELEASE_WIKI_SYNC_COMMAND="${RELEASE_WIKI_SYNC_COMMAND:-bash ./scripts/wiki-sync.sh}"
|
|
@@ -480,20 +477,6 @@ if ! (
|
|
|
480
477
|
exit 1
|
|
481
478
|
fi
|
|
482
479
|
|
|
483
|
-
if [ "$RELEASE_README_SYNC" = "1" ]; then
|
|
484
|
-
log "Sincronizando bloco dinâmico do README"
|
|
485
|
-
if ! (
|
|
486
|
-
cd "$PROJECT_ROOT" &&
|
|
487
|
-
bash -lc "$RELEASE_README_SYNC_COMMAND"
|
|
488
|
-
); then
|
|
489
|
-
if [ "$RELEASE_README_SYNC_REQUIRED" = "1" ]; then
|
|
490
|
-
printf '[release] Falha ao sincronizar README e RELEASE_README_SYNC_REQUIRED=1.\n' >&2
|
|
491
|
-
exit 1
|
|
492
|
-
fi
|
|
493
|
-
log "Falha ao sincronizar README. Continuando release (RELEASE_README_SYNC_REQUIRED=0)."
|
|
494
|
-
fi
|
|
495
|
-
fi
|
|
496
|
-
|
|
497
480
|
if [ "$RELEASE_WIKI_SYNC" = "1" ]; then
|
|
498
481
|
log "Sincronizando wiki do GitHub"
|
|
499
482
|
if ! (
|
|
@@ -563,11 +546,11 @@ if [ "$RELEASE_GITHUB_RELEASE" = "1" ]; then
|
|
|
563
546
|
--tag "$release_tag" \
|
|
564
547
|
--target "$local_target" \
|
|
565
548
|
--name "$local_name" \
|
|
566
|
-
--body-file "$release_body_file" \
|
|
567
549
|
--generate-notes "$generate_notes_bool" \
|
|
568
550
|
--prerelease "$prerelease_bool" \
|
|
569
551
|
--draft "$draft_bool" \
|
|
570
|
-
--latest "$latest_bool"
|
|
552
|
+
--latest "$latest_bool" \
|
|
553
|
+
--body-stdin "true" < "$release_body_file"
|
|
571
554
|
)"
|
|
572
555
|
else
|
|
573
556
|
release_output="$(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
2
3
|
|
|
3
4
|
import fs from 'node:fs/promises';
|
|
4
5
|
import path from 'node:path';
|
|
@@ -15,7 +16,7 @@ const MANUAL = 'MANUAL';
|
|
|
15
16
|
|
|
16
17
|
const sqlErrorRegex = /(sql syntax|syntax error|mysql|sqlite|postgres|odbc|query failed|unclosed quotation|ORA-\d+)/i;
|
|
17
18
|
|
|
18
|
-
const nowIso = () =>
|
|
19
|
+
const nowIso = () => __timeNowIso();
|
|
19
20
|
|
|
20
21
|
const safeJson = async (value) => {
|
|
21
22
|
try {
|
|
@@ -190,9 +191,9 @@ const testDdosSafe = async () => {
|
|
|
190
191
|
const idx = cursor;
|
|
191
192
|
cursor += 1;
|
|
192
193
|
if (idx >= total) return;
|
|
193
|
-
const startedAt =
|
|
194
|
+
const startedAt = __timeNowMs();
|
|
194
195
|
const res = await request('/healthz');
|
|
195
|
-
const elapsed =
|
|
196
|
+
const elapsed = __timeNowMs() - startedAt;
|
|
196
197
|
latencies.push(elapsed);
|
|
197
198
|
if (!res.ok) failures += 1;
|
|
198
199
|
if (res.status >= 500) serverErrors += 1;
|
|
@@ -202,8 +203,8 @@ const testDdosSafe = async () => {
|
|
|
202
203
|
await Promise.all(Array.from({ length: concurrency }, () => worker()));
|
|
203
204
|
latencies.sort((a, b) => a - b);
|
|
204
205
|
const p95 = latencies[Math.floor(latencies.length * 0.95)] || 0;
|
|
205
|
-
const failureRate =
|
|
206
|
-
const serverErrorRate =
|
|
206
|
+
const failureRate = failures / total;
|
|
207
|
+
const serverErrorRate = serverErrors / total;
|
|
207
208
|
const status = failureRate <= 0.1 && serverErrorRate === 0 ? PASS : WARN;
|
|
208
209
|
|
|
209
210
|
return {
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { nowIso as __timeNowIso } from '#time';
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs/promises';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import process from 'node:process';
|
|
8
|
+
|
|
9
|
+
const rawBaseUrl = String(process.env.SECURITY_WEB_SURFACE_BASE_URL || 'https://omnizap.shop').trim() || 'https://omnizap.shop';
|
|
10
|
+
const reportPath = String(process.env.SECURITY_WEB_SURFACE_REPORT_PATH || './temp/security-web-surface-report.json').trim();
|
|
11
|
+
const requestTimeoutMs = Math.max(1_000, Number(process.env.SECURITY_WEB_SURFACE_TIMEOUT_MS || 10_000));
|
|
12
|
+
|
|
13
|
+
const toBaseOrigin = (value) => {
|
|
14
|
+
const raw = String(value || '').trim();
|
|
15
|
+
if (!raw) return 'https://omnizap.shop';
|
|
16
|
+
try {
|
|
17
|
+
const parsed = new URL(raw);
|
|
18
|
+
return parsed.origin;
|
|
19
|
+
} catch {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = new URL(`https://${raw}`);
|
|
22
|
+
return parsed.origin;
|
|
23
|
+
} catch {
|
|
24
|
+
return 'https://omnizap.shop';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const baseOrigin = toBaseOrigin(rawBaseUrl);
|
|
30
|
+
|
|
31
|
+
const PASS = 'PASS';
|
|
32
|
+
const FAIL = 'FAIL';
|
|
33
|
+
|
|
34
|
+
const STATIC_REQUIRED_HEADERS = ['content-security-policy', 'permissions-policy', 'strict-transport-security', 'x-content-type-options', 'x-frame-options'];
|
|
35
|
+
|
|
36
|
+
const API_REQUIRED_HEADERS = ['content-security-policy', 'cross-origin-opener-policy', 'cross-origin-resource-policy', 'permissions-policy', 'strict-transport-security', 'x-content-type-options', 'x-frame-options'];
|
|
37
|
+
|
|
38
|
+
const checks = [
|
|
39
|
+
{
|
|
40
|
+
id: 1,
|
|
41
|
+
name: 'Root static page has security headers',
|
|
42
|
+
path: '/',
|
|
43
|
+
expectedStatuses: [200],
|
|
44
|
+
requiredHeaders: STATIC_REQUIRED_HEADERS,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 2,
|
|
48
|
+
name: 'Legal page has security headers',
|
|
49
|
+
path: '/notice-and-takedown/',
|
|
50
|
+
expectedStatuses: [200],
|
|
51
|
+
requiredHeaders: STATIC_REQUIRED_HEADERS,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 3,
|
|
55
|
+
name: 'Dotenv path is not exposed',
|
|
56
|
+
path: '/.env',
|
|
57
|
+
forbiddenStatuses: [200],
|
|
58
|
+
bodyLeakPatterns: [/DB_PASSWORD|MYSQL_PASSWORD|GITHUB_TOKEN|SECRET|PRIVATE_KEY/i],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 4,
|
|
62
|
+
name: 'Unknown path does not soft-fallback with 200',
|
|
63
|
+
path: '/__security_probe_nonexistent_omnizap__.txt',
|
|
64
|
+
expectedStatuses: [404],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 5,
|
|
68
|
+
name: 'Whitespace path fuzz does not return 200',
|
|
69
|
+
path: '/assets%20/',
|
|
70
|
+
forbiddenStatuses: [200],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 6,
|
|
74
|
+
name: 'API bootstrap keeps hardened headers',
|
|
75
|
+
path: '/api/home-bootstrap',
|
|
76
|
+
expectedStatuses: [200],
|
|
77
|
+
requiredHeaders: API_REQUIRED_HEADERS,
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const request = async (targetPath) => {
|
|
82
|
+
const normalizedPath = String(targetPath || '/').startsWith('/') ? String(targetPath || '/') : `/${String(targetPath || '/')}`;
|
|
83
|
+
const url = `${baseOrigin}${normalizedPath}`;
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timeout = setTimeout(() => controller.abort(), requestTimeoutMs);
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const response = await fetch(url, {
|
|
89
|
+
method: 'GET',
|
|
90
|
+
redirect: 'manual',
|
|
91
|
+
signal: controller.signal,
|
|
92
|
+
});
|
|
93
|
+
const text = await response.text();
|
|
94
|
+
return {
|
|
95
|
+
ok: true,
|
|
96
|
+
url,
|
|
97
|
+
status: response.status,
|
|
98
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
99
|
+
body: text,
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
url,
|
|
105
|
+
status: null,
|
|
106
|
+
headers: {},
|
|
107
|
+
body: '',
|
|
108
|
+
error: error?.message || String(error),
|
|
109
|
+
};
|
|
110
|
+
} finally {
|
|
111
|
+
clearTimeout(timeout);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const summarize = (items) =>
|
|
116
|
+
items.reduce(
|
|
117
|
+
(acc, item) => {
|
|
118
|
+
acc[item.status] = (acc[item.status] || 0) + 1;
|
|
119
|
+
return acc;
|
|
120
|
+
},
|
|
121
|
+
{ PASS: 0, FAIL: 0 },
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const runCheck = async (check) => {
|
|
125
|
+
const response = await request(check.path);
|
|
126
|
+
const reasons = [];
|
|
127
|
+
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
reasons.push(`network_error:${response.error || 'unknown'}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (Array.isArray(check.expectedStatuses) && check.expectedStatuses.length > 0) {
|
|
133
|
+
if (!check.expectedStatuses.includes(response.status)) {
|
|
134
|
+
reasons.push(`unexpected_status:${response.status}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (Array.isArray(check.forbiddenStatuses) && check.forbiddenStatuses.length > 0) {
|
|
139
|
+
if (check.forbiddenStatuses.includes(response.status)) {
|
|
140
|
+
reasons.push(`forbidden_status:${response.status}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const missingHeaders = [];
|
|
145
|
+
for (const headerName of check.requiredHeaders || []) {
|
|
146
|
+
const resolved = response.headers?.[headerName];
|
|
147
|
+
if (!resolved) missingHeaders.push(headerName);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (missingHeaders.length > 0) {
|
|
151
|
+
reasons.push(`missing_headers:${missingHeaders.join(',')}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const leakPatternHit = (check.bodyLeakPatterns || []).find((pattern) => pattern.test(String(response.body || '')));
|
|
155
|
+
if (leakPatternHit) {
|
|
156
|
+
reasons.push(`body_leak_pattern:${String(leakPatternHit)}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
id: check.id,
|
|
161
|
+
name: check.name,
|
|
162
|
+
path: check.path,
|
|
163
|
+
status: reasons.length > 0 ? FAIL : PASS,
|
|
164
|
+
reasons,
|
|
165
|
+
evidence: {
|
|
166
|
+
url: response.url,
|
|
167
|
+
status: response.status,
|
|
168
|
+
headers: response.headers,
|
|
169
|
+
body_preview: String(response.body || '').slice(0, 240),
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const writeReport = async (payload) => {
|
|
175
|
+
const absolutePath = path.resolve(process.cwd(), reportPath);
|
|
176
|
+
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
177
|
+
await fs.writeFile(absolutePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
|
178
|
+
return absolutePath;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const main = async () => {
|
|
182
|
+
const startedAt = __timeNowIso();
|
|
183
|
+
const results = [];
|
|
184
|
+
|
|
185
|
+
for (const check of checks) {
|
|
186
|
+
const result = await runCheck(check);
|
|
187
|
+
results.push(result);
|
|
188
|
+
console.log(`[security-web-surface] ${String(result.id).padStart(2, '0')} ${result.name}: ${result.status}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const summary = summarize(results);
|
|
192
|
+
const endedAt = __timeNowIso();
|
|
193
|
+
const report = {
|
|
194
|
+
meta: {
|
|
195
|
+
base_origin: baseOrigin,
|
|
196
|
+
started_at: startedAt,
|
|
197
|
+
ended_at: endedAt,
|
|
198
|
+
timeout_ms: requestTimeoutMs,
|
|
199
|
+
},
|
|
200
|
+
summary,
|
|
201
|
+
results,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const reportAbsolutePath = await writeReport(report);
|
|
205
|
+
console.log('[security-web-surface] ---');
|
|
206
|
+
console.log(`[security-web-surface] base_origin=${baseOrigin}`);
|
|
207
|
+
console.log(`[security-web-surface] summary=${JSON.stringify(summary)}`);
|
|
208
|
+
console.log(`[security-web-surface] report_path=${reportAbsolutePath}`);
|
|
209
|
+
|
|
210
|
+
if ((summary.FAIL || 0) > 0) {
|
|
211
|
+
process.exitCode = 1;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
main().catch((error) => {
|
|
216
|
+
console.error(`[security-web-surface] fatal_error=${error?.message || error}`);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
2
3
|
import http from 'node:http';
|
|
3
4
|
import https from 'node:https';
|
|
4
5
|
import { performance } from 'node:perf_hooks';
|
|
@@ -106,7 +107,7 @@ const runRequest = (pathName) =>
|
|
|
106
107
|
|
|
107
108
|
const runWorker = async ({ deadlineMs, workerIndex, stats }) => {
|
|
108
109
|
let requestIndex = workerIndex % requestPaths.length;
|
|
109
|
-
while (
|
|
110
|
+
while (__timeNowMs() < deadlineMs) {
|
|
110
111
|
const pathName = requestPaths[requestIndex % requestPaths.length];
|
|
111
112
|
requestIndex += 1;
|
|
112
113
|
|
|
@@ -129,7 +130,7 @@ const runWorker = async ({ deadlineMs, workerIndex, stats }) => {
|
|
|
129
130
|
};
|
|
130
131
|
|
|
131
132
|
const main = async () => {
|
|
132
|
-
const startedAt =
|
|
133
|
+
const startedAt = __timeNowMs();
|
|
133
134
|
const deadlineMs = startedAt + durationSeconds * 1000;
|
|
134
135
|
const stats = {
|
|
135
136
|
total: 0,
|
|
@@ -154,7 +155,7 @@ const main = async () => {
|
|
|
154
155
|
),
|
|
155
156
|
);
|
|
156
157
|
|
|
157
|
-
const elapsedSeconds = Math.max(0.001, (
|
|
158
|
+
const elapsedSeconds = Math.max(0.001, (__timeNowMs() - startedAt) / 1000);
|
|
158
159
|
const sortedLatencies = [...stats.latencies].sort((a, b) => a - b);
|
|
159
160
|
const p50 = quantile(sortedLatencies, 0.5);
|
|
160
161
|
const p90 = quantile(sortedLatencies, 0.9);
|
|
@@ -165,7 +166,7 @@ const main = async () => {
|
|
|
165
166
|
|
|
166
167
|
const summary = {
|
|
167
168
|
started_at: new Date(startedAt).toISOString(),
|
|
168
|
-
ended_at:
|
|
169
|
+
ended_at: __timeNowIso(),
|
|
169
170
|
base_url: baseUrl,
|
|
170
171
|
duration_seconds: Number(elapsedSeconds.toFixed(3)),
|
|
171
172
|
concurrency,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
3
|
import axios from 'axios';
|
|
3
4
|
|
|
@@ -35,7 +36,7 @@ export const createGoogleWebAuthService = ({ executeQuery, runSqlTransaction, ta
|
|
|
35
36
|
const USER_PASSWORD_TABLE = String(tables.STICKER_WEB_USER_PASSWORD || 'web_user_password').trim() || 'web_user_password';
|
|
36
37
|
|
|
37
38
|
const pruneExpiredGoogleSessions = () => {
|
|
38
|
-
const now =
|
|
39
|
+
const now = __timeNowMs();
|
|
39
40
|
for (const [token, session] of webGoogleSessionMap.entries()) {
|
|
40
41
|
if (!session || Number(session.expiresAt || 0) <= now) {
|
|
41
42
|
webGoogleSessionMap.delete(token);
|
|
@@ -145,15 +146,15 @@ export const createGoogleWebAuthService = ({ executeQuery, runSqlTransaction, ta
|
|
|
145
146
|
picture: String(row.picture_url || '').trim() || null,
|
|
146
147
|
ownerJid,
|
|
147
148
|
ownerPhone,
|
|
148
|
-
createdAt: Number.isFinite(createdAtRaw) ? createdAtRaw :
|
|
149
|
+
createdAt: Number.isFinite(createdAtRaw) ? createdAtRaw : __timeNowMs(),
|
|
149
150
|
expiresAt,
|
|
150
151
|
lastSeenAt: Number.isFinite(lastSeenAtRaw) ? lastSeenAtRaw : 0,
|
|
151
|
-
lastDbTouchAt:
|
|
152
|
+
lastDbTouchAt: __timeNowMs(),
|
|
152
153
|
};
|
|
153
154
|
};
|
|
154
155
|
|
|
155
156
|
const maybePruneExpiredGoogleSessionsFromDb = async () => {
|
|
156
|
-
const now =
|
|
157
|
+
const now = __timeNowMs();
|
|
157
158
|
if (now - googleWebSessionDbPruneAt < sessionDbPruneIntervalMs) return;
|
|
158
159
|
googleWebSessionDbPruneAt = now;
|
|
159
160
|
try {
|
|
@@ -368,7 +369,7 @@ export const createGoogleWebAuthService = ({ executeQuery, runSqlTransaction, ta
|
|
|
368
369
|
const createGoogleWebSession = (claims, { ownerJid } = {}) => {
|
|
369
370
|
pruneExpiredGoogleSessions();
|
|
370
371
|
const token = randomUUID();
|
|
371
|
-
const now =
|
|
372
|
+
const now = __timeNowMs();
|
|
372
373
|
const resolvedOwnerJid = normalizeJid(ownerJid) || buildGoogleOwnerJid(claims.sub);
|
|
373
374
|
const resolvedOwnerPhone = toWhatsAppPhoneDigits(resolvedOwnerJid) || '';
|
|
374
375
|
return {
|
|
@@ -394,7 +395,7 @@ export const createGoogleWebAuthService = ({ executeQuery, runSqlTransaction, ta
|
|
|
394
395
|
|
|
395
396
|
const issueAccessTokenForSession = (session) => {
|
|
396
397
|
if (!session?.sub) return '';
|
|
397
|
-
const expiresInSeconds = Math.max(60, Math.floor((Number(session.expiresAt || 0) -
|
|
398
|
+
const expiresInSeconds = Math.max(60, Math.floor((Number(session.expiresAt || 0) - __timeNowMs()) / 1000));
|
|
398
399
|
return (
|
|
399
400
|
signWebAuthJwt(
|
|
400
401
|
{
|
|
@@ -495,7 +496,7 @@ export const createGoogleWebAuthService = ({ executeQuery, runSqlTransaction, ta
|
|
|
495
496
|
|
|
496
497
|
const touchGoogleWebSessionActivity = (session) => {
|
|
497
498
|
if (!session?.token || !session?.sub) return;
|
|
498
|
-
const now =
|
|
499
|
+
const now = __timeNowMs();
|
|
499
500
|
session.lastSeenAt = now;
|
|
500
501
|
if (now - Number(session.lastDbTouchAt || 0) < sessionDbTouchIntervalMs) {
|
|
501
502
|
return;
|
|
@@ -15,7 +15,7 @@ const clampInt = (value, fallback, min, max) => {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const WEB_AUTH_JWT_SECRET = String(process.env.WEB_AUTH_JWT_SECRET || '').trim();
|
|
18
|
-
const WEB_AUTH_JWT_ISSUER = String(process.env.WEB_AUTH_JWT_ISSUER || 'omnizap
|
|
18
|
+
const WEB_AUTH_JWT_ISSUER = String(process.env.WEB_AUTH_JWT_ISSUER || 'omnizap').trim() || 'omnizap';
|
|
19
19
|
const WEB_AUTH_JWT_AUDIENCE = String(process.env.WEB_AUTH_JWT_AUDIENCE || 'omnizap-web').trim() || 'omnizap-web';
|
|
20
20
|
const WEB_AUTH_JWT_EXPIRES_IN = String(process.env.WEB_AUTH_JWT_EXPIRES_IN || '7d').trim() || '7d';
|
|
21
21
|
const WEB_AUTH_JWT_DISABLED = parseEnvBool(process.env.WEB_AUTH_JWT_DISABLED, false);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { createGoogleWebAuthRuntime, normalizeGoogleSubject } from './googleWebAuth/googleWebAuthRuntime.js';
|
|
2
3
|
import { isWebAuthJwtEnabled, signWebAuthJwt, verifyWebAuthJwt } from './jwt/webJwtService.js';
|
|
3
4
|
import userPasswordAuthService from './userPassword/index.js';
|
|
@@ -13,7 +14,7 @@ export const createStickerCatalogAuthContext = ({ executeQuery, runSqlTransactio
|
|
|
13
14
|
|
|
14
15
|
const safeName = sanitizeText(name || '', 80, { allowEmpty: true }) || '';
|
|
15
16
|
const normalizedOwnerJid = normalizeJid(ownerJid) || null;
|
|
16
|
-
const idempotencyKey = `google_web_welcome:${normalizedEmail}:${
|
|
17
|
+
const idempotencyKey = `google_web_welcome:${normalizedEmail}:${__timeNowIso().slice(0, 10)}`;
|
|
17
18
|
|
|
18
19
|
void queueWelcomeEmail({
|
|
19
20
|
to: normalizedEmail,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
3
|
|
|
3
4
|
const DEFAULT_LEGAL_VERSION = '2026-03-07';
|
|
@@ -129,7 +130,7 @@ export const createTermsAcceptanceHandler = ({ executeQuery, tables, logger, sen
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
const source = normalizeTermsAcceptanceSource(payload.source) || legalDefaultAcceptanceSource;
|
|
132
|
-
const acceptedAt =
|
|
133
|
+
const acceptedAt = __timeNow();
|
|
133
134
|
const acceptedAtClient = parseAcceptedAtClientDate(payload.accepted_at);
|
|
134
135
|
const cookies = parseCookies(req);
|
|
135
136
|
const sessionKey = normalizeTermsAcceptanceSessionKey(cookies[webSessionCookieName] || '');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { hashUserPassword, resolveUserPasswordPolicy, validateUserPassword, verifyUserPasswordHash } from './userPasswordCrypto.js';
|
|
2
3
|
|
|
3
4
|
const clampInt = (value, fallback, min, max) => {
|
|
@@ -139,7 +140,7 @@ export const createUserPasswordAuthService = ({ executeQuery, tables = {}, logge
|
|
|
139
140
|
return { locked: false, retryAfterSeconds: 0 };
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
const elapsedSeconds = Math.max(0, Math.floor((
|
|
143
|
+
const elapsedSeconds = Math.max(0, Math.floor((__timeNowMs() - lastFailedAtMs) / 1000));
|
|
143
144
|
const retryAfterSeconds = Math.max(0, lockoutSeconds - elapsedSeconds);
|
|
144
145
|
if (retryAfterSeconds <= 0) {
|
|
145
146
|
return { locked: false, retryAfterSeconds: 0 };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { createHash, pbkdf2 as pbkdf2Callback, randomInt, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
2
3
|
import { promisify } from 'node:util';
|
|
3
4
|
|
|
@@ -312,7 +313,7 @@ export const createUserPasswordRecoveryService = ({ executeQuery, userPasswordAu
|
|
|
312
313
|
const oldestCreatedAt = rows?.[0]?.oldest_created_at ? Date.parse(rows[0].oldest_created_at) : NaN;
|
|
313
314
|
if (!Number.isFinite(oldestCreatedAt)) return safeWindowSeconds;
|
|
314
315
|
|
|
315
|
-
const elapsedSeconds = Math.max(0, Math.floor((
|
|
316
|
+
const elapsedSeconds = Math.max(0, Math.floor((__timeNowMs() - oldestCreatedAt) / 1000));
|
|
316
317
|
return Math.max(1, safeWindowSeconds - elapsedSeconds);
|
|
317
318
|
};
|
|
318
319
|
|
|
@@ -334,7 +335,7 @@ export const createUserPasswordRecoveryService = ({ executeQuery, userPasswordAu
|
|
|
334
335
|
const row = Array.isArray(rows) ? rows[0] : null;
|
|
335
336
|
if (!row) return null;
|
|
336
337
|
const createdAtMs = row.created_at ? Date.parse(row.created_at) : NaN;
|
|
337
|
-
const elapsedSeconds = Number.isFinite(createdAtMs) ? Math.max(0, Math.floor((
|
|
338
|
+
const elapsedSeconds = Number.isFinite(createdAtMs) ? Math.max(0, Math.floor((__timeNowMs() - createdAtMs) / 1000)) : resendCooldownSeconds;
|
|
338
339
|
const retryAfterSeconds = Math.max(0, resendCooldownSeconds - elapsedSeconds);
|
|
339
340
|
return {
|
|
340
341
|
id: Number(row.id || 0),
|
|
@@ -496,7 +497,7 @@ export const createUserPasswordRecoveryService = ({ executeQuery, userPasswordAu
|
|
|
496
497
|
remote_ip: normalizeIp(requestMeta?.remoteIp) || null,
|
|
497
498
|
},
|
|
498
499
|
priority: 95,
|
|
499
|
-
idempotencyKey: `web_user_password_recovery:${knownUser.google_sub}:${normalizedPurpose}:${
|
|
500
|
+
idempotencyKey: `web_user_password_recovery:${knownUser.google_sub}:${normalizedPurpose}:${__timeNowIso().slice(0, 16)}`,
|
|
500
501
|
});
|
|
501
502
|
} catch (error) {
|
|
502
503
|
await executeQuery(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { scryptSync } from 'node:crypto';
|
|
2
3
|
|
|
3
4
|
const MY_PROFILE_DEFAULT_STATS = Object.freeze({
|
|
@@ -119,7 +120,7 @@ const toPasswordRecoverySessionExpiresAt = (claims) => {
|
|
|
119
120
|
const toPasswordRecoverySessionExpiresIn = (claims) => {
|
|
120
121
|
const expUnix = Number(claims?.exp || 0);
|
|
121
122
|
if (!Number.isFinite(expUnix) || expUnix <= 0) return null;
|
|
122
|
-
return Math.max(0, Math.floor(expUnix -
|
|
123
|
+
return Math.max(0, Math.floor(expUnix - __timeNowMs() / 1000));
|
|
123
124
|
};
|
|
124
125
|
|
|
125
126
|
const signPasswordRecoverySessionToken = ({ sub = '', email = '', ownerJid = '' } = {}, { isWebAuthJwtEnabled, signWebAuthJwt, passwordRecoverySessionAuthMethod, passwordRecoverySessionTtlSeconds }) => {
|
|
@@ -202,9 +203,7 @@ const isUnknownColumnError = (error, columnName = '') => {
|
|
|
202
203
|
if (code !== 'ER_BAD_FIELD_ERROR' && errno !== 1054) return false;
|
|
203
204
|
if (!columnName) return true;
|
|
204
205
|
const message = String(error?.message || '').toLowerCase();
|
|
205
|
-
const normalizedColumn = String(columnName
|
|
206
|
-
.trim()
|
|
207
|
-
.toLowerCase();
|
|
206
|
+
const normalizedColumn = String(columnName).trim().toLowerCase();
|
|
208
207
|
if (!normalizedColumn) return true;
|
|
209
208
|
return message.includes(`unknown column '${normalizedColumn}'`) || message.includes(`unknown column \`${normalizedColumn}\``);
|
|
210
209
|
};
|
|
@@ -233,7 +232,7 @@ export const createWebAccountAuthHandlers = ({ sendJson, readJsonBody, logger, p
|
|
|
233
232
|
return '';
|
|
234
233
|
};
|
|
235
234
|
|
|
236
|
-
const maybePrunePasswordLoginIdentityThrottle = async (nowMs =
|
|
235
|
+
const maybePrunePasswordLoginIdentityThrottle = async (nowMs = __timeNowMs()) => {
|
|
237
236
|
if (nowMs - passwordLoginIdentityPruneAt < 60 * 60 * 1000) return;
|
|
238
237
|
passwordLoginIdentityPruneAt = nowMs;
|
|
239
238
|
const staleAfterSeconds = Math.max(60 * 60, passwordLoginIdentityLockoutSeconds * 2);
|
|
@@ -266,12 +265,12 @@ export const createWebAccountAuthHandlers = ({ sendJson, readJsonBody, logger, p
|
|
|
266
265
|
);
|
|
267
266
|
const row = Array.isArray(rows) ? rows[0] : null;
|
|
268
267
|
const lockedUntilMs = Date.parse(String(row?.locked_until || ''));
|
|
269
|
-
if (!Number.isFinite(lockedUntilMs) || lockedUntilMs <=
|
|
268
|
+
if (!Number.isFinite(lockedUntilMs) || lockedUntilMs <= __timeNowMs()) {
|
|
270
269
|
return { locked: false, retryAfterSeconds: 0 };
|
|
271
270
|
}
|
|
272
271
|
return {
|
|
273
272
|
locked: true,
|
|
274
|
-
retryAfterSeconds: Math.max(1, Math.ceil((lockedUntilMs -
|
|
273
|
+
retryAfterSeconds: Math.max(1, Math.ceil((lockedUntilMs - __timeNowMs()) / 1000)),
|
|
275
274
|
};
|
|
276
275
|
} catch (error) {
|
|
277
276
|
logger?.warn?.('Falha ao consultar throttle distribuido de login por identidade.', {
|
|
@@ -321,12 +320,12 @@ export const createWebAccountAuthHandlers = ({ sendJson, readJsonBody, logger, p
|
|
|
321
320
|
);
|
|
322
321
|
const row = Array.isArray(rows) ? rows[0] : null;
|
|
323
322
|
const lockedUntilMs = Date.parse(String(row?.locked_until || ''));
|
|
324
|
-
if (!Number.isFinite(lockedUntilMs) || lockedUntilMs <=
|
|
323
|
+
if (!Number.isFinite(lockedUntilMs) || lockedUntilMs <= __timeNowMs()) {
|
|
325
324
|
return { locked: false, retryAfterSeconds: 0 };
|
|
326
325
|
}
|
|
327
326
|
return {
|
|
328
327
|
locked: true,
|
|
329
|
-
retryAfterSeconds: Math.max(1, Math.ceil((lockedUntilMs -
|
|
328
|
+
retryAfterSeconds: Math.max(1, Math.ceil((lockedUntilMs - __timeNowMs()) / 1000)),
|
|
330
329
|
};
|
|
331
330
|
} catch (error) {
|
|
332
331
|
logger?.warn?.('Falha ao registrar tentativa no throttle distribuido de login por identidade.', {
|
|
@@ -1201,7 +1200,7 @@ export const createWebAccountAuthHandlers = ({ sendJson, readJsonBody, logger, p
|
|
|
1201
1200
|
|
|
1202
1201
|
let avgDaily = 0;
|
|
1203
1202
|
if (usage.messages > 0 && usage.first_message_at) {
|
|
1204
|
-
const daysDiff = Math.max(1, (
|
|
1203
|
+
const daysDiff = Math.max(1, (__timeNowMs() - new Date(usage.first_message_at).getTime()) / (1000 * 60 * 60 * 24));
|
|
1205
1204
|
avgDaily = (usage.messages / daysDiff).toFixed(2);
|
|
1206
1205
|
}
|
|
1207
1206
|
|