From 1b2832257ea1029f8c449bfb84ed1a7f71f11176 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Fri, 11 Sep 2026 10:09:04 +0300 Subject: [PATCH] security: add server-backed account password gate --- public/core/account-security-v1774.js | 121 ++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 public/core/account-security-v1774.js diff --git a/public/core/account-security-v1774.js b/public/core/account-security-v1774.js new file mode 100644 index 0000000..489089e --- /dev/null +++ b/public/core/account-security-v1774.js @@ -0,0 +1,121 @@ +(()=>{ + 'use strict'; + if(window.CateriumAccountSecurityV1774)return; + + const VERSION='17.7.4'; + const MIN_PASSWORD_LENGTH=8; + const $=id=>document.getElementById(id); + const cloud=()=>window.SunCloudV2||null; + const client=()=>cloud()?.getClient?.()||null; + const mustChange=user=>Boolean(user?.app_metadata?.must_change_password===true); + let forcedOpen=false,checking=false; + + function installStyle(){ + if($('cateriumAccountSecurityV1774Style'))return; + const st=document.createElement('style'); + st.id='cateriumAccountSecurityV1774Style'; + st.textContent=` + .cat-password-gate-v1774{position:fixed;inset:0;z-index:200500;background:rgba(12,16,14,.72);backdrop-filter:blur(8px);display:grid;place-items:center;padding:18px} + .cat-password-gate-v1774-card{width:min(460px,100%);background:#fff;border-radius:20px;padding:22px;box-shadow:0 28px 90px rgba(0,0,0,.32)} + .cat-password-gate-v1774-card h2{margin:0 0 6px;color:#15364c}.cat-password-gate-v1774-card p{color:#6d746f;line-height:1.45} + .cat-password-gate-v1774-card label{display:block;margin-top:12px;font-size:12px;font-weight:800;color:#343a36}.cat-password-gate-v1774-card input{width:100%;box-sizing:border-box;margin-top:6px;padding:11px 12px;border:1px solid #d8ded9;border-radius:10px;font:inherit} + .cat-password-gate-v1774-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:16px}.cat-password-gate-v1774-actions button{border-radius:10px;padding:9px 12px;font-weight:850}.cat-password-gate-v1774-actions .primary{background:#15364c;color:#fff;border:1px solid #15364c}.cat-password-gate-v1774-actions .outline{background:#fff;border:1px solid #d5dad6;color:#15364c} + .cat-password-gate-v1774-error{min-height:20px;margin-top:10px;color:#9f2d2d;font-size:12px}.cat-password-gate-v1774-success{color:#1e6a3d} + .cat-profile-password-v1774{margin-top:10px;padding-top:10px;border-top:1px solid #e6e9e7;display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.cat-profile-password-v1774 small{display:block;color:#7a807c;margin-top:3px} + body.cat-password-required-v1774{overflow:hidden!important} + `; + document.head.appendChild(st); + } + + async function latestUser(){ + const c=client();if(!c)return null; + try{const result=await c.auth.getUser();return result.data?.user||null}catch(_){return null} + } + + async function signOutAndReload(){ + const c=client(); + try{await c?.auth?.signOut?.({scope:'local'})}catch(_){try{await cloud()?.signOut?.()}catch(__){}} + location.reload(); + } + + async function changePassword(currentPassword,newPassword){ + const c=client();if(!c)throw new Error('Сессия пользователя недоступна.'); + const result=await c.functions.invoke('caterium-change-password',{body:{current_password:currentPassword,new_password:newPassword}}); + const message=result.data?.error||result.error?.message; + if(result.error||result.data?.error)throw new Error(message||'Не удалось изменить пароль.'); + if(result.data?.status!=='changed')throw new Error('Сервер не подтвердил смену пароля.'); + return result.data; + } + + function closeVoluntary(host){if(!forcedOpen)host?.remove()} + + function openPasswordModal({forced=false}={}){ + installStyle(); + $('cateriumPasswordGateV1774')?.remove(); + forcedOpen=forced; + const host=document.createElement('div'); + host.id='cateriumPasswordGateV1774'; + host.className='cat-password-gate-v1774'; + host.innerHTML=`

${forced?'Создайте постоянный пароль':'Сменить пароль'}

${forced?'Вы вошли по временному паролю. Чтобы продолжить работу в Caterium, замените его на постоянный пароль.':'После смены пароля потребуется войти в Caterium заново.'}

${forced?'':''}
`; + document.body.appendChild(host); + if(forced)document.body.classList.add('cat-password-required-v1774'); + if(forced)$('catPasswordSignOutV1774').onclick=()=>signOutAndReload(); + else $('catPasswordCancelV1774').onclick=()=>closeVoluntary(host); + + const save=$('catPasswordSaveV1774'),err=$('catPasswordErrorV1774'); + const run=async()=>{ + const current=String($('catCurrentPasswordV1774')?.value||''); + const next=String($('catNewPasswordV1774')?.value||''); + const confirm=String($('catNewPassword2V1774')?.value||''); + if(!current){err.textContent='Введите текущий пароль.';return} + if(next.lengthsignOutAndReload(),650); + }catch(error){err.textContent=String(error?.message||error||'Не удалось изменить пароль.');save.disabled=false} + }; + save.onclick=run; + $('catNewPassword2V1774').addEventListener('keydown',e=>{if(e.key==='Enter')run()}); + setTimeout(()=>$('catCurrentPasswordV1774')?.focus(),50); + return true; + } + + async function ensureForcedGate(){ + if(checking)return;checking=true; + try{ + const user=await latestUser(); + if(!user){$('cateriumPasswordGateV1774')?.remove();document.body.classList.remove('cat-password-required-v1774');forcedOpen=false;return} + if(mustChange(user)){ + if(!$('cateriumPasswordGateV1774')||!forcedOpen)openPasswordModal({forced:true}); + }else if(forcedOpen){ + $('cateriumPasswordGateV1774')?.remove();document.body.classList.remove('cat-password-required-v1774');forcedOpen=false; + } + }finally{checking=false} + } + + function decorateProfile(){ + const c=client();if(!c)return; + const card=$('sunCloudV2Card');if(!card)return; + const profile=[...card.querySelectorAll('.sun-cloud-v2-box')].find(x=>x.querySelector('h3')?.textContent.trim()==='Профиль'); + if(!profile||profile.querySelector('.cat-profile-password-v1774'))return; + const row=document.createElement('div');row.className='cat-profile-password-v1774'; + row.innerHTML='
Безопасность аккаунтаСмена пароля для текущего аккаунта
'; + profile.appendChild(row);row.querySelector('#catProfilePasswordV1774').onclick=()=>openPasswordModal({forced:false}); + } + + function maintain(){installStyle();ensureForcedGate();decorateProfile()} + const observer=new MutationObserver(()=>{if(document.hidden)return;setTimeout(()=>{decorateProfile();ensureForcedGate()},50)}); + if(document.documentElement)observer.observe(document.documentElement,{childList:true,subtree:true}); + window.addEventListener('sun:cloud-state-applied',()=>setTimeout(maintain,80)); + window.addEventListener('focus',maintain); + document.addEventListener('visibilitychange',()=>{if(!document.hidden)maintain()}); + setInterval(()=>{if(!document.hidden)ensureForcedGate()},5000); + + window.CateriumAccountSecurityV1774={VERSION,mustChange,openPasswordModal,ensureForcedGate,changePassword,checks:()=>({version:VERSION,forcedOpen,gate:Boolean($('cateriumPasswordGateV1774')),profileButton:Boolean($('catProfilePasswordV1774'))})}; + if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',maintain,{once:true});else maintain(); +})();