@kaikybrofc/omnizap-system 2.3.1 → 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +82 -483
  2. package/app/controllers/messageController.js +473 -255
  3. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +83 -0
  4. package/app/modules/stickerModule/stickerCommand.js +7 -2
  5. package/app/modules/stickerModule/stickerTextCommand.js +7 -2
  6. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +1 -3
  7. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +224 -53
  8. package/app/observability/metrics.js +6 -3
  9. package/app/services/googleWebLinkService.js +77 -0
  10. package/app/services/lidMapService.js +83 -4
  11. package/database/index.js +2 -0
  12. package/database/migrations/20260301_0028_message_analysis_event.sql +32 -0
  13. package/database/migrations/20260301_0029_admin_action_audit.sql +16 -0
  14. package/package.json +1 -1
  15. package/public/index.html +12 -8
  16. package/public/js/apps/createPackApp.js +4 -4
  17. package/public/js/apps/homeApp.js +78 -34
  18. package/public/js/apps/loginApp.js +245 -35
  19. package/public/js/apps/stickersAdminApp.js +4 -10
  20. package/public/js/apps/stickersApp.js +1 -1
  21. package/public/js/apps/userApp.js +956 -55
  22. package/public/js/apps/userProfileApp.js +244 -0
  23. package/public/login/index.html +437 -101
  24. package/public/termos-de-uso/index.html +1 -1
  25. package/public/user/index.html +2 -181
  26. package/public/user/systemadm/index.html +774 -0
  27. package/server/controllers/stickerCatalog/nonCatalogHandlers.js +183 -0
  28. package/server/controllers/stickerCatalogController.js +1289 -368
  29. package/server/controllers/systemAdminController.js +141 -0
  30. package/server/controllers/userController.js +87 -0
  31. package/server/http/httpServer.js +72 -32
  32. package/server/middleware/cachePolicy.js +24 -0
  33. package/server/middleware/cachePolicyHelpers.js +1 -0
  34. package/server/middleware/rateLimit.js +89 -0
  35. package/server/middleware/requestLogger.js +16 -0
  36. package/server/middleware/requireAdminAuth.js +42 -0
  37. package/server/middleware/securityHeaders.js +6 -0
  38. package/server/routes/admin/systemAdminRouter.js +56 -0
  39. package/server/routes/health/healthRouter.js +41 -0
  40. package/server/routes/indexRouter.js +197 -0
  41. package/server/routes/metrics/metricsRouter.js +13 -0
  42. package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +44 -0
  43. package/server/routes/stickerCatalog/stickerApiRouter.js +84 -0
  44. package/server/routes/stickerCatalog/stickerDataRouter.js +140 -0
  45. package/server/routes/stickerCatalog/stickerSiteRouter.js +43 -0
  46. package/server/routes/user/userRouter.js +56 -0
  47. package/server/utils/safePath.js +26 -0
  48. package/server/routes/metricsRoute.js +0 -7
  49. package/server/routes/stickerCatalogRoute.js +0 -20
@@ -2,18 +2,32 @@
2
2
 
3
3
  const GOOGLE_GSI_SCRIPT_SRC = 'https://accounts.google.com/gsi/client';
4
4
  const DEFAULT_API_BASE_PATH = '/api/sticker-packs';
5
+ const LOGIN_CONSENT_STORAGE_KEY = 'omnizap_login_terms_consent_v1';
6
+ const LOGIN_CONSENT_HINT = 'Aceite os Termos de Uso e a Politica de Privacidade para continuar.';
7
+ const DEFAULT_SUCCESS_CHAT_LABEL = 'Abrir WhatsApp do bot';
8
+ const DEFAULT_SUCCESS_HOME_LABEL = 'Ir para o painel';
9
+ const ALREADY_LOGGED_HINT_TEXT = 'Nao e necessario fazer login novamente. Escolha uma opcao abaixo.';
5
10
 
6
11
  const root = document.getElementById('login-app-root');
7
12
 
8
13
  if (root) {
9
14
  const ui = {
15
+ loginCard: document.getElementById('login-main-card'),
10
16
  status: document.getElementById('login-status'),
11
17
  hint: document.getElementById('login-hint'),
12
18
  error: document.getElementById('login-error'),
13
19
  googleArea: document.getElementById('google-login-area'),
20
+ googleButtonShell: document.getElementById('google-login-button-shell'),
14
21
  googleButton: document.querySelector('[data-google-login-button]'),
15
22
  googleState: document.getElementById('google-login-state'),
23
+ consentBox: document.getElementById('login-consent-box'),
24
+ consentCheckbox: document.getElementById('login-consent-checkbox'),
25
+ consentError: document.getElementById('login-consent-error'),
26
+ alreadyLoggedBanner: document.getElementById('already-logged-banner'),
27
+ alreadyLoggedTitle: document.getElementById('already-logged-title'),
28
+ alreadyLoggedDetail: document.getElementById('already-logged-detail'),
16
29
  summary: document.getElementById('login-summary'),
30
+ summaryTitle: document.getElementById('login-summary-title'),
17
31
  summaryOwner: document.getElementById('login-summary-owner'),
18
32
  whatsappCta: document.getElementById('whatsapp-cta'),
19
33
  whatsappCtaLink: document.getElementById('whatsapp-cta-link'),
@@ -21,6 +35,7 @@ if (root) {
21
35
  successActions: document.getElementById('login-success-actions'),
22
36
  successChat: document.getElementById('login-success-chat'),
23
37
  successHome: document.getElementById('login-success-home'),
38
+ successCelebration: document.getElementById('login-success-celebration'),
24
39
  };
25
40
 
26
41
  const state = {
@@ -30,6 +45,8 @@ if (root) {
30
45
  googleReady: false,
31
46
  busy: false,
32
47
  authenticated: false,
48
+ consentAccepted: false,
49
+ successAnimationTimer: 0,
33
50
  botPhone: '',
34
51
  sessionOwnerPhone: '',
35
52
  hint: readWhatsAppHintFromUrl(window.location.search),
@@ -43,6 +60,25 @@ if (root) {
43
60
  element.textContent = String(value || '');
44
61
  };
45
62
 
63
+ const isAuthenticatedFlagEnabled = (value) => {
64
+ if (value === true || value === 1) return true;
65
+ if (typeof value === 'string') {
66
+ const normalized = value.trim().toLowerCase();
67
+ return normalized === 'true' || normalized === '1';
68
+ }
69
+ return false;
70
+ };
71
+
72
+ const isAuthenticatedGoogleSession = (sessionData) => {
73
+ if (!sessionData || typeof sessionData !== 'object') return false;
74
+ if (!isAuthenticatedFlagEnabled(sessionData.authenticated)) return false;
75
+ const provider = String(sessionData.provider || '').trim().toLowerCase();
76
+ if (provider && provider !== 'google') return false;
77
+ const ownerJid = String(sessionData.owner_jid || '').trim();
78
+ const userSub = String(sessionData?.user?.sub || '').trim();
79
+ return Boolean(ownerJid || userSub);
80
+ };
81
+
46
82
  const normalizeDigits = (value) => String(value || '').replace(/\D+/g, '');
47
83
 
48
84
  const showError = (message) => {
@@ -52,13 +88,110 @@ if (root) {
52
88
  if (safe) ui.error.textContent = safe;
53
89
  };
54
90
 
91
+ const showConsentError = (message) => {
92
+ if (!ui.consentError) return;
93
+ const safe = String(message || '').trim();
94
+ ui.consentError.hidden = !safe;
95
+ if (safe) ui.consentError.textContent = safe;
96
+ };
97
+
98
+ const persistConsentState = (accepted) => {
99
+ try {
100
+ if (accepted) {
101
+ window.localStorage.setItem(LOGIN_CONSENT_STORAGE_KEY, '1');
102
+ } else {
103
+ window.localStorage.removeItem(LOGIN_CONSENT_STORAGE_KEY);
104
+ }
105
+ } catch {
106
+ // Ignore storage errors (private mode or blocked storage).
107
+ }
108
+ };
109
+
110
+ const readConsentState = () => {
111
+ try {
112
+ return window.localStorage.getItem(LOGIN_CONSENT_STORAGE_KEY) === '1';
113
+ } catch {
114
+ return false;
115
+ }
116
+ };
117
+
118
+ const hideSuccessCelebration = () => {
119
+ if (!ui.successCelebration) return;
120
+ ui.successCelebration.classList.remove('is-visible');
121
+ window.setTimeout(() => {
122
+ if (!ui.successCelebration?.classList.contains('is-visible')) {
123
+ ui.successCelebration.hidden = true;
124
+ }
125
+ }, 320);
126
+ };
127
+
128
+ const playSuccessCelebration = () => {
129
+ if (!ui.successCelebration) return;
130
+ if (state.successAnimationTimer) {
131
+ window.clearTimeout(state.successAnimationTimer);
132
+ state.successAnimationTimer = 0;
133
+ }
134
+
135
+ ui.successCelebration.hidden = false;
136
+ ui.successCelebration.classList.remove('is-visible');
137
+ void ui.successCelebration.offsetWidth;
138
+ ui.successCelebration.classList.add('is-visible');
139
+
140
+ state.successAnimationTimer = window.setTimeout(() => {
141
+ hideSuccessCelebration();
142
+ state.successAnimationTimer = 0;
143
+ }, 2300);
144
+ };
145
+
55
146
  const setBusy = (value) => {
56
147
  state.busy = Boolean(value);
57
148
  if (ui.googleButton) {
58
- ui.googleButton.style.opacity = state.busy ? '0.55' : '1';
59
- ui.googleButton.style.pointerEvents = state.busy ? 'none' : 'auto';
149
+ const canInteract = !state.busy && state.consentAccepted;
150
+ ui.googleButton.style.opacity = canInteract ? '1' : '0.55';
151
+ ui.googleButton.style.pointerEvents = canInteract ? 'auto' : 'none';
60
152
  }
61
- setText(ui.googleState, state.busy ? 'Finalizando login...' : state.googleReady ? '' : 'Carregando login Google...');
153
+ setText(ui.googleState, state.busy ? 'Finalizando login...' : state.consentAccepted ? (state.googleReady ? '' : 'Carregando login Google...') : LOGIN_CONSENT_HINT);
154
+ };
155
+
156
+ const renderGoogleLoginControls = () => {
157
+ const hideLoginControls = state.authenticated;
158
+ if (ui.consentBox) {
159
+ ui.consentBox.hidden = hideLoginControls;
160
+ }
161
+ if (ui.googleButtonShell) {
162
+ ui.googleButtonShell.hidden = hideLoginControls || !state.consentAccepted;
163
+ }
164
+ };
165
+
166
+ const renderLoginCardVisibility = () => {
167
+ if (!ui.loginCard) return;
168
+ ui.loginCard.hidden = Boolean(state.authenticated);
169
+ };
170
+
171
+ const resolveGoogleButtonWidth = () => {
172
+ const directWidth = Math.floor(Number(ui.googleButton?.clientWidth || 0));
173
+ if (directWidth > 0) {
174
+ return Math.max(180, Math.min(320, directWidth));
175
+ }
176
+
177
+ const areaWidth = Math.floor(Number(ui.googleArea?.clientWidth || 0));
178
+ if (areaWidth > 0) {
179
+ const areaStyles = ui.googleArea ? window.getComputedStyle(ui.googleArea) : null;
180
+ const shellStyles = ui.googleButtonShell ? window.getComputedStyle(ui.googleButtonShell) : null;
181
+ const areaPaddingX = areaStyles ? Number.parseFloat(areaStyles.paddingLeft || '0') + Number.parseFloat(areaStyles.paddingRight || '0') : 0;
182
+ const shellPaddingX = shellStyles ? Number.parseFloat(shellStyles.paddingLeft || '0') + Number.parseFloat(shellStyles.paddingRight || '0') : 16;
183
+ const shellBorderX = shellStyles ? Number.parseFloat(shellStyles.borderLeftWidth || '0') + Number.parseFloat(shellStyles.borderRightWidth || '0') : 2;
184
+ const available = Math.floor(areaWidth - areaPaddingX - shellPaddingX - shellBorderX);
185
+ if (available > 0) {
186
+ return Math.max(180, Math.min(320, available));
187
+ }
188
+ }
189
+
190
+ const viewportAvailable = Math.floor(Number(window.innerWidth || 0)) - 96;
191
+ if (viewportAvailable > 0) {
192
+ return Math.max(180, Math.min(320, viewportAvailable));
193
+ }
194
+ return 280;
62
195
  };
63
196
 
64
197
  const formatPhone = (digits) => {
@@ -102,18 +235,47 @@ if (root) {
102
235
 
103
236
  const canUseGoogleLogin = () => Boolean(state.hint.phone);
104
237
 
105
- const renderSuccessActions = (sessionData) => {
238
+ const renderSuccessActions = (sessionData, options = {}) => {
106
239
  if (!ui.successActions) return;
107
- const authenticated = Boolean(sessionData?.authenticated);
240
+ const authenticated = isAuthenticatedFlagEnabled(sessionData?.authenticated);
108
241
  ui.successActions.hidden = !authenticated;
109
242
  if (!authenticated) return;
110
243
 
244
+ const chatLabel = String(options.chatLabel || DEFAULT_SUCCESS_CHAT_LABEL);
245
+ const homeLabel = String(options.homeLabel || DEFAULT_SUCCESS_HOME_LABEL);
246
+ const homeHref = String(options.homeHref || '/user/');
247
+
111
248
  if (ui.successChat) {
112
249
  ui.successChat.href = buildWhatsappMenuUrl(state.botPhone);
250
+ setText(ui.successChat, chatLabel);
113
251
  }
114
252
  if (ui.successHome) {
115
- ui.successHome.href = '/';
253
+ ui.successHome.href = homeHref;
254
+ setText(ui.successHome, homeLabel);
255
+ }
256
+ };
257
+
258
+ const renderAlreadyLoggedBanner = ({ visible = false, ownerPhone = '' } = {}) => {
259
+ if (!ui.alreadyLoggedBanner) return;
260
+ ui.alreadyLoggedBanner.hidden = !visible;
261
+ if (!visible) return;
262
+ setText(ui.alreadyLoggedTitle, 'Voce ja esta logado neste navegador.');
263
+ if (ownerPhone) {
264
+ setText(ui.alreadyLoggedDetail, `Sessao ativa para +${formatPhone(ownerPhone)}. Nao e necessario fazer login novamente.`);
265
+ return;
116
266
  }
267
+ setText(ui.alreadyLoggedDetail, ALREADY_LOGGED_HINT_TEXT);
268
+ };
269
+
270
+ const renderAlreadyLoggedInState = (sessionData) => {
271
+ const ownerPhone = String(sessionData?.owner_phone || '').trim();
272
+ state.authenticated = true;
273
+ state.sessionOwnerPhone = ownerPhone;
274
+
275
+ showError('');
276
+ showConsentError('');
277
+ hideSuccessCelebration();
278
+ renderSessionSummary(sessionData);
117
279
  };
118
280
 
119
281
  const renderWhatsAppCta = () => {
@@ -146,30 +308,35 @@ if (root) {
146
308
 
147
309
  const renderHint = () => {
148
310
  if (!state.hint.hasPayload) {
149
- setText(ui.hint, 'Voce abriu esta pagina direto. Por seguranca, gere seu link no WhatsApp clicando no botao abaixo e enviando "iniciar".');
311
+ setText(ui.hint, 'Abra o link que o bot envia no WhatsApp para liberar o login neste navegador.');
150
312
  return;
151
313
  }
152
314
 
153
315
  if (!state.hint.phone) {
154
- setText(ui.hint, 'Este link nao trouxe um numero de WhatsApp valido. Ele pode ter sido alterado ou expirado. Gere um novo link enviando "iniciar".');
316
+ setText(ui.hint, 'Este link nao tem um numero valido. Gere um novo enviando "iniciar" no bot.');
155
317
  return;
156
318
  }
157
319
 
158
- setText(ui.hint, `Numero detectado para vinculo: +${formatPhone(state.hint.phone)}.`);
320
+ setText(ui.hint, `Numero detectado: +${formatPhone(state.hint.phone)}.`);
159
321
  };
160
322
 
161
323
  const renderSessionSummary = (sessionData) => {
324
+ renderAlreadyLoggedBanner({ visible: false });
162
325
  if (!canUseGoogleLogin()) {
163
326
  state.authenticated = false;
164
327
  state.sessionOwnerPhone = '';
328
+ renderGoogleLoginControls();
329
+ renderLoginCardVisibility();
165
330
  if (ui.summary) ui.summary.hidden = true;
166
331
  renderSuccessActions(null);
167
332
  renderWhatsAppCta();
168
333
  return;
169
334
  }
170
335
 
171
- const authenticated = Boolean(sessionData?.authenticated);
336
+ const authenticated = isAuthenticatedGoogleSession(sessionData);
172
337
  state.authenticated = authenticated;
338
+ renderGoogleLoginControls();
339
+ renderLoginCardVisibility();
173
340
  renderSuccessActions(sessionData);
174
341
  if (ui.summary) ui.summary.hidden = !authenticated;
175
342
  if (!authenticated) {
@@ -181,18 +348,24 @@ if (root) {
181
348
  const ownerPhone = String(sessionData?.owner_phone || '').trim();
182
349
  state.sessionOwnerPhone = ownerPhone;
183
350
  if (ownerPhone) {
184
- setText(ui.summaryOwner, `WhatsApp vinculado: +${formatPhone(ownerPhone)}`);
351
+ if (ui.summary) ui.summary.dataset.state = 'ok';
352
+ setText(ui.summaryTitle, 'WhatsApp conectado');
353
+ setText(ui.summaryOwner, `+${formatPhone(ownerPhone)}`);
185
354
  renderWhatsAppCta();
186
355
  return;
187
356
  }
188
357
 
189
358
  if (state.hint.phone) {
190
- setText(ui.summaryOwner, `Numero detectado: +${formatPhone(state.hint.phone)}. Clique no botao do Google para concluir o vinculo.`);
359
+ if (ui.summary) ui.summary.dataset.state = 'pending';
360
+ setText(ui.summaryTitle, 'Vinculo em andamento');
361
+ setText(ui.summaryOwner, `Numero detectado: +${formatPhone(state.hint.phone)}`);
191
362
  renderWhatsAppCta();
192
363
  return;
193
364
  }
194
365
 
195
- setText(ui.summaryOwner, 'Conta Google ativa. Vincule o WhatsApp abrindo o link pelo "iniciar".');
366
+ if (ui.summary) ui.summary.dataset.state = 'ok';
367
+ setText(ui.summaryTitle, 'Conta ativa');
368
+ setText(ui.summaryOwner, 'Abra o link do WhatsApp para concluir o vinculo.');
196
369
  renderWhatsAppCta();
197
370
  };
198
371
 
@@ -231,7 +404,26 @@ if (root) {
231
404
  return payload;
232
405
  };
233
406
 
407
+ const syncConsentState = () => {
408
+ state.consentAccepted = Boolean(ui.consentCheckbox?.checked);
409
+ persistConsentState(state.consentAccepted);
410
+ if (ui.consentBox) {
411
+ ui.consentBox.classList.toggle('is-checked', state.consentAccepted);
412
+ }
413
+ if (state.consentAccepted) {
414
+ showConsentError('');
415
+ }
416
+ renderGoogleLoginControls();
417
+ setBusy(state.busy);
418
+ };
419
+
234
420
  const handleGoogleCredential = async (credential) => {
421
+ if (!state.consentAccepted) {
422
+ showConsentError(LOGIN_CONSENT_HINT);
423
+ setBusy(false);
424
+ return;
425
+ }
426
+
235
427
  const token = String(credential || '').trim();
236
428
  if (!token) {
237
429
  showError('Falha ao receber token do Google. Tente novamente.');
@@ -249,16 +441,18 @@ if (root) {
249
441
  body: JSON.stringify(buildSessionPayload(token)),
250
442
  });
251
443
  const sessionData = sessionPayload?.data || {};
252
- if (!sessionData?.authenticated) {
444
+ if (!isAuthenticatedGoogleSession(sessionData)) {
253
445
  throw new Error('Nao foi possivel criar a sessao Google.');
254
446
  }
255
- setText(ui.status, 'Login concluido. Seu acesso foi vinculado com sucesso.');
447
+ setText(ui.status, 'Conta Google detectada');
256
448
  renderSessionSummary(sessionData);
257
449
  setText(ui.googleState, 'Login Google ativo.');
450
+ playSuccessCelebration();
258
451
  } catch (error) {
259
452
  showError(error?.message || 'Falha ao concluir login Google.');
260
- setText(ui.status, 'Nao foi possivel concluir o login agora.');
453
+ setText(ui.status, 'Falha ao validar conta Google');
261
454
  renderSessionSummary(null);
455
+ hideSuccessCelebration();
262
456
  } finally {
263
457
  setBusy(false);
264
458
  }
@@ -310,22 +504,21 @@ if (root) {
310
504
  });
311
505
 
312
506
  ui.googleButton.innerHTML = '';
313
- const measuredWidth = Math.floor(Number(ui.googleButton.clientWidth || 0));
314
- const buttonWidth = Math.max(180, Math.min(320, measuredWidth || 320));
507
+ const buttonWidth = resolveGoogleButtonWidth();
315
508
  accounts.renderButton(ui.googleButton, {
316
509
  type: 'standard',
317
- theme: 'filled_black',
510
+ theme: 'outline',
318
511
  size: 'large',
319
- text: 'signin_with',
512
+ text: 'continue_with',
320
513
  shape: 'pill',
321
514
  width: buttonWidth,
322
515
  });
323
516
 
324
517
  state.googleReady = true;
325
- setText(ui.googleState, '');
518
+ setBusy(state.busy);
326
519
  } catch (error) {
327
520
  state.googleReady = false;
328
- setText(ui.googleState, '');
521
+ setBusy(state.busy);
329
522
  showError(error?.message || 'Falha ao carregar login Google.');
330
523
  }
331
524
  };
@@ -382,24 +575,27 @@ if (root) {
382
575
  };
383
576
 
384
577
  const loadCurrentSession = async () => {
385
- if (!canUseGoogleLogin()) {
386
- renderSessionSummary(null);
387
- return;
388
- }
389
578
  try {
390
579
  const payload = await fetchJson(sessionApiPath, { method: 'GET' });
391
580
  const sessionData = payload?.data || {};
392
- if (sessionData?.authenticated) {
393
- setText(ui.status, 'Sessao Google ativa neste navegador.');
394
- renderSessionSummary(sessionData);
581
+ if (isAuthenticatedGoogleSession(sessionData)) {
582
+ renderAlreadyLoggedInState(sessionData);
583
+ return true;
584
+ }
585
+ if (canUseGoogleLogin()) {
586
+ setText(ui.status, 'Entre com Google para continuar');
395
587
  } else {
396
- setText(ui.status, 'Use o login Google abaixo para entrar no OmniZap.');
397
- renderSessionSummary(null);
588
+ state.authenticated = false;
589
+ state.sessionOwnerPhone = '';
398
590
  }
591
+ renderSessionSummary(null);
399
592
  } catch {
400
- setText(ui.status, 'Nao foi possivel validar sua sessao atual.');
593
+ if (canUseGoogleLogin()) {
594
+ setText(ui.status, 'Nao foi possivel validar sua sessao');
595
+ }
401
596
  renderSessionSummary(null);
402
597
  }
598
+ return false;
403
599
  };
404
600
 
405
601
  const renderGoogleLoginGate = () => {
@@ -407,8 +603,12 @@ if (root) {
407
603
  if (ui.googleArea) {
408
604
  ui.googleArea.hidden = !allowed;
409
605
  }
606
+ renderGoogleLoginControls();
607
+ if (!allowed) {
608
+ renderAlreadyLoggedBanner({ visible: false });
609
+ }
410
610
  if (!allowed) {
411
- setText(ui.status, 'Para fazer login, abra esta pagina pelo link do WhatsApp (envie "iniciar" no bot).');
611
+ setText(ui.status, 'Abra o link enviado no WhatsApp para continuar');
412
612
  setText(ui.googleState, '');
413
613
  if (ui.summary) ui.summary.hidden = true;
414
614
  renderSuccessActions(null);
@@ -418,6 +618,12 @@ if (root) {
418
618
  };
419
619
 
420
620
  const init = async () => {
621
+ renderAlreadyLoggedBanner({ visible: false });
622
+ if (ui.consentCheckbox) {
623
+ ui.consentCheckbox.checked = readConsentState();
624
+ ui.consentCheckbox.addEventListener('change', syncConsentState);
625
+ }
626
+ syncConsentState();
421
627
  renderHint();
422
628
  if (ui.whatsappCta && state.hint.phone) {
423
629
  ui.whatsappCta.hidden = true;
@@ -426,9 +632,13 @@ if (root) {
426
632
  renderSuccessActions(null);
427
633
  const allowGoogleLogin = renderGoogleLoginGate();
428
634
  await loadBotPhone();
429
- if (!allowGoogleLogin) return;
635
+ if (!allowGoogleLogin) {
636
+ renderSessionSummary(null);
637
+ return;
638
+ }
639
+ const alreadyLogged = await loadCurrentSession();
640
+ if (alreadyLogged) return;
430
641
  await loadConfig();
431
- await loadCurrentSession();
432
642
  await mountGoogleButton();
433
643
  };
434
644
 
@@ -188,7 +188,7 @@ function loadScript(src) {
188
188
 
189
189
  async function fetchJson(url, options = {}) {
190
190
  const response = await fetch(url, {
191
- credentials: 'same-origin',
191
+ credentials: 'include',
192
192
  ...options,
193
193
  });
194
194
 
@@ -890,7 +890,7 @@ function renderSystemTab() {
890
890
  <h3 class="panel-title">Autenticacao admin</h3>
891
891
  </div>
892
892
  <div class="kv-grid">
893
- <div class="kv-item"><span class="kv-key">Admin email</span><span class="kv-value break-all">${escapeHtml(state.adminStatus?.admin_email || '-')}</span></div>
893
+ <div class="kv-item"><span class="kv-key">Login provider</span><span class="kv-value">${escapeHtml(state.adminStatus?.session?.authenticated ? 'Google + senha' : 'Google')}</span></div>
894
894
  <div class="kv-item"><span class="kv-key">Google session</span><span class="kv-value">${authGoogle?.authenticated ? 'Ativa' : 'Inativa'}</span></div>
895
895
  <div class="kv-item"><span class="kv-key">Elegivel</span><span class="kv-value">${canUnlockAdmin() ? 'Sim' : 'Nao'}</span></div>
896
896
  <div class="kv-item"><span class="kv-key">Seu papel</span><span class="kv-value">${escapeHtml(getAdminRole() || 'owner')}</span></div>
@@ -1060,7 +1060,6 @@ function renderUnlockView() {
1060
1060
  const googleSession = google?.user ? google : { authenticated: false };
1061
1061
  const localGoogleCache = readLocalGoogleAuthCache();
1062
1062
  const hasLocalCache = Boolean(localGoogleCache?.user?.sub);
1063
- const adminEmail = String(adminStatus?.admin_email || 'ADM_EMAIL');
1064
1063
  const googleConfigEnabled = Boolean(state.googleAuthConfig?.enabled && state.googleAuthConfig?.clientId);
1065
1064
 
1066
1065
  return `
@@ -1087,7 +1086,7 @@ function renderUnlockView() {
1087
1086
  <div class="account-box">
1088
1087
  <p class="row-title">${escapeHtml(googleSession.user?.name || 'Conta Google')}</p>
1089
1088
  <p class="row-sub break-all">${escapeHtml(googleSession.user?.email || '')}</p>
1090
- <p class="row-meta">${canUnlockAdmin() ? 'Conta elegivel para admin.' : 'Conta Google logada nao bate com ADM_EMAIL.'}</p>
1089
+ <p class="row-meta">${canUnlockAdmin() ? 'Conta elegivel para admin.' : 'Conta Google logada nao esta elegivel para admin.'}</p>
1091
1090
  </div>
1092
1091
  `
1093
1092
  : `
@@ -1117,7 +1116,7 @@ function renderUnlockView() {
1117
1116
  <form data-form="admin-unlock" class="form-grid">
1118
1117
  <input class="search-input" type="password" name="password" placeholder="Digite a senha do painel" autocomplete="current-password" />
1119
1118
  <button class="primary-btn" type="submit" ${state.busy ? 'disabled' : ''}>${state.busy ? 'Validando...' : 'Desbloquear Painel'}</button>
1120
- ${!canUnlockAdmin() ? `<p class="hint warning">A senha so desbloqueia apos sessao Google elegivel (${escapeHtml(adminEmail)} ou moderador autorizado).</p>` : '<p class="hint">Sessao Google elegivel detectada. Informe a senha correspondente.</p>'}
1119
+ ${!canUnlockAdmin() ? '<p class="hint warning">A senha so desbloqueia apos sessao Google elegivel (owner ou moderador autorizado).</p>' : '<p class="hint">Sessao Google elegivel detectada. Informe a senha correspondente.</p>'}
1121
1120
  </form>
1122
1121
  </article>
1123
1122
  </div>
@@ -1318,8 +1317,6 @@ async function unlockAdmin(password) {
1318
1317
  async () => {
1319
1318
  if (!canUnlockAdmin()) {
1320
1319
  const google = state.adminStatus?.google || {};
1321
- const adminEmail = String(state.adminStatus?.admin_email || '').trim();
1322
- const loggedEmail = String(google?.user?.email || '').trim();
1323
1320
  if (!google?.authenticated) {
1324
1321
  const local = readLocalGoogleAuthCache();
1325
1322
  if (local?.user?.email) {
@@ -1327,9 +1324,6 @@ async function unlockAdmin(password) {
1327
1324
  }
1328
1325
  throw new Error('Faca login Google no site com o email admin antes de digitar a senha.');
1329
1326
  }
1330
- if (loggedEmail && adminEmail && loggedEmail.toLowerCase() !== adminEmail.toLowerCase()) {
1331
- throw new Error(`Email logado (${loggedEmail}) e diferente do ADM_EMAIL (${adminEmail}).`);
1332
- }
1333
1327
  throw new Error('Conta Google atual nao esta elegivel para desbloquear o painel.');
1334
1328
  }
1335
1329
 
@@ -2293,7 +2293,7 @@ function StickersApp() {
2293
2293
  let attempt = 0;
2294
2294
  while (true) {
2295
2295
  try {
2296
- const response = await fetch(url, { credentials: 'same-origin', ...opts });
2296
+ const response = await fetch(url, { credentials: 'include', ...opts });
2297
2297
  const rawText = await response.text().catch(() => '');
2298
2298
  let payload = {};
2299
2299
  if (rawText) {