82 lines
2.2 KiB
Go
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
|
|
}
|