security: require verified email for client signups
This commit is contained in:
parent
606572eb87
commit
f066ad82c7
192
public/core/auth-security-v1774.js
Normal file
192
public/core/auth-security-v1774.js
Normal file
@ -0,0 +1,192 @@
|
||||
(()=>{
|
||||
'use strict';
|
||||
|
||||
const VERSION='17.7.3-auth-security-v1';
|
||||
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,companyName){
|
||||
try{
|
||||
localStorage.setItem(PENDING_REGISTRATION_KEY,JSON.stringify({
|
||||
email:String(email||'').trim().toLowerCase(),
|
||||
companyName:String(companyName||'').trim()||'Новая компания',
|
||||
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 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 class="sun-cloud-auth-error" id="sunGateErrorV3"></div></div>`;
|
||||
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=`<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 class="sun-cloud-auth-error" id="sunGateErrorV3"></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();
|
||||
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);
|
||||
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 ещё не активирована на сервере. Регистрация остановлена, чтобы не создавать неподтверждённый аккаунт.');
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
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;}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
window.CateriumAuthSecurityV1774=Object.freeze({VERSION,redirectUrl});
|
||||
})();
|
||||
Loading…
Reference in New Issue
Block a user