Add password change functionality
- Add POST /api/auth/password API endpoint
- Requires authentication and current password verification
- Invalidates all other sessions after password change
- Keeps current session active
- Add window.changePassword() console function
- Matches existing login flow pattern
- Usage: changePassword("current", "new")
- Add 'lookbook set-password' CLI command
- Interactive password reset (no current password required)
- Useful for recovery scenarios
- Invalidates all sessions
- Add session.QDeleteAllExcept() and session.QDeleteAll()
- Support for invalidating sessions after password change
This commit is contained in:
parent
5b472de209
commit
523831cb8d
6 changed files with 178 additions and 6 deletions
|
|
@ -25,6 +25,11 @@ type loginResponse struct {
|
|||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type changePasswordRequest struct {
|
||||
CurrentPassword string `json:"currentPassword"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
}
|
||||
|
||||
// HandleLogin handles POST /api/auth/login
|
||||
// If no password is set, it sets the password. Otherwise, it verifies the password.
|
||||
func HandleLogin(rc *RequestContext, w http.ResponseWriter, r *http.Request) error {
|
||||
|
|
@ -126,6 +131,55 @@ func HandleAuthStatus(rc *RequestContext, w http.ResponseWriter, r *http.Request
|
|||
})
|
||||
}
|
||||
|
||||
// HandleChangePassword handles POST /api/auth/password
|
||||
func HandleChangePassword(rc *RequestContext, w http.ResponseWriter, r *http.Request) error {
|
||||
if !rc.RequireAdmin(w) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var req changePasswordRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||
}
|
||||
|
||||
if req.CurrentPassword == "" || req.NewPassword == "" {
|
||||
return writeJSON(w, http.StatusBadRequest, map[string]string{"error": "both passwords required"})
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Get current password hash
|
||||
adm, err := admin.QGet(ctx, rc.DB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify current password
|
||||
if err := bcrypt.CompareHashAndPassword(adm.PasswordHash, []byte(req.CurrentPassword)); err != nil {
|
||||
return writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid current password"})
|
||||
}
|
||||
|
||||
// Hash new password
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update password
|
||||
if err := admin.QSetPassword(ctx, rc.DB, hash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Invalidate all other sessions (keep current session)
|
||||
cookie, _ := r.Cookie("session_id")
|
||||
if cookie != nil {
|
||||
session.QDeleteAllExcept(ctx, rc.DB, cookie.Value)
|
||||
}
|
||||
|
||||
return writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func generateSessionID() (string, error) {
|
||||
b := make([]byte, 32)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue