@mindfulauth/core 1.2.1 → 2.0.0-beta.10
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/README.md +2 -14
- package/dist/astro/ForgotPasswordScript.astro +64 -0
- package/dist/astro/LoginScript.astro +209 -0
- package/dist/astro/MagicLoginScript.astro +62 -0
- package/dist/astro/MagicRegisterScript.astro +73 -0
- package/dist/astro/MainScript.astro +236 -0
- package/dist/astro/RegisterPasswordScript.astro +118 -0
- package/dist/astro/ResendVerificationScript.astro +51 -0
- package/dist/astro/ResetPasswordScript.astro +155 -0
- package/dist/astro/SecurityScript.astro +449 -0
- package/dist/astro/TurnstileInit.astro +112 -0
- package/dist/astro/VerifyEmailScript.astro +72 -0
- package/dist/astro/VerifyMagicLinkScript.astro +195 -0
- package/dist/astro/index.d.ts +13 -0
- package/dist/astro/index.d.ts.map +1 -0
- package/dist/astro/index.js +15 -0
- package/dist/core/auth-handler.d.ts.map +1 -0
- package/dist/{auth-handler.js → core/auth-handler.js} +7 -4
- package/dist/{auth.d.ts → core/auth.d.ts} +1 -1
- package/dist/core/auth.d.ts.map +1 -0
- package/dist/{auth.js → core/auth.js} +1 -1
- package/dist/core/config.d.ts +47 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +95 -0
- package/dist/core/csp.d.ts +19 -0
- package/dist/core/csp.d.ts.map +1 -0
- package/dist/core/csp.js +36 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +17 -0
- package/dist/core/middleware.d.ts.map +1 -0
- package/dist/core/middleware.js +108 -0
- package/dist/core/security.d.ts +5 -0
- package/dist/core/security.d.ts.map +1 -0
- package/dist/{security.js → core/security.js} +1 -11
- package/dist/{types.d.ts → core/types.d.ts} +0 -6
- package/dist/core/types.d.ts.map +1 -0
- package/package.json +25 -15
- package/dist/auth-handler.d.ts.map +0 -1
- package/dist/auth.d.ts.map +0 -1
- package/dist/config.d.ts +0 -69
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -165
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -13
- package/dist/middleware.d.ts.map +0 -1
- package/dist/middleware.js +0 -76
- package/dist/security.d.ts +0 -11
- package/dist/security.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
- /package/dist/{auth-handler.d.ts → core/auth-handler.d.ts} +0 -0
- /package/dist/{middleware.d.ts → core/middleware.d.ts} +0 -0
- /package/dist/{types.js → core/types.js} +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mindful Auth - Verify Magic Link Script Component
|
|
3
|
+
// Provides: Magic link verification with 2FA support
|
|
4
|
+
---
|
|
5
|
+
<script is:inline>
|
|
6
|
+
// Verify Magic Link Script - Astro Optimized
|
|
7
|
+
|
|
8
|
+
function getPathParams() {
|
|
9
|
+
const pathParts = window.location.pathname.split('/');
|
|
10
|
+
return {
|
|
11
|
+
recordid: pathParts[2],
|
|
12
|
+
token: pathParts[3]
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function handleVerifyMagicLinkSubmit(event) {
|
|
17
|
+
if (event) event.preventDefault();
|
|
18
|
+
|
|
19
|
+
const form = document.querySelector('[data-mindfulauth-form="verify-magic-link"]');
|
|
20
|
+
if (!form) return;
|
|
21
|
+
const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
|
|
22
|
+
if (!messageEl) return;
|
|
23
|
+
const twoFACodeEl = form.querySelector('[data-mindfulauth-field="twofa-code"]');
|
|
24
|
+
const submitBtn = form.querySelector('button[type="submit"]');
|
|
25
|
+
|
|
26
|
+
// Skip API call on localhost to prevent production logs pollution
|
|
27
|
+
const hostname = window.location.hostname;
|
|
28
|
+
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.local')) {
|
|
29
|
+
messageEl.textContent = 'Magic link verification skipped on localhost.';
|
|
30
|
+
console.log('[Verify Magic Link] Skipping API call on localhost');
|
|
31
|
+
if (form) form.style.display = 'none';
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { recordid, token } = getPathParams();
|
|
36
|
+
|
|
37
|
+
if (!recordid || !token) {
|
|
38
|
+
messageEl.textContent = "Invalid or expired magic link. Please request a new one.";
|
|
39
|
+
if (form) form.style.display = 'none';
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Get Turnstile token
|
|
44
|
+
const turnstileToken = form.querySelector('[name="cf-turnstile-response"]')?.value;
|
|
45
|
+
if (!turnstileToken) {
|
|
46
|
+
messageEl.textContent = "Bot protection validation required.";
|
|
47
|
+
if (submitBtn) submitBtn.disabled = true;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
messageEl.textContent = "Verifying magic link...";
|
|
52
|
+
if (submitBtn) submitBtn.disabled = true;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const requestBody = {
|
|
56
|
+
'cf-turnstile-response': turnstileToken
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Include 2FA code if provided
|
|
60
|
+
if (twoFACodeEl && twoFACodeEl.value) {
|
|
61
|
+
requestBody.twoFACode = twoFACodeEl.value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const endpoint = `/auth/verify-magic-link/${recordid}/${token}`;
|
|
65
|
+
console.log('[Verify Magic Link] Making request to:', endpoint);
|
|
66
|
+
console.log('[Verify Magic Link] Request body:', requestBody);
|
|
67
|
+
|
|
68
|
+
const response = await window.apiFetch(endpoint, {
|
|
69
|
+
body: JSON.stringify(requestBody)
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
console.log('[Verify Magic Link] Response status:', response.status);
|
|
73
|
+
const result = await response.json();
|
|
74
|
+
console.log('[Verify Magic Link] Response body:', result);
|
|
75
|
+
|
|
76
|
+
// Check if 2FA is required
|
|
77
|
+
if (result.requires2FA) {
|
|
78
|
+
// Reset Turnstile to generate a fresh token for the 2FA submission
|
|
79
|
+
// (Turnstile tokens are single-use; first request consumed it)
|
|
80
|
+
window.turnstile?.reset();
|
|
81
|
+
|
|
82
|
+
// Show the 2FA input field
|
|
83
|
+
const twoFAContainer = form.querySelector('[data-mindfulauth-field="twofa-code-container"]');
|
|
84
|
+
if (twoFAContainer) {
|
|
85
|
+
twoFAContainer.removeAttribute('hidden');
|
|
86
|
+
twoFAContainer.classList && twoFAContainer.classList.remove('hidden');
|
|
87
|
+
twoFAContainer.style.setProperty('display', 'block', 'important');
|
|
88
|
+
// Try common display values
|
|
89
|
+
if (window.getComputedStyle(twoFAContainer).display === 'none') {
|
|
90
|
+
twoFAContainer.style.setProperty('display', 'flex', 'important');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (twoFACodeEl) {
|
|
94
|
+
twoFACodeEl.focus();
|
|
95
|
+
}
|
|
96
|
+
messageEl.textContent = '2FA code is required to complete login.';
|
|
97
|
+
if (submitBtn) {
|
|
98
|
+
submitBtn.textContent = 'Verify 2FA Code';
|
|
99
|
+
submitBtn.disabled = false;
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (result.success) {
|
|
105
|
+
console.log('[Verify Magic Link] Verification successful');
|
|
106
|
+
messageEl.textContent = result.message || 'Login successful! Redirecting...';
|
|
107
|
+
if (form) form.style.display = 'none';
|
|
108
|
+
|
|
109
|
+
// Redirect to secure area (match login.js pattern)
|
|
110
|
+
if (result.redirect) {
|
|
111
|
+
console.log('[Verify Magic Link] Redirecting to:', result.redirect);
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
window.location.assign(result.redirect);
|
|
114
|
+
}, 200);
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
console.error('[Verify Magic Link] Verification failed:', result.message);
|
|
118
|
+
throw new Error(result.message || 'Verification failed.');
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error('[Verify Magic Link] Error:', error);
|
|
122
|
+
messageEl.textContent = `Error: ${error.message}`;
|
|
123
|
+
if (submitBtn) submitBtn.disabled = false;
|
|
124
|
+
window.turnstile?.reset();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// --- MAIN EXECUTION ---
|
|
129
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
130
|
+
const form = document.querySelector('[data-mindfulauth-form="verify-magic-link"]');
|
|
131
|
+
if (!form) return;
|
|
132
|
+
|
|
133
|
+
// Initialize message and disable button until Turnstile is ready
|
|
134
|
+
const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
|
|
135
|
+
const submitBtn = form.querySelector('button[type="submit"]');
|
|
136
|
+
if (messageEl) {
|
|
137
|
+
messageEl.textContent = 'Loading bot protection...';
|
|
138
|
+
}
|
|
139
|
+
if (submitBtn) {
|
|
140
|
+
submitBtn.disabled = true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Initially hide 2FA field - it will be shown if needed
|
|
144
|
+
const twoFAContainer = form.querySelector('[data-mindfulauth-field="twofa-code-container"]');
|
|
145
|
+
if (twoFAContainer) {
|
|
146
|
+
twoFAContainer.style.display = 'none';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Wait for Turnstile to be ready
|
|
150
|
+
let turnstileReady = false;
|
|
151
|
+
const checkTurnstile = setInterval(() => {
|
|
152
|
+
const turnstileToken = form.querySelector('[name="cf-turnstile-response"]')?.value;
|
|
153
|
+
if (turnstileToken) {
|
|
154
|
+
console.log('[Verify Magic Link] Turnstile token loaded - ready for user verification');
|
|
155
|
+
clearInterval(checkTurnstile);
|
|
156
|
+
turnstileReady = true;
|
|
157
|
+
const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
|
|
158
|
+
if (messageEl) {
|
|
159
|
+
messageEl.textContent = 'Ready to verify. Click the button below to sign in.';
|
|
160
|
+
}
|
|
161
|
+
const submitBtn = form.querySelector('button[type="submit"]');
|
|
162
|
+
if (submitBtn) {
|
|
163
|
+
submitBtn.disabled = false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}, 100);
|
|
167
|
+
|
|
168
|
+
// Check for Turnstile load every 500ms for up to 30 seconds
|
|
169
|
+
// (don't show error proactively - only if user tries to submit)
|
|
170
|
+
let turnstileCheckCount = 0;
|
|
171
|
+
const extendedCheckTurnstile = setInterval(() => {
|
|
172
|
+
const turnstileToken = form.querySelector('[name="cf-turnstile-response"]')?.value;
|
|
173
|
+
if (turnstileToken && !turnstileReady) {
|
|
174
|
+
clearInterval(extendedCheckTurnstile);
|
|
175
|
+
turnstileReady = true;
|
|
176
|
+
const messageEl = document.querySelector('[data-mindfulauth-field="message"]');
|
|
177
|
+
if (messageEl) {
|
|
178
|
+
messageEl.textContent = 'Ready to verify. Click the button below to sign in.';
|
|
179
|
+
}
|
|
180
|
+
const submitBtn = form.querySelector('button[type="submit"]');
|
|
181
|
+
if (submitBtn) {
|
|
182
|
+
submitBtn.disabled = false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
turnstileCheckCount++;
|
|
186
|
+
// Stop checking after 30 seconds
|
|
187
|
+
if (turnstileCheckCount > 60) {
|
|
188
|
+
clearInterval(extendedCheckTurnstile);
|
|
189
|
+
}
|
|
190
|
+
}, 500);
|
|
191
|
+
|
|
192
|
+
// Also handle explicit form submission (for 2FA code entry)
|
|
193
|
+
form.addEventListener('submit', handleVerifyMagicLinkSubmit);
|
|
194
|
+
});
|
|
195
|
+
</script>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { default as MAuthMainScript } from './MainScript.astro';
|
|
2
|
+
export { default as MAuthTurnstileInit } from './TurnstileInit.astro';
|
|
3
|
+
export { default as MAuthLoginScript } from './LoginScript.astro';
|
|
4
|
+
export { default as MAuthRegisterPasswordScript } from './RegisterPasswordScript.astro';
|
|
5
|
+
export { default as MAuthForgotPasswordScript } from './ForgotPasswordScript.astro';
|
|
6
|
+
export { default as MAuthMagicLoginScript } from './MagicLoginScript.astro';
|
|
7
|
+
export { default as MAuthMagicRegisterScript } from './MagicRegisterScript.astro';
|
|
8
|
+
export { default as MAuthResendVerificationScript } from './ResendVerificationScript.astro';
|
|
9
|
+
export { default as MAuthResetPasswordScript } from './ResetPasswordScript.astro';
|
|
10
|
+
export { default as MAuthVerifyEmailScript } from './VerifyEmailScript.astro';
|
|
11
|
+
export { default as MAuthVerifyMagicLinkScript } from './VerifyMagicLinkScript.astro';
|
|
12
|
+
export { default as MAuthSecurityScript } from './SecurityScript.astro';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/astro/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AACxF,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,6BAA6B,EAAE,MAAM,kCAAkC,CAAC;AAC5F,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Mindful Auth - Astro Components Barrel Export
|
|
2
|
+
// Usage: import { MAuthLoginScript, MAuthTurnstileInit } from '@mindfulauth/astro';
|
|
3
|
+
// or: import MAuthLoginScript from '@mindfulauth/astro/LoginScript.astro';
|
|
4
|
+
export { default as MAuthMainScript } from './MainScript.astro';
|
|
5
|
+
export { default as MAuthTurnstileInit } from './TurnstileInit.astro';
|
|
6
|
+
export { default as MAuthLoginScript } from './LoginScript.astro';
|
|
7
|
+
export { default as MAuthRegisterPasswordScript } from './RegisterPasswordScript.astro';
|
|
8
|
+
export { default as MAuthForgotPasswordScript } from './ForgotPasswordScript.astro';
|
|
9
|
+
export { default as MAuthMagicLoginScript } from './MagicLoginScript.astro';
|
|
10
|
+
export { default as MAuthMagicRegisterScript } from './MagicRegisterScript.astro';
|
|
11
|
+
export { default as MAuthResendVerificationScript } from './ResendVerificationScript.astro';
|
|
12
|
+
export { default as MAuthResetPasswordScript } from './ResetPasswordScript.astro';
|
|
13
|
+
export { default as MAuthVerifyEmailScript } from './VerifyEmailScript.astro';
|
|
14
|
+
export { default as MAuthVerifyMagicLinkScript } from './VerifyMagicLinkScript.astro';
|
|
15
|
+
export { default as MAuthSecurityScript } from './SecurityScript.astro';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-handler.d.ts","sourceRoot":"","sources":["../../src/core/auth-handler.ts"],"names":[],"mappings":"AAoEA,2EAA2E;AAC3E,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CA6BpH;AAED,gEAAgE;AAChE,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyDrH"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Auth proxy handler for Mindful Auth
|
|
2
2
|
// Forwards authentication requests to the central Mindful Auth service
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { env } from 'cloudflare:workers';
|
|
4
|
+
import { CENTRAL_AUTH_ORIGIN, ALLOWED_AUTH_METHODS, MAX_BODY_SIZE_BYTES, AUTH_PROXY_TIMEOUT_MS } from './config.js';
|
|
5
|
+
import { sanitizeEndpoint } from './security.js';
|
|
5
6
|
const JSON_HEADERS = { 'Content-Type': 'application/json' };
|
|
6
7
|
const jsonError = (error, status) => new Response(JSON.stringify({ error }), { status, headers: JSON_HEADERS });
|
|
7
8
|
/** Build proxy headers from incoming request */
|
|
@@ -60,7 +61,8 @@ function buildResponse(data, status, cookie) {
|
|
|
60
61
|
}
|
|
61
62
|
/** Handle GET requests (email verification, password reset links, etc.) */
|
|
62
63
|
export async function handleAuthGet(rawEndpoint, request, url, locals) {
|
|
63
|
-
|
|
64
|
+
// ASTRO 6 CHANGE: Environment variables accessed from 'cloudflare:workers' env directly.
|
|
65
|
+
const internalApiKey = env.INTERNAL_API_KEY || import.meta.env.INTERNAL_API_KEY;
|
|
64
66
|
if (!internalApiKey)
|
|
65
67
|
console.error('[auth-handler] INTERNAL_API_KEY not configured');
|
|
66
68
|
const endpoint = sanitizeEndpoint(rawEndpoint);
|
|
@@ -94,7 +96,8 @@ export async function handleAuthPost(rawEndpoint, request, url, locals) {
|
|
|
94
96
|
const contentLength = request.headers.get('content-length');
|
|
95
97
|
if (contentLength && parseInt(contentLength) > MAX_BODY_SIZE_BYTES)
|
|
96
98
|
return jsonError('Payload too large', 413);
|
|
97
|
-
|
|
99
|
+
// ASTRO 6 CHANGE: Environment variables accessed from 'cloudflare:workers' env directly.
|
|
100
|
+
const internalApiKey = env.INTERNAL_API_KEY || import.meta.env.INTERNAL_API_KEY;
|
|
98
101
|
if (!internalApiKey)
|
|
99
102
|
console.error('[auth-handler] INTERNAL_API_KEY not configured');
|
|
100
103
|
const endpoint = sanitizeEndpoint(rawEndpoint);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SessionValidationResult } from './types';
|
|
1
|
+
import type { SessionValidationResult } from './types.js';
|
|
2
2
|
/** Validate session with Mindful Auth central service */
|
|
3
3
|
export declare function validateSession(request: Request, tenantDomain: string, pathname: string, internalApiKey: string): Promise<SessionValidationResult>;
|
|
4
4
|
/** Validate memberid in URL matches session (or just check structure if sessionRecordId is null) */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,yDAAyD;AACzD,wBAAsB,eAAe,CACjC,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,uBAAuB,CAAC,CAsClC;AAED,oGAAoG;AACpG,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,CAerI"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Authentication and session validation for Mindful Auth
|
|
2
|
-
import { CENTRAL_AUTH_ORIGIN, SESSION_VALIDATION_TIMEOUT_MS } from './config';
|
|
2
|
+
import { CENTRAL_AUTH_ORIGIN, SESSION_VALIDATION_TIMEOUT_MS } from './config.js';
|
|
3
3
|
/** Validate session with Mindful Auth central service */
|
|
4
4
|
export async function validateSession(request, tenantDomain, pathname, internalApiKey) {
|
|
5
5
|
const sessionId = request.headers.get('Cookie')?.match(/session_id=([^;]+)/)?.[1];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const CENTRAL_AUTH_ORIGIN = "https://api.mindfulauth.com";
|
|
2
|
+
export declare const ALLOWED_AUTH_METHODS: string[];
|
|
3
|
+
export declare const MAX_BODY_SIZE_BYTES = 1048576;
|
|
4
|
+
export declare const AUTH_PROXY_TIMEOUT_MS = 15000;
|
|
5
|
+
export declare const SESSION_VALIDATION_TIMEOUT_MS = 10000;
|
|
6
|
+
/**
|
|
7
|
+
* Returns the combined list of assets to skip session validation for.
|
|
8
|
+
* Merges defaults with any custom assets configured via mauthSecurityConfig() in astro.config.mjs.
|
|
9
|
+
*/
|
|
10
|
+
export declare function GET_SKIP_ASSETS(): string[];
|
|
11
|
+
/**
|
|
12
|
+
* Configure Mindful Auth security settings including custom skip assets.
|
|
13
|
+
* Call in astro.config.mjs and pass the result to getMauthViteDefines().
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // astro.config.mjs
|
|
17
|
+
* const mauthCfg = mauthSecurityConfig({
|
|
18
|
+
* skipAssets: ['/sitemap.xml', '/manifest.webmanifest']
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* export default defineConfig({
|
|
22
|
+
* vite: { define: getMauthViteDefines(mauthCfg) }
|
|
23
|
+
* });
|
|
24
|
+
*/
|
|
25
|
+
export declare function mauthSecurityConfig(options?: {
|
|
26
|
+
skipAssets?: string[];
|
|
27
|
+
}): typeof options;
|
|
28
|
+
/**
|
|
29
|
+
* Converts the result of mauthSecurityConfig() into Vite define entries.
|
|
30
|
+
* Pass the output to vite.define in astro.config.mjs so custom values are
|
|
31
|
+
* baked into the SSR bundle at build time.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getMauthViteDefines(options?: {
|
|
34
|
+
skipAssets?: string[];
|
|
35
|
+
}): Record<string, string>;
|
|
36
|
+
export declare const PUBLIC_ROUTES: string[];
|
|
37
|
+
export declare const PUBLIC_PREFIXES: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Returns security headers to be added to every SSR response.
|
|
40
|
+
* CSP (Content-Security-Policy) for script-src and style-src is handled by
|
|
41
|
+
* Astro 6's native security.csp in astro.config.mjs using hashes.
|
|
42
|
+
* The remaining headers here cover transport security, framing, and permissions.
|
|
43
|
+
*
|
|
44
|
+
* Note: X-Frame-Options: DENY prevents this portal from being embedded in iframes on any domain, protecting against clickjacking attacks.
|
|
45
|
+
*/
|
|
46
|
+
export declare function GET_SECURITY_HEADERS(): Record<string, string>;
|
|
47
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AAGjE,eAAO,MAAM,oBAAoB,UAAkB,CAAC;AACpD,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAC3C,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAC3C,eAAO,MAAM,6BAA6B,QAAQ,CAAC;AAenD;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAE1C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE;IAC1C,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB,GAAG,OAAO,OAAO,CAEjB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE;IAC1C,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIzB;AAID,eAAO,MAAM,aAAa,UAQzB,CAAC;AAIF,eAAO,MAAM,eAAe,UAO3B,CAAC;AAMF;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAU7D"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Configuration for the Astro Portal
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// API Endpoints
|
|
5
|
+
export const CENTRAL_AUTH_ORIGIN = 'https://api.mindfulauth.com';
|
|
6
|
+
// Request & Timeout Configuration
|
|
7
|
+
export const ALLOWED_AUTH_METHODS = ['GET', 'POST'];
|
|
8
|
+
export const MAX_BODY_SIZE_BYTES = 1048576; // 1MB limit
|
|
9
|
+
export const AUTH_PROXY_TIMEOUT_MS = 15000;
|
|
10
|
+
export const SESSION_VALIDATION_TIMEOUT_MS = 10000;
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Static Assets & Route Configuration
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Static assets to skip session validation (favicon, robots.txt, etc.)
|
|
15
|
+
// SECURITY CRITICAL: Only add actual static file requests here.
|
|
16
|
+
// Examples of safe entries: /favicon.ico, /robots.txt, /sitemap.xml
|
|
17
|
+
// NEVER add application routes like [memberid]/dashboard, [memberid]/profile, [memberid]/secure/* - this COMPLETELY bypasses authentication. If you add a route here, unauthenticated users can access it without logging in.
|
|
18
|
+
const DEFAULT_SKIP_ASSETS = ['/favicon.ico', '/robots.txt', '/.well-known/security.txt'];
|
|
19
|
+
/**
|
|
20
|
+
* Returns the combined list of assets to skip session validation for.
|
|
21
|
+
* Merges defaults with any custom assets configured via mauthSecurityConfig() in astro.config.mjs.
|
|
22
|
+
*/
|
|
23
|
+
export function GET_SKIP_ASSETS() {
|
|
24
|
+
return [...DEFAULT_SKIP_ASSETS, ...__MAUTH_SKIP_ASSETS__];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Configure Mindful Auth security settings including custom skip assets.
|
|
28
|
+
* Call in astro.config.mjs and pass the result to getMauthViteDefines().
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // astro.config.mjs
|
|
32
|
+
* const mauthCfg = mauthSecurityConfig({
|
|
33
|
+
* skipAssets: ['/sitemap.xml', '/manifest.webmanifest']
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* export default defineConfig({
|
|
37
|
+
* vite: { define: getMauthViteDefines(mauthCfg) }
|
|
38
|
+
* });
|
|
39
|
+
*/
|
|
40
|
+
export function mauthSecurityConfig(options) {
|
|
41
|
+
return options;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Converts the result of mauthSecurityConfig() into Vite define entries.
|
|
45
|
+
* Pass the output to vite.define in astro.config.mjs so custom values are
|
|
46
|
+
* baked into the SSR bundle at build time.
|
|
47
|
+
*/
|
|
48
|
+
export function getMauthViteDefines(options) {
|
|
49
|
+
return {
|
|
50
|
+
__MAUTH_SKIP_ASSETS__: JSON.stringify(options?.skipAssets ?? []),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Public routes that do not require authentication
|
|
54
|
+
// ⚠️ DO NOT EDIT - These are critical for the authentication system to work correctly
|
|
55
|
+
export const PUBLIC_ROUTES = [
|
|
56
|
+
'/',
|
|
57
|
+
'/login',
|
|
58
|
+
'/register',
|
|
59
|
+
'/magic-login',
|
|
60
|
+
'/magic-register',
|
|
61
|
+
'/forgot-password',
|
|
62
|
+
'/resend-verification',
|
|
63
|
+
];
|
|
64
|
+
// Dynamic public routes (prefix matching)
|
|
65
|
+
// ⚠️ DO NOT EDIT - These are critical for the authentication system to work correctly
|
|
66
|
+
export const PUBLIC_PREFIXES = [
|
|
67
|
+
'/auth/',
|
|
68
|
+
'/email-verified/',
|
|
69
|
+
'/reset-password/',
|
|
70
|
+
'/verify-email/',
|
|
71
|
+
'/verify-magic-link/',
|
|
72
|
+
'/api/public/',
|
|
73
|
+
];
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// Security Headers
|
|
76
|
+
// ============================================================================
|
|
77
|
+
/**
|
|
78
|
+
* Returns security headers to be added to every SSR response.
|
|
79
|
+
* CSP (Content-Security-Policy) for script-src and style-src is handled by
|
|
80
|
+
* Astro 6's native security.csp in astro.config.mjs using hashes.
|
|
81
|
+
* The remaining headers here cover transport security, framing, and permissions.
|
|
82
|
+
*
|
|
83
|
+
* Note: X-Frame-Options: DENY prevents this portal from being embedded in iframes on any domain, protecting against clickjacking attacks.
|
|
84
|
+
*/
|
|
85
|
+
export function GET_SECURITY_HEADERS() {
|
|
86
|
+
return {
|
|
87
|
+
'X-Content-Type-Options': 'nosniff',
|
|
88
|
+
'X-Frame-Options': 'DENY',
|
|
89
|
+
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
90
|
+
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
|
|
91
|
+
'Permissions-Policy': 'geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()',
|
|
92
|
+
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
93
|
+
'Cross-Origin-Resource-Policy': 'same-origin',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scans the mindfulauth/astro/ directory at build time and returns SHA-384
|
|
3
|
+
* hashes for all <script is:inline> blocks found in .astro component files.
|
|
4
|
+
*
|
|
5
|
+
* Astro's static CSP analysis cannot resolve dynamically rendered components,
|
|
6
|
+
* so hashes must be declared manually in astro.config.mjs. This function
|
|
7
|
+
* computes them automatically so no manual maintenance is needed.
|
|
8
|
+
*
|
|
9
|
+
* When published as a package, this function resolves the astro/ directory
|
|
10
|
+
* relative to its own location — no consumer configuration required.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // astro.config.mjs
|
|
14
|
+
* // import { getScriptHashes } from '@mindfulauth/core';
|
|
15
|
+
*
|
|
16
|
+
* scriptDirective: { hashes: getScriptHashes() }
|
|
17
|
+
*/
|
|
18
|
+
export declare function getScriptHashes(): string[];
|
|
19
|
+
//# sourceMappingURL=csp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csp.d.ts","sourceRoot":"","sources":["../../src/core/csp.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAS1C"}
|
package/dist/core/csp.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Build-time CSP utilities for Mindful Auth
|
|
3
|
+
// Import this in astro.config.mjs only — not at SSR runtime.
|
|
4
|
+
// ============================================================================
|
|
5
|
+
import { readFileSync, readdirSync } from 'fs';
|
|
6
|
+
import { createHash } from 'crypto';
|
|
7
|
+
import { join, dirname } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
/**
|
|
11
|
+
* Scans the mindfulauth/astro/ directory at build time and returns SHA-384
|
|
12
|
+
* hashes for all <script is:inline> blocks found in .astro component files.
|
|
13
|
+
*
|
|
14
|
+
* Astro's static CSP analysis cannot resolve dynamically rendered components,
|
|
15
|
+
* so hashes must be declared manually in astro.config.mjs. This function
|
|
16
|
+
* computes them automatically so no manual maintenance is needed.
|
|
17
|
+
*
|
|
18
|
+
* When published as a package, this function resolves the astro/ directory
|
|
19
|
+
* relative to its own location — no consumer configuration required.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // astro.config.mjs
|
|
23
|
+
* // import { getScriptHashes } from '@mindfulauth/core';
|
|
24
|
+
*
|
|
25
|
+
* scriptDirective: { hashes: getScriptHashes() }
|
|
26
|
+
*/
|
|
27
|
+
export function getScriptHashes() {
|
|
28
|
+
const dir = join(__dirname, '../astro');
|
|
29
|
+
return readdirSync(dir)
|
|
30
|
+
.filter(f => f.endsWith('.astro'))
|
|
31
|
+
.flatMap(file => {
|
|
32
|
+
const content = readFileSync(join(dir, file), 'utf8');
|
|
33
|
+
return [...content.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/g)]
|
|
34
|
+
.map((m) => 'sha384-' + createHash('sha384').update(m[1], 'utf8').digest('base64'));
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAGA,cAAc,YAAY,CAAC;AAG3B,cAAc,aAAa,CAAC;AAG5B,cAAc,WAAW,CAAC;AAM1B,cAAc,eAAe,CAAC;AAQ9B,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Mindful Auth Core - Main exports
|
|
2
|
+
// Types
|
|
3
|
+
export * from './types.js';
|
|
4
|
+
// Configuration
|
|
5
|
+
export * from './config.js';
|
|
6
|
+
// Authentication
|
|
7
|
+
export * from './auth.js';
|
|
8
|
+
// Auth handler for API routes — NOT re-exported here.
|
|
9
|
+
// auth-handler.ts imports 'cloudflare:workers' which is only available at runtime (SSR), not at config-load time. Import it directly where needed: import { handleAuthProxy } from './auth-handler.js';
|
|
10
|
+
// Security utilities
|
|
11
|
+
export * from './security.js';
|
|
12
|
+
// Middleware — NOT re-exported here.
|
|
13
|
+
// middleware.ts imports 'astro:middleware' and 'cloudflare:workers' which are
|
|
14
|
+
// only available at runtime (SSR), not at config-load time.
|
|
15
|
+
// Import it directly: import { onRequest } from './middleware.js';
|
|
16
|
+
// Build-time CSP utilities
|
|
17
|
+
export * from './csp.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/core/middleware.ts"],"names":[],"mappings":"AAqCA,eAAO,MAAM,SAAS,mCAqFpB,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Global middleware for session validation
|
|
2
|
+
// Runs before all route handlers to enforce authentication
|
|
3
|
+
//
|
|
4
|
+
// ASTRO 6 MIGRATION:
|
|
5
|
+
// - Astro v6 removed context.locals.runtime.env. Env vars now import from 'cloudflare:workers'.
|
|
6
|
+
// - cloudflare:workers is marked external in astro.config.vite.ssr to avoid esbuild scan errors.
|
|
7
|
+
// - Dev mode bypass uses import.meta.env.DEV (build-time constant: true in dev, false in prod).
|
|
8
|
+
import { defineMiddleware } from 'astro:middleware';
|
|
9
|
+
import { env } from 'cloudflare:workers';
|
|
10
|
+
import { PUBLIC_ROUTES, PUBLIC_PREFIXES, GET_SECURITY_HEADERS, GET_SKIP_ASSETS } from './config.js';
|
|
11
|
+
import { sanitizePath } from './security.js';
|
|
12
|
+
import { validateSession, validateMemberIdInUrl } from './auth.js';
|
|
13
|
+
/** Check if a path is a public route (no auth required) */
|
|
14
|
+
function isPublicRoute(pathname) {
|
|
15
|
+
return PUBLIC_ROUTES.includes(pathname) ||
|
|
16
|
+
PUBLIC_PREFIXES.some((prefix) => pathname.startsWith(prefix));
|
|
17
|
+
}
|
|
18
|
+
/** Add security headers to a response */
|
|
19
|
+
// NOTE: In Cloudflare Workers, Response.headers is immutable.
|
|
20
|
+
// We must create a new Response with a fresh Headers object instead of mutating.
|
|
21
|
+
// CSP for script-src/style-src is handled by Astro 6's native security.csp (via <meta> tag).
|
|
22
|
+
function addSecurityHeaders(response) {
|
|
23
|
+
const newHeaders = new Headers(response.headers);
|
|
24
|
+
Object.entries(GET_SECURITY_HEADERS()).forEach(([key, value]) => {
|
|
25
|
+
newHeaders.set(key, value);
|
|
26
|
+
});
|
|
27
|
+
return new Response(response.body, {
|
|
28
|
+
status: response.status,
|
|
29
|
+
statusText: response.statusText,
|
|
30
|
+
headers: newHeaders,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// Main middleware function
|
|
34
|
+
export const onRequest = defineMiddleware(async (context, next) => {
|
|
35
|
+
const { request, url, redirect, locals } = context;
|
|
36
|
+
const pathname = url.pathname;
|
|
37
|
+
// Redirect HTTP to HTTPS (skip in dev mode and localhost)
|
|
38
|
+
if (!import.meta.env.DEV && url.protocol === 'http:' && url.hostname !== 'localhost' && url.hostname !== '127.0.0.1') {
|
|
39
|
+
const httpsUrl = url.toString().replace('http://', 'https://');
|
|
40
|
+
return redirect(httpsUrl, 307);
|
|
41
|
+
}
|
|
42
|
+
// Skip middleware for static assets FIRST (before dev mode)
|
|
43
|
+
if (GET_SKIP_ASSETS().includes(pathname)) {
|
|
44
|
+
return next();
|
|
45
|
+
}
|
|
46
|
+
// DEV MODE: Skip auth
|
|
47
|
+
// import.meta.env.DEV is a build-time constant replaced by Vite:
|
|
48
|
+
// - true during local dev (never included in prod build)
|
|
49
|
+
// - false in production builds (tree-shaken out entirely)
|
|
50
|
+
// Localhost auth is skipped because Mindful Auth blocks localhost requests.
|
|
51
|
+
if (import.meta.env.DEV) {
|
|
52
|
+
// Check if public route first
|
|
53
|
+
if (isPublicRoute(pathname)) {
|
|
54
|
+
locals.recordId = null;
|
|
55
|
+
return next();
|
|
56
|
+
}
|
|
57
|
+
// For protected routes, extract or create mock recordId
|
|
58
|
+
// Match memberid with trailing slash
|
|
59
|
+
const match = pathname.match(/^\/([a-zA-Z0-9-]+)(?:\/|$)/);
|
|
60
|
+
locals.recordId = match ? match[1] : 'dev-user-123';
|
|
61
|
+
return next();
|
|
62
|
+
}
|
|
63
|
+
// PREVIEW MODE: Skip auth (for testing without Mindful Auth on localhost)
|
|
64
|
+
// npm run preview builds the project first, so import.meta.env.DEV is false.
|
|
65
|
+
// We need a runtime check for localhost to simulate auth in preview.
|
|
66
|
+
if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
|
|
67
|
+
if (isPublicRoute(pathname)) {
|
|
68
|
+
locals.recordId = null;
|
|
69
|
+
return addSecurityHeaders(await next());
|
|
70
|
+
}
|
|
71
|
+
const match = pathname.match(/^\/([a-zA-Z0-9-]+)(?:\/|$)/);
|
|
72
|
+
locals.recordId = match ? match[1] : 'preview-user-123';
|
|
73
|
+
return addSecurityHeaders(await next());
|
|
74
|
+
}
|
|
75
|
+
// Sanitize path
|
|
76
|
+
const safePath = sanitizePath(pathname);
|
|
77
|
+
if (!safePath) {
|
|
78
|
+
return new Response('Bad Request', { status: 400 });
|
|
79
|
+
}
|
|
80
|
+
// Public routes - no auth needed
|
|
81
|
+
if (isPublicRoute(safePath)) {
|
|
82
|
+
locals.recordId = null;
|
|
83
|
+
return addSecurityHeaders(await next());
|
|
84
|
+
}
|
|
85
|
+
// Protected route - validate session
|
|
86
|
+
// ASTRO 6 CHANGE: Environment variables are accessed directly from 'cloudflare:workers' env,
|
|
87
|
+
// not from context.locals.runtime.env (which was removed).
|
|
88
|
+
const internalApiKey = env.INTERNAL_API_KEY || import.meta.env.INTERNAL_API_KEY;
|
|
89
|
+
if (!internalApiKey) {
|
|
90
|
+
console.error('[middleware] CRITICAL: INTERNAL_API_KEY not configured');
|
|
91
|
+
return new Response('Internal Server Error', { status: 500 });
|
|
92
|
+
}
|
|
93
|
+
// URL must have memberid
|
|
94
|
+
if (!validateMemberIdInUrl(safePath, null).valid) {
|
|
95
|
+
return new Response('Forbidden: URL must include memberid', { status: 403 });
|
|
96
|
+
}
|
|
97
|
+
// Validate session
|
|
98
|
+
const session = await validateSession(request, url.hostname, safePath, internalApiKey);
|
|
99
|
+
if (!session.valid) {
|
|
100
|
+
return redirect('/login');
|
|
101
|
+
}
|
|
102
|
+
// Validate memberid matches session
|
|
103
|
+
if (!validateMemberIdInUrl(safePath, session.recordId).valid) {
|
|
104
|
+
return new Response('Forbidden: Invalid user ID in URL', { status: 403 });
|
|
105
|
+
}
|
|
106
|
+
locals.recordId = session.recordId;
|
|
107
|
+
return addSecurityHeaders(await next());
|
|
108
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Sanitize endpoint path (prevents ../ traversal and encoded variants) */
|
|
2
|
+
export declare function sanitizeEndpoint(endpoint: string): string | null;
|
|
3
|
+
/** Sanitize URL path (prevents ../ traversal and encoded variants) */
|
|
4
|
+
export declare function sanitizePath(pathname: string): string | null;
|
|
5
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/core/security.ts"],"names":[],"mappings":"AAkBA,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIhE;AAED,sEAAsE;AACtE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI5D"}
|