Cleanup: remove dead auth middleware and fix security log leak
CI / test (push) Failing after 12m28s
CI / test (push) Failing after 12m28s
- Remove dead AdminOnly() and AdminTokenAuth() middleware (unused since JWT switch) - Remove dead RequireScope() middleware (also unused) - Fix seed.go: only log password when auto-generated (was logging every time) - Fix seed.go: use admin.Username (normalized) instead of raw username in log - Simplify generateRandomPassword: 12 bytes (24 hex chars) instead of 24 bytes then truncate - Me handler: use claims.Subject (UUID) for lookup instead of username - Remove unused /logout endpoint (stateless JWT)
This commit is contained in:
@@ -66,6 +66,4 @@ func (h *LoginHandler) Login(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *LoginHandler) Logout(c *gin.Context) {
|
|
||||||
c.Status(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -29,7 +29,12 @@ func (h *MeHandler) Me(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var user db.AdminUser
|
var user db.AdminUser
|
||||||
if err := h.db.Where("username = ?", claims.Username).First(&user).Error; err != nil {
|
userID, err := uuid.Parse(claims.Subject)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": gin.H{"code": "unauthorized", "message": "Invalid token claims"}})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.db.Where("id = ?", userID).First(&user).Error; err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": gin.H{"code": "unauthorized", "message": "User not found"}})
|
c.JSON(http.StatusUnauthorized, gin.H{"error": gin.H{"code": "unauthorized", "message": "User not found"}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,84 +65,6 @@ func APIKeyAuth(authService *auth.Service) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RequireScope(authService *auth.Service, scope string) gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
apiKey, exists := c.Get(ApiKeyCtx)
|
|
||||||
if !exists {
|
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
||||||
"error": gin.H{
|
|
||||||
"code": "invalid_api_key",
|
|
||||||
"message": "Authentication required",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key := apiKey.(*db.ApiKey)
|
|
||||||
if !authService.HasScope(key, scope) {
|
|
||||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
|
||||||
"error": gin.H{
|
|
||||||
"code": "insufficient_scope",
|
|
||||||
"message": "API key lacks required scope: " + scope,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func AdminOnly() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
apiKey, exists := c.Get(ApiKeyCtx)
|
|
||||||
if !exists {
|
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
||||||
"error": gin.H{
|
|
||||||
"code": "unauthorized",
|
|
||||||
"message": "Admin access required",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key := apiKey.(*db.ApiKey)
|
|
||||||
if !key.IsAdmin {
|
|
||||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
|
||||||
"error": gin.H{
|
|
||||||
"code": "unauthorized",
|
|
||||||
"message": "Admin access required",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func AdminTokenAuth(token string) gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
adminToken := c.GetHeader("X-Admin-Token")
|
|
||||||
if adminToken == "" {
|
|
||||||
adminToken = c.GetHeader("Authorization")
|
|
||||||
adminToken = strings.TrimPrefix(adminToken, "Bearer ")
|
|
||||||
}
|
|
||||||
|
|
||||||
if adminToken != token {
|
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
||||||
"error": gin.H{
|
|
||||||
"code": "unauthorized",
|
|
||||||
"message": "Invalid admin token",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func AdminSessionAuth(jwtService *auth.AdminJWTService) gin.HandlerFunc {
|
func AdminSessionAuth(jwtService *auth.AdminJWTService) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
authHeader := c.GetHeader("Authorization")
|
authHeader := c.GetHeader("Authorization")
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ func New(cfg *config.Config, db *gorm.DB, llamaManager *llama.Manager) *gin.Engi
|
|||||||
// Admin API v1 (JWT session auth)
|
// Admin API v1 (JWT session auth)
|
||||||
adminV1 := v1.Group("/admin")
|
adminV1 := v1.Group("/admin")
|
||||||
adminV1.POST("/login", adminLoginHandler.Login)
|
adminV1.POST("/login", adminLoginHandler.Login)
|
||||||
adminV1.POST("/logout", adminLoginHandler.Logout)
|
|
||||||
adminSession := adminV1.Group("")
|
adminSession := adminV1.Group("")
|
||||||
adminSession.Use(middleware.AdminSessionAuth(adminJWTService))
|
adminSession.Use(middleware.AdminSessionAuth(adminJWTService))
|
||||||
adminSession.GET("/me", adminMeHandler.Me)
|
adminSession.GET("/me", adminMeHandler.Me)
|
||||||
|
|||||||
+7
-4
@@ -16,7 +16,7 @@ func generateRandomPassword(length int) (string, error) {
|
|||||||
if _, err := rand.Read(bytes); err != nil {
|
if _, err := rand.Read(bytes); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return hex.EncodeToString(bytes)[:length], nil
|
return hex.EncodeToString(bytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SeedAdminUser(db *gorm.DB, username, password string) error {
|
func SeedAdminUser(db *gorm.DB, username, password string) error {
|
||||||
@@ -28,13 +28,14 @@ func SeedAdminUser(db *gorm.DB, username, password string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pwd := password
|
pwd := password
|
||||||
|
autoGenerated := false
|
||||||
if pwd == "" {
|
if pwd == "" {
|
||||||
var err error
|
var err error
|
||||||
pwd, err = generateRandomPassword(24)
|
pwd, err = generateRandomPassword(12)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
slog.Warn("no ADMIN_PASSWORD set — generated random password (save this, it won't be shown again)")
|
autoGenerated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost)
|
hash, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost)
|
||||||
@@ -54,6 +55,8 @@ func SeedAdminUser(db *gorm.DB, username, password string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("admin user seeded", "username", admin.Username)
|
slog.Info("admin user seeded", "username", admin.Username)
|
||||||
slog.Warn("ADMIN USER: username=" + username + " password=" + pwd + " (save this, it won't be shown again)")
|
if autoGenerated {
|
||||||
|
slog.Warn("ADMIN USER: username=" + admin.Username + " password=" + pwd + " (save this, it won't be shown again)")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user