ilhamdev commited on
Commit
20c9c67
·
1 Parent(s): 19a8e56

fix(security): restore and enhance Cloudflare Turnstile anti-bot widget before start action

Browse files
Files changed (4) hide show
  1. frontend/app.js +50 -18
  2. frontend/index.html +1 -1
  3. frontend/style.css +31 -9
  4. main.go +1 -2
frontend/app.js CHANGED
@@ -16,6 +16,7 @@ function trackEvent(eventName, eventParams = {}) {
16
  }
17
 
18
  const DEFAULT_HF_BACKEND = 'https://ilhamdev-wa-full-pfp.hf.space';
 
19
 
20
  function getDefaultBackendUrl() {
21
  const saved = localStorage.getItem('wa_pfp_backend_url');
@@ -47,8 +48,8 @@ const state = {
47
  socket: null,
48
  backendUrl: getDefaultBackendUrl(),
49
  isProcessing: false,
50
- turnstileEnabled: false,
51
- turnstileSiteKey: null,
52
  turnstileToken: null,
53
  turnstileWidgetId: null,
54
  };
@@ -139,13 +140,16 @@ let isWakingUp = false;
139
  function updateStartButtonState() {
140
  if (!state.currentImageBase64) {
141
  btnStart.disabled = true;
 
142
  return;
143
  }
144
  if (state.turnstileEnabled && !state.turnstileToken) {
145
  btnStart.disabled = true;
 
146
  return;
147
  }
148
  btnStart.disabled = false;
 
149
  }
150
 
151
  function resetTurnstile() {
@@ -159,13 +163,21 @@ function resetTurnstile() {
159
  }
160
  if (turnstileFeedback) {
161
  turnstileFeedback.textContent = '';
162
- turnstileFeedback.className = 'turnstile-feedback';
163
  }
164
  updateStartButtonState();
165
  }
166
 
167
  let turnstileRetryCount = 0;
168
- const MAX_TURNSTILE_RETRIES = 10;
 
 
 
 
 
 
 
 
169
 
170
  function renderTurnstileWidget() {
171
  if (!state.turnstileEnabled || !state.turnstileSiteKey) {
@@ -184,12 +196,12 @@ function renderTurnstileWidget() {
184
  const currentTheme = document.documentElement.getAttribute('data-theme') || 'auto';
185
  state.turnstileWidgetId = window.turnstile.render('#turnstile-widget', {
186
  sitekey: state.turnstileSiteKey,
187
- theme: currentTheme,
188
  callback: (token) => {
189
  state.turnstileToken = token;
190
  if (turnstileFeedback) {
191
  turnstileFeedback.textContent = 'Verifikasi keamanan berhasil.';
192
- turnstileFeedback.className = 'turnstile-feedback success';
193
  }
194
  trackEvent('turnstile_success');
195
  updateStartButtonState();
@@ -197,8 +209,8 @@ function renderTurnstileWidget() {
197
  'expired-callback': () => {
198
  state.turnstileToken = null;
199
  if (turnstileFeedback) {
200
- turnstileFeedback.textContent = 'Verifikasi kedaluwarsa. Silakan verifikasi ulang.';
201
- turnstileFeedback.className = 'turnstile-feedback error';
202
  }
203
  updateStartButtonState();
204
  if (window.turnstile && state.turnstileWidgetId !== null) {
@@ -208,8 +220,8 @@ function renderTurnstileWidget() {
208
  'error-callback': () => {
209
  state.turnstileToken = null;
210
  if (turnstileFeedback) {
211
- turnstileFeedback.textContent = 'Gagal memuat tantangan Cloudflare Turnstile.';
212
- turnstileFeedback.className = 'turnstile-feedback error';
213
  }
214
  updateStartButtonState();
215
  },
@@ -224,7 +236,7 @@ function renderTurnstileWidget() {
224
  } else {
225
  if (turnstileFeedback) {
226
  turnstileFeedback.textContent = 'Gagal memuat Cloudflare Turnstile. Periksa koneksi internet atau matikan ad-blocker.';
227
- turnstileFeedback.className = 'turnstile-feedback error';
228
  }
229
  }
230
  }
@@ -239,16 +251,21 @@ async function fetchBackendConfig() {
239
  });
240
  if (res.ok) {
241
  const data = await res.json();
242
- if (data.turnstile_enabled && data.turnstile_site_key) {
243
  state.turnstileEnabled = true;
244
- state.turnstileSiteKey = data.turnstile_site_key;
 
 
245
  renderTurnstileWidget();
246
  } else {
247
- state.turnstileEnabled = false;
248
- state.turnstileSiteKey = null;
249
- state.turnstileToken = null;
250
- if (turnstileWrapper) turnstileWrapper.classList.add('hidden');
251
- updateStartButtonState();
 
 
 
252
  }
253
  }
254
  } catch (err) {
@@ -764,6 +781,17 @@ function applyTheme(theme) {
764
  if (themeIconSun) themeIconSun.classList.add('hidden');
765
  if (themeIconMoon) themeIconMoon.classList.remove('hidden');
766
  }
 
 
 
 
 
 
 
 
 
 
 
767
  }
768
 
769
  function initTheme() {
@@ -794,6 +822,10 @@ document.addEventListener('DOMContentLoaded', () => {
794
  initSettings();
795
  initCopyButton();
796
 
 
 
 
 
797
  btnStart.addEventListener('click', startProcess);
798
  btnCancelSession.addEventListener('click', cancelSession);
799
  btnRestart.addEventListener('click', restartApp);
 
16
  }
17
 
18
  const DEFAULT_HF_BACKEND = 'https://ilhamdev-wa-full-pfp.hf.space';
19
+ const DEFAULT_TURNSTILE_SITE_KEY = '0x4AAAAAAFQXC-hDYo4lIswr';
20
 
21
  function getDefaultBackendUrl() {
22
  const saved = localStorage.getItem('wa_pfp_backend_url');
 
48
  socket: null,
49
  backendUrl: getDefaultBackendUrl(),
50
  isProcessing: false,
51
+ turnstileEnabled: true,
52
+ turnstileSiteKey: DEFAULT_TURNSTILE_SITE_KEY,
53
  turnstileToken: null,
54
  turnstileWidgetId: null,
55
  };
 
140
  function updateStartButtonState() {
141
  if (!state.currentImageBase64) {
142
  btnStart.disabled = true;
143
+ btnStart.title = 'Pilih foto profil terlebih dahulu';
144
  return;
145
  }
146
  if (state.turnstileEnabled && !state.turnstileToken) {
147
  btnStart.disabled = true;
148
+ btnStart.title = 'Selesaikan verifikasi keamanan Cloudflare di atas terlebih dahulu';
149
  return;
150
  }
151
  btnStart.disabled = false;
152
+ btnStart.title = 'Mulai pasang foto profil';
153
  }
154
 
155
  function resetTurnstile() {
 
163
  }
164
  if (turnstileFeedback) {
165
  turnstileFeedback.textContent = '';
166
+ turnstileFeedback.className = 'turnstile-note';
167
  }
168
  updateStartButtonState();
169
  }
170
 
171
  let turnstileRetryCount = 0;
172
+ const MAX_TURNSTILE_RETRIES = 20;
173
+
174
+ // Callback otomatis saat script Cloudflare Turnstile selesai dimuat
175
+ window.onloadTurnstileCallback = function() {
176
+ console.log('[Turnstile] Cloudflare Turnstile API siap.');
177
+ if (state.turnstileEnabled && state.turnstileSiteKey) {
178
+ renderTurnstileWidget();
179
+ }
180
+ };
181
 
182
  function renderTurnstileWidget() {
183
  if (!state.turnstileEnabled || !state.turnstileSiteKey) {
 
196
  const currentTheme = document.documentElement.getAttribute('data-theme') || 'auto';
197
  state.turnstileWidgetId = window.turnstile.render('#turnstile-widget', {
198
  sitekey: state.turnstileSiteKey,
199
+ theme: currentTheme === 'light' ? 'light' : 'dark',
200
  callback: (token) => {
201
  state.turnstileToken = token;
202
  if (turnstileFeedback) {
203
  turnstileFeedback.textContent = 'Verifikasi keamanan berhasil.';
204
+ turnstileFeedback.className = 'turnstile-note success';
205
  }
206
  trackEvent('turnstile_success');
207
  updateStartButtonState();
 
209
  'expired-callback': () => {
210
  state.turnstileToken = null;
211
  if (turnstileFeedback) {
212
+ turnstileFeedback.textContent = 'Verifikasi kedaluwarsa. Silakan centang ulang.';
213
+ turnstileFeedback.className = 'turnstile-note error';
214
  }
215
  updateStartButtonState();
216
  if (window.turnstile && state.turnstileWidgetId !== null) {
 
220
  'error-callback': () => {
221
  state.turnstileToken = null;
222
  if (turnstileFeedback) {
223
+ turnstileFeedback.textContent = 'Gagal memuat verifikasi Cloudflare Turnstile.';
224
+ turnstileFeedback.className = 'turnstile-note error';
225
  }
226
  updateStartButtonState();
227
  },
 
236
  } else {
237
  if (turnstileFeedback) {
238
  turnstileFeedback.textContent = 'Gagal memuat Cloudflare Turnstile. Periksa koneksi internet atau matikan ad-blocker.';
239
+ turnstileFeedback.className = 'turnstile-note error';
240
  }
241
  }
242
  }
 
251
  });
252
  if (res.ok) {
253
  const data = await res.json();
254
+ if (data.turnstile_enabled !== false && (data.turnstile_site_key || state.turnstileSiteKey)) {
255
  state.turnstileEnabled = true;
256
+ if (data.turnstile_site_key) {
257
+ state.turnstileSiteKey = data.turnstile_site_key;
258
+ }
259
  renderTurnstileWidget();
260
  } else {
261
+ // Jika server lokal secara sengaja mematikan Turnstile
262
+ if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
263
+ state.turnstileEnabled = false;
264
+ state.turnstileSiteKey = null;
265
+ state.turnstileToken = null;
266
+ if (turnstileWrapper) turnstileWrapper.classList.add('hidden');
267
+ updateStartButtonState();
268
+ }
269
  }
270
  }
271
  } catch (err) {
 
781
  if (themeIconSun) themeIconSun.classList.add('hidden');
782
  if (themeIconMoon) themeIconMoon.classList.remove('hidden');
783
  }
784
+
785
+ // Sinkronkan tema Turnstile dengan tema halaman
786
+ if (window.turnstile && state.turnstileWidgetId !== null) {
787
+ try {
788
+ window.turnstile.remove(state.turnstileWidgetId);
789
+ state.turnstileWidgetId = null;
790
+ renderTurnstileWidget();
791
+ } catch (e) {
792
+ // Abaikan jika tidak didukung
793
+ }
794
+ }
795
  }
796
 
797
  function initTheme() {
 
822
  initSettings();
823
  initCopyButton();
824
 
825
+ // Inisialisasi awal Turnstile & konfigurasi backend
826
+ renderTurnstileWidget();
827
+ fetchBackendConfig();
828
+
829
  btnStart.addEventListener('click', startProcess);
830
  btnCancelSession.addEventListener('click', cancelSession);
831
  btnRestart.addEventListener('click', restartApp);
frontend/index.html CHANGED
@@ -105,7 +105,7 @@
105
  <link rel="stylesheet" href="style.css">
106
 
107
  <!-- Cloudflare Turnstile API -->
108
- <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" async defer></script>
109
  </head>
110
  <body>
111
  <div class="app-layout">
 
105
  <link rel="stylesheet" href="style.css">
106
 
107
  <!-- Cloudflare Turnstile API -->
108
+ <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback&render=explicit" async defer></script>
109
  </head>
110
  <body>
111
  <div class="app-layout">
frontend/style.css CHANGED
@@ -1100,35 +1100,57 @@ button, input, select, textarea {
1100
  }
1101
 
1102
  /* ==========================================================================
1103
- Turnstile Container
1104
  ========================================================================== */
1105
 
1106
- .turnstile-container {
 
1107
  display: flex;
1108
  flex-direction: column;
1109
  align-items: center;
1110
- margin-bottom: 24px;
 
 
 
 
 
 
1111
  width: 100%;
 
 
 
 
 
 
1112
  }
1113
 
1114
- .turnstile-slot {
 
1115
  min-height: 65px;
 
 
 
1116
  max-width: 100%;
1117
  overflow-x: auto;
1118
  }
1119
 
1120
- .turnstile-note {
1121
- font-size: 0.8rem;
1122
- font-weight: 600;
 
1123
  margin-top: 8px;
1124
  text-align: center;
 
 
1125
  }
1126
 
1127
- .turnstile-note.success {
 
1128
  color: var(--color-success);
1129
  }
1130
 
1131
- .turnstile-note.error {
 
1132
  color: var(--color-secondary);
1133
  }
1134
 
 
1100
  }
1101
 
1102
  /* ==========================================================================
1103
+ Cloudflare Turnstile Anti-Bot Verification
1104
  ========================================================================== */
1105
 
1106
+ .turnstile-container,
1107
+ .turnstile-wrapper {
1108
  display: flex;
1109
  flex-direction: column;
1110
  align-items: center;
1111
+ justify-content: center;
1112
+ margin: 18px 0 14px;
1113
+ padding: 14px 16px;
1114
+ background-color: var(--bg-canvas);
1115
+ border: 2px solid var(--border-color);
1116
+ border-radius: var(--radius-md);
1117
+ box-shadow: 2px 2px 0 var(--shadow-color);
1118
  width: 100%;
1119
+ transition: border-color var(--speed-normal), box-shadow var(--speed-normal);
1120
+ }
1121
+
1122
+ .turnstile-container:hover,
1123
+ .turnstile-wrapper:hover {
1124
+ border-color: var(--color-primary);
1125
  }
1126
 
1127
+ .turnstile-slot,
1128
+ .turnstile-widget {
1129
  min-height: 65px;
1130
+ display: flex;
1131
+ align-items: center;
1132
+ justify-content: center;
1133
  max-width: 100%;
1134
  overflow-x: auto;
1135
  }
1136
 
1137
+ .turnstile-note,
1138
+ .turnstile-feedback {
1139
+ font-size: 0.82rem;
1140
+ font-weight: 700;
1141
  margin-top: 8px;
1142
  text-align: center;
1143
+ color: var(--text-sub);
1144
+ line-height: 1.4;
1145
  }
1146
 
1147
+ .turnstile-note.success,
1148
+ .turnstile-feedback.success {
1149
  color: var(--color-success);
1150
  }
1151
 
1152
+ .turnstile-note.error,
1153
+ .turnstile-feedback.error {
1154
  color: var(--color-secondary);
1155
  }
1156
 
main.go CHANGED
@@ -128,8 +128,7 @@ func registerConfigEndpoint(mux *http.ServeMux) {
128
  w.Header().Set("Content-Type", "application/json")
129
 
130
  siteKey := os.Getenv("TURNSTILE_SITE_KEY")
131
- secretKey := os.Getenv("TURNSTILE_SECRET_KEY")
132
- isEnabled := siteKey != "" && secretKey != ""
133
 
134
  _ = json.NewEncoder(w).Encode(map[string]interface{}{
135
  "turnstile_enabled": isEnabled,
 
128
  w.Header().Set("Content-Type", "application/json")
129
 
130
  siteKey := os.Getenv("TURNSTILE_SITE_KEY")
131
+ isEnabled := siteKey != ""
 
132
 
133
  _ = json.NewEncoder(w).Encode(map[string]interface{}{
134
  "turnstile_enabled": isEnabled,