Merge pull request #119 from naturallaw777/copilot/fix-change-passwords-button
[WIP] Fix non-functional change passwords button in Hub
This commit is contained in:
@@ -2952,6 +2952,69 @@ async def api_security_status():
|
|||||||
return {"status": status, "warning": warning}
|
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 user management ────────────────────────────────────────
|
||||||
|
|
||||||
MATRIX_USERS_FILE = "/var/lib/secrets/matrix-users"
|
MATRIX_USERS_FILE = "/var/lib/secrets/matrix-users"
|
||||||
|
|||||||
@@ -606,7 +606,7 @@ function renderAutolaunchToggle(enabled) {
|
|||||||
'<div class="security-inline-banner">' +
|
'<div class="security-inline-banner">' +
|
||||||
'<span class="security-inline-icon">⚠</span>' +
|
'<span class="security-inline-icon">⚠</span>' +
|
||||||
'<span class="security-inline-text">' + msg + '</span>' +
|
'<span class="security-inline-text">' + msg + '</span>' +
|
||||||
'<a class="security-inline-link" href="/onboarding?step=passwords">Change Passwords</a>' +
|
'<a class="security-inline-link" href="#" onclick="openServiceDetailModal(\'root-password-setup.service\', \'System Passwords\', \'passwords\'); return false;">Change Passwords</a>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -280,6 +280,10 @@ async function openServiceDetailModal(unit, name, icon) {
|
|||||||
'<button class="matrix-action-btn" id="matrix-add-user-btn">➕ Add New User</button>' +
|
'<button class="matrix-action-btn" id="matrix-add-user-btn">➕ Add New User</button>' +
|
||||||
'<button class="matrix-action-btn" id="matrix-change-pw-btn">🔑 Change Password</button>' +
|
'<button class="matrix-action-btn" id="matrix-change-pw-btn">🔑 Change Password</button>' +
|
||||||
'</div>' : "") +
|
'</div>' : "") +
|
||||||
|
(unit === "root-password-setup.service" ?
|
||||||
|
'<hr class="matrix-actions-divider"><div class="matrix-actions-row">' +
|
||||||
|
'<button class="matrix-action-btn" id="sys-change-pw-btn">🔑 Change Password</button>' +
|
||||||
|
'</div>' : "") +
|
||||||
'</div>';
|
'</div>';
|
||||||
} else if (!data.enabled && !data.feature) {
|
} else if (!data.enabled && !data.feature) {
|
||||||
html += '<div class="svc-detail-section">' +
|
html += '<div class="svc-detail-section">' +
|
||||||
@@ -366,6 +370,11 @@ async function openServiceDetailModal(unit, name, icon) {
|
|||||||
if (changePwBtn) changePwBtn.addEventListener("click", function() { openMatrixChangePasswordModal(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) {
|
if (data.feature) {
|
||||||
var addonBtn = document.getElementById("svc-detail-addon-btn");
|
var addonBtn = document.getElementById("svc-detail-addon-btn");
|
||||||
if (addonBtn) {
|
if (addonBtn) {
|
||||||
@@ -535,4 +544,63 @@ function openMatrixChangePasswordModal(unit, name, icon) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openSystemChangePasswordModal(unit, name, icon) {
|
||||||
|
if (!$credsBody) return;
|
||||||
|
$credsBody.innerHTML =
|
||||||
|
'<div class="matrix-form-group"><label class="matrix-form-label" for="sys-chpw-new">New Password</label>' +
|
||||||
|
'<input class="matrix-form-input" type="password" id="sys-chpw-new" placeholder="New strong password" autocomplete="new-password"></div>' +
|
||||||
|
'<div class="matrix-form-group"><label class="matrix-form-label" for="sys-chpw-confirm">Confirm Password</label>' +
|
||||||
|
'<input class="matrix-form-input" type="password" id="sys-chpw-confirm" placeholder="Confirm new password" autocomplete="new-password"></div>' +
|
||||||
|
'<div class="matrix-form-actions">' +
|
||||||
|
'<button class="matrix-form-back" id="sys-chpw-back-btn">← Back</button>' +
|
||||||
|
'<button class="matrix-form-submit" id="sys-chpw-submit-btn">Change Password</button>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="matrix-form-result" id="sys-chpw-result"></div>';
|
||||||
|
|
||||||
|
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"); }
|
function closeCredsModal() { if ($credsModal) $credsModal.classList.remove("open"); }
|
||||||
|
|||||||
Reference in New Issue
Block a user