From 06bdf999a655cf4a4aca6cfbaed63a9d39841f84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:41:02 +0000 Subject: [PATCH 1/2] Initial plan From ff1632dcda3c5f0f280fc52db9ea63cde5c9a299 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:44:57 +0000 Subject: [PATCH 2/2] Fix Change Passwords button: add API endpoint, system password modal, fix security banner link Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/bf43bea9-9f93-4f7b-b6fd-c76714e7f25b Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com> --- app/sovran_systemsos_web/server.py | 63 +++++++++++++++++ .../static/js/features.js | 2 +- .../static/js/service-detail.js | 68 +++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index a7fb835..f626b70 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -2952,6 +2952,69 @@ async def api_security_status(): return {"status": status, "warning": warning} +# ── System password change ──────────────────────────────────────── + +FREE_PASSWORD_FILE = "/var/lib/secrets/free-password" + + +class ChangePasswordRequest(BaseModel): + new_password: str + confirm_password: str + + +@app.post("/api/change-password") +async def api_change_password(req: ChangePasswordRequest): + """Change the system 'free' user password. + + Updates /etc/shadow via chpasswd and writes the new password to + /var/lib/secrets/free-password so the Hub credentials view stays in sync. + Also clears the legacy security-status and security-warning files so the + security banner disappears after a successful change. + """ + if not req.new_password: + raise HTTPException(status_code=400, detail="New password must not be empty.") + if req.new_password != req.confirm_password: + raise HTTPException(status_code=400, detail="Passwords do not match.") + if len(req.new_password) < 8: + raise HTTPException(status_code=400, detail="Password must be at least 8 characters long.") + + # Update /etc/shadow via chpasswd + try: + result = subprocess.run( + ["chpasswd"], + input=f"free:{req.new_password}", + capture_output=True, + text=True, + ) + if result.returncode != 0: + detail = (result.stderr or result.stdout).strip() or "chpasswd failed." + raise HTTPException(status_code=500, detail=detail) + except HTTPException: + raise + except Exception as exc: + raise HTTPException(status_code=500, detail=f"Failed to update system password: {exc}") + + # Write new password to secrets file so Hub credentials stay in sync + try: + os.makedirs(os.path.dirname(FREE_PASSWORD_FILE), exist_ok=True) + with open(FREE_PASSWORD_FILE, "w") as f: + f.write(req.new_password) + os.chmod(FREE_PASSWORD_FILE, 0o600) + except Exception as exc: + raise HTTPException(status_code=500, detail=f"Failed to write secrets file: {exc}") + + # Clear legacy security status so the warning banner is removed + for path in (SECURITY_STATUS_FILE, SECURITY_WARNING_FILE): + try: + os.remove(path) + except FileNotFoundError: + pass + except Exception: + pass # Non-fatal; don't block a successful password change + + return {"ok": True} + + # ── Matrix user management ──────────────────────────────────────── MATRIX_USERS_FILE = "/var/lib/secrets/matrix-users" diff --git a/app/sovran_systemsos_web/static/js/features.js b/app/sovran_systemsos_web/static/js/features.js index eaeee3c..831ceff 100644 --- a/app/sovran_systemsos_web/static/js/features.js +++ b/app/sovran_systemsos_web/static/js/features.js @@ -606,7 +606,7 @@ function renderAutolaunchToggle(enabled) { '
' + '' + '' + msg + '' + - 'Change Passwords' + + 'Change Passwords' + '
'; } diff --git a/app/sovran_systemsos_web/static/js/service-detail.js b/app/sovran_systemsos_web/static/js/service-detail.js index 9ed9eef..9ab3592 100644 --- a/app/sovran_systemsos_web/static/js/service-detail.js +++ b/app/sovran_systemsos_web/static/js/service-detail.js @@ -280,6 +280,10 @@ async function openServiceDetailModal(unit, name, icon) { '' + '' + '' : "") + + (unit === "root-password-setup.service" ? + '
' + + '' + + '
' : "") + ''; } else if (!data.enabled && !data.feature) { html += '
' + @@ -366,6 +370,11 @@ async function openServiceDetailModal(unit, name, icon) { if (changePwBtn) changePwBtn.addEventListener("click", function() { openMatrixChangePasswordModal(unit, name, icon); }); } + if (unit === "root-password-setup.service") { + var sysPwBtn = document.getElementById("sys-change-pw-btn"); + if (sysPwBtn) sysPwBtn.addEventListener("click", function() { openSystemChangePasswordModal(unit, name, icon); }); + } + if (data.feature) { var addonBtn = document.getElementById("svc-detail-addon-btn"); if (addonBtn) { @@ -535,4 +544,63 @@ function openMatrixChangePasswordModal(unit, name, icon) { }); } +function openSystemChangePasswordModal(unit, name, icon) { + if (!$credsBody) return; + $credsBody.innerHTML = + '
' + + '
' + + '
' + + '
' + + '
' + + '' + + '' + + '
' + + '
'; + + document.getElementById("sys-chpw-back-btn").addEventListener("click", function() { + openServiceDetailModal(unit, name, icon); + }); + + document.getElementById("sys-chpw-submit-btn").addEventListener("click", async function() { + var submitBtn = document.getElementById("sys-chpw-submit-btn"); + var resultEl = document.getElementById("sys-chpw-result"); + var newPassword = document.getElementById("sys-chpw-new").value || ""; + var confirmPassword = document.getElementById("sys-chpw-confirm").value || ""; + + if (!newPassword || !confirmPassword) { + resultEl.className = "matrix-form-result error"; + resultEl.textContent = "Both password fields are required."; + return; + } + + submitBtn.disabled = true; + submitBtn.textContent = "Changing…"; + resultEl.className = "matrix-form-result"; + resultEl.textContent = ""; + + try { + await apiFetch("/api/change-password", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ new_password: newPassword, confirm_password: confirmPassword }) + }); + resultEl.className = "matrix-form-result success"; + resultEl.textContent = "✅ System password changed successfully."; + submitBtn.textContent = "Change Password"; + submitBtn.disabled = false; + // Hide the legacy security banner if it's visible + if (typeof _securityIsLegacy !== "undefined" && _securityIsLegacy) { + _securityIsLegacy = false; + var banner = document.querySelector(".security-inline-banner"); + if (banner) banner.remove(); + } + } catch (err) { + resultEl.className = "matrix-form-result error"; + resultEl.textContent = "❌ " + (err.message || "Failed to change password."); + submitBtn.textContent = "Change Password"; + submitBtn.disabled = false; + } + }); +} + function closeCredsModal() { if ($credsModal) $credsModal.classList.remove("open"); }