@mindfulauth/core 2.0.0-beta.5 → 2.0.0-beta.6

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.
@@ -0,0 +1,490 @@
1
+ ---
2
+ // Mindful Auth - Security Settings Script Component
3
+ // Provides: Change password, 2FA management, recovery codes, add auth method
4
+ ---
5
+ <script is:inline>
6
+ // Security Settings Script - Astro Optimized
7
+ // Combines: Change Password + 2FA Management + Add Authentication Method
8
+
9
+ // ============================================================================
10
+ // QRCODE DYNAMIC LOADING (bundled via qrcode npm package)
11
+ // ============================================================================
12
+
13
+ // Capture CDN origin at load time for loading co-hosted libraries
14
+ const __cdnOrigin = (() => {
15
+ try {
16
+ if (document.currentScript && document.currentScript.src) {
17
+ return new URL(document.currentScript.src).origin;
18
+ }
19
+ } catch (_) {}
20
+ return '';
21
+ })();
22
+
23
+ /**
24
+ * Dynamically loads the bundled QRCode library from the same CDN origin.
25
+ * Uses the qrcode npm package (bundled as IIFE via esbuild).
26
+ * Exposes QRCode.toCanvas(), QRCode.toDataURL(), QRCode.toString()
27
+ * @returns {Promise<void>}
28
+ */
29
+ async function loadQRCodeLibrary() {
30
+ return new Promise((resolve, reject) => {
31
+ if (typeof QRCode !== 'undefined' && QRCode.toCanvas) {
32
+ resolve();
33
+ return;
34
+ }
35
+
36
+ const script = document.createElement('script');
37
+ script.src = `${__cdnOrigin}/lib/qrcode.js`;
38
+ script.onload = () => resolve();
39
+ script.onerror = () => reject(new Error('Failed to load QR code library'));
40
+ document.head.appendChild(script);
41
+ });
42
+ }
43
+
44
+ // ============================================================================
45
+ // RECORDID EXTRACTION HELPER
46
+ // ============================================================================
47
+
48
+ /**
49
+ * Extracts recordid from window variable (injected server-side by backend)
50
+ * Backend MUST inject this variable for all implementations
51
+ * Architecture
52
+ * - All protected routes require /{memberid}/page URL structure
53
+ * - Backend extracts memberid from URL path and validates against session
54
+ * - Frontend receives memberid via window.MEMBERID injection
55
+ * @returns {string|null} The recordid if found, null otherwise
56
+ */
57
+ function getRecordId() {
58
+ return window.MEMBERID || null;
59
+ }
60
+
61
+ // ============================================================================
62
+ // CHANGE PASSWORD
63
+ // ============================================================================
64
+
65
+ async function handleChangePasswordSubmit(event) {
66
+ event.preventDefault();
67
+ const form = event.target;
68
+ const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
69
+ const newPasswordEl = form.querySelector('[data-mindfulauth-field="new-password"]');
70
+ const confirmPasswordEl = form.querySelector('[data-mindfulauth-field="confirm-password"]');
71
+ const twoFACodeEl = form.querySelector('[data-mindfulauth-field="twofa-code"]');
72
+ const twoFAContainer = form.querySelector('[data-mindfulauth-field="twofa-container"]');
73
+ const submitBtn = form.querySelector('button[type="submit"]');
74
+
75
+ messageEl.textContent = '';
76
+ const newPassword = newPasswordEl.value;
77
+ const confirmPassword = confirmPasswordEl.value;
78
+
79
+ if (newPassword !== confirmPassword) {
80
+ messageEl.textContent = 'Passwords do not match.';
81
+ return;
82
+ }
83
+
84
+ const recordid = getRecordId();
85
+
86
+ if (!recordid) {
87
+ messageEl.textContent = 'Error: Could not identify user. Please log in again.';
88
+ return;
89
+ }
90
+
91
+ messageEl.textContent = 'Validating password...';
92
+
93
+ try {
94
+ // First validate password against policy
95
+ const passwordValidateResponse = await window.apiFetch('/auth/validate-password', {
96
+ body: JSON.stringify({ password: newPassword }),
97
+ });
98
+
99
+ const passwordValidateResult = await passwordValidateResponse.json();
100
+ if (!passwordValidateResult.success) {
101
+ throw new Error(passwordValidateResult.message);
102
+ }
103
+
104
+ // Check if user has 2FA enabled (use cache)
105
+ messageEl.textContent = 'Checking security settings...';
106
+ const statusResult = await window.get2FAStatus();
107
+
108
+ // If 2FA is enabled and code not provided yet, show the input and wait
109
+ if (statusResult.isEnabled) {
110
+ if (!twoFACodeEl || !twoFACodeEl.value) {
111
+ // Show the 2FA input field
112
+ if (twoFAContainer) {
113
+ twoFAContainer.removeAttribute('hidden');
114
+ twoFAContainer.classList && twoFAContainer.classList.remove('hidden');
115
+ twoFAContainer.style.display = 'block';
116
+ }
117
+ if (twoFACodeEl) {
118
+ twoFACodeEl.focus();
119
+ }
120
+ messageEl.textContent = '2FA code is required for password change.';
121
+ if (submitBtn) {
122
+ submitBtn.textContent = 'Update Password with 2FA';
123
+ }
124
+ return;
125
+ }
126
+
127
+ const twoFACode = twoFACodeEl.value;
128
+ if (twoFACode.length !== 6) {
129
+ messageEl.textContent = 'Please enter a valid 6-digit 2FA code.';
130
+ return;
131
+ }
132
+ }
133
+
134
+ messageEl.textContent = 'Updating password...';
135
+
136
+ const requestBody = { recordid, newPassword };
137
+ if (statusResult.isEnabled && twoFACodeEl && twoFACodeEl.value) {
138
+ requestBody.twoFACode = twoFACodeEl.value;
139
+ }
140
+
141
+ const response = await window.apiFetch('/auth/change-password', {
142
+ body: JSON.stringify(requestBody),
143
+ });
144
+
145
+ const result = await response.json();
146
+ if (result.success) {
147
+ messageEl.textContent = 'Password updated successfully! Redirecting to login...';
148
+ form.reset();
149
+
150
+ // Password change invalidates all sessions for security
151
+ setTimeout(() => {
152
+ window.location.assign('/login');
153
+ }, 2000);
154
+
155
+ } else {
156
+ throw new Error(result.message || 'An unknown error occurred.');
157
+ }
158
+ } catch (error) {
159
+ messageEl.textContent = `Error: ${error.message}`;
160
+ }
161
+ }
162
+
163
+ function initChangePassword() {
164
+ const form = document.querySelector('[data-mindfulauth-form="change-password"]');
165
+ if (!form) return;
166
+
167
+ // Initially hide 2FA field - it will be shown during submit if needed
168
+ const twoFAContainer = form.querySelector('[data-mindfulauth-field="twofa-container"]');
169
+ if (twoFAContainer) {
170
+ twoFAContainer.style.display = 'none';
171
+ }
172
+
173
+ form.addEventListener('submit', handleChangePasswordSubmit);
174
+ }
175
+
176
+ // ============================================================================
177
+ // 2FA MANAGEMENT
178
+ // ============================================================================
179
+
180
+ function init2FA() {
181
+ const enableBtn = document.querySelector('[data-mindfulauth-field="enable-btn"]');
182
+ if (!enableBtn) return;
183
+
184
+ const messageEl = document.querySelector('[data-mindfulauth-field="twofa-message"]');
185
+ const setupDiv = document.querySelector('[data-mindfulauth-field="setup"]');
186
+ const qrCodeContainer = document.querySelector('[data-mindfulauth-field="qr-code-container"]');
187
+ const verifyForm = document.querySelector('[data-mindfulauth-form="verify-2fa"]');
188
+ const tokenInput = document.querySelector('[data-mindfulauth-field="token"]');
189
+ const recoveryContainer = document.querySelector('[data-mindfulauth-field="recovery-codes-container"]');
190
+ const recoveryList = document.querySelector('[data-mindfulauth-field="recovery-codes-list"]');
191
+ const copyBtn = document.querySelector('[data-mindfulauth-field="copy-codes-btn"]');
192
+ const copyMessageEl = document.querySelector('[data-mindfulauth-field="copy-message"]');
193
+ const disableForm = document.querySelector('[data-mindfulauth-form="disable-2fa"]');
194
+ const disablePasswordInput = document.querySelector('[data-mindfulauth-field="disable-password"]');
195
+ const disableMessageEl = document.querySelector('[data-mindfulauth-field="disable-message"]');
196
+ const disableBtn = document.querySelector('[data-mindfulauth-field="disable-btn"]');
197
+ const enableSection = document.querySelector('[data-mindfulauth-section="enable"]');
198
+ const disableSection = document.querySelector('[data-mindfulauth-section="disable"]');
199
+ const recordid = getRecordId();
200
+
201
+ // --- START 2FA Setup ---
202
+ async function handleEnable2FA() {
203
+ if (!recordid) {
204
+ messageEl.textContent = "Session not found. Please log in again.";
205
+ return;
206
+ }
207
+ if (!qrCodeContainer || !setupDiv) {
208
+ messageEl.textContent = 'Unable to start 2FA (missing UI elements).';
209
+ return;
210
+ }
211
+
212
+ enableBtn.disabled = true;
213
+ enableBtn.style.display = 'none';
214
+ setupDiv.removeAttribute('hidden');
215
+ setupDiv.classList && setupDiv.classList.remove('hidden');
216
+ setupDiv.style.display = 'flex';
217
+ qrCodeContainer.innerHTML = '';
218
+
219
+ messageEl.textContent = 'Loading QR code generator...';
220
+ try {
221
+ // Load QRCode library dynamically
222
+ await loadQRCodeLibrary();
223
+
224
+ messageEl.textContent = 'Generating secret key...';
225
+ const response = await window.apiFetch('/auth/setup-2fa', { body: JSON.stringify({ recordid }) });
226
+ const result = await response.json();
227
+ if (result.success) {
228
+ qrCodeContainer.innerHTML = '';
229
+
230
+ const canvas = document.createElement('canvas');
231
+ await QRCode.toCanvas(canvas, result.otpauthUri, { width: 256, margin: 2 });
232
+ qrCodeContainer.appendChild(canvas);
233
+
234
+ messageEl.textContent = 'Scan the QR code with your authenticator app and enter the code below.';
235
+ } else {
236
+ throw new Error(result.message || 'Failed to start 2FA setup.');
237
+ }
238
+ } catch (error) {
239
+ messageEl.textContent = `Error: ${error.message}`;
240
+ enableBtn.disabled = false;
241
+ enableBtn.style.display = '';
242
+ setupDiv.style.display = 'none';
243
+ setupDiv.classList && setupDiv.classList.add('hidden');
244
+ setupDiv.setAttribute('hidden', '');
245
+ }
246
+ }
247
+
248
+ enableBtn.addEventListener('click', handleEnable2FA);
249
+
250
+ // --- VERIFY and ENABLE 2FA ---
251
+ if (verifyForm) {
252
+ verifyForm.addEventListener('submit', async (event) => {
253
+ event.preventDefault();
254
+ const token = tokenInput.value;
255
+ if (!recordid || !token || token.length !== 6) {
256
+ messageEl.textContent = "Please enter a valid 6-digit code.";
257
+ return;
258
+ }
259
+ messageEl.textContent = 'Verifying code...';
260
+
261
+ try {
262
+ const response = await window.apiFetch('/auth/verify-2fa-setup', {
263
+ body: JSON.stringify({ recordid, token })
264
+ });
265
+
266
+ const result = await response.json();
267
+ if (result.success) {
268
+ setupDiv.style.display = 'none';
269
+ messageEl.textContent = result.message;
270
+
271
+ if (result.recoveryCodes && result.recoveryCodes.length > 0) {
272
+ recoveryList.innerHTML = '';
273
+ result.recoveryCodes.forEach(code => {
274
+ const li = document.createElement('li');
275
+ li.textContent = code;
276
+ recoveryList.appendChild(li);
277
+ });
278
+ recoveryContainer.removeAttribute('hidden');
279
+ recoveryContainer.style.display = 'flex';
280
+ }
281
+
282
+ // Invalidate cached 2FA status
283
+ if (typeof window !== 'undefined') {
284
+ window.__2faStatus = undefined;
285
+ checkInitial2FAStatus();
286
+ }
287
+ } else {
288
+ throw new Error(result.message || 'Verification failed.');
289
+ }
290
+ } catch (error) {
291
+ messageEl.textContent = `Error: ${error.message}`;
292
+ }
293
+ });
294
+ }
295
+
296
+ // --- DISABLE 2FA ---
297
+ if (disableForm) {
298
+ disableForm.addEventListener('submit', async (event) => {
299
+ event.preventDefault();
300
+ const password = disablePasswordInput.value;
301
+ if (!recordid) {
302
+ disableMessageEl.textContent = 'Session not found. Please log in again.';
303
+ return;
304
+ }
305
+ if (!password) {
306
+ disableMessageEl.textContent = 'Password is required.';
307
+ return;
308
+ }
309
+
310
+ disableBtn.disabled = true;
311
+ disableMessageEl.textContent = 'Disabling...';
312
+
313
+ try {
314
+ const response = await window.apiFetch('/auth/disable-2fa', {
315
+ body: JSON.stringify({ recordid, password }),
316
+ });
317
+
318
+ const result = await response.json();
319
+ disableMessageEl.textContent = result.message;
320
+
321
+ if (result.success) {
322
+ if (typeof window !== 'undefined') {
323
+ window.__2faStatus = undefined;
324
+ }
325
+
326
+ disableBtn.disabled = true;
327
+ setTimeout(() => window.location.reload(), 2000);
328
+ } else {
329
+ disableBtn.disabled = false;
330
+ }
331
+ } catch (error) {
332
+ disableMessageEl.textContent = `Error: ${error.message}`;
333
+ disableBtn.disabled = false;
334
+ }
335
+ });
336
+ }
337
+
338
+ // --- CHECK 2FA STATUS ON LOAD ---
339
+ async function checkInitial2FAStatus() {
340
+ try {
341
+ const result = await window.get2FAStatus();
342
+ if (result.isEnabled) {
343
+ if (enableSection) enableSection.style.display = 'none';
344
+ if (disableSection) disableSection.style.display = 'flex';
345
+ } else {
346
+ if (enableSection) enableSection.style.display = 'flex';
347
+ if (disableSection) disableSection.style.display = 'none';
348
+ }
349
+ } catch (error) {
350
+ if (enableSection) enableSection.style.display = 'none';
351
+ if (disableSection) disableSection.style.display = 'none';
352
+ }
353
+ }
354
+
355
+ checkInitial2FAStatus();
356
+
357
+ // --- COPY RECOVERY CODES ---
358
+ if (copyBtn) {
359
+ copyBtn.addEventListener('click', function() {
360
+ const codes = Array.from(recoveryList.querySelectorAll('li')).map(li => li.textContent).join('\n');
361
+ navigator.clipboard.writeText(codes).then(() => {
362
+ copyMessageEl.textContent = 'Copied to clipboard!';
363
+ setTimeout(() => copyMessageEl.textContent = '', 2000);
364
+ }).catch(err => {
365
+ copyMessageEl.textContent = 'Failed to copy.';
366
+ });
367
+ });
368
+ }
369
+ }
370
+
371
+ // ============================================================================
372
+ // ADD AUTHENTICATION METHOD
373
+ // ============================================================================
374
+
375
+ async function handleAddAuthMethodSubmit(event) {
376
+ event.preventDefault();
377
+ const form = event.target;
378
+ const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
379
+ const methodEl = form.querySelector('[data-mindfulauth-field="method"]');
380
+ const passwordEl = form.querySelector('[data-mindfulauth-field="new-password"]');
381
+ const submitBtn = form.querySelector('button[type="submit"]');
382
+
383
+ const selectedMethod = methodEl.querySelector('input[name="method"]:checked')?.value;
384
+
385
+ if (!selectedMethod) {
386
+ messageEl.textContent = 'Please select an authentication method.';
387
+ return;
388
+ }
389
+
390
+ if (selectedMethod === 'Password') {
391
+ if (!passwordEl.value || passwordEl.value.length < 8) {
392
+ messageEl.textContent = 'Password must be at least 8 characters.';
393
+ return;
394
+ }
395
+ }
396
+
397
+ messageEl.textContent = 'Adding authentication method...';
398
+ if (submitBtn) {
399
+ submitBtn.disabled = true;
400
+ }
401
+
402
+ try {
403
+ const body = {
404
+ method: selectedMethod
405
+ };
406
+
407
+ if (selectedMethod === 'Password') {
408
+ body.password = passwordEl.value;
409
+ }
410
+
411
+ const response = await window.apiFetch('/auth/add-authentication-method', {
412
+ body: JSON.stringify(body)
413
+ });
414
+
415
+ const result = await response.json();
416
+
417
+ if (result.success) {
418
+ messageEl.textContent = result.message || `${selectedMethod} authentication has been added to your account.`;
419
+ messageEl.style.color = 'green';
420
+
421
+ if (passwordEl) {
422
+ passwordEl.value = '';
423
+ }
424
+
425
+ setTimeout(() => {
426
+ event.target.style.display = 'none';
427
+ }, 2000);
428
+ } else {
429
+ throw new Error(result.message || 'Failed to add authentication method.');
430
+ }
431
+ } catch (error) {
432
+ messageEl.textContent = `Error: ${error.message}`;
433
+ messageEl.style.color = 'red';
434
+ } finally {
435
+ if (submitBtn) {
436
+ submitBtn.disabled = false;
437
+ }
438
+ }
439
+ }
440
+
441
+ function handleMethodChange(event) {
442
+ const passwordContainerEl = document.querySelector('[data-mindfulauth-field="password-container"]');
443
+
444
+ if (!passwordContainerEl) return;
445
+
446
+ if (event.target.value === 'Password') {
447
+ passwordContainerEl.removeAttribute('hidden');
448
+ if (passwordContainerEl.classList) {
449
+ passwordContainerEl.classList.remove('hidden');
450
+ }
451
+ passwordContainerEl.style.display = 'flex';
452
+ } else {
453
+ passwordContainerEl.style.display = 'none';
454
+ if (passwordContainerEl.classList) {
455
+ passwordContainerEl.classList.add('hidden');
456
+ }
457
+ passwordContainerEl.setAttribute('hidden', '');
458
+ }
459
+ }
460
+
461
+ function initAddAuthMethod() {
462
+ const form = document.querySelector('[data-mindfulauth-form="add-auth"]');
463
+ if (!form) return;
464
+
465
+ form.addEventListener('submit', handleAddAuthMethodSubmit);
466
+
467
+ // Bind radio change handlers
468
+ const radios = document.querySelectorAll('input[name="method"]');
469
+ radios.forEach(radio => {
470
+ radio.addEventListener('change', handleMethodChange);
471
+ });
472
+
473
+ // Set initial state
474
+ const checkedRadio = document.querySelector('input[name="method"]:checked');
475
+ if (checkedRadio) {
476
+ handleMethodChange({ target: checkedRadio });
477
+ }
478
+ }
479
+
480
+ // ============================================================================
481
+ // MAIN INITIALIZATION
482
+ // ============================================================================
483
+
484
+ document.addEventListener('DOMContentLoaded', function() {
485
+ // Initialize all security modules
486
+ initChangePassword();
487
+ init2FA();
488
+ initAddAuthMethod();
489
+ });
490
+ </script>
@@ -0,0 +1,112 @@
1
+ ---
2
+ // Mindful Auth - Turnstile Initialization Component
3
+ // Provides: Cloudflare Turnstile widget initialization and auto-config
4
+ ---
5
+ <script is:inline>
6
+ // Shared Cloudflare Turnstile initialization utilities - Astro Optimized
7
+ // Loaded only on pages that require bot protection (login, forgot password, reset password)
8
+
9
+ (function () {
10
+ window.initTurnstile = function initTurnstile(sitekey) {
11
+ if (!sitekey) {
12
+ return;
13
+ }
14
+
15
+ const container = document.querySelector('[data-mindfulauth-turnstile]');
16
+ if (container && window.turnstile) {
17
+ // Read customization from data attributes
18
+ const theme = container.getAttribute('data-theme') || 'light';
19
+ const size = container.getAttribute('data-size') || 'flexible';
20
+ const language = container.getAttribute('data-language') || 'auto';
21
+ const appearance = container.getAttribute('data-appearance') || 'always';
22
+
23
+ container.textContent = '';
24
+ try {
25
+ window.turnstile.render(container, {
26
+ sitekey: sitekey,
27
+ theme: theme,
28
+ size: size,
29
+ language: language,
30
+ appearance: appearance,
31
+ callback: function(token) {
32
+ // Find the hidden input field for the token (if it exists)
33
+ const form = container.closest('form') || document.querySelector('[data-mindfulauth-form]');
34
+ if (form) {
35
+ const tokenInput = form.querySelector('[name="cf-turnstile-response"]');
36
+ if (tokenInput) {
37
+ tokenInput.value = token;
38
+ }
39
+ }
40
+ },
41
+ errorCallback: function() {
42
+ // Create error message safely without innerHTML
43
+ var errorDiv = document.createElement('div');
44
+ errorDiv.style.cssText = 'padding: 10px; background: #fee; color: #c33; border: 1px solid #faa; border-radius: 4px;';
45
+ errorDiv.textContent = 'Bot protection widget failed to load. Please check your configuration.';
46
+ container.textContent = '';
47
+ container.appendChild(errorDiv);
48
+ }
49
+ });
50
+ } catch (error) {
51
+ // Create error message safely without innerHTML
52
+ var errorDiv = document.createElement('div');
53
+ errorDiv.style.cssText = 'padding: 10px; background: #fee; color: #c33; border: 1px solid #faa; border-radius: 4px;';
54
+ errorDiv.textContent = 'Bot protection widget failed to load. Please check your configuration.';
55
+ container.textContent = '';
56
+ container.appendChild(errorDiv);
57
+ }
58
+ }
59
+ };
60
+
61
+ window.ensureTurnstileReady = function ensureTurnstileReady(callback, attempts = 0) {
62
+ if (window.turnstile) {
63
+ callback();
64
+ } else if (attempts < 50) {
65
+ setTimeout(() => window.ensureTurnstileReady(callback, attempts + 1), 100);
66
+ }
67
+ };
68
+
69
+ // Auto-initialize Turnstile on page load if container exists
70
+ // Fetches tenant config to get the correct sitekey for this hostname
71
+ window.autoInitTurnstile = async function autoInitTurnstile() {
72
+ const container = document.querySelector('[data-mindfulauth-turnstile]');
73
+ if (!container) {
74
+ return;
75
+ }
76
+
77
+ // Skip API call on localhost to prevent production logs pollution
78
+ const hostname = window.location.hostname;
79
+ if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.local')) {
80
+ console.log('[Turnstile] Skipping initialization on localhost');
81
+ return;
82
+ }
83
+
84
+ try {
85
+ const configResponse = await fetch(`/auth/get-tenant-config`, {
86
+ headers: {
87
+ 'X-Tenant-Domain': window.location.hostname
88
+ }
89
+ });
90
+
91
+ if (!configResponse.ok) {
92
+ console.error('[Turnstile] Failed to fetch tenant config: HTTP ' + configResponse.status);
93
+ return;
94
+ }
95
+
96
+ const config = await configResponse.json();
97
+ if (!config.success || !config.turnstileSitekey) {
98
+ console.error('[Turnstile] Invalid tenant config response:', config);
99
+ return;
100
+ }
101
+ window.ensureTurnstileReady(() => {
102
+ window.initTurnstile(config.turnstileSitekey);
103
+ });
104
+ } catch (error) {
105
+ console.error('[Turnstile] Failed to initialize:', error instanceof Error ? error.message : String(error));
106
+ }
107
+ };
108
+
109
+ // Call auto-init when DOM is ready
110
+ document.addEventListener('DOMContentLoaded', window.autoInitTurnstile);
111
+ })();
112
+ </script>
@@ -0,0 +1,72 @@
1
+ ---
2
+ // Mindful Auth - Verify Email Script Component
3
+ // Provides: Email verification handling
4
+ ---
5
+ <script is:inline>
6
+ // Verify email script - Astro Optimized
7
+ function getPathParams() {
8
+ const pathParts = window.location.pathname.split('/');
9
+ return {
10
+ recordid: pathParts[2],
11
+ token: pathParts[3]
12
+ };
13
+ }
14
+
15
+ async function verifyEmail() {
16
+ const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
17
+ const { recordid, token } = getPathParams();
18
+
19
+ // Skip API call on localhost to prevent production logs pollution
20
+ const hostname = window.location.hostname;
21
+ if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.local')) {
22
+ if (messageEl) {
23
+ messageEl.textContent = 'Email verification skipped on localhost.';
24
+ }
25
+ console.log('[Verify Email] Skipping API call on localhost');
26
+ return;
27
+ }
28
+
29
+ if (!recordid || !token) {
30
+ if (messageEl) {
31
+ messageEl.textContent = "Invalid verification link. Please check your email for the correct link.";
32
+ }
33
+ return;
34
+ }
35
+
36
+ if (messageEl) {
37
+ messageEl.textContent = "Verifying your email...";
38
+ }
39
+
40
+ try {
41
+ const response = await window.apiFetch(`/auth/verify-email/${recordid}/${token}`, {
42
+ method: 'POST'
43
+ });
44
+
45
+ const result = await response.json();
46
+
47
+ if (messageEl) {
48
+ messageEl.textContent = result.message || (result.success
49
+ ? 'Email verified successfully! You can now log in to your account.'
50
+ : 'Verification failed. Please try again or contact support.');
51
+ }
52
+
53
+ // Show login button/link if verification succeeded
54
+ if (result.success) {
55
+ const loginLinkEl = document.querySelector('[data-mindfulauth-field="login-link"]');
56
+ if (loginLinkEl) {
57
+ loginLinkEl.style.display = 'block';
58
+ }
59
+ }
60
+ } catch (error) {
61
+ if (messageEl) {
62
+ messageEl.textContent = 'Error: ' + (error.message || 'Verification failed. Please try again.');
63
+ }
64
+ }
65
+ }
66
+
67
+ // --- MAIN EXECUTION ---
68
+ document.addEventListener('DOMContentLoaded', function() {
69
+ // Auto-verify on page load
70
+ verifyEmail();
71
+ });
72
+ </script>