add logging
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
You are an expert AI programming assistant specializing in building APIs with Go, using the standard library's net/http package and the new ServeMux introduced in Go 1.22.
|
||||
|
||||
Always use the latest stable version of Go (1.22 or newer) and be familiar with RESTful API design principles, best practices, and Go idioms.
|
||||
|
||||
Follow the user's requirements carefully & to the letter.
|
||||
|
||||
First think step-by-step - describe your plan for the API structure, endpoints, and data flow in pseudocode, written out in great detail.
|
||||
|
||||
Confirm the plan, then write code!
|
||||
|
||||
Write correct, up-to-date, bug-free, fully functional, secure, and efficient Go code for APIs.
|
||||
|
||||
Use echo package for rest API development:
|
||||
Implement proper error handling, including custom error types when beneficial.
|
||||
Use appropriate status codes and format JSON responses correctly.
|
||||
Implement input validation for API endpoints.
|
||||
Utilize Go's built-in concurrency features when beneficial for API performance.
|
||||
Follow RESTful API design principles and best practices.
|
||||
Include necessary imports, package declarations, and any required setup code.
|
||||
Implement proper logging using the custom logger `func (b *LogConfig) GetLogger() *slog.Logger` in the internal/logger package.
|
||||
Use middleware for cross-cutting concerns (e.g., logging, authentication).
|
||||
Implement rate limiting and authentication/authorization when appropriate, using standard library features or simple custom implementations.
|
||||
Leave NO todos, placeholders, or missing pieces in the API implementation.
|
||||
Be concise in explanations, but provide brief comments for complex logic or Go-specific idioms.
|
||||
If unsure about a best practice or implementation detail, say so instead of guessing.
|
||||
Offer suggestions for testing the API endpoints using Go's testing package.
|
||||
Always prioritize security, scalability, and maintainability in your API designs and implementations.
|
||||
|
||||
Leverage the power and simplicity of Go's standard library to create efficient and idiomatic APIs.
|
||||
@@ -2,6 +2,8 @@ package rbac
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -17,6 +19,7 @@ type JWTConfig struct {
|
||||
RequiredGroups []string // Groups that are required for access (any one of these)
|
||||
ContextKey string // Key to store the claims in the context
|
||||
ErrorHandler func(echo.Context, error) error
|
||||
Logger *slog.Logger // Logger instance for middleware
|
||||
}
|
||||
|
||||
// DefaultJWTConfig is the default JWT auth middleware config
|
||||
@@ -45,36 +48,56 @@ func extractToken(config JWTConfig) func(echo.Context) (string, error) {
|
||||
}
|
||||
|
||||
// validateGroups checks if the user belongs to any of the required groups
|
||||
func validateGroups(claims jwt.MapClaims, requiredGroups []string) bool {
|
||||
func validateGroups(claims jwt.MapClaims, requiredGroups []string, logger *slog.Logger) bool {
|
||||
if len(requiredGroups) == 0 {
|
||||
logger.Debug("no required groups specified, access granted")
|
||||
return true
|
||||
}
|
||||
|
||||
groups, ok := claims["cognito:groups"].([]interface{})
|
||||
if !ok {
|
||||
logger.Warn("no groups found in claims", "user", claims["sub"])
|
||||
return false
|
||||
}
|
||||
|
||||
userGroups := make([]string, 0)
|
||||
for _, group := range groups {
|
||||
if groupStr, ok := group.(string); ok {
|
||||
userGroups = append(userGroups, groupStr)
|
||||
}
|
||||
}
|
||||
|
||||
for _, reqGroup := range requiredGroups {
|
||||
for _, group := range groups {
|
||||
if groupStr, ok := group.(string); ok && strings.EqualFold(groupStr, reqGroup) {
|
||||
logger.Debug("group validation successful",
|
||||
"user", claims["sub"],
|
||||
"required_group", reqGroup,
|
||||
"user_groups", userGroups)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.Warn("group validation failed",
|
||||
"user", claims["sub"],
|
||||
"required_groups", requiredGroups,
|
||||
"user_groups", userGroups)
|
||||
return false
|
||||
}
|
||||
|
||||
// parseJWT parses and validates the JWT token
|
||||
func parseJWT(tokenString string, keyProvider KeyProvider) (*jwt.Token, jwt.MapClaims, error) {
|
||||
func parseJWT(tokenString string, keyProvider KeyProvider, logger *slog.Logger) (*jwt.Token, jwt.MapClaims, error) {
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
logger.Error("unexpected signing method", "method", token.Method.Alg())
|
||||
return nil, errors.New("unexpected signing method")
|
||||
}
|
||||
|
||||
// Get key ID from token header
|
||||
kid, ok := token.Header["kid"].(string)
|
||||
if !ok {
|
||||
logger.Error("no key ID in token header")
|
||||
return nil, errors.New("no key ID (kid) in token header")
|
||||
}
|
||||
|
||||
@@ -83,14 +106,19 @@ func parseJWT(tokenString string, keyProvider KeyProvider) (*jwt.Token, jwt.MapC
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Error("failed to parse JWT", "error", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok || !token.Valid {
|
||||
logger.Error("invalid token claims")
|
||||
return nil, nil, errors.New("invalid token")
|
||||
}
|
||||
|
||||
logger.Debug("JWT parsed successfully",
|
||||
"user", claims["sub"],
|
||||
"email", claims["email"])
|
||||
return token, claims, nil
|
||||
}
|
||||
|
||||
@@ -112,6 +140,9 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
|
||||
if config.KeyProvider == nil {
|
||||
panic("JWT middleware requires a KeyProvider")
|
||||
}
|
||||
if config.Logger == nil {
|
||||
config.Logger = slog.Default()
|
||||
}
|
||||
|
||||
extractor := extractToken(config)
|
||||
|
||||
@@ -119,21 +150,39 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
|
||||
return func(c echo.Context) error {
|
||||
tokenString, err := extractor(c)
|
||||
if err != nil {
|
||||
config.Logger.Error("failed to extract token",
|
||||
"error", err,
|
||||
"path", c.Request().URL.Path,
|
||||
"method", c.Request().Method)
|
||||
return config.ErrorHandler(c, err)
|
||||
}
|
||||
|
||||
_, claims, err := parseJWT(tokenString, config.KeyProvider)
|
||||
_, claims, err := parseJWT(tokenString, config.KeyProvider, config.Logger)
|
||||
if err != nil {
|
||||
config.Logger.Error("failed to validate JWT",
|
||||
"error", err,
|
||||
"path", c.Request().URL.Path,
|
||||
"method", c.Request().Method)
|
||||
return config.ErrorHandler(c, err)
|
||||
}
|
||||
|
||||
// Check required groups if specified
|
||||
if !validateGroups(claims, config.RequiredGroups) {
|
||||
if !validateGroups(claims, config.RequiredGroups, config.Logger) {
|
||||
config.Logger.Warn("insufficient permissions",
|
||||
"user", claims["sub"],
|
||||
"path", c.Request().URL.Path,
|
||||
"method", c.Request().Method)
|
||||
return echo.NewHTTPError(http.StatusForbidden, "insufficient permissions")
|
||||
}
|
||||
|
||||
// Store user information in context
|
||||
c.Set(config.ContextKey, claims)
|
||||
|
||||
config.Logger.Info("authenticated request",
|
||||
"user", claims["sub"],
|
||||
"path", c.Request().URL.Path,
|
||||
"method", c.Request().Method)
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
@@ -151,14 +200,14 @@ func tokenFromHeader(header string, authScheme string) func(echo.Context) (strin
|
||||
return func(c echo.Context) (string, error) {
|
||||
auth := c.Request().Header.Get(header)
|
||||
if auth == "" {
|
||||
return "", errors.New("missing auth header")
|
||||
return "", fmt.Errorf("missing auth header: %s", header)
|
||||
}
|
||||
|
||||
l := len(authScheme)
|
||||
if len(auth) > l+1 && auth[:l] == authScheme {
|
||||
return auth[l+1:], nil
|
||||
}
|
||||
return "", errors.New("invalid auth header")
|
||||
return "", fmt.Errorf("invalid auth header format for scheme: %s", authScheme)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user