@mindfulauth/core 2.0.1 → 3.0.0

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 (33) hide show
  1. package/dist/authScripts/ForgotPasswordScript.astro +64 -0
  2. package/dist/authScripts/LoginScript.astro +209 -0
  3. package/dist/authScripts/MagicLoginScript.astro +62 -0
  4. package/dist/authScripts/MagicRegisterScript.astro +73 -0
  5. package/dist/authScripts/MainScript.astro +236 -0
  6. package/dist/authScripts/RegisterPasswordScript.astro +118 -0
  7. package/dist/authScripts/ResendVerificationScript.astro +51 -0
  8. package/dist/authScripts/ResetPasswordScript.astro +155 -0
  9. package/dist/authScripts/SecurityScript.astro +449 -0
  10. package/dist/authScripts/TurnstileInit.astro +112 -0
  11. package/dist/authScripts/VerifyEmailScript.astro +72 -0
  12. package/dist/authScripts/VerifyMagicLinkScript.astro +195 -0
  13. package/dist/authScripts/index.d.ts +13 -0
  14. package/dist/authScripts/index.d.ts.map +1 -0
  15. package/dist/authScripts/index.js +15 -0
  16. package/dist/components/MAuthEmailInput.astro +12 -0
  17. package/dist/components/MAuthForm.astro +10 -0
  18. package/dist/components/MAuthMessage.astro +8 -0
  19. package/dist/components/MAuthNameInput.astro +11 -0
  20. package/dist/components/MAuthPasswordChangePending.astro +8 -0
  21. package/dist/components/MAuthPasswordInput.astro +12 -0
  22. package/dist/components/MAuthSubmitButton.astro +8 -0
  23. package/dist/components/MAuthTurnstile.astro +11 -0
  24. package/dist/components/MAuthTwoFACodeInput.astro +13 -0
  25. package/dist/components/MAuthTwoFASection.astro +11 -0
  26. package/dist/components/index.d.ts +11 -0
  27. package/dist/components/index.d.ts.map +1 -0
  28. package/dist/components/index.js +13 -0
  29. package/dist/core/csp.d.ts +2 -2
  30. package/dist/core/csp.js +3 -3
  31. package/dist/layouts/MAuthProtected.astro +56 -0
  32. package/dist/layouts/MAuthPublic.astro +68 -0
  33. package/package.json +15 -9
@@ -0,0 +1,64 @@
1
+ ---
2
+ // Mindful Auth - Forgot Password Script Component
3
+ // Provides: Forgot password form handling
4
+ ---
5
+ <script is:inline>
6
+ // Main Forgot Password Script - Astro Optimized
7
+ async function handleForgotPasswordSubmit(event) {
8
+ event.preventDefault();
9
+ const form = event.target;
10
+ const emailInput = form.querySelector('[data-mindfulauth-field="email"]');
11
+ const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
12
+ const submitBtn = form.querySelector('button[type="submit"]');
13
+
14
+ // Skip API call on localhost to prevent production logs pollution
15
+ const hostname = window.location.hostname;
16
+ if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.local')) {
17
+ messageEl.textContent = 'Password reset is disabled on localhost. Use a real domain for testing.';
18
+ console.log('[Forgot Password] Blocked form submission on localhost');
19
+ return;
20
+ }
21
+
22
+ messageEl.textContent = 'Sending...';
23
+ submitBtn.disabled = true;
24
+
25
+ try {
26
+ const response = await window.apiFetch('/auth/forgot-password', {
27
+ body: JSON.stringify({
28
+ 'cf-turnstile-response': form.querySelector('[name="cf-turnstile-response"]')?.value,
29
+ email: emailInput.value
30
+ }),
31
+ });
32
+
33
+ const result = await response.json();
34
+ messageEl.textContent = result.message;
35
+
36
+ } catch (error) {
37
+ messageEl.textContent = 'An error occurred. Please try again.';
38
+ } finally {
39
+ submitBtn.disabled = false;
40
+ window.turnstile?.reset();
41
+ }
42
+ }
43
+
44
+ // --- MAIN EXECUTION ---
45
+ document.addEventListener('DOMContentLoaded', function() {
46
+ const form = document.querySelector('[data-mindfulauth-form="forgot-password"]');
47
+ if (!form) return;
48
+
49
+ // Check for password change required scenario (redirected from login)
50
+ const urlParams = new URLSearchParams(window.location.search);
51
+ const reason = urlParams.get('reason');
52
+ const passwordChangePendingMsg = document.querySelector('[data-mindfulauth-field="password-change-pending"]');
53
+
54
+ if (reason === 'password_change_required') {
55
+ // Show security warning
56
+ if (passwordChangePendingMsg) {
57
+ passwordChangePendingMsg.textContent = 'Your account requires a password change for security reasons. Please complete the form below to set a new password.';
58
+ passwordChangePendingMsg.style.display = 'block';
59
+ }
60
+ }
61
+
62
+ form.addEventListener('submit', handleForgotPasswordSubmit);
63
+ });
64
+ </script>
@@ -0,0 +1,209 @@
1
+ ---
2
+ // Mindful Auth - Login Script Component
3
+ // Provides: Login form handling with 2FA support
4
+ ---
5
+ <script is:inline>
6
+ // Login Script - Astro Optimized
7
+ let userEmailFor2FA = '';
8
+
9
+ async function handleLoginSubmit(event) {
10
+ event.preventDefault();
11
+ const form = event.target;
12
+ const emailInput = form.querySelector('[data-mindfulauth-field="email"]');
13
+ const passwordInput = form.querySelector('[data-mindfulauth-field="password"]');
14
+ const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
15
+ const submitBtn = form.querySelector('button[type="submit"]');
16
+ const twoFactorSection = form.querySelector('[data-mindfulauth-field="twofa-section"]');
17
+ const twoFactorInput = form.querySelector('[data-mindfulauth-field="twofa-code"]');
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
+ messageEl.textContent = 'Authentication is disabled on localhost. Use a real domain for testing.';
23
+ console.log('[Login] Blocked form submission on localhost');
24
+ submitBtn.disabled = false;
25
+ return;
26
+ }
27
+
28
+ messageEl.textContent = 'Verifying...';
29
+ submitBtn.disabled = true;
30
+
31
+ try {
32
+ if (userEmailFor2FA) {
33
+ // Verify the 2FA code using same login endpoint
34
+ const token = twoFactorInput.value;
35
+ const response = await window.apiFetch('/auth/login', {
36
+ body: JSON.stringify({
37
+ email: userEmailFor2FA,
38
+ password: passwordInput.value,
39
+ twoFACode: token
40
+ })
41
+ });
42
+
43
+ const result = await response.json();
44
+
45
+ // Successful 2FA verification
46
+ if (result.authorized) {
47
+ messageEl.textContent = 'Success! Redirecting...';
48
+
49
+ // Use redirectUrl from API response
50
+ const redirectUrl = result.redirectUrl;
51
+
52
+ // Wait for cookies to be processed before redirecting
53
+ setTimeout(() => {
54
+ window.location.assign(redirectUrl);
55
+ }, 200);
56
+ } else {
57
+ throw new Error(result.message || 'Invalid 2FA code.');
58
+ }
59
+ } else {
60
+ // Initial login attempt (Turnstile REQUIRED)
61
+ const tokenInput = form.querySelector('[name="cf-turnstile-response"]');
62
+ if (!tokenInput || !tokenInput.value) {
63
+ messageEl.textContent = 'Please complete the CAPTCHA challenge.';
64
+ submitBtn.disabled = false;
65
+ return;
66
+ }
67
+
68
+ // Use raw fetch to handle 403 password_change_required before global handler
69
+ let response = await fetch('/auth/login', {
70
+ method: 'POST',
71
+ credentials: 'include',
72
+ headers: {
73
+ 'Content-Type': 'application/json',
74
+ 'X-Requested-With': 'XMLHttpRequest',
75
+ 'X-Tenant-Domain': window.location.hostname
76
+ },
77
+ body: JSON.stringify({
78
+ 'cf-turnstile-response': tokenInput.value,
79
+ email: emailInput.value,
80
+ password: passwordInput.value
81
+ }),
82
+ });
83
+
84
+ const result = await response.json();
85
+
86
+ // Check for password change required FIRST (403 response)
87
+ if (result.password_change_required) {
88
+ window.location.assign('/forgot-password?reason=password_change_required');
89
+ return;
90
+ }
91
+
92
+ // Handle response normally for other cases
93
+ if (!response.ok && response.status !== 403) {
94
+ throw new Error(result.message || 'Login failed.');
95
+ }
96
+
97
+ // Successful login without 2FA
98
+ if (result.authorized) {
99
+ // Use redirectUrl from API response
100
+ const redirectUrl = result.redirectUrl;
101
+
102
+ messageEl.textContent = 'Success! Redirecting...';
103
+
104
+ // Wait for cookies to be processed before redirecting
105
+ setTimeout(() => {
106
+ window.location.assign(redirectUrl);
107
+ }, 200);
108
+
109
+ } else if (result.two_factor_required) {
110
+ userEmailFor2FA = emailInput.value;
111
+ messageEl.textContent = 'Please enter your two-factor authentication code.';
112
+ emailInput.parentElement.style.display = 'none';
113
+ passwordInput.parentElement.style.display = 'none';
114
+ const turnstileContainer = form.querySelector('[data-mindfulauth-turnstile]');
115
+ if (turnstileContainer) turnstileContainer.style.display = 'none';
116
+ twoFactorSection.style.display = 'flex';
117
+ twoFactorInput.focus();
118
+ submitBtn.textContent = 'Verify Code';
119
+
120
+ // Show recovery link when 2FA section appears
121
+ const recoveryLink = form.querySelector('[data-mindfulauth-field="use-recovery-link"]');
122
+ if (recoveryLink) {
123
+ recoveryLink.style.display = '';
124
+ }
125
+ } else {
126
+ throw new Error(result.message || 'Login failed.');
127
+ }
128
+ }
129
+
130
+ // If there's an error in the 2FA step, reset the form to the password step.
131
+ } catch (error) {
132
+ messageEl.textContent = `Error: ${error.message}`;
133
+ userEmailFor2FA = '';
134
+ twoFactorSection.style.display = 'none';
135
+ emailInput.parentElement.style.display = '';
136
+ passwordInput.parentElement.style.display = '';
137
+ const turnstileContainer = form.querySelector('[data-mindfulauth-turnstile]');
138
+ if (turnstileContainer) turnstileContainer.style.display = '';
139
+ submitBtn.textContent = 'Login';
140
+ if (window.turnstile && typeof window.turnstile.reset === 'function') {
141
+ window.turnstile.reset();
142
+ }
143
+ } finally {
144
+ submitBtn.disabled = false;
145
+ }
146
+ }
147
+
148
+ // --- MAIN EXECUTION ---
149
+ document.addEventListener('DOMContentLoaded', function() {
150
+ // Check for verification success parameter
151
+ const urlParams = new URLSearchParams(window.location.search);
152
+ if (urlParams.get('verified') === '1') {
153
+ const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
154
+ if (messageEl) {
155
+ messageEl.textContent = '✓ Email verified successfully! You can now log in.';
156
+ messageEl.style.color = 'green';
157
+ }
158
+ }
159
+
160
+ const form = document.querySelector('[data-mindfulauth-form="login"]');
161
+ if (!form) return;
162
+
163
+ form.addEventListener('submit', handleLoginSubmit);
164
+
165
+ // --- Add event listener for the recovery code link ---
166
+ const recoveryLink = form.querySelector('[data-mindfulauth-field="use-recovery-link"]');
167
+ if (recoveryLink) {
168
+ recoveryLink.addEventListener('click', (event) => {
169
+ event.preventDefault();
170
+ event.stopPropagation();
171
+ const twoFactorInput = form.querySelector('[data-mindfulauth-field="twofa-code"]');
172
+ const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
173
+
174
+ // Remove the restrictions for the recovery code
175
+ twoFactorInput.removeAttribute('maxlength');
176
+ twoFactorInput.removeAttribute('pattern');
177
+ twoFactorInput.removeAttribute('inputmode');
178
+ twoFactorInput.value = '';
179
+
180
+ // Update UI for recovery code entry
181
+ twoFactorInput.placeholder = 'e.g. ABCDEF1234';
182
+ twoFactorInput.focus();
183
+ messageEl.textContent = 'Enter one of your saved recovery codes.';
184
+ recoveryLink.style.display = 'none';
185
+ }, true);
186
+ }
187
+
188
+ // Delegated fallback (handles dynamic rendering)
189
+ document.addEventListener('click', (e) => {
190
+ const link = e.target && e.target.closest && e.target.closest('[data-mindfulauth-field="use-recovery-link"]');
191
+ if (link) {
192
+ e.preventDefault();
193
+ e.stopPropagation();
194
+ const twoFactorInput = form.querySelector('[data-mindfulauth-field="twofa-code"]');
195
+ const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
196
+
197
+ twoFactorInput.removeAttribute('maxlength');
198
+ twoFactorInput.removeAttribute('pattern');
199
+ twoFactorInput.removeAttribute('inputmode');
200
+ twoFactorInput.value = '';
201
+
202
+ twoFactorInput.placeholder = 'e.g. ABCDEF1234';
203
+ twoFactorInput.focus();
204
+ messageEl.textContent = 'Enter one of your saved recovery codes.';
205
+ link.style.display = 'none';
206
+ }
207
+ }, true);
208
+ });
209
+ </script>
@@ -0,0 +1,62 @@
1
+ ---
2
+ // Mindful Auth - Magic Login Script Component
3
+ // Provides: Magic link login form handling
4
+ ---
5
+ <script is:inline>
6
+ // Magic Link Login Script - Astro Optimized
7
+ async function handleMagicLoginSubmit(event) {
8
+ event.preventDefault();
9
+ const form = event.target;
10
+ const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
11
+ const emailEl = form.querySelector('[data-mindfulauth-field="email"]');
12
+ const submitBtn = form.querySelector('button[type="submit"]');
13
+
14
+ // Skip API call on localhost to prevent production logs pollution
15
+ const hostname = window.location.hostname;
16
+ if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.local')) {
17
+ messageEl.textContent = 'Magic link login is disabled on localhost. Use a real domain for testing.';
18
+ console.log('[Magic Login] Blocked form submission on localhost');
19
+ return;
20
+ }
21
+
22
+ if (!emailEl.value) {
23
+ messageEl.textContent = 'Please enter your email address.';
24
+ return;
25
+ }
26
+
27
+ messageEl.textContent = 'Sending magic link...';
28
+ submitBtn.disabled = true;
29
+
30
+ try {
31
+ const response = await window.apiFetch('/auth/request-magic-link', {
32
+ body: JSON.stringify({
33
+ email: emailEl.value,
34
+ 'cf-turnstile-response': form.querySelector('[name="cf-turnstile-response"]')?.value
35
+ })
36
+ });
37
+
38
+ const result = await response.json();
39
+
40
+ if (result.success) {
41
+ emailEl.value = '';
42
+ form.style.display = 'none';
43
+ messageEl.textContent = 'If an account with that email exists, a magic link has been sent. Please check your email.';
44
+ } else {
45
+ throw new Error(result.message || 'Failed to send magic link.');
46
+ }
47
+ } catch (error) {
48
+ messageEl.textContent = `Error: ${error.message}`;
49
+ } finally {
50
+ submitBtn.disabled = false;
51
+ window.turnstile?.reset();
52
+ }
53
+ }
54
+
55
+ // --- MAIN EXECUTION ---
56
+ document.addEventListener('DOMContentLoaded', function() {
57
+ const form = document.querySelector('[data-mindfulauth-form="magic-login"]');
58
+ if (!form) return;
59
+
60
+ form.addEventListener('submit', handleMagicLoginSubmit);
61
+ });
62
+ </script>
@@ -0,0 +1,73 @@
1
+ ---
2
+ // Mindful Auth - Magic Register Script Component
3
+ // Provides: Magic link registration form handling
4
+ ---
5
+ <script is:inline>
6
+ // Magic Link Registration Script - Astro Optimized
7
+
8
+ async function handleMagicRegisterSubmit(event) {
9
+ event.preventDefault();
10
+ const form = event.target;
11
+ const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
12
+ const nameEl = form.querySelector('[data-mindfulauth-field="name"]');
13
+ const emailEl = form.querySelector('[data-mindfulauth-field="email"]');
14
+ const submitBtn = form.querySelector('button[type="submit"]');
15
+
16
+ // Skip API call on localhost to prevent production logs pollution
17
+ const hostname = window.location.hostname;
18
+ if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.local')) {
19
+ messageEl.textContent = 'Magic link registration is disabled on localhost. Use a real domain for testing.';
20
+ console.log('[Magic Register] Blocked form submission on localhost');
21
+ return;
22
+ }
23
+
24
+ if (!nameEl.value || !emailEl.value) {
25
+ messageEl.textContent = 'Please enter your name and email address.';
26
+ return;
27
+ }
28
+
29
+ messageEl.textContent = 'Creating your account...';
30
+ submitBtn.disabled = true;
31
+
32
+ try {
33
+ const response = await window.apiFetch('/auth/register-magic-link', {
34
+ body: JSON.stringify({
35
+ name: nameEl.value,
36
+ email: emailEl.value,
37
+ 'cf-turnstile-response': form.querySelector('[name="cf-turnstile-response"]')?.value
38
+ })
39
+ });
40
+
41
+ const result = await response.json();
42
+
43
+ if (result.success) {
44
+ messageEl.textContent = result.message || 'Account created! Please check your email to verify your account.';
45
+ nameEl.value = '';
46
+ emailEl.value = '';
47
+ form.style.display = 'none';
48
+
49
+ // Show success message prominently
50
+ const successMessageEl = form.querySelector('[data-mindfulauth-field="success-message"]');
51
+ if (successMessageEl) {
52
+ successMessageEl.textContent = 'Account created successfully! Please check your email and click the verification link before logging in.';
53
+ successMessageEl.style.display = 'block';
54
+ }
55
+ } else {
56
+ throw new Error(result.message || 'Failed to create account.');
57
+ }
58
+ } catch (error) {
59
+ messageEl.textContent = `Error: ${error.message}`;
60
+ } finally {
61
+ submitBtn.disabled = false;
62
+ window.turnstile?.reset();
63
+ }
64
+ }
65
+
66
+ // --- MAIN EXECUTION ---
67
+ document.addEventListener('DOMContentLoaded', function() {
68
+ const form = document.querySelector('[data-mindfulauth-form="magic-register"]');
69
+ if (!form) return;
70
+
71
+ form.addEventListener('submit', handleMagicRegisterSubmit);
72
+ });
73
+ </script>
@@ -0,0 +1,236 @@
1
+ ---
2
+ // Mindful Auth - Main Script Component
3
+ // Provides: frame-busting, auth response handler, global fetch wrapper,
4
+ // 2FA status cache, cookie checks, logout functionality
5
+ ---
6
+ <script is:inline>
7
+ // Main Auth Script - Astro Optimized
8
+ // Frame-busting, fetch wrapper, 2FA status, logout, cookie checks
9
+
10
+ // ============================================================================
11
+ // FRAME-BUSTING PROTECTION
12
+ // ============================================================================
13
+
14
+ (function() {
15
+ if (window.self !== window.top) {
16
+ try {
17
+ window.top.location = window.self.location;
18
+ } catch (e) {
19
+ document.documentElement.style.display = 'none';
20
+ }
21
+ }
22
+ })();
23
+
24
+ // ============================================================================
25
+ // AUTHENTICATION RESPONSE HANDLER
26
+ // ============================================================================
27
+
28
+ window.handleAuthResponse = async function (response) {
29
+ if (!response.ok) {
30
+ let message = `Request failed (${response.status})`;
31
+ try {
32
+ const ct = response.headers.get('content-type') || '';
33
+ if (ct.includes('application/json')) {
34
+ const error = await response.json();
35
+ message = error && error.message ? error.message : message;
36
+ } else {
37
+ const txt = await response.text();
38
+ if (txt) message = txt;
39
+ }
40
+ } catch (_) {
41
+ }
42
+ throw new Error(message);
43
+ }
44
+ return response;
45
+ };
46
+
47
+ // ============================================================================
48
+ // GLOBAL FETCH WRAPPER
49
+ // ============================================================================
50
+
51
+ window.apiFetch = async function (endpoint, options = {}) {
52
+ const defaultHeaders = {
53
+ 'Content-Type': 'application/json',
54
+ 'X-Requested-With': 'XMLHttpRequest',
55
+ 'X-Tenant-Domain': window.location.hostname
56
+ };
57
+
58
+ const defaultOptions = {
59
+ method: 'POST',
60
+ credentials: 'include',
61
+ headers: defaultHeaders
62
+ };
63
+
64
+ const mergedOptions = {
65
+ ...defaultOptions,
66
+ ...options,
67
+ headers: {
68
+ ...defaultHeaders,
69
+ ...options.headers
70
+ }
71
+ };
72
+
73
+ try {
74
+ let bodyData = mergedOptions.body ? JSON.parse(mergedOptions.body) : {};
75
+ mergedOptions.body = JSON.stringify(bodyData);
76
+
77
+ const response = await fetch(endpoint, mergedOptions);
78
+ return await handleAuthResponse(response);
79
+ } catch (error) {
80
+ const safeMessage = error && error.message ? error.message : 'Unknown error';
81
+ if (typeof console !== 'undefined' && console.error) {
82
+ console.error('Request failed:', safeMessage);
83
+ }
84
+ throw error;
85
+ }
86
+ };
87
+
88
+ // ============================================================================
89
+ // GLOBAL 2FA STATUS CACHE HELPER
90
+ // ============================================================================
91
+
92
+ window.get2FAStatus = async function() {
93
+ // Skip API call on localhost to prevent production logs pollution
94
+ const hostname = window.location.hostname;
95
+ if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.local')) {
96
+ console.log('[2FA Status] Skipping API call on localhost');
97
+ window.__2faStatus = { isEnabled: false, localhost: true };
98
+ return window.__2faStatus;
99
+ }
100
+
101
+ if (window.__2faStatus !== undefined) {
102
+ return window.__2faStatus;
103
+ }
104
+
105
+ if (window.__2faStatusPromise) {
106
+ return await window.__2faStatusPromise;
107
+ }
108
+
109
+ window.__2faStatusPromise = (async () => {
110
+ try {
111
+ const response = await window.apiFetch('/auth/get-2fa-status', {
112
+ method: 'POST',
113
+ headers: { 'Content-Type': 'application/json' }
114
+ });
115
+ const result = await response.json();
116
+ window.__2faStatus = result;
117
+ return result;
118
+ } catch (error) {
119
+ window.__2faStatus = { isEnabled: false, error: error.message };
120
+ return window.__2faStatus;
121
+ } finally {
122
+ window.__2faStatusPromise = null;
123
+ }
124
+ })();
125
+
126
+ return await window.__2faStatusPromise;
127
+ };
128
+
129
+ // ============================================================================
130
+ // COOKIE CHECK UTILITIES
131
+ // ============================================================================
132
+
133
+ window.checkCookiesEnabled = function () {
134
+ document.cookie = 'test_cookie=1; path=/; SameSite=Lax';
135
+ const enabled = document.cookie.indexOf('test_cookie') !== -1;
136
+ document.cookie = 'test_cookie=1; path=/; max-age=0';
137
+ return enabled;
138
+ };
139
+
140
+ window.showCookieWarning = function () {
141
+ if (!window.checkCookiesEnabled()) {
142
+ const warningBanner = document.createElement('div');
143
+ warningBanner.id = 'cookie-warning-banner';
144
+ warningBanner.style.cssText = `
145
+ position: fixed;
146
+ top: 0;
147
+ left: 0;
148
+ right: 0;
149
+ background: #f44336;
150
+ color: white;
151
+ padding: 12px 20px;
152
+ text-align: center;
153
+ font-size: 14px;
154
+ font-weight: 500;
155
+ z-index: 10000;
156
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
157
+ `;
158
+ warningBanner.textContent = '⚠️ Cookies are required for authentication. Please enable cookies in your browser.';
159
+ document.body.insertBefore(warningBanner, document.body.firstChild);
160
+ return false;
161
+ }
162
+ return true;
163
+ };
164
+
165
+ // ============================================================================
166
+ // LOGOUT FUNCTIONALITY
167
+ // ============================================================================
168
+
169
+ window.__performLogout = async function () {
170
+ if (window.__logoutInProgress) {
171
+ return;
172
+ }
173
+ window.__logoutInProgress = true;
174
+ try {
175
+ try {
176
+ const response = await window.apiFetch('/auth/logout', { method: 'POST' });
177
+ await response.json().catch(() => {});
178
+ } catch (error) {
179
+ // Silently continue - redirect to login regardless
180
+ }
181
+ } finally {
182
+ window.location.href = '/login';
183
+ window.__logoutInProgress = false;
184
+ }
185
+ };
186
+
187
+ window.setupLogoutButton = function () {
188
+ const logoutBtn = document.querySelector('[data-mindfulauth-action="logout"]');
189
+
190
+ if (!logoutBtn) {
191
+ return;
192
+ }
193
+
194
+ // Prevent duplicate listeners
195
+ if (logoutBtn.dataset.logoutAttached === 'true') {
196
+ return;
197
+ }
198
+ logoutBtn.dataset.logoutAttached = 'true';
199
+
200
+ const handleLogout = function (event) {
201
+ event.preventDefault();
202
+ event.stopPropagation();
203
+ event.stopImmediatePropagation();
204
+ window.__performLogout();
205
+ };
206
+
207
+ logoutBtn.addEventListener('click', handleLogout, true);
208
+ logoutBtn.addEventListener('click', handleLogout);
209
+ };
210
+
211
+ window.attachGlobalLogoutHandler = function () {
212
+ if (window.__globalLogoutHandlerAttached) return;
213
+ window.__globalLogoutHandlerAttached = true;
214
+
215
+ document.addEventListener('click', function (event) {
216
+ const target = event.target;
217
+ if (!target) return;
218
+ const button = target.closest && target.closest('#logout-button');
219
+ if (button) {
220
+ event.preventDefault();
221
+ event.stopPropagation();
222
+ window.__performLogout();
223
+ }
224
+ }, true);
225
+ };
226
+
227
+ // ============================================================================
228
+ // MAIN INITIALIZATION
229
+ // ============================================================================
230
+
231
+ document.addEventListener('DOMContentLoaded', function() {
232
+ window.showCookieWarning();
233
+ window.setupLogoutButton();
234
+ window.attachGlobalLogoutHandler();
235
+ });
236
+ </script>