diff --git a/public/core/auth-security-v1774.js b/public/core/auth-security-v1774.js index 96c4fd8..3cd535b 100644 --- a/public/core/auth-security-v1774.js +++ b/public/core/auth-security-v1774.js @@ -1,7 +1,7 @@ (()=>{ 'use strict'; - const VERSION='17.7.3-auth-security-v1'; + const VERSION='17.8.1-auth-security-promo'; const PENDING_REGISTRATION_KEY='sunPendingRegistrationV23'; let busy=false; @@ -10,183 +10,57 @@ const client=()=>cloud()?.getClient?.()||null; const esc=value=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(value??'')):String(value??''); - function redirectUrl(){ - try{ - const u=new URL(location.href); - u.hash=''; - return u.toString(); - }catch(_){return location.href.split('#')[0];} + function redirectUrl(){try{const u=new URL(location.href);u.hash='';return u.toString();}catch(_){return location.href.split('#')[0];}} + function savePendingRegistration(email,companyName,promoCode){try{localStorage.setItem(PENDING_REGISTRATION_KEY,JSON.stringify({email:String(email||'').trim().toLowerCase(),companyName:String(companyName||'').trim()||'Новая компания',promoCode:String(promoCode||'').trim().toUpperCase(),createdAt:new Date().toISOString()}));}catch(_){}} + function clearPendingRegistration(){try{localStorage.removeItem(PENDING_REGISTRATION_KEY)}catch(_){}} + function setError(root,message){const node=root?.querySelector?.('#sunGateErrorV3');if(node)node.textContent=String(message||'');} + function friendlySignupError(error){const message=String(error?.message||error||'Не удалось создать аккаунт.');if(/email address not authorized/i.test(message))return 'Не удалось отправить письмо подтверждения. Обратитесь к администратору Caterium.';if(/rate limit|rate_limit|too many/i.test(message))return 'Слишком много попыток регистрации. Попробуйте немного позже.';return message;} + async function signOutUnsafeSession(c){try{await c?.auth?.signOut?.()}catch(_){}} + + function ensurePromoField(){ + const fields=$('sunGateRegisterFieldsV27');if(!fields||$('sunGatePromoV181'))return; + const company=$('sunGateCompanyV3');const label=document.createElement('label');label.id='sunGatePromoLabelV181';label.innerHTML='Промокод пробной версииПромокод выдаёт разработчик Caterium.'; + if(company?.parentElement)company.parentElement.insertAdjacentElement('afterend',label);else fields.appendChild(label); + const input=$('sunGatePromoV181');input?.addEventListener('input',()=>{input.value=input.value.toUpperCase().replace(/\s+/g,'');const h=$('sunGatePromoHintV181');if(h)h.textContent='Промокод выдаёт разработчик Caterium.';}); + input?.addEventListener('blur',async()=>{const code=String(input.value||'').trim(),email=String($('sunGateEmailV3')?.value||'').trim().toLowerCase(),h=$('sunGatePromoHintV181');if(!code||!h)return;try{const c=client();if(!c)return;const {data,error}=await c.rpc('caterium_trial_promo_preview',{p_code:code,p_email:email||null});if(error)throw error;h.textContent=data?.valid?`Промокод принят · ${Number(data.trial_days||14)} дней пробного доступа`:(data?.reason||'Промокод недействителен');}catch(_){h.textContent='Не удалось проверить промокод.';}}); } - function savePendingRegistration(email,companyName){ - try{ - localStorage.setItem(PENDING_REGISTRATION_KEY,JSON.stringify({ - email:String(email||'').trim().toLowerCase(), - companyName:String(companyName||'').trim()||'Новая компания', - createdAt:new Date().toISOString() - })); - }catch(_){} + async function validatePromo(c,email,code){ + if(!code)throw new Error('Введите промокод пробной версии Caterium.'); + const {data,error}=await c.rpc('caterium_trial_promo_preview',{p_code:code,p_email:email});if(error)throw error; + if(!data?.valid)throw new Error(data?.reason||'Промокод недействителен.');return data; } - function clearPendingRegistration(){ - try{localStorage.removeItem(PENDING_REGISTRATION_KEY)}catch(_){} - } - - function setError(root,message){ - const node=root?.querySelector?.('#sunGateErrorV3'); - if(node)node.textContent=String(message||''); - } - - function friendlySignupError(error){ - const message=String(error?.message||error||'Не удалось создать аккаунт.'); - if(/email address not authorized/i.test(message)){ - return 'Не удалось отправить письмо подтверждения. Обратитесь к администратору Caterium.'; - } - if(/rate limit|rate_limit|too many/i.test(message)){ - return 'Слишком много попыток регистрации. Попробуйте немного позже.'; - } - return message; - } - - async function signOutUnsafeSession(c){ - try{await c?.auth?.signOut?.()}catch(_){} - } - - function showPublicConfirmation(gate,email){ - if(!gate)return; - gate.innerHTML=`
Caterium

Подтвердите email

Безопасная регистрация Caterium

Мы отправили письмо на ${esc(email)}. Откройте письмо и нажмите ссылку подтверждения. После подтверждения Caterium откроется автоматически.

`; - gate.querySelector('#sunAuthGoLoginV1774')?.addEventListener('click',()=>{gate.remove();document.body.classList.remove('sun-cloud-auth-required');setTimeout(()=>window.SunEnterprise?.ensureAuthGate?.(),30);location.reload();},{once:true}); - } - - function showInviteConfirmation(gate,email){ - if(!gate)return; - gate.innerHTML=`
Caterium

Подтвердите email

Приглашение сохранено

Письмо подтверждения отправлено на ${esc(email)}. Подтвердите адрес по ссылке в письме. После возврата в Caterium приглашение останется доступно.

`; - gate.querySelector('#sunInviteReloadV1774')?.addEventListener('click',()=>location.reload(),{once:true}); - } + function showPublicConfirmation(gate,email){if(!gate)return;gate.innerHTML=`
Caterium

Подтвердите email

Безопасная регистрация Caterium

Мы отправили письмо на ${esc(email)}. Откройте письмо и нажмите ссылку подтверждения. После подтверждения Caterium откроется автоматически и активирует пробную версию по вашему промокоду.

`;gate.querySelector('#sunAuthGoLoginV1774')?.addEventListener('click',()=>{gate.remove();document.body.classList.remove('sun-cloud-auth-required');setTimeout(()=>window.SunEnterprise?.ensureAuthGate?.(),30);location.reload();},{once:true});} + function showInviteConfirmation(gate,email){if(!gate)return;gate.innerHTML=`
Caterium

Подтвердите email

Приглашение сохранено

Письмо подтверждения отправлено на ${esc(email)}. Подтвердите адрес по ссылке в письме. После возврата в Caterium приглашение останется доступно.

`;gate.querySelector('#sunInviteReloadV1774')?.addEventListener('click',()=>location.reload(),{once:true});} async function publicSignup(gate){ - if(busy)return; - const c=client(); - if(!c){setError(gate,'Облачный сервис не подключён.');return;} - const email=String($('sunGateEmailV3')?.value||'').trim().toLowerCase(); - const password=String($('sunGatePasswordV3')?.value||''); - const password2=String($('sunGatePassword2V27')?.value||''); - const companyName=String($('sunGateCompanyV3')?.value||'').trim(); - if(!companyName){setError(gate,'Введите название компании.');return;} - if(!email||password.length<6){setError(gate,'Введите email и пароль минимум из 6 символов.');return;} - if(password!==password2){setError(gate,'Пароли не совпадают.');return;} - - busy=true; - const button=$('sunGateSubmitV3');if(button)button.disabled=true; - setError(gate,'Создаю безопасный аккаунт…'); - savePendingRegistration(email,companyName); + if(busy)return;const c=client();if(!c){setError(gate,'Облачный сервис не подключён.');return;} + const email=String($('sunGateEmailV3')?.value||'').trim().toLowerCase(),password=String($('sunGatePasswordV3')?.value||''),password2=String($('sunGatePassword2V27')?.value||''),companyName=String($('sunGateCompanyV3')?.value||'').trim(),promoCode=String($('sunGatePromoV181')?.value||'').trim().toUpperCase(); + if(!companyName){setError(gate,'Введите название компании.');return;}if(!email||password.length<6){setError(gate,'Введите email и пароль минимум из 6 символов.');return;}if(password!==password2){setError(gate,'Пароли не совпадают.');return;} + busy=true;const button=$('sunGateSubmitV3');if(button)button.disabled=true;setError(gate,'Проверяю промокод…'); try{ - const existing=await c.auth.signInWithPassword({email,password}); - if(!existing.error&&existing.data?.session){ - location.reload(); - return; - } - const result=await c.auth.signUp({ - email, - password, - options:{ - emailRedirectTo:redirectUrl(), - data:{company_name:companyName} - } - }); - if(result.error)throw result.error; - if(result.data?.session){ - await signOutUnsafeSession(c); - throw new Error('Защита email ещё не активирована на сервере. Регистрация остановлена, чтобы не создавать неподтверждённый аккаунт.'); - } + await validatePromo(c,email,promoCode);setError(gate,'Создаю безопасный аккаунт…');savePendingRegistration(email,companyName,promoCode); + const existing=await c.auth.signInWithPassword({email,password});if(!existing.error&&existing.data?.session){location.reload();return;} + const result=await c.auth.signUp({email,password,options:{emailRedirectTo:redirectUrl(),data:{company_name:companyName,promo_code:promoCode}}});if(result.error)throw result.error; + if(result.data?.session){await signOutUnsafeSession(c);throw new Error('Защита email ещё не активирована на сервере. Регистрация остановлена, чтобы не создавать неподтверждённый аккаунт.');} showPublicConfirmation(gate,email); - }catch(error){ - clearPendingRegistration(); - setError(gate,friendlySignupError(error)); - if(button)button.disabled=false; - }finally{busy=false;} - } - - async function acceptInviteAfterLogin(token,gate){ - const api=cloud(); - const ws=await api?.acceptInvite?.(token); - if(!ws)throw new Error('Не удалось присоединиться к компании.'); - try{const u=new URL(location.href);u.searchParams.delete('invite');history.replaceState(null,'',u.toString())}catch(_){} - location.reload(); + }catch(error){clearPendingRegistration();setError(gate,friendlySignupError(error));if(button)button.disabled=false;}finally{busy=false;} } + async function acceptInviteAfterLogin(token,gate){const api=cloud();const ws=await api?.acceptInvite?.(token);if(!ws)throw new Error('Не удалось присоединиться к компании.');try{const u=new URL(location.href);u.searchParams.delete('invite');history.replaceState(null,'',u.toString())}catch(_){}location.reload();} async function inviteSignup(gate){ - if(busy)return; - const c=client(); - if(!c){setError(gate,'Облачный сервис не подключён.');return;} - const email=String($('sunInviteEmailV27')?.value||'').trim().toLowerCase(); - const name=String($('sunInviteNameV27')?.value||'').trim(); - const password=String($('sunInvitePasswordV27')?.value||''); - const password2=String($('sunInvitePassword2V27')?.value||''); - let token='';try{token=String(new URL(location.href).searchParams.get('invite')||'').trim()}catch(_){} - if(!token){setError(gate,'Ссылка приглашения повреждена.');return;} - if(!email||password.length<6){setError(gate,'Пароль должен содержать минимум 6 символов.');return;} - if(password!==password2){setError(gate,'Пароли не совпадают.');return;} - - busy=true; - const button=$('sunInviteJoinV27');if(button)button.disabled=true; - setError(gate,'Проверяю аккаунт…'); - try{ - const existing=await c.auth.signInWithPassword({email,password}); - if(!existing.error&&existing.data?.session){ - await acceptInviteAfterLogin(token,gate); - return; - } - const result=await c.auth.signUp({ - email, - password, - options:{ - emailRedirectTo:redirectUrl(), - data:{name} - } - }); - if(result.error)throw result.error; - if(result.data?.session){ - await signOutUnsafeSession(c); - throw new Error('Защита email ещё не активирована на сервере. Приглашение остановлено, чтобы не создавать неподтверждённый аккаунт.'); - } - showInviteConfirmation(gate,email); - }catch(error){ - setError(gate,friendlySignupError(error)); - if(button)button.disabled=false; - }finally{busy=false;} + if(busy)return;const c=client();if(!c){setError(gate,'Облачный сервис не подключён.');return;} + const email=String($('sunInviteEmailV27')?.value||'').trim().toLowerCase(),name=String($('sunInviteNameV27')?.value||'').trim(),password=String($('sunInvitePasswordV27')?.value||''),password2=String($('sunInvitePassword2V27')?.value||'');let token='';try{token=String(new URL(location.href).searchParams.get('invite')||'').trim()}catch(_){} + if(!token){setError(gate,'Ссылка приглашения повреждена.');return;}if(!email||password.length<6){setError(gate,'Пароль должен содержать минимум 6 символов.');return;}if(password!==password2){setError(gate,'Пароли не совпадают.');return;} + busy=true;const button=$('sunInviteJoinV27');if(button)button.disabled=true;setError(gate,'Проверяю аккаунт…'); + try{const existing=await c.auth.signInWithPassword({email,password});if(!existing.error&&existing.data?.session){await acceptInviteAfterLogin(token,gate);return;}const result=await c.auth.signUp({email,password,options:{emailRedirectTo:redirectUrl(),data:{name}}});if(result.error)throw result.error;if(result.data?.session){await signOutUnsafeSession(c);throw new Error('Защита email ещё не активирована на сервере. Приглашение остановлено, чтобы не создавать неподтверждённый аккаунт.');}showInviteConfirmation(gate,email);}catch(error){setError(gate,friendlySignupError(error));if(button)button.disabled=false;}finally{busy=false;} } - function publicRegistrationMode(){ - const fields=$('sunGateRegisterFieldsV27'); - return Boolean(fields&&!fields.hidden); - } + function publicRegistrationMode(){const fields=$('sunGateRegisterFieldsV27');return Boolean(fields&&!fields.hidden);} + document.addEventListener('click',event=>{const target=event.target instanceof Element?event.target:null;if(!target)return;if(target.closest('#sunGateSubmitV3')&&publicRegistrationMode()){event.preventDefault();event.stopImmediatePropagation();publicSignup($('sunCloudAuthGateV3')).catch(error=>setError($('sunCloudAuthGateV3'),friendlySignupError(error)));return;}if(target.closest('#sunInviteJoinV27')){event.preventDefault();event.stopImmediatePropagation();inviteSignup($('sunCloudAuthGateV3')).catch(error=>setError($('sunCloudAuthGateV3'),friendlySignupError(error)));}},true); + document.addEventListener('keydown',event=>{if(event.key!=='Enter')return;const target=event.target instanceof Element?event.target:null;if(target?.id==='sunGatePassword2V27'&&publicRegistrationMode()){event.preventDefault();event.stopImmediatePropagation();publicSignup($('sunCloudAuthGateV3')).catch(error=>setError($('sunCloudAuthGateV3'),friendlySignupError(error)));}else if(target?.id==='sunInvitePassword2V27'){event.preventDefault();event.stopImmediatePropagation();inviteSignup($('sunCloudAuthGateV3')).catch(error=>setError($('sunCloudAuthGateV3'),friendlySignupError(error)));}},true); - document.addEventListener('click',event=>{ - const target=event.target instanceof Element?event.target:null; - if(!target)return; - if(target.closest('#sunGateSubmitV3')&&publicRegistrationMode()){ - event.preventDefault();event.stopImmediatePropagation(); - publicSignup($('sunCloudAuthGateV3')).catch(error=>setError($('sunCloudAuthGateV3'),friendlySignupError(error))); - return; - } - if(target.closest('#sunInviteJoinV27')){ - event.preventDefault();event.stopImmediatePropagation(); - inviteSignup($('sunCloudAuthGateV3')).catch(error=>setError($('sunCloudAuthGateV3'),friendlySignupError(error))); - } - },true); - - document.addEventListener('keydown',event=>{ - if(event.key!=='Enter')return; - const target=event.target instanceof Element?event.target:null; - if(target?.id==='sunGatePassword2V27'&&publicRegistrationMode()){ - event.preventDefault();event.stopImmediatePropagation(); - publicSignup($('sunCloudAuthGateV3')).catch(error=>setError($('sunCloudAuthGateV3'),friendlySignupError(error))); - }else if(target?.id==='sunInvitePassword2V27'){ - event.preventDefault();event.stopImmediatePropagation(); - inviteSignup($('sunCloudAuthGateV3')).catch(error=>setError($('sunCloudAuthGateV3'),friendlySignupError(error))); - } - },true); - - window.CateriumAuthSecurityV1774=Object.freeze({VERSION,redirectUrl}); + const promoObserver=new MutationObserver(()=>ensurePromoField());promoObserver.observe(document.documentElement,{childList:true,subtree:true});if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',ensurePromoField,{once:true});else ensurePromoField(); + window.CateriumAuthSecurityV1774=Object.freeze({VERSION,redirectUrl,ensurePromoField}); })(); \ No newline at end of file