commit e2b82ba68384d84859f173eff5c46ffa66eadd1c Author: DeveloperDurp Date: Sat May 4 08:28:22 2024 -0500 initial commit diff --git a/auth.go b/auth.go new file mode 100644 index 0000000..744c1b4 --- /dev/null +++ b/auth.go @@ -0,0 +1,126 @@ +package middleware + +import ( + "context" + "encoding/json" + "net/http" + "os" + "strings" + "time" + + "github.com/MicahParks/keyfunc" + "github.com/golang-jwt/jwt/v4" + "gitlab.com/developerdurp/logger" +) + +type StandardMessage struct { + Message string `json:"message" example:"message"` +} + +func failedReponse(message string, w http.ResponseWriter) { + response := StandardMessage{ + Message: message, + } + + w.WriteHeader(http.StatusUnauthorized) + w.Header().Set("Content-Type", "application/json") + + err := json.NewEncoder(w).Encode(response) + if err != nil { + logger.LogError("Failed to Encode") + } +} + +func AuthMiddleware(next http.Handler, allowedGroups []string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var groups []string + JwksURL := os.Getenv("jwksurl") + tokenString := w.Header().Get("Authorization") + + if tokenString == "" { + failedReponse("No Token Detected", w) + } + + tokenString = strings.TrimPrefix(tokenString, "Bearer ") + + ctx, cancel := context.WithCancel(context.Background()) + + options := keyfunc.Options{ + Ctx: ctx, + RefreshErrorHandler: func(err error) { + logger.LogError("There was an error with the jwt.Keyfunc" + err.Error()) + }, + RefreshInterval: time.Hour, + RefreshRateLimit: time.Minute * 5, + RefreshTimeout: time.Second * 10, + RefreshUnknownKID: true, + } + + jwks, err := keyfunc.Get(JwksURL, options) + if err != nil { + failedReponse("Failed to create JWKS:"+err.Error(), w) + + cancel() + jwks.EndBackground() + return + } + + token, err := jwt.Parse(tokenString, jwks.Keyfunc) + if err != nil { + failedReponse(err.Error(), w) + + cancel() + jwks.EndBackground() + return + } + + if !token.Valid { + failedReponse("Token is invalid: "+err.Error(), w) + + cancel() + jwks.EndBackground() + return + } + + cancel() + jwks.EndBackground() + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + failedReponse("Invalid authorization token claims", w) + return + } + + groupsClaim, ok := claims["groups"].([]interface{}) + if !ok { + failedReponse("Missing or invalid groups claim in the authorization token", w) + return + } + + for _, group := range groupsClaim { + if groupName, ok := group.(string); ok { + groups = append(groups, groupName) + } + } + + isAllowed := false + for _, allowedGroup := range allowedGroups { + for _, group := range groups { + if group == allowedGroup { + isAllowed = true + break + } + } + if isAllowed { + break + } + } + + if !isAllowed { + failedReponse("Unaothorized to use this endpoint", w) + return + } + + next.ServeHTTP(w, r) + }) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..296ff8c --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module gitlab.com/developerdurp/middleware + +go 1.22.0 + +require ( + github.com/MicahParks/keyfunc v1.9.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + gitlab.com/developerdurp/logger v1.0.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..258c6d1 --- /dev/null +++ b/go.sum @@ -0,0 +1,7 @@ +github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= +github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +gitlab.com/developerdurp/logger v1.0.0 h1:wozbKR26RVoFVaUgJV2x8WsZHLWOJBqaSCSTpK7crfk= +gitlab.com/developerdurp/logger v1.0.0/go.mod h1:x6gZvBeEq8oQUXeQoLY2m78mYJjvb5KE+7Vb5AcS8oo= diff --git a/logging.go b/logging.go new file mode 100644 index 0000000..4441935 --- /dev/null +++ b/logging.go @@ -0,0 +1,32 @@ +package middleware + +import ( + "log" + "net/http" + "time" +) + +type wrappedWriter struct { + http.ResponseWriter + statusCode int +} + +func (w *wrappedWriter) WriteHeader(statusCode int) { + w.ResponseWriter.WriteHeader(statusCode) + w.statusCode = statusCode +} + +func Logging(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + wrapped := &wrappedWriter{ + ResponseWriter: w, + statusCode: http.StatusOK, + } + + next.ServeHTTP(wrapped, r) + + log.Println(wrapped.statusCode, r.Method, r.URL.Path, time.Since(start)) + }) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..387301b --- /dev/null +++ b/main.go @@ -0,0 +1,16 @@ +package middleware + +import "net/http" + +type Middleware func(http.Handler) http.Handler + +func CreateStack(xs ...Middleware) Middleware { + return func(next http.Handler) http.Handler { + for i := len(xs) - 1; i >= 0; i-- { + x := xs[i] + next = x(next) + } + + return next + } +}