caterium-app/public/core/auth-security-v1774.js
2026-09-14 12:23:00 +03:00

93 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(()=>{
'use strict';
const VERSION='17.8.2-company-onboarding';
const PENDING_REGISTRATION_KEY='sunPendingRegistrationV23';
let busy=false;
const $=id=>document.getElementById(id);
const cloud=()=>window.SunCloudV2||null;
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 savePendingRegistration(email,promoCode){try{localStorage.setItem(PENDING_REGISTRATION_KEY,JSON.stringify({email:String(email||'').trim().toLowerCase(),companyName:'Новая компания',promoCode:String(promoCode||'').trim().toUpperCase(),createdAt:new Date().toISOString()}));}catch(_){}}
function pendingRegistration(){try{return JSON.parse(localStorage.getItem(PENDING_REGISTRATION_KEY)||'null')}catch(_){return null}}
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 ensureRegistrationFields(){
const fields=$('sunGateRegisterFieldsV27');if(!fields)return;
const company=$('sunGateCompanyV3');if(company?.parentElement)company.parentElement.remove();
if(!$('sunGatePromoV181')){
const label=document.createElement('label');label.id='sunGatePromoLabelV181';label.innerHTML='<span>Промокод пробной версии</span><input id="sunGatePromoV181" autocomplete="off" maxlength="32" placeholder="Например, CTM-A7K4P9"><small id="sunGatePromoHintV181" style="display:block;margin-top:5px;opacity:.72">Промокод создаёт компанию и активирует пробный период.</small>';
fields.appendChild(label);
const input=$('sunGatePromoV181');input?.addEventListener('input',()=>{input.value=input.value.toUpperCase().replace(/\s+/g,'');});
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='Не удалось проверить промокод.';}});
}
}
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 showPublicConfirmation(gate,email){if(!gate)return;gate.innerHTML=`<div class="sun-cloud-auth-card"><div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>Подтвердите email</h2><div class="hint">Регистрация Caterium</div></div></div><p class="hint">Мы отправили письмо на <b>${esc(email)}</b>. После подтверждения войдите в Caterium — компания и пробный период будут созданы автоматически. Название компании вы укажете в настройках.</p><div class="sun-cloud-auth-actions"><button class="primary" id="sunAuthGoLoginV1774" type="button">Перейти ко входу</button></div></div>`;gate.querySelector('#sunAuthGoLoginV1774')?.addEventListener('click',()=>location.reload(),{once:true});}
function showInviteConfirmation(gate,email){if(!gate)return;gate.innerHTML=`<div class="sun-cloud-auth-card"><div class="sun-cloud-auth-brand"><img src="caterium-login-logo.png" alt="Caterium"><div><h2>Подтвердите email</h2><div class="hint">Приглашение сохранено</div></div></div><p class="hint">Письмо подтверждения отправлено на <b>${esc(email)}</b>. После подтверждения войдите по ссылке приглашения — Caterium автоматически подключит вас к компании.</p><div class="sun-cloud-auth-actions"><button class="primary" id="sunInviteReloadV1774" type="button">Я подтвердил email</button></div></div>`;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(),password=String($('sunGatePasswordV3')?.value||''),password2=String($('sunGatePassword2V27')?.value||''),promoCode=String($('sunGatePromoV181')?.value||'').trim().toUpperCase();
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{
await validatePromo(c,email,promoCode);savePendingRegistration(email,promoCode);setError(gate,'Создаю аккаунт…');
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:{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){const ws=await cloud()?.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(),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);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;}
}
async function finishOwnerOnboarding(gate){
if(busy)return false;const c=client(),api=cloud(),pending=pendingRegistration();if(!c||!api||!pending?.promoCode)return false;
const session=api.getSession?.();const email=String(session?.user?.email||'').trim().toLowerCase();if(!email||email!==String(pending.email||'').toLowerCase())return false;
busy=true;setError(gate,'Подключаю рабочую базу…');
try{
const user=session.user;const metadata={...(user.user_metadata||{}),promo_code:pending.promoCode};
const updated=await c.auth.updateUser({data:metadata});if(updated.error)throw updated.error;
const created=await c.rpc('sun_create_workspace',{p_name:'Новая компания'});if(created.error)throw created.error;
clearPendingRegistration();await api.reloadMemberships?.();location.reload();return true;
}catch(error){setError(gate,friendlySignupError(error));return false;}finally{busy=false;}
}
function neutralizeLegacyCompanyUI(){
const gate=$('sunCloudAuthGateV3');if(!gate)return;
ensureRegistrationFields();
const recovery=$('sunGateRecoveryCompanyV25');
if(recovery){
recovery.closest('label')?.remove();
const button=$('sunGateRetryWorkspaceV3');if(button){button.textContent='Проверить доступ';button.onclick=async()=>{setError(gate,'Проверяю доступ…');try{const found=await cloud()?.reloadMemberships?.();if(found){location.reload();return;}if(await finishOwnerOnboarding(gate))return;setError(gate,'Аккаунт не привязан к компании. Если вы сотрудник — попросите владельца добавить вас в «Пользователи и права». Для новой компании зарегистрируйте пробный период по промокоду.');}catch(e){setError(gate,e?.message||String(e));}};}
const title=gate.querySelector('h2');if(title)title.textContent='Нет доступа к компании';
const hints=gate.querySelectorAll('.hint');if(hints[1])hints[1].textContent='Этот аккаунт пока не привязан к рабочей компании.';
}
gate.querySelectorAll('#sunCloudCreateWorkspaceV2,[data-create]').forEach(el=>el.remove());
}
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);
const observer=new MutationObserver(()=>neutralizeLegacyCompanyUI());observer.observe(document.documentElement,{childList:true,subtree:true});
const init=()=>{neutralizeLegacyCompanyUI();setTimeout(()=>{const gate=$('sunCloudAuthGateV3');if(gate&&pendingRegistration())finishOwnerOnboarding(gate);},500)};
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init,{once:true});else init();
window.CateriumAuthSecurityV1774=Object.freeze({VERSION,redirectUrl,ensureRegistrationFields});
})();