Replace token-based admin auth with JWT session authentication
CI / test (push) Failing after 12m45s

- Add AdminUser model (bcrypt hashed passwords) and admin_users table
- Add AdminJWTService for HS256 JWT sessions (24h TTL)
- Add AdminSessionAuth middleware for /api/v1/admin/* routes
- Add admin handlers: login, logout, me, change-password, users CRUD
- Keys and model management routes now require admin JWT session
- Remove ADMIN_TOKEN, add ADMIN_USERNAME, ADMIN_PASSWORD env vars
- Update frontend: username/password login, admin_session storage, AdminUsers CRUD view
This commit is contained in:
2026-07-31 17:15:29 -04:00
parent b18d4bd146
commit 4d34c6d31a
21 changed files with 828 additions and 57 deletions
+49
View File
@@ -14,6 +14,7 @@ import (
const (
ApiKeyCtx = "api_key"
ApiKeyIDCtx = "api_key_id"
AdminCtx = "admin_user"
)
func APIKeyAuth(authService *auth.Service) gin.HandlerFunc {
@@ -142,6 +143,46 @@ func AdminTokenAuth(token string) gin.HandlerFunc {
}
}
func AdminSessionAuth(jwtService *auth.AdminJWTService) gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": gin.H{
"code": "unauthorized",
"message": "Authorization header required",
},
})
return
}
token := strings.TrimPrefix(authHeader, "Bearer ")
if token == authHeader || strings.HasPrefix(token, auth.TokenPrefix) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": gin.H{
"code": "unauthorized",
"message": "Admin session required",
},
})
return
}
claims, err := jwtService.VerifyToken(token)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": gin.H{
"code": "unauthorized",
"message": "Invalid or expired session",
},
})
return
}
c.Set(AdminCtx, claims)
c.Next()
}
}
func GetAPIKeyID(c *gin.Context) uuid.UUID {
id, _ := c.Get(ApiKeyIDCtx)
return id.(uuid.UUID)
@@ -154,3 +195,11 @@ func GetAPIKey(c *gin.Context) *db.ApiKey {
}
return key.(*db.ApiKey)
}
func GetAdminClaims(c *gin.Context) *auth.AdminClaims {
claims, _ := c.Get(AdminCtx)
if claims == nil {
return nil
}
return claims.(*auth.AdminClaims)
}