package middleware import ( "strings" "time" "github.com/MicahParks/keyfunc/v3" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/warmbly/warmbly/internal/errx" ) const ( googleIssuer = "https://accounts.google.com" ) type OidcHandler struct { ServiceAccount string KeySet keyfunc.Keyfunc AppEnv string } func (h *OidcHandler) Middleware() gin.HandlerFunc { return func(c *gin.Context) { if h.AppEnv == "dev" { c.Next() return } // No key set means the GCP Cloud Tasks OIDC path isn't configured (the // default local dispatcher calls handlers in-process). Fail closed // rather than deref a nil key set if a webhook request slips through. if h.KeySet == nil { errx.Handle(c, errx.ErrForbidden) return } auth := c.GetHeader("Authorization") if auth == "" || !strings.HasPrefix(auth, "Bearer ") { errx.Handle(c, errx.ErrForbidden) return } tokenStr := strings.TrimPrefix(auth, "Bearer ") token, err := jwt.Parse(tokenStr, h.KeySet.Keyfunc, jwt.WithLeeway(10*time.Second)) if err != nil { errx.Handle(c, errx.ErrForbidden) return } if !token.Valid { errx.Handle(c, errx.ErrForbidden) return } claims, ok := token.Claims.(jwt.MapClaims) if !ok { errx.Handle(c, errx.ErrForbidden) return } if iss, _ := claims.GetIssuer(); iss != googleIssuer { errx.Handle(c, errx.ErrForbidden) return } if sAccount, _ := claims.GetSubject(); sAccount != h.ServiceAccount { errx.Handle(c, errx.ErrForbidden) return } c.Next() } }