From aafad18d6a0476d60cb3996b2ce93c4df4ff9391 Mon Sep 17 00:00:00 2001 From: pavlov346346-source Date: Sun, 13 Sep 2026 01:59:36 +0300 Subject: [PATCH] fix: remove the racy forced password-change gate for new employees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit showPasswordChangeGate() replaced the auth gate's content and returned, but the same login also fires onAuthStateChange, which kicks off loadMemberships() independently. Once that resolved (workspace found), ensureAuthGate()'s periodic poll (every 5s, plus assorted event-driven calls) saw signedIn+workspace and tore the gate down immediately, regardless of whether a password-change form was currently blocking it - the employee would see the "create a new password" screen flash and vanish before they could use it, dropping them straight into the app still on their temporary password. Removes the forced gate entirely (mustChangePassword/ showPasswordChangeGate) and adds a "Пароль" box to the account settings card instead, so changing your password is a deliberate, always-available action rather than a one-shot dialog racing the rest of the login flow. Co-Authored-By: Claude Sonnet 5 --- public/app-runtime.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/public/app-runtime.js b/public/app-runtime.js index 8629180..c9c0829 100644 --- a/public/app-runtime.js +++ b/public/app-runtime.js @@ -2897,6 +2897,7 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс ${connected&&!signed?`

Войти в профиль

`:''} ${!connected?`

Подключение недоступно

Техническое подключение настраивается только из кабинета разработчика.

`:''} ${signed?`

Профиль

${esc(session.user.email||session.user.id)}
`:''} + ${signed?`

Пароль

`:''} ${signed&&!selected?`

Рабочая компания

${membershipsLoading?'Загружаю доступные рабочие базы…':'У этого аккаунта пока нет доступной рабочей базы.'}

`:''} ${signed&&selected?`

Рабочая компания

${esc(workspace.name)}${supportMode?'· поддержка, только просмотр':''}
Роль: ${supportMode?'Разработчик · поддержка':esc(roleLabel(workspace.role))}${workspace.display_name?` · ${esc(workspace.display_name)}`:''}
${!supportMode&&memberships.length>1?``:''}
`:''} ${signed&&selected&&!supportMode?`

Синхронизация

Принудительно загрузить актуальные заказы, клиентов и остальные данные с сервера, заменив локальную копию на этом устройстве. Перед загрузкой автоматически создаётся резервная копия.

`:''} @@ -2914,6 +2915,15 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс $('sunCloudSignInV2')?.addEventListener('click',()=>signIn(false)); $('sunCloudSignUpV2')?.addEventListener('click',()=>signIn(true)); $('sunCloudSignOutV2')?.addEventListener('click',signOutAndReload); + $('sunAccountChangePasswordV1')?.addEventListener('click',async()=>{ + const p1i=$('sunAccountNewPasswordV1'),p2i=$('sunAccountNewPassword2V1'); + const p1=String(p1i?.value||''),p2=String(p2i?.value||''); + if(p1.length<8)return toast('Новый пароль должен содержать минимум 8 символов.','warn'); + if(p1!==p2)return toast('Пароли не совпадают.','warn'); + if(!client)return toast('Облачный сервис не подключён.','error'); + try{const r=await client.auth.updateUser({password:p1});if(r.error)throw r.error;if(p1i)p1i.value='';if(p2i)p2i.value='';toast('Пароль изменён.','success');} + catch(e){toast(String(e?.message||e||'Не удалось изменить пароль.'),'error');} + }); $('sunCloudCreateWorkspaceV2')?.addEventListener('click',createWorkspace); $('sunCloudReloadMembershipsV2')?.addEventListener('click',async()=>{const tenantSwitched=await loadMemberships();if(tenantSwitched){location.reload();return;}renderCloudUI();if(workspace){subscribeRealtime();await pullRemote({replace:true,quiet:true});}}); $('sunCloudMigrateV2')?.addEventListener('click',migrateLocalToCloud); @@ -3564,14 +3574,12 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс bindPasswordEyes(gate);const btn=gate.querySelector('#sunInviteJoinV27');const run=async()=>{const c=client(),email=String(preview.email||'').trim(),name=String(gate.querySelector('#sunInviteNameV27')?.value||'').trim(),password=String(gate.querySelector('#sunInvitePasswordV27')?.value||''),password2=String(gate.querySelector('#sunInvitePassword2V27')?.value||''),err=gate.querySelector('#sunGateErrorV3');if(password.length<6){err.textContent='Пароль должен содержать минимум 6 символов.';return;}if(password!==password2){err.textContent='Пароли не совпадают.';return;}btn.disabled=true;err.textContent='Проверяю аккаунт…';try{let r=await c.auth.signInWithPassword({email,password});if(r.error){r=await c.auth.signUp({email,password,options:{data:{name,registration_source:'caterium_invite_signup'}}});if(r.error)throw r.error;if(!r.data.session){const retry=await c.auth.signInWithPassword({email,password});if(retry.error)throw new Error('Этот email уже зарегистрирован. Введите пароль от существующего аккаунта.');r=retry;}}const userId=r.data.user?.id||r.data.session?.user?.id||'';if(userId)sessionStorage.setItem(`sunCloudQuickPinUnlockedV3:${userId}`,'1');err.textContent='Аккаунт готов. Подключаю компанию…';setTimeout(()=>finishInvite(token,gate),180);}catch(e){err.textContent=String(e?.message||e||'Не удалось войти или создать аккаунт.');btn.disabled=false;}};btn.onclick=run;gate.querySelector('#sunInvitePassword2V27').addEventListener('keydown',e=>{if(e.key==='Enter')run()}); } - function mustChangePassword(user){return Boolean(user?.user_metadata?.must_change_password);} - - function showPasswordChangeGate(gate,c,user,onDone){ - gate.innerHTML=`
Caterium

Создайте новый пароль

Первый вход сотрудника

Временный пароль сработал. Теперь придумайте свой постоянный пароль.

${passwordField('sunEmployeeNewPasswordV28','Новый пароль','new-password')}${passwordField('sunEmployeeNewPassword2V28','Подтвердите пароль','new-password')}
`; - bindPasswordEyes(gate); - const run=async()=>{const p1=String(gate.querySelector('#sunEmployeeNewPasswordV28')?.value||''),p2=String(gate.querySelector('#sunEmployeeNewPassword2V28')?.value||''),err=gate.querySelector('#sunGateErrorV3'),btn=gate.querySelector('#sunEmployeeSavePasswordV28');if(p1.length<8){err.textContent='Новый пароль должен содержать минимум 8 символов.';return;}if(p1!==p2){err.textContent='Пароли не совпадают.';return;}btn.disabled=true;err.textContent='Сохраняю пароль…';try{const nextData={...(user?.user_metadata||{}),must_change_password:false};const r=await c.auth.updateUser({password:p1,data:nextData});if(r.error)throw r.error;err.textContent='Пароль сохранён.';setTimeout(()=>onDone?.(),120);}catch(e){err.textContent=String(e?.message||e||'Не удалось изменить пароль.');btn.disabled=false;}}; - gate.querySelector('#sunEmployeeSavePasswordV28').onclick=run;gate.querySelector('#sunEmployeeNewPassword2V28').addEventListener('keydown',e=>{if(e.key==='Enter')run();}); - } + // Password changes now happen from account settings (see cloud-sync-v2.js's + // renderCloudUI "Пароль" box) instead of a forced post-login gate: the old + // gate raced with membership loading (loadMemberships finishing and + // ensureAuthGate's periodic poll tearing the gate down as soon as + // signedIn+workspace was true) and could disappear before the employee + // ever saw it. function ensureAuthGate(){ const st=cloud()?.status?.();if(!st?.connected)return; @@ -3593,7 +3601,7 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс gate.innerHTML=`
Caterium

Вход в Caterium

Введите данные своего аккаунта
${passwordField('sunGatePasswordV3','Пароль','current-password')}

Нет аккаунта?

`;document.body.appendChild(gate);bindPasswordEyes(gate); const emailInput=gate.querySelector('#sunGateEmailV3'),passwordInput=gate.querySelector('#sunGatePasswordV3'),password2Input=gate.querySelector('#sunGatePassword2V27'),companyInput=gate.querySelector('#sunGateCompanyV3'),submitBtn=gate.querySelector('#sunGateSubmitV3'),gateError=gate.querySelector('#sunGateErrorV3');let registerMode=false; const setMode=(registration)=>{registerMode=Boolean(registration);gate.querySelector('#sunGateRegisterFieldsV27').hidden=!registerMode;gate.querySelector('#sunGateConfirmWrapV27').hidden=!registerMode;gate.querySelector('#sunGateTitleV3').textContent=registerMode?'Создать аккаунт Caterium':'Вход в Caterium';gate.querySelector('#sunGateSubtitleV3').textContent=registerMode?'Заполните данные — после регистрации Caterium откроется автоматически':'Введите данные своего аккаунта';submitBtn.textContent=registerMode?'Создать аккаунт':'Войти';gate.querySelector('#sunGateSwitchTextV27').textContent=registerMode?'Уже есть аккаунт?':'Нет аккаунта?';gate.querySelector('#sunGateSwitchV27').textContent=registerMode?'Войти':'Создать аккаунт';passwordInput.autocomplete=registerMode?'new-password':'current-password';gateError.textContent='';if(registerMode)setTimeout(()=>companyInput.focus(),30);else setTimeout(()=>emailInput.focus(),30)}; - async function auth(){const c=client(),email=emailInput.value.trim(),password=passwordInput.value,password2=password2Input.value,companyName=String(companyInput.value||'').trim();if(!c){gateError.textContent='Облачный сервис не подключён.';return;}if(registerMode&&!companyName){gateError.textContent='Введите название компании.';return;}if(!email||password.length<6){gateError.textContent='Введите email и пароль минимум из 6 символов.';return;}if(registerMode&&password!==password2){gateError.textContent='Пароли не совпадают.';return;}submitBtn.disabled=true;gateError.textContent=registerMode?'Создаю аккаунт…':'Выполняю вход…';try{let r;if(registerMode){savePendingRegistration(email,companyName);const existing=await c.auth.signInWithPassword({email,password});if(!existing.error&&existing.data.session){r=existing;}else{r=await c.auth.signUp({email,password,options:{data:{company_name:companyName,registration_source:'caterium_public_signup'}}});if(r.error)throw r.error;if(!r.data.session){const retry=await c.auth.signInWithPassword({email,password});if(retry.error){clearPendingRegistration();throw new Error('Этот email уже зарегистрирован. Войдите в аккаунт или восстановите пароль.');}r=retry;}}}else r=await c.auth.signInWithPassword({email,password});if(r.error)throw r.error;const authUser=r.data.user||r.data.session?.user||null;const userId=authUser?.id||'';if(userId)sessionStorage.setItem(`sunCloudQuickPinUnlockedV3:${userId}`,'1');if(!registerMode&&mustChangePassword(authUser)){showPasswordChangeGate(gate,c,authUser,()=>{gate.remove();document.body.classList.remove('sun-cloud-auth-required');setTimeout(()=>ensureAuthGate(),80);});return;}gateError.textContent=registerMode?'Аккаунт готов. Создаю компанию…':'Вход выполнен. Загружаю рабочую базу…';setTimeout(async()=>{await cloud()?.reloadMemberships?.();if(registerMode&&workspace()){clearPendingRegistration();gate.remove();document.body.classList.remove('sun-cloud-auth-required');await cloud()?.pull?.();applyNavPermissions();return;}if(getPendingRegistration(email)&&!workspace()){await finishPendingRegistration(gate,email);return;}if(workspace()){gate.remove();document.body.classList.remove('sun-cloud-auth-required');await cloud()?.pull?.();applyNavPermissions();}else{gate.remove();ensureAuthGate();}},180);}catch(err){gateError.textContent=String(err?.message||err||'Не удалось выполнить операцию.')}finally{submitBtn.disabled=false}} + async function auth(){const c=client(),email=emailInput.value.trim(),password=passwordInput.value,password2=password2Input.value,companyName=String(companyInput.value||'').trim();if(!c){gateError.textContent='Облачный сервис не подключён.';return;}if(registerMode&&!companyName){gateError.textContent='Введите название компании.';return;}if(!email||password.length<6){gateError.textContent='Введите email и пароль минимум из 6 символов.';return;}if(registerMode&&password!==password2){gateError.textContent='Пароли не совпадают.';return;}submitBtn.disabled=true;gateError.textContent=registerMode?'Создаю аккаунт…':'Выполняю вход…';try{let r;if(registerMode){savePendingRegistration(email,companyName);const existing=await c.auth.signInWithPassword({email,password});if(!existing.error&&existing.data.session){r=existing;}else{r=await c.auth.signUp({email,password,options:{data:{company_name:companyName,registration_source:'caterium_public_signup'}}});if(r.error)throw r.error;if(!r.data.session){const retry=await c.auth.signInWithPassword({email,password});if(retry.error){clearPendingRegistration();throw new Error('Этот email уже зарегистрирован. Войдите в аккаунт или восстановите пароль.');}r=retry;}}}else r=await c.auth.signInWithPassword({email,password});if(r.error)throw r.error;const authUser=r.data.user||r.data.session?.user||null;const userId=authUser?.id||'';if(userId)sessionStorage.setItem(`sunCloudQuickPinUnlockedV3:${userId}`,'1');gateError.textContent=registerMode?'Аккаунт готов. Создаю компанию…':'Вход выполнен. Загружаю рабочую базу…';setTimeout(async()=>{await cloud()?.reloadMemberships?.();if(registerMode&&workspace()){clearPendingRegistration();gate.remove();document.body.classList.remove('sun-cloud-auth-required');await cloud()?.pull?.();applyNavPermissions();return;}if(getPendingRegistration(email)&&!workspace()){await finishPendingRegistration(gate,email);return;}if(workspace()){gate.remove();document.body.classList.remove('sun-cloud-auth-required');await cloud()?.pull?.();applyNavPermissions();}else{gate.remove();ensureAuthGate();}},180);}catch(err){gateError.textContent=String(err?.message||err||'Не удалось выполнить операцию.')}finally{submitBtn.disabled=false}} gate.querySelector('#sunGateSwitchV27').onclick=()=>setMode(!registerMode);submitBtn.onclick=auth;passwordInput.addEventListener('keydown',e=>{if(e.key==='Enter'&&!registerMode)auth()});password2Input.addEventListener('keydown',e=>{if(e.key==='Enter'&®isterMode)auth()});setMode(false); }