feat: first-run auth setup in wizard, auto-hash passwords on save

This commit is contained in:
Claus Lohmar 2026-08-06 13:03:56 +01:00
parent eaa612278d
commit 7064829921
2 changed files with 31 additions and 0 deletions

12
api.go
View file

@ -110,6 +110,18 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
}
}
// Hash new master password if provided (not masked, not empty, not already hashed).
if newCfg.Auth.Master.Password != "" && newCfg.Auth.Master.Password != "********" &&
!strings.HasPrefix(newCfg.Auth.Master.Password, "$2a$") {
if hash, err := HashPassword(newCfg.Auth.Master.Password); err == nil {
newCfg.Auth.Master.Password = hash
}
}
// Preserve master password if masked in UI.
if newCfg.Auth.Master.Password == "********" && s.appConfig.Auth.Master.Password != "" {
newCfg.Auth.Master.Password = s.appConfig.Auth.Master.Password
}
*s.appConfig = newCfg
if err := SaveConfig(*s.appConfig, "/opt/nextnvr/config.yaml"); err != nil {

View file

@ -245,6 +245,14 @@ async function renderCameraCards() {
<input type="password" id="wiz-pass" value="" style="width:120px;margin:0 4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
</label>
</div>
<div style="margin-top:24px;padding:16px;border:1px solid var(--border);border-radius:var(--radius);max-width:500px;margin-left:auto;margin-right:auto;text-align:center">
<p style="font-size:14px;font-weight:600;color:var(--text);margin-bottom:12px">🔐 Set Admin Password</p>
<div style="display:flex;gap:12px;justify-content:center;flex-wrap:wrap">
<input type="text" id="wiz-admin-user" value="admin" placeholder="Username" style="width:130px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;font-size:13px">
<input type="password" id="wiz-admin-pass" value="" placeholder="Password" style="width:130px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;font-size:13px">
</div>
<p style="font-size:11px;color:var(--text-muted);margin-top:8px">Leave blank to skip access will remain open.</p>
</div>
<button class="btn-primary" style="margin-top:20px;font-size:16px;padding:10px 32px" onclick="runWizard()">
🔍 Scan Network
</button>
@ -346,12 +354,23 @@ document.getElementById('btn-save').addEventListener('click', async () => {
});
try {
// Include auth settings if present (first-run wizard).
const adminUser = document.getElementById('wiz-admin-user')?.value;
const adminPass = document.getElementById('wiz-admin-pass')?.value;
// Fetch current config, then post updated version.
const cr = await fetch(API + '/config');
const cj = await cr.json();
const cfg = cj.data || {};
cfg.cameras = updatedCameras;
// Set auth if admin credentials were provided.
if (adminUser && adminPass) {
cfg.auth = cfg.auth || {};
cfg.auth.enabled = true;
cfg.auth.master = { username: adminUser, password: adminPass, enabled: true };
}
const r = await fetch(API + '/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },