From cb9172d06902d0f4093bde8444f0f759cb537e9d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 03:24:31 +0000 Subject: [PATCH 1/4] Initial plan From 6ac9a7cd4ca24b2058c7f8f90ee9244cf5ac7527 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 03:30:26 +0000 Subject: [PATCH 2/4] fix migration-safe free password flow for desktop roles Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/59fc567c-4bd4-44ab-a2ff-8e74854030e5 Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com> --- app/sovran_systemsos_web/server.py | 39 ++++++ app/sovran_systemsos_web/static/onboarding.js | 77 ++++++++++- .../templates/onboarding.html | 31 ++++- modules/credentials.nix | 120 ++++++++++++++---- 4 files changed, 242 insertions(+), 25 deletions(-) diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index e3306c2..ea58c03 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -84,6 +84,7 @@ AUTOLAUNCH_DISABLE_FLAG = "/var/lib/sovran/hub-autolaunch-disabled" # ── Hub web authentication ──────────────────────────────────────── FREE_PASSWORD_FILE = "/var/lib/secrets/free-password" +MIGRATION_NEWPASS_FILE = "/var/lib/secrets/free-password-migration-newpass" HUB_SESSION_SECRET_FILE = "/var/lib/secrets/hub-session-secret" SESSION_COOKIE_NAME = "hub_session" SESSION_MAX_AGE = 86400 # 24 hours @@ -574,6 +575,18 @@ def _check_password(submitted: str) -> bool: return hmac.compare_digest(submitted.encode(), stored.encode()) +def _ensure_onboarding_reopened_for_migration() -> None: + """Re-open onboarding when a migration password disclosure is pending.""" + if not os.path.isfile(MIGRATION_NEWPASS_FILE): + return + try: + os.remove(ONBOARDING_FLAG) + except FileNotFoundError: + pass + except OSError as exc: + logger.warning("Could not clear onboarding flag for migration flow: %s", exc) + + def _record_failure(client_ip: str) -> None: """Record a failed login attempt and apply a rate-limit delay. @@ -1942,6 +1955,7 @@ async def index(request: Request): @app.get("/onboarding", response_class=HTMLResponse) async def onboarding(request: Request): + _ensure_onboarding_reopened_for_migration() return templates.TemplateResponse("onboarding.html", { "request": request, "onboarding_js_hash": _ONBOARDING_JS_HASH, @@ -1950,6 +1964,7 @@ async def onboarding(request: Request): @app.get("/api/onboarding/status") async def api_onboarding_status(): + _ensure_onboarding_reopened_for_migration() complete = os.path.exists(ONBOARDING_FLAG) return {"complete": complete} @@ -1986,6 +2001,30 @@ async def api_onboarding_complete(): return {"ok": True} +@app.get("/api/migration/password-status") +async def api_migration_password_status(): + """Return whether a migration-generated password is awaiting acknowledgement.""" + try: + with open(MIGRATION_NEWPASS_FILE, "r") as f: + return {"pending": True, "password": f.read().strip()} + except FileNotFoundError: + return {"pending": False} + except OSError as exc: + raise HTTPException(status_code=500, detail=f"Could not read migration password: {exc}") + + +@app.post("/api/migration/password-acknowledge") +async def api_migration_password_acknowledge(): + """Acknowledge and clear the migration password disclosure marker.""" + try: + os.remove(MIGRATION_NEWPASS_FILE) + except FileNotFoundError: + pass + except OSError as exc: + raise HTTPException(status_code=500, detail=f"Could not clear migration password: {exc}") + return {"ok": True} + + # ── Auto-launch endpoints ───────────────────────────────────────── @app.get("/api/autolaunch/status") diff --git a/app/sovran_systemsos_web/static/onboarding.js b/app/sovran_systemsos_web/static/onboarding.js index b6bd2f4..551c4ea 100644 --- a/app/sovran_systemsos_web/static/onboarding.js +++ b/app/sovran_systemsos_web/static/onboarding.js @@ -33,6 +33,8 @@ const DOMAIN_DEFS = [ var _currentStep = 1; var _servicesData = null; var _domainsData = null; +var _migrationPending = false; +var _migrationOccurred = false; // ── Helpers ─────────────────────────────────────────────────────── @@ -65,6 +67,48 @@ function setStatus(elId, msg, type) { el.className = "onboarding-save-status" + (type ? " onboarding-save-status--" + type : ""); } +function updateStep5Checklist() { + var checklist = document.getElementById("onboarding-checklist"); + if (!checklist) return; + var existing = document.getElementById("onboarding-migration-check"); + if (_migrationOccurred) { + if (!existing) { + var li = document.createElement("li"); + li.id = "onboarding-migration-check"; + li.textContent = "✅ Migration password noted"; + checklist.appendChild(li); + } + return; + } + if (existing) existing.remove(); +} + +function showMigrationStep(password) { + for (var i = 1; i <= TOTAL_STEPS; i++) { + var panel = document.getElementById("step-" + i); + if (panel) panel.style.display = "none"; + } + var migrationPanel = document.getElementById("step-migration"); + if (migrationPanel) migrationPanel.style.display = ""; + var pw = document.getElementById("migration-password-value"); + if (pw) pw.textContent = password || ""; + var progressBar = document.getElementById("onboarding-progress-bar"); + if (progressBar) progressBar.style.display = "none"; + var nav = document.getElementById("onboarding-steps-nav"); + if (nav) nav.style.display = "none"; +} + +function showStep1FromMigration() { + var migrationPanel = document.getElementById("step-migration"); + if (migrationPanel) migrationPanel.style.display = "none"; + var progressBar = document.getElementById("onboarding-progress-bar"); + if (progressBar) progressBar.style.display = ""; + var nav = document.getElementById("onboarding-steps-nav"); + if (nav) nav.style.display = ""; + showStep(1); + loadStep1(); +} + // ── Progress / step navigation ──────────────────────────────────── function updateProgress(step) { @@ -566,6 +610,24 @@ async function completeOnboarding() { // ── Event wiring ────────────────────────────────────────────────── function wireNavButtons() { + var migrationContinue = document.getElementById("migration-password-continue"); + if (migrationContinue) migrationContinue.addEventListener("click", async function() { + migrationContinue.disabled = true; + migrationContinue.textContent = "Continuing…"; + setStatus("migration-password-status", "Saving acknowledgement…", "info"); + try { + await apiFetch("/api/migration/password-acknowledge", { method: "POST" }); + _migrationPending = false; + _migrationOccurred = true; + updateStep5Checklist(); + showStep1FromMigration(); + } catch (err) { + setStatus("migration-password-status", "⚠ " + err.message, "error"); + migrationContinue.disabled = false; + migrationContinue.textContent = "I've written it down — Continue →"; + } + }); + // Step 1 → next var s1next = document.getElementById("step-1-next"); if (s1next) s1next.addEventListener("click", function() { showStep(nextStep(1)); }); @@ -627,6 +689,19 @@ document.addEventListener("DOMContentLoaded", async function() { } catch (_) {} wireNavButtons(); - updateProgress(1); + + try { + var migration = await apiFetch("/api/migration/password-status"); + if (migration && migration.pending) { + _migrationPending = true; + _migrationOccurred = true; + updateStep5Checklist(); + showMigrationStep(migration.password || ""); + return; + } + } catch (_) {} + + updateStep5Checklist(); + showStep(1); loadStep1(); }); diff --git a/app/sovran_systemsos_web/templates/onboarding.html b/app/sovran_systemsos_web/templates/onboarding.html index 39ac22f..685fee5 100644 --- a/app/sovran_systemsos_web/templates/onboarding.html +++ b/app/sovran_systemsos_web/templates/onboarding.html @@ -21,7 +21,7 @@