@@ -0,0 +1,81 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user