(async () => { // ------------------------------- // Funzione: fetch + decrypt via Web Crypto API // ------------------------------- async function fetchAndDecrypt(fetchUrl, token) { // 1️⃣ Build the URL and call the endpoint const url = new URL(fetchUrl); url.searchParams.set('token', token); const resp = await fetch(url.toString(), { method: 'GET' }); if (!resp.ok) { throw new Error(`Fetch failed: ${resp.status}`); } // 2️⃣ Parse the JSON envelope const { token: B64 } = await resp.json(); // map -/_ back to +/ let b64 = B64.replace(/-/g, '+').replace(/_/g, '/'); // pad with '=' to multiple of 4 while (b64.length % 4) b64 += '='; // decode const jsonStr = atob(b64); return JSON.parse(jsonStr); } // ------------------------------- // Impostazioni iniziali // ------------------------------- const usp = new URLSearchParams(window.location.search); const token = usp.get('token'); const fetchUrl = usp.get('fetch_url'); const memberIdParam = usp.get('member_id'); let nextUrl = usp.get('next'); const resetPasswordUrl = usp.get('reset_password_url') ; const csu = window.Kajabi?.currentSiteUser?.id; let email, password, rememberMe, memberId; try { // 1️⃣ Se il member_id in GET coincide con l’utente loggato => redirect immediato // if (memberIdParam && csu != null && String(csu) === memberIdParam) { // console.log(`Member ID ${memberIdParam} coincide con l'utente; redirect a next.`); // window.location.href = nextUrl; // return; // // 2️⃣ Altrimenti, se ho token+fetchUrl, fetch+decrypt // } else if (token && fetchUrl) { console.log('Token presente; recupero credenziali cifrate.'); const creds = await fetchAndDecrypt(fetchUrl, token); ({ next: nextUrl, email, password, remember_me: rememberMe, member_id: memberId } = creds); // 3️⃣ Fallback sui GET params } else { console.log('Fallback: uso GET params standard.'); nextUrl = usp.get('next'); email = usp.get('email'); password = usp.get('password'); rememberMe= usp.get('remember_me') || usp.get('remember'); memberId = usp.get('member_id'); } if (!nextUrl) { nextUrl = '/library'; } // ------------------------------- // Helpers comuni // ------------------------------- const utils = { getCsrfToken() { const m = document.querySelector('meta[name="csrf-token"]'); return m ? m.getAttribute('content') : null; }, deleteAllCookies() { document.cookie.split(';').forEach(c => { const name = c.split('=')[0].trim(); ['', `;domain=${location.hostname}`].forEach(suffix => { document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/` + suffix; }); }); }, redirect(url) { window.location.href = url; }, isOnTargetPage(url) { try { const cur = new URL(location.href, location.origin); const tgt = new URL(url, location.origin); return cur.origin === tgt.origin && cur.pathname === tgt.pathname && cur.search === tgt.search; } catch { return false; } }, async fetchHiddenFields(path) { const r = await fetch(path); if (!r.ok) throw new Error(`${path} fetch failed: ${r.status}`); const doc = new DOMParser().parseFromString(await r.text(), 'text/html'); const utf8 = doc.querySelector('input[name="utf8"]')?.value; const auth_token = doc.querySelector('input[name="authenticity_token"]')?.value; if (!utf8 || !auth_token) throw new Error('Hidden fields missing'); return { utf8, auth_token }; } }; // ------------------------------- // Flow di logout server-side // ------------------------------- async function serverLogout() { const csrf = utils.getCsrfToken(); if (!csrf) throw new Error('CSRF token not found'); await fetch('/logout', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ '_method': 'delete', 'authenticity_token': csrf }) }); console.log('Server-side logout attempt done.'); } // ------------------------------- // Flow di login // ------------------------------- // ------------------------------- // Beacon endpoint Brain per client-side errors (incident 2026-05-25). // Estrae l'origin Brain dal resetPasswordUrl (stesso host). // ------------------------------- function brainClientErrorUrl() { try { const u = new URL(resetPasswordUrl); return `${u.origin}/sso/__client_error__`; } catch { return null; } } async function reportClientError(errorType) { const url = brainClientErrorUrl(); if (!url) return; try { await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ slg_token: token || '', error_type: errorType }), keepalive: true, }); } catch (err) { console.error('Brain beacon POST failed:', err); } } function showUserError(message) { try { // Sostituisce il loader (se presente) con un alert visibile. const supportMail = 'assistenza@datamasters.it'; const html = `
Sincronizzazione credenziali fallita
${message}
Contatta il supporto: ${supportMail}
`; document.body.innerHTML = html; } catch (err) { console.error('showUserError failed:', err); alert(`Sincronizzazione credenziali fallita. ${message}. Contatta assistenza@datamasters.it.`); } } async function performLogin(retry = false) { const { utf8, auth_token } = await utils.fetchHiddenFields('/login'); const params = new URLSearchParams({ utf8, authenticity_token: auth_token }); if (email) params.append('member[email]', email); if (password) params.append('member[password]', password); if (rememberMe) params.append('member[remember_me]', rememberMe); const resp = await fetch('/login', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params.toString(), redirect: 'manual' }); console.log('Login attempt status:', resp.status, 'type:', resp.type); if (resp.type === 'opaqueredirect') { console.log('Login succeeded.'); return utils.redirect(nextUrl); } // ❌ Login failed if (!retry) { console.warn('Login failed. Calling recovery API and retrying once.'); let resetOk = false; try { const resetUrl = new URL(resetPasswordUrl); // already absolute if (token) resetUrl.searchParams.set('token', token); // const (no implicit global); status check rigoroso -> abort + beacon // se !=200 (incident 2026-05-25: il vecchio codice loggava e proseguiva // al retry con LE STESSE credenziali, mascherando i fallimenti reset). const r = await fetch(resetUrl.toString(), { method: 'GET' }); if (r.status !== 200) { console.error('Reset API call failed with status:', r.status); await reportClientError('reset_password_failed'); showUserError('Il sistema non e\' riuscito a risincronizzare la tua password.'); return; // ABORT — no retry con credenziali stale } resetOk = true; } catch (err) { console.error('Reset API call failed:', err); await reportClientError('reset_password_failed'); showUserError('Errore di comunicazione con il sistema di sincronizzazione.'); return; } if (!resetOk) return; // Retry login once con credenziali (potenzialmente) appena sincronizzate. return await performLogin(true); } // ❌ Retry also failed: NO redirect anonimo, ABORT + beacon + messaggio await reportClientError('login_retry_failed'); showUserError('La verifica delle credenziali e\' fallita anche dopo la sincronizzazione.'); return; } // ------------------------------- // Verifica accesso a pagina protetta // ------------------------------- async function probeAndMaybeLogin() { if (utils.isOnTargetPage(nextUrl)) { console.log(`Already on ${nextUrl}`); return; } const probe = await fetch(nextUrl, { method: 'GET', redirect: 'manual' }); if (probe.type === 'opaqueredirect' || probe.status === 302) { console.log('Protected, initiating login flow.'); await performLogin(); } else { console.log('Already authenticated, redirecting.'); utils.redirect(nextUrl); } } // ------------------------------- // Inizio flusso principale // ------------------------------- if (csu == null) { console.warn('Nessun currentSiteUser.id; salto controlli.'); return utils.redirect(nextUrl); } const actualId = String(csu); if (actualId === '-1') { console.log('Utente non loggato; provo login.'); await performLogin(); } if (memberId && memberId !== actualId) { console.log('Member ID mismatch; logout + re-login.'); utils.deleteAllCookies(); await serverLogout(); await performLogin(); } await probeAndMaybeLogin(); } catch (err) { console.error('Errore nel flusso:', err); // window.location.href = nextUrl || '/'; } })();