feat: the password is turned off by hand in the account, with a passkey in place; password change; tabs sized by their titles
This commit is contained in:
1 parent
67222e2e05
commit
6dd1120a41
9 files changed
+212
-25
No files matched your search
@@ -26,8 +26,8 @@ import (
|
||||
)
|
||||
|
||||
// Passkeys (WebAuthn): an admin signs in with a key held by the device - a
|
||||
// fingerprint, the phone's lock, a hardware key - instead of a password. Once
|
||||
// an admin has a key, the password no longer signs them in.
|
||||
// fingerprint, the phone's lock, a hardware key - instead of a password. With
|
||||
// a key in place, the admin can turn the password off in their account.
|
||||
//
|
||||
// Attestation is not checked ("none"): the panel trusts any authenticator,
|
||||
// it only needs the public key and, at sign-in, a signature made with it.
|
||||
@@ -353,7 +353,73 @@ func (s *Server) handleAccount(w http.ResponseWriter, r *http.Request) {
|
||||
for _, k := range keys {
|
||||
list = append(list, map[string]any{"id": k.ID, "name": k.Name, "created": k.CreatedAt, "last_used": k.LastUsed})
|
||||
}
|
||||
writeJSON(w, map[string]any{"username": a.Username, "keys": list})
|
||||
writeJSON(w, map[string]any{"username": a.Username, "keys": list, "password_off": s.db.PasswordOff(a.ID)})
|
||||
}
|
||||
|
||||
// POST /api/panel/account/password/change - a new password, given the
|
||||
// current one.
|
||||
func (s *Server) handlePasswordChange(w http.ResponseWriter, r *http.Request) {
|
||||
a := s.currentAdmin(r)
|
||||
var in struct {
|
||||
Current string `json:"current"`
|
||||
New string `json:"new"`
|
||||
}
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4<<10)).Decode(&in); err != nil {
|
||||
writeJSON(w, map[string]any{"ok": false, "error": "плохой запрос"})
|
||||
return
|
||||
}
|
||||
ip := clientIP(r)
|
||||
if s.limiter.blocked(ip) {
|
||||
writeJSON(w, map[string]any{"ok": false, "error": "Слишком много попыток. Подождите немного."})
|
||||
return
|
||||
}
|
||||
if !checkPassword(a.PassHash, in.Current) {
|
||||
s.limiter.fin(ip, false)
|
||||
writeJSON(w, map[string]any{"ok": false, "error": "текущий пароль не тот"})
|
||||
return
|
||||
}
|
||||
if len(in.New) < 8 {
|
||||
writeJSON(w, map[string]any{"ok": false, "error": "новый пароль - не короче 8 символов"})
|
||||
return
|
||||
}
|
||||
hash, err := hashPassword(in.New)
|
||||
if err == nil {
|
||||
err = s.db.SetAdminPassword(a.ID, hash)
|
||||
}
|
||||
if err != nil {
|
||||
writeJSON(w, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
s.limiter.fin(ip, true)
|
||||
s.db.Audit(a.ID, "", "password_change")
|
||||
writeJSON(w, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
// POST /api/panel/account/password {"off": bool} - turn signing in with the
|
||||
// password off or on again. Off only with a passkey to sign in with.
|
||||
func (s *Server) handlePasswordToggle(w http.ResponseWriter, r *http.Request) {
|
||||
a := s.currentAdmin(r)
|
||||
var in struct {
|
||||
Off bool `json:"off"`
|
||||
}
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<10)).Decode(&in); err != nil {
|
||||
writeJSON(w, map[string]any{"ok": false, "error": "плохой запрос"})
|
||||
return
|
||||
}
|
||||
if in.Off && s.db.CountAdminKeys(a.ID) == 0 {
|
||||
writeJSON(w, map[string]any{"ok": false, "error": "сначала добавьте ключ доступа"})
|
||||
return
|
||||
}
|
||||
if err := s.db.SetPasswordOff(a.ID, in.Off); err != nil {
|
||||
writeJSON(w, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
action := "password_on"
|
||||
if in.Off {
|
||||
action = "password_off"
|
||||
}
|
||||
s.db.Audit(a.ID, "", action)
|
||||
writeJSON(w, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
// POST /api/panel/account/keys/begin - options to make a new passkey with.
|
||||
|
||||
Reference in new issue
Block a user