@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.
- package/.env.example +54 -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 +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +226 -55
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +143 -3
- 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 +625 -124
- 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 +88 -9
- 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 +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 +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/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 +352 -16
- 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 +12 -5
- package/public/comandos/commands-catalog.json +2253 -78
- package/public/js/apps/commandsReactApp.js +267 -87
- package/public/js/apps/createPackApp.js +3 -3
- 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/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 +10 -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 +317 -0
- package/scripts/security-web-surface-check.mjs +218 -0
- package/server/controllers/admin/adminPanelHandlers.js +253 -3
- package/server/controllers/admin/systemAdminController.js +267 -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 +254 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +3 -2
- package/server/http/httpServer.js +8 -4
- 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/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +2 -1
- package/app/modules/playModule/local/installYtDlp.js +0 -25
- package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
ENV_FILE="${ENV_FILE:-$PROJECT_ROOT/.env}"
|
|
6
|
+
|
|
7
|
+
SESSION_ID_PATTERN='^[a-zA-Z0-9:_-]+$'
|
|
8
|
+
SESSION_ID_MAX_LENGTH=64
|
|
9
|
+
|
|
10
|
+
SESSION_ID=""
|
|
11
|
+
SESSION_PREFIX="work"
|
|
12
|
+
SESSION_WEIGHT=1
|
|
13
|
+
SET_PRIMARY=0
|
|
14
|
+
CONNECT_NOW=1
|
|
15
|
+
RESET_AUTH=0
|
|
16
|
+
CLEAR_AUTH_FILES=0
|
|
17
|
+
ALLOW_REUSE=0
|
|
18
|
+
|
|
19
|
+
log() {
|
|
20
|
+
printf '[new-whatsapp-session] %s\n' "$*"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fail() {
|
|
24
|
+
printf '[new-whatsapp-session] erro: %s\n' "$*" >&2
|
|
25
|
+
exit 1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
require_cmd() {
|
|
29
|
+
if ! command -v "$1" >/dev/null 2>&1; then
|
|
30
|
+
fail "comando ausente: $1"
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
usage() {
|
|
35
|
+
cat <<'EOF'
|
|
36
|
+
Uso:
|
|
37
|
+
bash scripts/new-whatsapp-session.sh [opcoes]
|
|
38
|
+
|
|
39
|
+
Opcoes:
|
|
40
|
+
--session <id> Define o session_id manualmente.
|
|
41
|
+
--prefix <valor> Prefixo para gerar session_id automatico (padrao: work).
|
|
42
|
+
--weight <1-1000> Peso da sessao em BAILEYS_SESSION_WEIGHTS (padrao: 1).
|
|
43
|
+
--primary Define a nova sessao como BAILEYS_PRIMARY_SESSION_ID.
|
|
44
|
+
--reuse Permite usar um session_id ja existente.
|
|
45
|
+
--reset-auth Limpa credenciais atuais da sessao no MySQL antes do QR.
|
|
46
|
+
--clear-auth-files Junto com --reset-auth, remove app/connection/auth/*.json.
|
|
47
|
+
--no-connect Apenas atualiza .env (nao abre conexao para QR agora).
|
|
48
|
+
--help Mostra esta ajuda.
|
|
49
|
+
|
|
50
|
+
Exemplos:
|
|
51
|
+
npm run new:work
|
|
52
|
+
npm run new:work -- --session suporte_2 --primary
|
|
53
|
+
bash scripts/new-whatsapp-session.sh --prefix operador --no-connect
|
|
54
|
+
EOF
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
strip_wrapping_quotes() {
|
|
58
|
+
local value="${1:-}"
|
|
59
|
+
if [[ "$value" == \"*\" && "$value" == *\" ]]; then
|
|
60
|
+
value="${value:1:${#value}-2}"
|
|
61
|
+
elif [[ "$value" == \'*\' && "$value" == *\' ]]; then
|
|
62
|
+
value="${value:1:${#value}-2}"
|
|
63
|
+
fi
|
|
64
|
+
printf '%s' "$value"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
read_env_value() {
|
|
68
|
+
local key="$1"
|
|
69
|
+
local raw=""
|
|
70
|
+
|
|
71
|
+
if [[ -f "$ENV_FILE" ]]; then
|
|
72
|
+
raw="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d'=' -f2- || true)"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
raw="${raw//$'\r'/}"
|
|
76
|
+
strip_wrapping_quotes "$raw"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
split_entries() {
|
|
80
|
+
printf '%s' "${1:-}" | tr ',;\n' '\n' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' | awk 'NF'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
validate_session_id() {
|
|
84
|
+
local value="$1"
|
|
85
|
+
[[ -n "$value" ]] || return 1
|
|
86
|
+
[[ "${#value}" -le "$SESSION_ID_MAX_LENGTH" ]] || return 1
|
|
87
|
+
[[ "$value" =~ $SESSION_ID_PATTERN ]] || return 1
|
|
88
|
+
return 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
upsert_env_value() {
|
|
92
|
+
local key="$1"
|
|
93
|
+
local value="$2"
|
|
94
|
+
local temp_file
|
|
95
|
+
temp_file="$(mktemp)"
|
|
96
|
+
|
|
97
|
+
awk -v key="$key" -v value="$value" '
|
|
98
|
+
BEGIN {
|
|
99
|
+
replaced = 0;
|
|
100
|
+
prefix = key "=";
|
|
101
|
+
}
|
|
102
|
+
index($0, prefix) == 1 {
|
|
103
|
+
if (replaced == 0) {
|
|
104
|
+
print prefix value;
|
|
105
|
+
replaced = 1;
|
|
106
|
+
}
|
|
107
|
+
next;
|
|
108
|
+
}
|
|
109
|
+
{ print; }
|
|
110
|
+
END {
|
|
111
|
+
if (replaced == 0) {
|
|
112
|
+
print prefix value;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
' "$ENV_FILE" >"$temp_file"
|
|
116
|
+
|
|
117
|
+
mv "$temp_file" "$ENV_FILE"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
is_valid_weight() {
|
|
121
|
+
local value="$1"
|
|
122
|
+
[[ "$value" =~ ^[0-9]+$ ]] || return 1
|
|
123
|
+
((value >= 1 && value <= 1000)) || return 1
|
|
124
|
+
return 0
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
while [[ $# -gt 0 ]]; do
|
|
128
|
+
case "$1" in
|
|
129
|
+
--session)
|
|
130
|
+
[[ $# -ge 2 ]] || fail "faltou valor para --session"
|
|
131
|
+
SESSION_ID="$2"
|
|
132
|
+
shift 2
|
|
133
|
+
;;
|
|
134
|
+
--prefix)
|
|
135
|
+
[[ $# -ge 2 ]] || fail "faltou valor para --prefix"
|
|
136
|
+
SESSION_PREFIX="$2"
|
|
137
|
+
shift 2
|
|
138
|
+
;;
|
|
139
|
+
--weight)
|
|
140
|
+
[[ $# -ge 2 ]] || fail "faltou valor para --weight"
|
|
141
|
+
SESSION_WEIGHT="$2"
|
|
142
|
+
shift 2
|
|
143
|
+
;;
|
|
144
|
+
--primary)
|
|
145
|
+
SET_PRIMARY=1
|
|
146
|
+
shift
|
|
147
|
+
;;
|
|
148
|
+
--reuse)
|
|
149
|
+
ALLOW_REUSE=1
|
|
150
|
+
shift
|
|
151
|
+
;;
|
|
152
|
+
--reset-auth)
|
|
153
|
+
RESET_AUTH=1
|
|
154
|
+
shift
|
|
155
|
+
;;
|
|
156
|
+
--clear-auth-files)
|
|
157
|
+
CLEAR_AUTH_FILES=1
|
|
158
|
+
shift
|
|
159
|
+
;;
|
|
160
|
+
--no-connect)
|
|
161
|
+
CONNECT_NOW=0
|
|
162
|
+
shift
|
|
163
|
+
;;
|
|
164
|
+
-h|--help)
|
|
165
|
+
usage
|
|
166
|
+
exit 0
|
|
167
|
+
;;
|
|
168
|
+
*)
|
|
169
|
+
fail "opcao invalida: $1 (use --help)"
|
|
170
|
+
;;
|
|
171
|
+
esac
|
|
172
|
+
done
|
|
173
|
+
|
|
174
|
+
[[ -f "$ENV_FILE" ]] || fail "arquivo .env nao encontrado em: $ENV_FILE"
|
|
175
|
+
is_valid_weight "$SESSION_WEIGHT" || fail "peso invalido em --weight: $SESSION_WEIGHT (use 1..1000)"
|
|
176
|
+
|
|
177
|
+
if [[ -n "$SESSION_ID" ]]; then
|
|
178
|
+
validate_session_id "$SESSION_ID" || fail "session_id invalido: \"$SESSION_ID\""
|
|
179
|
+
else
|
|
180
|
+
validate_session_id "$SESSION_PREFIX" || fail "prefixo invalido para session_id: \"$SESSION_PREFIX\""
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
declare -A SEEN_SESSION_IDS=()
|
|
184
|
+
declare -a SESSION_IDS=()
|
|
185
|
+
|
|
186
|
+
existing_session_ids_raw="$(read_env_value BAILEYS_SESSION_IDS)"
|
|
187
|
+
legacy_session_id_raw="$(read_env_value BAILEYS_AUTH_SESSION_ID)"
|
|
188
|
+
if [[ -z "$existing_session_ids_raw" ]]; then
|
|
189
|
+
existing_session_ids_raw="$legacy_session_id_raw"
|
|
190
|
+
fi
|
|
191
|
+
if [[ -z "$existing_session_ids_raw" ]]; then
|
|
192
|
+
existing_session_ids_raw="default"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
while IFS= read -r candidate; do
|
|
196
|
+
[[ -n "$candidate" ]] || continue
|
|
197
|
+
if ! validate_session_id "$candidate"; then
|
|
198
|
+
log "ignorado session_id invalido no .env: $candidate"
|
|
199
|
+
continue
|
|
200
|
+
fi
|
|
201
|
+
if [[ -z "${SEEN_SESSION_IDS[$candidate]+x}" ]]; then
|
|
202
|
+
SEEN_SESSION_IDS["$candidate"]=1
|
|
203
|
+
SESSION_IDS+=("$candidate")
|
|
204
|
+
fi
|
|
205
|
+
done < <(split_entries "$existing_session_ids_raw")
|
|
206
|
+
|
|
207
|
+
if [[ "${#SESSION_IDS[@]}" -eq 0 ]]; then
|
|
208
|
+
SESSION_IDS=("default")
|
|
209
|
+
SEEN_SESSION_IDS["default"]=1
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
if [[ -z "$SESSION_ID" ]]; then
|
|
213
|
+
base_id="${SESSION_PREFIX}-$(date -u +%Y%m%d%H%M%S)"
|
|
214
|
+
generated_id="$base_id"
|
|
215
|
+
suffix=1
|
|
216
|
+
while [[ -n "${SEEN_SESSION_IDS[$generated_id]+x}" ]]; do
|
|
217
|
+
suffix=$((suffix + 1))
|
|
218
|
+
generated_id="${base_id}-${suffix}"
|
|
219
|
+
done
|
|
220
|
+
SESSION_ID="$generated_id"
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
if [[ -n "${SEEN_SESSION_IDS[$SESSION_ID]+x}" && "$ALLOW_REUSE" != "1" ]]; then
|
|
224
|
+
fail "session_id \"$SESSION_ID\" ja existe. Use --reuse para permitir reutilizacao."
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
if [[ -z "${SEEN_SESSION_IDS[$SESSION_ID]+x}" ]]; then
|
|
228
|
+
SESSION_IDS+=("$SESSION_ID")
|
|
229
|
+
SEEN_SESSION_IDS["$SESSION_ID"]=1
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
current_primary_session_id="$(read_env_value BAILEYS_PRIMARY_SESSION_ID)"
|
|
233
|
+
if ! validate_session_id "$current_primary_session_id"; then
|
|
234
|
+
current_primary_session_id="${SESSION_IDS[0]}"
|
|
235
|
+
fi
|
|
236
|
+
if [[ -z "${SEEN_SESSION_IDS[$current_primary_session_id]+x}" ]]; then
|
|
237
|
+
current_primary_session_id="${SESSION_IDS[0]}"
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
if [[ "$SET_PRIMARY" == "1" ]]; then
|
|
241
|
+
current_primary_session_id="$SESSION_ID"
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
declare -A SESSION_WEIGHTS=()
|
|
245
|
+
weights_raw="$(read_env_value BAILEYS_SESSION_WEIGHTS)"
|
|
246
|
+
while IFS= read -r raw_entry; do
|
|
247
|
+
[[ -n "$raw_entry" ]] || continue
|
|
248
|
+
separator='='
|
|
249
|
+
if [[ "$raw_entry" == *":"* && "$raw_entry" != *"="* ]]; then
|
|
250
|
+
separator=':'
|
|
251
|
+
fi
|
|
252
|
+
if [[ "$raw_entry" != *"$separator"* ]]; then
|
|
253
|
+
continue
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
raw_session="${raw_entry%%"$separator"*}"
|
|
257
|
+
raw_weight="${raw_entry#*"$separator"}"
|
|
258
|
+
if ! validate_session_id "$raw_session"; then
|
|
259
|
+
continue
|
|
260
|
+
fi
|
|
261
|
+
if is_valid_weight "$raw_weight"; then
|
|
262
|
+
SESSION_WEIGHTS["$raw_session"]="$raw_weight"
|
|
263
|
+
fi
|
|
264
|
+
done < <(split_entries "$weights_raw")
|
|
265
|
+
|
|
266
|
+
SESSION_WEIGHTS["$SESSION_ID"]="$SESSION_WEIGHT"
|
|
267
|
+
for existing_session in "${SESSION_IDS[@]}"; do
|
|
268
|
+
if [[ -z "${SESSION_WEIGHTS[$existing_session]+x}" ]]; then
|
|
269
|
+
SESSION_WEIGHTS["$existing_session"]=1
|
|
270
|
+
fi
|
|
271
|
+
done
|
|
272
|
+
|
|
273
|
+
session_ids_value="$(IFS=,; printf '%s' "${SESSION_IDS[*]}")"
|
|
274
|
+
declare -a weight_entries=()
|
|
275
|
+
for listed_session in "${SESSION_IDS[@]}"; do
|
|
276
|
+
weight_entries+=("${listed_session}=${SESSION_WEIGHTS[$listed_session]}")
|
|
277
|
+
done
|
|
278
|
+
weights_value="$(IFS=,; printf '%s' "${weight_entries[*]}")"
|
|
279
|
+
|
|
280
|
+
upsert_env_value "BAILEYS_SESSION_IDS" "$session_ids_value"
|
|
281
|
+
upsert_env_value "BAILEYS_PRIMARY_SESSION_ID" "$current_primary_session_id"
|
|
282
|
+
upsert_env_value "BAILEYS_SESSION_WEIGHTS" "$weights_value"
|
|
283
|
+
|
|
284
|
+
legacy_auth_session_id="$(read_env_value BAILEYS_AUTH_SESSION_ID)"
|
|
285
|
+
if [[ -z "$legacy_auth_session_id" || "$SET_PRIMARY" == "1" ]]; then
|
|
286
|
+
upsert_env_value "BAILEYS_AUTH_SESSION_ID" "$current_primary_session_id"
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
if [[ "$RESET_AUTH" == "1" ]]; then
|
|
290
|
+
reset_args=(--session "$SESSION_ID")
|
|
291
|
+
if [[ "$CLEAR_AUTH_FILES" == "1" ]]; then
|
|
292
|
+
reset_args+=(--clear-auth-files)
|
|
293
|
+
fi
|
|
294
|
+
bash "$PROJECT_ROOT/scripts/clear-whatsapp-session.sh" "${reset_args[@]}"
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
log "Sessao preparada com sucesso."
|
|
298
|
+
log "session_id: $SESSION_ID"
|
|
299
|
+
log "BAILEYS_SESSION_IDS: $session_ids_value"
|
|
300
|
+
log "BAILEYS_PRIMARY_SESSION_ID: $current_primary_session_id"
|
|
301
|
+
|
|
302
|
+
if [[ "$CONNECT_NOW" == "1" ]]; then
|
|
303
|
+
require_cmd node
|
|
304
|
+
log "Inicializando schema do banco..."
|
|
305
|
+
node "$PROJECT_ROOT/database/init.js"
|
|
306
|
+
|
|
307
|
+
log "Abrindo conexao da sessao '$SESSION_ID' para leitura do QR Code..."
|
|
308
|
+
export BAILEYS_SESSION_IDS="$SESSION_ID"
|
|
309
|
+
export BAILEYS_PRIMARY_SESSION_ID="$SESSION_ID"
|
|
310
|
+
export BAILEYS_AUTH_SESSION_ID="$SESSION_ID"
|
|
311
|
+
|
|
312
|
+
log "Apos conectar no WhatsApp, use Ctrl+C para encerrar."
|
|
313
|
+
exec node "$PROJECT_ROOT/app/connection/socketController.js"
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
log "Concluido sem abrir conexao. Para abrir QR agora use:"
|
|
317
|
+
log " BAILEYS_SESSION_IDS=$SESSION_ID BAILEYS_PRIMARY_SESSION_ID=$SESSION_ID BAILEYS_AUTH_SESSION_ID=$SESSION_ID node app/connection/socketController.js"
|
|
@@ -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
|
+
});
|