Add change password (Preferences > Change Password)

This commit is contained in:
2026-05-25 08:44:15 +02:00
parent ed4d0b261f
commit 55a9ae816f
3 changed files with 55 additions and 0 deletions
+26
View File
@@ -164,6 +164,16 @@
</select> </select>
</div> </div>
<h3>Change Password</h3>
<div class="panel-section">
<form @submit.prevent="changePassword" class="admin-form">
<input v-model="pwCurrent" type="password" placeholder="Current password" required />
<input v-model="pwNew" type="password" placeholder="New password" required />
<button type="submit">Change</button>
</form>
<p v-if="pwMsg" class="admin-msg">{{ pwMsg }}</p>
</div>
<h3>Two-Factor Authentication</h3> <h3>Two-Factor Authentication</h3>
<div class="panel-section" v-if="!totpEnabled"> <div class="panel-section" v-if="!totpEnabled">
<button @click="setupTOTP" class="action-btn">Enable 2FA</button> <button @click="setupTOTP" class="action-btn">Enable 2FA</button>
@@ -293,6 +303,11 @@ const totpUri = ref('')
const totpSecret = ref('') const totpSecret = ref('')
const totpCode = ref('') const totpCode = ref('')
// Password change
const pwCurrent = ref('')
const pwNew = ref('')
const pwMsg = ref('')
// Git remotes // Git remotes
const gitRemotes = ref([]) const gitRemotes = ref([])
const newRemote = ref({ name: '', url: '' }) const newRemote = ref({ name: '', url: '' })
@@ -527,6 +542,17 @@ function applyThemeFromPrefs() {
savePrefs() savePrefs()
} }
async function changePassword() {
try {
await api('/api/auth/change-password', { current_password: pwCurrent.value, new_password: pwNew.value })
pwMsg.value = 'Password changed successfully'
pwCurrent.value = ''
pwNew.value = ''
} catch (e) {
pwMsg.value = 'Failed — check current password'
}
}
// ─── TOTP ──────────────────────────────────────────────────────────────────── // ─── TOTP ────────────────────────────────────────────────────────────────────
async function setupTOTP() { async function setupTOTP() {
+28
View File
@@ -92,6 +92,34 @@ func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
writeJSON(w, 200, map[string]string{"status": "ok"}) writeJSON(w, 200, map[string]string{"status": "ok"})
} }
func (s *Server) handleChangePassword(w http.ResponseWriter, r *http.Request) {
var req struct {
CurrentPassword string `json:"current_password"`
NewPassword string `json:"new_password"`
}
if err := decodeBody(r, &req); err != nil || req.CurrentPassword == "" || req.NewPassword == "" {
writeJSON(w, 400, map[string]string{"error": "current_password and new_password required"})
return
}
userID := getUserID(r)
var hash string
s.db.QueryRow("SELECT password_hash FROM users WHERE id = ?", userID).Scan(&hash)
if !auth.CheckPassword(hash, req.CurrentPassword) {
writeJSON(w, 401, map[string]string{"error": "current password is incorrect"})
return
}
newHash, err := auth.HashPassword(req.NewPassword)
if err != nil {
writeJSON(w, 500, map[string]string{"error": "failed to hash password"})
return
}
s.db.Exec("UPDATE users SET password_hash = ?, updated_at = datetime('now') WHERE id = ?", newHash, userID)
writeJSON(w, 200, map[string]string{"status": "password changed"})
}
// ─── Users ─────────────────────────────────────────────────────────────────── // ─── Users ───────────────────────────────────────────────────────────────────
func (s *Server) handleCreateUser(w http.ResponseWriter, r *http.Request) { func (s *Server) handleCreateUser(w http.ResponseWriter, r *http.Request) {
+1
View File
@@ -27,6 +27,7 @@ func NewRouter(db *sql.DB, dataDir, secret string) http.Handler {
// Users (admin) // Users (admin)
mux.HandleFunc("POST /api/users/create", s.requireAdmin(s.handleCreateUser)) mux.HandleFunc("POST /api/users/create", s.requireAdmin(s.handleCreateUser))
mux.HandleFunc("POST /api/users/list", s.requireAdmin(s.handleListUsers)) mux.HandleFunc("POST /api/users/list", s.requireAdmin(s.handleListUsers))
mux.HandleFunc("POST /api/auth/change-password", s.requireAuth(s.handleChangePassword))
// Files // Files
mux.HandleFunc("POST /api/files/list", s.requireAuth(s.handleListFiles)) mux.HandleFunc("POST /api/files/list", s.requireAuth(s.handleListFiles))