feat: add multi-source breed encyclopedia and two-hour refresh sessions
ci-cd / verify (push) Successful in 1m59s
ci-cd / deploy (push) Skipped

This commit is contained in:
2026-10-05 13:38:24 +08:00
parent 607d709100
commit 9d7cf720a5
73 changed files with 25771 additions and 264 deletions
+30 -8
View File
@@ -37,11 +37,17 @@ func (a *API) Register(r *gin.Engine) {
api.POST("/auth/login", a.login)
api.POST("/auth/dev", a.devLogin)
api.POST("/admin/login", a.adminLogin)
api.POST("/auth/refresh", a.refreshToken("user"))
api.POST("/admin/refresh", a.refreshToken("admin"))
api.POST("/auth/logout", a.revokeRefresh("user"))
api.POST("/admin/logout", a.revokeRefresh("admin"))
user := api.Group("")
user.Use(a.requireRole("user"))
user.GET("/environment", a.environment)
user.GET("/me", a.me)
user.GET("/breeds", a.listBreeds)
user.GET("/breeds/:id", a.breedDetail)
user.GET("/pets", a.listPets)
user.POST("/pets", a.createPet)
user.GET("/pets/:id", a.getPet)
@@ -72,6 +78,10 @@ func (a *API) Register(r *gin.Engine) {
admin := api.Group("/admin")
admin.Use(a.requireRole("admin"))
admin.GET("/breeds", a.adminBreeds)
admin.POST("/breeds", a.createBreed)
admin.PUT("/breeds/:id", a.updateBreed)
admin.PUT("/breeds/:id/enabled", a.setBreedEnabled)
admin.GET("/stats", a.adminStats)
admin.GET("/users", a.adminUsers)
admin.GET("/users/:id/pets", a.adminUserPets)
@@ -86,18 +96,16 @@ func (a *API) Register(r *gin.Engine) {
}
type claims struct {
Role string `json:"role"`
Role string `json:"role"`
SessionID string `json:"sid,omitempty"`
jwt.RegisteredClaims
}
func (a *API) token(id, role string) (string, error) {
expire := a.cfg.JWT.ExpireHours
if expire <= 0 {
expire = 168
}
func (a *API) token(id, role string) (string, error) { return a.sessionToken(id, role, "") }
func (a *API) sessionToken(id, role, sessionID string) (string, error) {
now := time.Now()
t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims{Role: role, RegisteredClaims: jwt.RegisteredClaims{
Subject: id, IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(expire) * time.Hour)),
t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims{Role: role, SessionID: sessionID, RegisteredClaims: jwt.RegisteredClaims{
ID: uuid.NewString(), Subject: id, IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(2 * time.Hour)),
}})
return t.SignedString([]byte(a.cfg.JWT.Secret))
}
@@ -127,6 +135,20 @@ func (a *API) requireRole(role string) gin.HandlerFunc {
c.Abort()
return
}
// Reject credentials issued with the old seven-day lifetime.
if cl.IssuedAt == nil || cl.ExpiresAt == nil || cl.ExpiresAt.Sub(cl.IssuedAt.Time) > 2*time.Hour {
fail(c, http.StatusUnauthorized, "登录已过期")
c.Abort()
return
}
if cl.SessionID != "" {
var count int64
if !validID(cl.SessionID) || a.db.Model(&model.RefreshSession{}).Where("id = ? AND actor_id = ? AND role = ? AND revoked = false AND expires_at > ?", cl.SessionID, cl.Subject, role, time.Now()).Count(&count).Error != nil || count != 1 {
fail(c, http.StatusUnauthorized, "登录已过期")
c.Abort()
return
}
}
if role == "user" {
var user model.User
if err := a.db.Select("id", "disabled").First(&user, "id = ?", cl.Subject).Error; err != nil || user.Disabled {