@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.
- 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 +490 -0
- package/dist/astro/TurnstileInit.astro +112 -0
- package/dist/astro/VerifyEmailScript.astro +72 -0
- package/dist/astro/VerifyMagicLinkScript.astro +195 -0
- package/package.json +17 -12
- package/dist/auth-handler.d.ts +0 -5
- package/dist/auth-handler.d.ts.map +0 -1
- package/dist/auth-handler.js +0 -154
- package/dist/auth.d.ts +0 -9
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js +0 -56
- package/dist/config.d.ts +0 -49
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -95
- 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 +0 -2
- package/dist/middleware.d.ts.map +0 -1
- package/dist/middleware.js +0 -108
- package/dist/security.d.ts +0 -5
- package/dist/security.d.ts.map +0 -1
- package/dist/security.js +0 -31
- package/dist/types.d.ts +0 -22
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
|
@@ -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>
|
package/package.json
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindfulauth/core",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.6",
|
|
4
4
|
"description": "Mindful Auth core authentication library for Astro 6",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
6
|
+
"main": "./dist/core/index.js",
|
|
7
|
+
"types": "./dist/core/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.js"
|
|
10
|
+
"types": "./dist/core/index.d.ts",
|
|
11
|
+
"import": "./dist/core/index.js"
|
|
12
12
|
},
|
|
13
|
+
"./astro": {
|
|
14
|
+
"types": "./dist/astro/index.d.ts",
|
|
15
|
+
"import": "./dist/astro/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./astro/*.astro": "./dist/astro/*.astro",
|
|
13
18
|
"./middleware": {
|
|
14
|
-
"types": "./dist/middleware.d.ts",
|
|
15
|
-
"import": "./dist/middleware.js"
|
|
19
|
+
"types": "./dist/core/middleware.d.ts",
|
|
20
|
+
"import": "./dist/core/middleware.js"
|
|
16
21
|
},
|
|
17
22
|
"./auth-handler": {
|
|
18
|
-
"types": "./dist/auth-handler.d.ts",
|
|
19
|
-
"import": "./dist/auth-handler.js"
|
|
23
|
+
"types": "./dist/core/auth-handler.d.ts",
|
|
24
|
+
"import": "./dist/core/auth-handler.js"
|
|
20
25
|
},
|
|
21
26
|
"./config": {
|
|
22
|
-
"types": "./dist/config.d.ts",
|
|
23
|
-
"import": "./dist/config.js"
|
|
27
|
+
"types": "./dist/core/config.d.ts",
|
|
28
|
+
"import": "./dist/core/config.js"
|
|
24
29
|
}
|
|
25
30
|
},
|
|
26
31
|
"files": [
|
|
@@ -28,7 +33,7 @@
|
|
|
28
33
|
"README.md"
|
|
29
34
|
],
|
|
30
35
|
"scripts": {
|
|
31
|
-
"build": "tsc",
|
|
36
|
+
"build": "tsc && cp src/astro/*.astro dist/astro/",
|
|
32
37
|
"dev": "tsc --watch",
|
|
33
38
|
"prepublishOnly": "npm run build"
|
|
34
39
|
},
|
package/dist/auth-handler.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
/** Handle GET requests (email verification, password reset links, etc.) */
|
|
2
|
-
export declare function handleAuthGet(rawEndpoint: string, request: Request, url: URL, locals?: any): Promise<Response>;
|
|
3
|
-
/** Handle POST requests (login, 2FA, password changes, etc.) */
|
|
4
|
-
export declare function handleAuthPost(rawEndpoint: string, request: Request, url: URL, locals?: any): Promise<Response>;
|
|
5
|
-
//# sourceMappingURL=auth-handler.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth-handler.d.ts","sourceRoot":"","sources":["../src/auth-handler.ts"],"names":[],"mappings":"AAwEA,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"}
|
package/dist/auth-handler.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// Auth proxy handler for Mindful Auth
|
|
2
|
-
// Forwards authentication requests to the central Mindful Auth service
|
|
3
|
-
//
|
|
4
|
-
// ASTRO 6 MIGRATION:
|
|
5
|
-
// - Astro v6 removed context.locals.runtime.env. Env vars now import from 'cloudflare:workers'.
|
|
6
|
-
// - Note: @cloudflare/workers-types must be installed and referenced in env.d.ts.
|
|
7
|
-
import { env } from 'cloudflare:workers';
|
|
8
|
-
import { CENTRAL_AUTH_ORIGIN, ALLOWED_AUTH_METHODS, MAX_BODY_SIZE_BYTES, AUTH_PROXY_TIMEOUT_MS } from './config';
|
|
9
|
-
import { sanitizeEndpoint } from './security';
|
|
10
|
-
const JSON_HEADERS = { 'Content-Type': 'application/json' };
|
|
11
|
-
const jsonError = (error, status) => new Response(JSON.stringify({ error }), { status, headers: JSON_HEADERS });
|
|
12
|
-
/** Build proxy headers from incoming request */
|
|
13
|
-
function buildProxyHeaders(request, tenantDomain, apiKey) {
|
|
14
|
-
const headers = {
|
|
15
|
-
'Content-Type': 'application/json',
|
|
16
|
-
'X-Tenant-Domain': tenantDomain,
|
|
17
|
-
'X-Requested-With': 'XMLHttpRequest'
|
|
18
|
-
};
|
|
19
|
-
if (apiKey)
|
|
20
|
-
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
21
|
-
const cookie = request.headers.get('Cookie');
|
|
22
|
-
if (cookie)
|
|
23
|
-
headers['Cookie'] = cookie;
|
|
24
|
-
const clientIp = request.headers.get('cf-connecting-ip') ||
|
|
25
|
-
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
|
|
26
|
-
request.headers.get('x-real-ip');
|
|
27
|
-
if (clientIp)
|
|
28
|
-
headers['X-Original-Client-IP'] = clientIp;
|
|
29
|
-
const userAgent = request.headers.get('user-agent');
|
|
30
|
-
if (userAgent)
|
|
31
|
-
headers['User-Agent'] = userAgent;
|
|
32
|
-
return headers;
|
|
33
|
-
}
|
|
34
|
-
/** Fetch with timeout */
|
|
35
|
-
async function fetchWithTimeout(url, options) {
|
|
36
|
-
const controller = new AbortController();
|
|
37
|
-
const timeoutId = setTimeout(() => controller.abort(), AUTH_PROXY_TIMEOUT_MS);
|
|
38
|
-
try {
|
|
39
|
-
return await fetch(url, { ...options, signal: controller.signal });
|
|
40
|
-
}
|
|
41
|
-
finally {
|
|
42
|
-
clearTimeout(timeoutId);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
/** Extract mirrored session cookie (removes Domain to use tenant domain) */
|
|
46
|
-
function getMirroredCookie(resp) {
|
|
47
|
-
const setCookie = resp.headers.get('set-cookie');
|
|
48
|
-
return setCookie?.includes('session_id=')
|
|
49
|
-
? setCookie.replace(/Domain=[^;]+;?\s*/i, '')
|
|
50
|
-
: null;
|
|
51
|
-
}
|
|
52
|
-
/** Check if redirect is safe (relative, no open redirect) */
|
|
53
|
-
function isSafeRedirect(url) {
|
|
54
|
-
return url.startsWith('/') && !url.startsWith('//');
|
|
55
|
-
}
|
|
56
|
-
/** Build response with standard headers */
|
|
57
|
-
function buildResponse(data, status, cookie) {
|
|
58
|
-
const headers = new Headers({
|
|
59
|
-
'Content-Type': 'application/json',
|
|
60
|
-
'Cache-Control': 'no-store, no-cache, must-revalidate, private'
|
|
61
|
-
});
|
|
62
|
-
if (cookie)
|
|
63
|
-
headers.set('Set-Cookie', cookie);
|
|
64
|
-
return new Response(JSON.stringify(data), { status, headers });
|
|
65
|
-
}
|
|
66
|
-
/** Handle GET requests (email verification, password reset links, etc.) */
|
|
67
|
-
export async function handleAuthGet(rawEndpoint, request, url, locals) {
|
|
68
|
-
// ASTRO 6 CHANGE: Environment variables accessed from 'cloudflare:workers' env directly.
|
|
69
|
-
const internalApiKey = env.INTERNAL_API_KEY || import.meta.env.INTERNAL_API_KEY;
|
|
70
|
-
if (!internalApiKey)
|
|
71
|
-
console.error('[auth-handler] INTERNAL_API_KEY not configured');
|
|
72
|
-
const endpoint = sanitizeEndpoint(rawEndpoint);
|
|
73
|
-
if (!endpoint)
|
|
74
|
-
return jsonError('Bad request', 400);
|
|
75
|
-
const apiUrl = new URL(`${CENTRAL_AUTH_ORIGIN}/auth/${endpoint}`);
|
|
76
|
-
url.searchParams.forEach((v, k) => apiUrl.searchParams.set(k, v));
|
|
77
|
-
let resp;
|
|
78
|
-
try {
|
|
79
|
-
resp = await fetchWithTimeout(apiUrl.toString(), {
|
|
80
|
-
method: 'GET',
|
|
81
|
-
headers: buildProxyHeaders(request, url.hostname, internalApiKey)
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
catch (e) {
|
|
85
|
-
if (e.name === 'AbortError')
|
|
86
|
-
return jsonError('Request timeout', 504);
|
|
87
|
-
throw e;
|
|
88
|
-
}
|
|
89
|
-
// Handle redirect
|
|
90
|
-
const location = resp.headers.get('Location');
|
|
91
|
-
if (resp.status === 302 && location && isSafeRedirect(location)) {
|
|
92
|
-
return new Response(null, { status: 302, headers: { Location: location } });
|
|
93
|
-
}
|
|
94
|
-
return buildResponse(await resp.json(), resp.status, getMirroredCookie(resp));
|
|
95
|
-
}
|
|
96
|
-
/** Handle POST requests (login, 2FA, password changes, etc.) */
|
|
97
|
-
export async function handleAuthPost(rawEndpoint, request, url, locals) {
|
|
98
|
-
if (!ALLOWED_AUTH_METHODS.includes(request.method))
|
|
99
|
-
return jsonError('Method not allowed', 405);
|
|
100
|
-
const contentLength = request.headers.get('content-length');
|
|
101
|
-
if (contentLength && parseInt(contentLength) > MAX_BODY_SIZE_BYTES)
|
|
102
|
-
return jsonError('Payload too large', 413);
|
|
103
|
-
// ASTRO 6 CHANGE: Environment variables accessed from 'cloudflare:workers' env directly.
|
|
104
|
-
const internalApiKey = env.INTERNAL_API_KEY || import.meta.env.INTERNAL_API_KEY;
|
|
105
|
-
if (!internalApiKey)
|
|
106
|
-
console.error('[auth-handler] INTERNAL_API_KEY not configured');
|
|
107
|
-
const endpoint = sanitizeEndpoint(rawEndpoint);
|
|
108
|
-
if (!endpoint)
|
|
109
|
-
return jsonError('Bad request', 400);
|
|
110
|
-
// Parse request body
|
|
111
|
-
let body = null;
|
|
112
|
-
try {
|
|
113
|
-
const text = await request.text();
|
|
114
|
-
if (text) {
|
|
115
|
-
JSON.parse(text);
|
|
116
|
-
body = text;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
return jsonError('Invalid JSON', 400);
|
|
121
|
-
}
|
|
122
|
-
let resp;
|
|
123
|
-
try {
|
|
124
|
-
resp = await fetchWithTimeout(`${CENTRAL_AUTH_ORIGIN}/auth/${endpoint}`, {
|
|
125
|
-
method: 'POST',
|
|
126
|
-
headers: buildProxyHeaders(request, url.hostname, internalApiKey),
|
|
127
|
-
body
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
catch (e) {
|
|
131
|
-
if (e.name === 'AbortError')
|
|
132
|
-
return jsonError('Request timeout', 504);
|
|
133
|
-
throw e;
|
|
134
|
-
}
|
|
135
|
-
const data = await resp.json();
|
|
136
|
-
const cookie = getMirroredCookie(resp);
|
|
137
|
-
// Handle HTTP redirect
|
|
138
|
-
const location = resp.headers.get('Location');
|
|
139
|
-
if (resp.status === 302 && location && isSafeRedirect(location)) {
|
|
140
|
-
return new Response(null, { status: 302, headers: { Location: location } });
|
|
141
|
-
}
|
|
142
|
-
// Handle JSON redirect (loginsuccessredirect)
|
|
143
|
-
const redirectUrl = data.loginsuccessredirect;
|
|
144
|
-
if (typeof redirectUrl === 'string' && isSafeRedirect(redirectUrl)) {
|
|
145
|
-
return new Response(null, {
|
|
146
|
-
status: 302,
|
|
147
|
-
headers: { Location: redirectUrl, ...(cookie && { 'Set-Cookie': cookie }) }
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
// Remove sensitive data
|
|
151
|
-
delete data.sessionToken;
|
|
152
|
-
delete data.maxAgeSeconds;
|
|
153
|
-
return buildResponse(data, resp.status, cookie);
|
|
154
|
-
}
|
package/dist/auth.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { SessionValidationResult } from './types';
|
|
2
|
-
/** Validate session with Mindful Auth central service */
|
|
3
|
-
export declare function validateSession(request: Request, tenantDomain: string, pathname: string, internalApiKey: string): Promise<SessionValidationResult>;
|
|
4
|
-
/** Validate memberid in URL matches session (or just check structure if sessionRecordId is null) */
|
|
5
|
-
export declare function validateMemberIdInUrl(pathname: string, sessionRecordId: string | null): {
|
|
6
|
-
valid: boolean;
|
|
7
|
-
strippedPathname?: string;
|
|
8
|
-
};
|
|
9
|
-
//# sourceMappingURL=auth.d.ts.map
|
package/dist/auth.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAEvD,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"}
|
package/dist/auth.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
// Authentication and session validation for Mindful Auth
|
|
2
|
-
import { CENTRAL_AUTH_ORIGIN, SESSION_VALIDATION_TIMEOUT_MS } from './config';
|
|
3
|
-
/** Validate session with Mindful Auth central service */
|
|
4
|
-
export async function validateSession(request, tenantDomain, pathname, internalApiKey) {
|
|
5
|
-
const sessionId = request.headers.get('Cookie')?.match(/session_id=([^;]+)/)?.[1];
|
|
6
|
-
if (!sessionId)
|
|
7
|
-
return { valid: false, reason: 'no-session' };
|
|
8
|
-
const controller = new AbortController();
|
|
9
|
-
const timeoutId = setTimeout(() => controller.abort(), SESSION_VALIDATION_TIMEOUT_MS);
|
|
10
|
-
try {
|
|
11
|
-
const clientIp = request.headers.get('cf-connecting-ip') ||
|
|
12
|
-
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
|
|
13
|
-
request.headers.get('x-real-ip');
|
|
14
|
-
const resp = await fetch(`${CENTRAL_AUTH_ORIGIN}/auth/validate-session`, {
|
|
15
|
-
method: 'POST',
|
|
16
|
-
headers: {
|
|
17
|
-
'Content-Type': 'application/json',
|
|
18
|
-
'X-Tenant-Domain': tenantDomain,
|
|
19
|
-
'X-Internal-Api-Key': internalApiKey,
|
|
20
|
-
...(clientIp && { 'X-Original-Client-IP': clientIp }),
|
|
21
|
-
...(request.headers.get('user-agent') && { 'User-Agent': request.headers.get('user-agent') })
|
|
22
|
-
},
|
|
23
|
-
body: JSON.stringify({ sessionId, requestedUrl: pathname }),
|
|
24
|
-
signal: controller.signal
|
|
25
|
-
});
|
|
26
|
-
const result = await resp.json();
|
|
27
|
-
if (!result.valid)
|
|
28
|
-
return { valid: false, reason: 'invalid-session' };
|
|
29
|
-
const recordId = result.data?.sub || result.recordId;
|
|
30
|
-
if (!recordId)
|
|
31
|
-
return { valid: false, reason: 'invalid-record-id' };
|
|
32
|
-
return { valid: true, recordId };
|
|
33
|
-
}
|
|
34
|
-
catch (e) {
|
|
35
|
-
console.error('[auth] Session validation failed:', e.message);
|
|
36
|
-
return { valid: false, reason: 'error' };
|
|
37
|
-
}
|
|
38
|
-
finally {
|
|
39
|
-
clearTimeout(timeoutId);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
/** Validate memberid in URL matches session (or just check structure if sessionRecordId is null) */
|
|
43
|
-
export function validateMemberIdInUrl(pathname, sessionRecordId) {
|
|
44
|
-
const segments = pathname.split('/').filter(Boolean);
|
|
45
|
-
if (segments.length === 0)
|
|
46
|
-
return { valid: false };
|
|
47
|
-
const urlMemberId = segments[0];
|
|
48
|
-
// Pre-check mode: just validate URL has memberid
|
|
49
|
-
if (sessionRecordId === null) {
|
|
50
|
-
return { valid: !!urlMemberId, strippedPathname: pathname };
|
|
51
|
-
}
|
|
52
|
-
// Validate memberid matches session
|
|
53
|
-
if (urlMemberId !== sessionRecordId)
|
|
54
|
-
return { valid: false };
|
|
55
|
-
return { valid: true, strippedPathname: '/' + segments.slice(1).join('/') };
|
|
56
|
-
}
|
package/dist/config.d.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
export declare const CENTRAL_AUTH_ORIGIN = "https://api.mindfulauth.com";
|
|
2
|
-
export declare const CDN_ORIGIN = "https://cdn.mindfulauth.com";
|
|
3
|
-
export declare const ALLOWED_AUTH_METHODS: string[];
|
|
4
|
-
export declare const MAX_BODY_SIZE_BYTES = 1048576;
|
|
5
|
-
export declare const AUTH_PROXY_TIMEOUT_MS = 15000;
|
|
6
|
-
export declare const SESSION_VALIDATION_TIMEOUT_MS = 10000;
|
|
7
|
-
/**
|
|
8
|
-
* Returns the combined list of assets to skip session validation for.
|
|
9
|
-
* Merges defaults with any custom assets configured via mauthSecurityConfig() in astro.config.mjs.
|
|
10
|
-
*/
|
|
11
|
-
export declare function GET_SKIP_ASSETS(): string[];
|
|
12
|
-
/**
|
|
13
|
-
* Configure Mindful Auth security settings including custom skip assets.
|
|
14
|
-
* Call in astro.config.mjs and pass the result to getMauthViteDefines().
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* // astro.config.mjs
|
|
18
|
-
* const mauthCfg = mauthSecurityConfig({
|
|
19
|
-
* skipAssets: ['/sitemap.xml', '/manifest.webmanifest']
|
|
20
|
-
* });
|
|
21
|
-
*
|
|
22
|
-
* export default defineConfig({
|
|
23
|
-
* vite: { define: getMauthViteDefines(mauthCfg) }
|
|
24
|
-
* });
|
|
25
|
-
*/
|
|
26
|
-
export declare function mauthSecurityConfig(options?: {
|
|
27
|
-
skipAssets?: string[];
|
|
28
|
-
}): typeof options;
|
|
29
|
-
/**
|
|
30
|
-
* Converts the result of mauthSecurityConfig() into Vite define entries.
|
|
31
|
-
* Pass the output to vite.define in astro.config.mjs so custom values are
|
|
32
|
-
* baked into the SSR bundle at build time.
|
|
33
|
-
*/
|
|
34
|
-
export declare function getMauthViteDefines(options?: {
|
|
35
|
-
skipAssets?: string[];
|
|
36
|
-
}): Record<string, string>;
|
|
37
|
-
export declare const PUBLIC_ROUTES: string[];
|
|
38
|
-
export declare const PUBLIC_PREFIXES: string[];
|
|
39
|
-
/**
|
|
40
|
-
* Returns security headers to be added to every SSR response.
|
|
41
|
-
* CSP (Content-Security-Policy) for script-src and style-src is handled by
|
|
42
|
-
* Astro 6's native security.csp in astro.config.mjs using hashes.
|
|
43
|
-
* The remaining headers here cover transport security, framing, and permissions.
|
|
44
|
-
*
|
|
45
|
-
* Note: X-Frame-Options: SAMEORIGIN covers clickjacking protection
|
|
46
|
-
* (equivalent to CSP frame-ancestors 'self', which cannot be set via meta tag).
|
|
47
|
-
*/
|
|
48
|
-
export declare function GET_SECURITY_HEADERS(): Record<string, string>;
|
|
49
|
-
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AACjE,eAAO,MAAM,UAAU,gCAAgC,CAAC;AAGxD,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;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQ7D"}
|
package/dist/config.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Configuration for the Astro Portal
|
|
3
|
-
// ============================================================================
|
|
4
|
-
// API Endpoints
|
|
5
|
-
export const CENTRAL_AUTH_ORIGIN = 'https://api.mindfulauth.com';
|
|
6
|
-
export const CDN_ORIGIN = 'https://cdn.mindfulauth.com';
|
|
7
|
-
// Request & Timeout Configuration
|
|
8
|
-
export const ALLOWED_AUTH_METHODS = ['GET', 'POST'];
|
|
9
|
-
export const MAX_BODY_SIZE_BYTES = 1048576; // 1MB limit
|
|
10
|
-
export const AUTH_PROXY_TIMEOUT_MS = 15000;
|
|
11
|
-
export const SESSION_VALIDATION_TIMEOUT_MS = 10000;
|
|
12
|
-
// ============================================================================
|
|
13
|
-
// Static Assets & Route Configuration
|
|
14
|
-
// ============================================================================
|
|
15
|
-
// Static assets to skip session validation (favicon, robots.txt, etc.)
|
|
16
|
-
// SECURITY CRITICAL: Only add actual static file requests here.
|
|
17
|
-
// Examples of safe entries: /favicon.ico, /robots.txt, /sitemap.xml
|
|
18
|
-
// 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.
|
|
19
|
-
const DEFAULT_SKIP_ASSETS = ['/favicon.ico', '/robots.txt', '/.well-known/security.txt'];
|
|
20
|
-
/**
|
|
21
|
-
* Returns the combined list of assets to skip session validation for.
|
|
22
|
-
* Merges defaults with any custom assets configured via mauthSecurityConfig() in astro.config.mjs.
|
|
23
|
-
*/
|
|
24
|
-
export function GET_SKIP_ASSETS() {
|
|
25
|
-
return [...DEFAULT_SKIP_ASSETS, ...__MAUTH_SKIP_ASSETS__];
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Configure Mindful Auth security settings including custom skip assets.
|
|
29
|
-
* Call in astro.config.mjs and pass the result to getMauthViteDefines().
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* // astro.config.mjs
|
|
33
|
-
* const mauthCfg = mauthSecurityConfig({
|
|
34
|
-
* skipAssets: ['/sitemap.xml', '/manifest.webmanifest']
|
|
35
|
-
* });
|
|
36
|
-
*
|
|
37
|
-
* export default defineConfig({
|
|
38
|
-
* vite: { define: getMauthViteDefines(mauthCfg) }
|
|
39
|
-
* });
|
|
40
|
-
*/
|
|
41
|
-
export function mauthSecurityConfig(options) {
|
|
42
|
-
return options;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Converts the result of mauthSecurityConfig() into Vite define entries.
|
|
46
|
-
* Pass the output to vite.define in astro.config.mjs so custom values are
|
|
47
|
-
* baked into the SSR bundle at build time.
|
|
48
|
-
*/
|
|
49
|
-
export function getMauthViteDefines(options) {
|
|
50
|
-
return {
|
|
51
|
-
__MAUTH_SKIP_ASSETS__: JSON.stringify(options?.skipAssets ?? []),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
// Public routes that do not require authentication
|
|
55
|
-
// ⚠️ DO NOT EDIT - These are critical for the authentication system to work correctly
|
|
56
|
-
export const PUBLIC_ROUTES = [
|
|
57
|
-
'/',
|
|
58
|
-
'/login',
|
|
59
|
-
'/register',
|
|
60
|
-
'/magic-login',
|
|
61
|
-
'/magic-register',
|
|
62
|
-
'/forgot-password',
|
|
63
|
-
'/resend-verification',
|
|
64
|
-
];
|
|
65
|
-
// Dynamic public routes (prefix matching)
|
|
66
|
-
// ⚠️ DO NOT EDIT - These are critical for the authentication system to work correctly
|
|
67
|
-
export const PUBLIC_PREFIXES = [
|
|
68
|
-
'/auth/',
|
|
69
|
-
'/email-verified/',
|
|
70
|
-
'/reset-password/',
|
|
71
|
-
'/verify-email/',
|
|
72
|
-
'/verify-magic-link/',
|
|
73
|
-
'/api/public/',
|
|
74
|
-
];
|
|
75
|
-
// ============================================================================
|
|
76
|
-
// Security Headers
|
|
77
|
-
// ============================================================================
|
|
78
|
-
/**
|
|
79
|
-
* Returns security headers to be added to every SSR response.
|
|
80
|
-
* CSP (Content-Security-Policy) for script-src and style-src is handled by
|
|
81
|
-
* Astro 6's native security.csp in astro.config.mjs using hashes.
|
|
82
|
-
* The remaining headers here cover transport security, framing, and permissions.
|
|
83
|
-
*
|
|
84
|
-
* Note: X-Frame-Options: SAMEORIGIN covers clickjacking protection
|
|
85
|
-
* (equivalent to CSP frame-ancestors 'self', which cannot be set via meta tag).
|
|
86
|
-
*/
|
|
87
|
-
export function GET_SECURITY_HEADERS() {
|
|
88
|
-
return {
|
|
89
|
-
'X-Content-Type-Options': 'nosniff',
|
|
90
|
-
'X-Frame-Options': 'SAMEORIGIN',
|
|
91
|
-
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
92
|
-
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
|
93
|
-
'Permissions-Policy': 'geolocation=(), microphone=(), camera=()',
|
|
94
|
-
};
|
|
95
|
-
}
|
package/dist/index.d.ts
DELETED
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,SAAS,CAAC;AAGxB,cAAc,UAAU,CAAC;AAGzB,cAAc,QAAQ,CAAC;AAGvB,cAAc,gBAAgB,CAAC;AAG/B,cAAc,YAAY,CAAC;AAG3B,cAAc,cAAc,CAAC"}
|
package/dist/index.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// Mindful Auth Core - Main exports
|
|
2
|
-
// Types
|
|
3
|
-
export * from './types';
|
|
4
|
-
// Configuration
|
|
5
|
-
export * from './config';
|
|
6
|
-
// Authentication
|
|
7
|
-
export * from './auth';
|
|
8
|
-
// Auth handler for API routes
|
|
9
|
-
export * from './auth-handler';
|
|
10
|
-
// Security utilities
|
|
11
|
-
export * from './security';
|
|
12
|
-
// Middleware
|
|
13
|
-
export * from './middleware';
|
package/dist/middleware.d.ts
DELETED
package/dist/middleware.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAqCA,eAAO,MAAM,SAAS,mCAqFpB,CAAC"}
|