feat: first-run auth setup in wizard, auto-hash passwords on save
This commit is contained in:
parent
eaa612278d
commit
7064829921
2 changed files with 31 additions and 0 deletions
12
api.go
12
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
|
*s.appConfig = newCfg
|
||||||
|
|
||||||
if err := SaveConfig(*s.appConfig, "/opt/nextnvr/config.yaml"); err != nil {
|
if err := SaveConfig(*s.appConfig, "/opt/nextnvr/config.yaml"); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -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">
|
<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>
|
</label>
|
||||||
</div>
|
</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()">
|
<button class="btn-primary" style="margin-top:20px;font-size:16px;padding:10px 32px" onclick="runWizard()">
|
||||||
🔍 Scan Network
|
🔍 Scan Network
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -346,11 +354,22 @@ document.getElementById('btn-save').addEventListener('click', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
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.
|
// Fetch current config, then post updated version.
|
||||||
const cr = await fetch(API + '/config');
|
const cr = await fetch(API + '/config');
|
||||||
const cj = await cr.json();
|
const cj = await cr.json();
|
||||||
const cfg = cj.data || {};
|
const cfg = cj.data || {};
|
||||||
cfg.cameras = updatedCameras;
|
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', {
|
const r = await fetch(API + '/config', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue