diff --git a/api.go b/api.go index 8c204a5..36f3de0 100644 --- a/api.go +++ b/api.go @@ -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 { diff --git a/public/app.js b/public/app.js index acd2712..e1f9fb1 100644 --- a/public/app.js +++ b/public/app.js @@ -245,6 +245,14 @@ async function renderCameraCards() { +
+

🔐 Set Admin Password

+
+ + +
+

Leave blank to skip — access will remain open.

+
@@ -346,11 +354,22 @@ 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',