Implement security overhaul: remove seal/legacy system, add Security modal and random passwords

Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/6e7593c4-f741-4ddc-9bce-8c558a4af014

Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-09 01:58:42 +00:00
committed by GitHub
parent 477d265de8
commit 2fae4ccc79
13 changed files with 743 additions and 659 deletions

View File

@@ -1,25 +1,22 @@
/* Sovran_SystemsOS Hub — First-Boot Onboarding Wizard
Drives the 6-step post-install setup flow. */
Drives the 5-step post-install setup flow. */
"use strict";
// ── Constants ─────────────────────────────────────────────────────
const TOTAL_STEPS = 6;
const TOTAL_STEPS = 5;
// Steps to skip per role (steps 4 and 5 involve domain/port setup)
// Steps 2 (timezone/locale) and 3 (password) are NEVER skipped — all roles need them.
// Steps to skip per role (steps 3 and 4 involve domain/port setup)
// Step 2 (timezone/locale) is NEVER skipped — all roles need it.
const ROLE_SKIP_STEPS = {
"desktop": [4, 5],
"node": [4, 5],
"desktop": [3, 4],
"node": [3, 4],
};
// ── Role state (loaded at init) ───────────────────────────────────
var _onboardingRole = "server_plus_desktop";
// Password default state (loaded at step 2)
var _passwordIsDefault = true;
// Domains that may need configuration, with service unit mapping for enabled check
const DOMAIN_DEFS = [
{ name: "matrix", label: "Matrix (Synapse)", unit: "matrix-synapse.service", needsDdns: true },
@@ -96,8 +93,7 @@ function showStep(step) {
if (step === 2) loadStep2();
if (step === 3) loadStep3();
if (step === 4) loadStep4();
if (step === 5) loadStep5();
// Step 6 (Complete) is static — no lazy-load needed
// Step 5 (Complete) is static — no lazy-load needed
}
// Return the next step number, skipping over role-excluded steps
@@ -273,135 +269,12 @@ async function saveStep2() {
return true;
}
// ── Step 3: Create Your Password ─────────────────────────────────
// ── Step 3: Domain Configuration ─────────────────────────────────
async function loadStep3() {
var body = document.getElementById("step-3-body");
if (!body) return;
var nextBtn = document.getElementById("step-3-next");
try {
var result = await apiFetch("/api/security/password-is-default");
_passwordIsDefault = result.is_default !== false;
} catch (_) {
_passwordIsDefault = true;
}
if (_passwordIsDefault) {
// Factory-sealed scenario: password must be set before continuing
if (nextBtn) nextBtn.textContent = "Set Password & Continue \u2192";
body.innerHTML =
'<div class="onboarding-password-group">' +
'<label class="onboarding-domain-label" for="pw-new">New Password</label>' +
'<div class="onboarding-password-input-wrap">' +
'<input class="onboarding-password-input" type="password" id="pw-new" autocomplete="new-password" placeholder="At least 8 characters" />' +
'<button type="button" class="onboarding-password-toggle" data-target="pw-new" aria-label="Show password">👁</button>' +
'</div>' +
'<p class="onboarding-password-hint">Minimum 8 characters</p>' +
'</div>' +
'<div class="onboarding-password-group">' +
'<label class="onboarding-domain-label" for="pw-confirm">Confirm Password</label>' +
'<div class="onboarding-password-input-wrap">' +
'<input class="onboarding-password-input" type="password" id="pw-confirm" autocomplete="new-password" placeholder="Re-enter your password" />' +
'<button type="button" class="onboarding-password-toggle" data-target="pw-confirm" aria-label="Show password">👁</button>' +
'</div>' +
'</div>' +
'<div class="onboarding-password-warning">⚠️ Write this password down — it cannot be recovered.</div>';
// Wire show/hide toggles
body.querySelectorAll(".onboarding-password-toggle").forEach(function(btn) {
btn.addEventListener("click", function() {
var inp = document.getElementById(btn.dataset.target);
if (inp) inp.type = (inp.type === "password") ? "text" : "password";
});
});
} else {
// DIY install scenario: password already set by installer
if (nextBtn) nextBtn.textContent = "Continue \u2192";
body.innerHTML =
'<div class="onboarding-password-success">✅ Your password was already set during installation.</div>' +
'<details class="onboarding-password-optional">' +
'<summary>Change it anyway</summary>' +
'<div style="margin-top:14px;">' +
'<div class="onboarding-password-group">' +
'<label class="onboarding-domain-label" for="pw-new">New Password</label>' +
'<div class="onboarding-password-input-wrap">' +
'<input class="onboarding-password-input" type="password" id="pw-new" autocomplete="new-password" placeholder="At least 8 characters" />' +
'<button type="button" class="onboarding-password-toggle" data-target="pw-new" aria-label="Show password">👁</button>' +
'</div>' +
'<p class="onboarding-password-hint">Minimum 8 characters</p>' +
'</div>' +
'<div class="onboarding-password-group">' +
'<label class="onboarding-domain-label" for="pw-confirm">Confirm Password</label>' +
'<div class="onboarding-password-input-wrap">' +
'<input class="onboarding-password-input" type="password" id="pw-confirm" autocomplete="new-password" placeholder="Re-enter your password" />' +
'<button type="button" class="onboarding-password-toggle" data-target="pw-confirm" aria-label="Show password">👁</button>' +
'</div>' +
'</div>' +
'<div class="onboarding-password-warning">⚠️ Write this password down — it cannot be recovered.</div>' +
'</div>' +
'</details>';
// Wire show/hide toggles
body.querySelectorAll(".onboarding-password-toggle").forEach(function(btn) {
btn.addEventListener("click", function() {
var inp = document.getElementById(btn.dataset.target);
if (inp) inp.type = (inp.type === "password") ? "text" : "password";
});
});
}
}
async function saveStep3() {
var newPw = document.getElementById("pw-new");
var confirmPw = document.getElementById("pw-confirm");
// If no fields visible or both empty and password already set → skip
if (!newPw || !newPw.value.trim()) {
if (!_passwordIsDefault) return true; // already set, no change requested
setStatus("step-3-status", "⚠ Please enter a password.", "error");
return false;
}
var pw = newPw.value;
var cpw = confirmPw ? confirmPw.value : "";
if (pw.length < 8) {
setStatus("step-3-status", "⚠ Password must be at least 8 characters.", "error");
return false;
}
if (pw !== cpw) {
setStatus("step-3-status", "⚠ Passwords do not match.", "error");
return false;
}
setStatus("step-3-status", "Saving password…", "info");
try {
await apiFetch("/api/change-password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ new_password: pw, confirm_password: cpw }),
});
} catch (err) {
setStatus("step-3-status", "⚠ " + err.message, "error");
return false;
}
setStatus("step-3-status", "✓ Password saved", "ok");
_passwordIsDefault = false;
return true;
}
// ── Step 4: Domain Configuration ─────────────────────────────────
async function loadStep4() {
var body = document.getElementById("step-4-body");
if (!body) return;
try {
// Fetch services, domains, and network info in parallel
var results = await Promise.all([
@@ -542,8 +415,8 @@ async function loadStep4() {
});
}
async function saveStep4() {
setStatus("step-4-status", "Saving domains…", "info");
async function saveStep3() {
setStatus("step-3-status", "Saving domains…", "info");
var errors = [];
// Save each domain input
@@ -583,18 +456,18 @@ async function saveStep4() {
}
if (errors.length > 0) {
setStatus("step-4-status", "⚠ Some errors: " + errors.join("; "), "error");
setStatus("step-3-status", "⚠ Some errors: " + errors.join("; "), "error");
return false;
}
setStatus("step-4-status", "✓ Saved", "ok");
setStatus("step-3-status", "✓ Saved", "ok");
return true;
}
// ── Step 5: Port Forwarding ───────────────────────────────────────
// ── Step 4: Port Forwarding ───────────────────────────────────────
async function loadStep5() {
var body = document.getElementById("step-5-body");
async function loadStep4() {
var body = document.getElementById("step-4-body");
if (!body) return;
body.innerHTML = '<p class="onboarding-loading">Checking ports…</p>';
@@ -675,10 +548,10 @@ async function loadStep5() {
body.innerHTML = html;
}
// ── Step 6: Complete ──────────────────────────────────────────────
// ── Step 5: Complete ──────────────────────────────────────────────
async function completeOnboarding() {
var btn = document.getElementById("step-6-finish");
var btn = document.getElementById("step-5-finish");
if (btn) { btn.disabled = true; btn.textContent = "Finishing…"; }
try {
@@ -709,36 +582,24 @@ function wireNavButtons() {
if (ok) showStep(nextStep(2));
});
// Step 3 → 4 (save password first)
// Step 3 → 4 (save domains first)
var s3next = document.getElementById("step-3-next");
if (s3next) s3next.addEventListener("click", async function() {
s3next.disabled = true;
var origText = s3next.textContent;
s3next.textContent = "Saving…";
var ok = await saveStep3();
await saveStep3();
s3next.disabled = false;
s3next.textContent = origText;
if (ok) showStep(nextStep(3));
s3next.textContent = "Save & Continue →";
showStep(nextStep(3));
});
// Step 4 → 5 (save domains first)
// Step 4 → 5 (port forwarding — no save needed)
var s4next = document.getElementById("step-4-next");
if (s4next) s4next.addEventListener("click", async function() {
s4next.disabled = true;
s4next.textContent = "Saving…";
await saveStep4();
s4next.disabled = false;
s4next.textContent = "Save & Continue →";
showStep(nextStep(4));
});
if (s4next) s4next.addEventListener("click", function() { showStep(nextStep(4)); });
// Step 5 → 6 (port forwarding — no save needed)
var s5next = document.getElementById("step-5-next");
if (s5next) s5next.addEventListener("click", function() { showStep(nextStep(5)); });
// Step 6: finish
var s6finish = document.getElementById("step-6-finish");
if (s6finish) s6finish.addEventListener("click", completeOnboarding);
// Step 5: finish
var s5finish = document.getElementById("step-5-finish");
if (s5finish) s5finish.addEventListener("click", completeOnboarding);
// Back buttons
document.querySelectorAll(".onboarding-btn-back").forEach(function(btn) {