security: add server-backed account password gate
This commit is contained in:
parent
f824769985
commit
1b2832257e
121
public/core/account-security-v1774.js
Normal file
121
public/core/account-security-v1774.js
Normal file
@ -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=`<div class="cat-password-gate-v1774-card"><h2>${forced?'Создайте постоянный пароль':'Сменить пароль'}</h2><p>${forced?'Вы вошли по временному паролю. Чтобы продолжить работу в Caterium, замените его на постоянный пароль.':'После смены пароля потребуется войти в Caterium заново.'}</p><label>Текущий пароль<input id="catCurrentPasswordV1774" type="password" autocomplete="current-password"></label><label>Новый пароль<input id="catNewPasswordV1774" type="password" autocomplete="new-password"></label><label>Повторите новый пароль<input id="catNewPassword2V1774" type="password" autocomplete="new-password"></label><div id="catPasswordErrorV1774" class="cat-password-gate-v1774-error"></div><div class="cat-password-gate-v1774-actions">${forced?'<button class="outline" id="catPasswordSignOutV1774" type="button">Выйти</button>':'<button class="outline" id="catPasswordCancelV1774" type="button">Отмена</button>'}<button class="primary" id="catPasswordSaveV1774" type="button">Сохранить пароль</button></div></div>`;
|
||||||
|
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.length<MIN_PASSWORD_LENGTH){err.textContent=`Новый пароль должен содержать минимум ${MIN_PASSWORD_LENGTH} символов.`;return}
|
||||||
|
if(next!==confirm){err.textContent='Пароли не совпадают.';return}
|
||||||
|
if(next===current){err.textContent='Новый пароль должен отличаться от текущего.';return}
|
||||||
|
save.disabled=true;err.textContent='Сохраняю новый пароль…';err.classList.remove('cat-password-gate-v1774-success');
|
||||||
|
try{
|
||||||
|
await changePassword(current,next);
|
||||||
|
err.textContent='Пароль изменён. Сейчас потребуется войти заново.';
|
||||||
|
err.classList.add('cat-password-gate-v1774-success');
|
||||||
|
setTimeout(()=>signOutAndReload(),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='<div><b>Безопасность аккаунта</b><small>Смена пароля для текущего аккаунта</small></div><button type="button" class="outline" id="catProfilePasswordV1774">Сменить пароль</button>';
|
||||||
|
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();
|
||||||
|
})();
|
||||||
Loading…
Reference in New Issue
Block a user