Add change password (Preferences > Change Password)
This commit is contained in:
@@ -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() {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user