Files
omnex/control-plane/api/internal/auth/middleware.go
T
Nuxgrid 6b4274d12b
docker-publish / publish (push) Failing after 14m14s
feat: add api
2026-07-27 16:06:19 +02:00

82 lines
2.2 KiB
Go

package auth
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/omnex/control-plane/api/internal/session"
)
const ctxPrincipalKey = "omnex.principal"
// Principal : identité authentifiée injectée dans le contexte de requête.
type Principal struct {
UserID string
Username string
Role Role
SessionID string
}
// tokenFromRequest lit le JWT : cookie httpOnly en priorité, sinon Bearer.
func tokenFromRequest(c *gin.Context) string {
if ck, err := c.Request.Cookie(session.CookieName); err == nil && ck.Value != "" {
return ck.Value
}
if t, ok := strings.CutPrefix(c.GetHeader("Authorization"), "Bearer "); ok {
return t
}
return ""
}
// RequireAuth valide le JWT (signature) PUIS l'existence de la session Redis.
// Un logout supprime la session : le JWT devient alors invalide avant expiration.
func RequireAuth(iss *Issuer, mgr session.Manager) gin.HandlerFunc {
return func(c *gin.Context) {
raw := tokenFromRequest(c)
if raw == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "authentification requise"})
return
}
claims, err := iss.Verify(raw)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "token invalide"})
return
}
_, found, err := mgr.Get(c.Request.Context(), claims.SessionID)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "erreur serveur"})
return
}
if !found {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "session expirée ou révoquée"})
return
}
c.Set(ctxPrincipalKey, Principal{UserID: claims.Subject, Username: claims.Username, Role: claims.Role, SessionID: claims.SessionID})
c.Next()
}
}
// RequireRole impose un rôle minimal (admin > rôle demandé).
func RequireRole(role Role) gin.HandlerFunc {
return func(c *gin.Context) {
p := PrincipalFrom(c)
if p == nil || (p.Role != role && p.Role != RoleAdmin) {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "accès refusé"})
return
}
c.Next()
}
}
// PrincipalFrom récupère l'identité injectée par RequireAuth.
func PrincipalFrom(c *gin.Context) *Principal {
v, ok := c.Get(ctxPrincipalKey)
if !ok {
return nil
}
p, _ := v.(Principal)
return &p
}