feat: add password change and session invalidation

This commit is contained in:
OpenClaw Agent
2026-09-17 02:33:51 +08:00
parent dea1473424
commit a60801173f
3 changed files with 43 additions and 0 deletions
+37
View File
@@ -12,6 +12,7 @@ import (
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
const sessionCookie = "am_session"
@@ -144,6 +145,8 @@ func (a *App) login(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "session generation failed"})
return
}
// Keep the session table bounded without requiring a background scheduler.
a.db.Where("expires_at <= ?", time.Now()).Delete(&Session{})
session := Session{UserID: user.ID, TokenHash: hashToken(token), ExpiresAt: time.Now().Add(7 * 24 * time.Hour)}
if err := a.db.Create(&session).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "session creation failed"})
@@ -162,6 +165,40 @@ func (a *App) logout(c *gin.Context) {
c.Status(http.StatusNoContent)
}
func (a *App) changePassword(c *gin.Context) {
user := c.MustGet("user").(*User)
var in struct {
CurrentPassword string `json:"currentPassword"`
NewPassword string `json:"newPassword"`
}
if c.BindJSON(&in) != nil || in.CurrentPassword == "" || len(in.NewPassword) < 8 {
c.JSON(http.StatusBadRequest, gin.H{"error": "currentPassword and newPassword(8+) required"})
return
}
var stored User
if err := a.db.First(&stored, user.ID).Error; err != nil || checkPassword(stored.PasswordHash, in.CurrentPassword) != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "current password is incorrect"})
return
}
hash, err := hashPassword(in.NewPassword)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "password hashing failed"})
return
}
if err := a.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&User{}).Where("id = ?", user.ID).Update("password_hash", hash).Error; err != nil {
return err
}
return tx.Where("user_id = ?", user.ID).Delete(&Session{}).Error
}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "password change failed"})
return
}
http.SetCookie(c.Writer, &http.Cookie{Name: sessionCookie, Value: "", Path: "/", MaxAge: -1, HttpOnly: true, SameSite: http.SameSiteLaxMode})
a.audit(user.ID, "auth.password.change", "user", fmt.Sprint(user.ID), "password changed; sessions revoked")
c.Status(http.StatusNoContent)
}
func (a *App) me(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"user": a.currentUser(c)})
}