Files
query-orchestration/internal/cognitoauth/auth.go
T
jay brown 525d3e0b7b docs
2025-04-24 13:28:49 -07:00

169 lines
5.2 KiB
Go

package cognitoauth
import (
"context"
"fmt"
"net/http"
"os"
"queryorchestration/internal/serviceconfig/auth"
"strings"
"github.com/labstack/echo/v4"
"github.com/lestrrat-go/jwx/v2/jwt"
)
// RegisterRoutes registers all authentication-related routes to the Echo engine.
// It configures login and callback endpoints, and applies JWT authentication middleware.
// If the DISABLE_AUTH environment variable is set to "true", authentication is bypassed.
//
// Parameters:
// - e: The Echo instance to register routes on
// - config: Configuration provider for authentication settings
func RegisterRoutes(e *echo.Echo, config auth.ConfigProvider) {
// check the auth feature flag
fmt.Println("Registering routes for Auth.")
disableAuth := os.Getenv("DISABLE_AUTH")
if strings.ToLower(disableAuth) == "true" {
fmt.Println("Auth is disabled. Skipping auth routes.")
return // Do nothing if auth is not enabled
}
// Register the login route - the middleware will handle this
e.GET(config.GetAuthLoginPath(), func(c echo.Context) error {
// This is handled by the middleware
return nil
})
// Register callback route - the middleware will handle this
e.GET(config.GetAuthCallbackPath(), func(c echo.Context) error {
// This handler will only be called for requests that don't have a code parameter
// (those are handled directly in the middleware)
// Get user info from context
userClaims, _ := c.Get("user_claims").(map[string]interface{})
userGroups, _ := c.Get("user_groups").([]string)
return c.JSON(http.StatusOK, map[string]interface{}{
"authenticated": true,
"username": userClaims["cognito:username"],
"email": userClaims["email"],
"groups": userGroups,
})
})
// Apply the JWT Auth middleware first
e.Use(JWTAuthMiddleware(config))
// Then apply the token validation middleware
e.Use(TokenValidationMiddleware(config))
}
// GetTokenFromRequest extracts the JWT token from the HTTP request.
// It first attempts to retrieve the token from the Authorization header
// in Bearer token format. If not found, it then tries to retrieve the token
// from the "auth_token" cookie. Returns an empty string if no token is found.
//
// Parameters:
// - c: The Echo context containing the HTTP request
//
// Returns:
// - string: The extracted JWT token, or an empty string if not found
func GetTokenFromRequest(c echo.Context) string {
// First try from Authorization header
authHeader := c.Request().Header.Get("Authorization")
if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") {
return authHeader[7:]
}
// Then try from cookie
tokenCookie, err := c.Cookie("auth_token")
if err == nil && tokenCookie.Value != "" {
return tokenCookie.Value
}
return ""
}
// GetUserInfo retrieves user information from the Echo context.
// It first attempts to retrieve user info directly from the context.
// If not found there, it tries to extract and parse the JWT token,
// then extract user information from the token claims.
//
// Parameters:
// - c: The Echo context containing the HTTP request and context values
//
// Returns:
// - UserInfo: Structure containing username, email, and groups
// - bool: True if user information was successfully retrieved, false otherwise
func GetUserInfo(c echo.Context) (UserInfo, bool) {
// Try to get user info from context
userInfo, ok := c.Get("user_info").(UserInfo)
if ok {
return userInfo, true
}
// Try to get token and extract user info
token := GetTokenFromRequest(c)
if token == "" {
return UserInfo{}, false
}
// Parse token to extract claims
parsedToken, err := jwt.Parse([]byte(token), jwt.WithVerify(false))
if err != nil {
return UserInfo{}, false
}
claims, err := parsedToken.AsMap(context.Background())
if err != nil {
return UserInfo{}, false
}
// Extract user info
userInfo = ExtractUserInfo(claims)
return userInfo, true
}
// RequireGroups creates a middleware that enforces group-based authorization.
// It checks if the authenticated user belongs to at least one of the specified groups.
// If the user is not authenticated, it returns a 401 Unauthorized response.
// If the user is authenticated but doesn't belong to any of the required groups,
// it returns a 403 Forbidden response with detailed information.
//
// Parameters:
// - groups: Variable number of group names that grant access
//
// Returns:
// - echo.MiddlewareFunc: Middleware function for Echo framework
func RequireGroups(groups ...string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
userInfo, ok := GetUserInfo(c)
if !ok {
return c.JSON(http.StatusUnauthorized, map[string]string{
"error": "Authentication required",
})
}
// Check if user has any of the required groups
for _, requiredGroup := range groups {
for _, userGroup := range userInfo.Groups {
if userGroup == requiredGroup {
return next(c)
}
}
}
return c.JSON(http.StatusForbidden, map[string]interface{}{
"error": "Insufficient permissions",
"message": "User doesn't have the required group membership",
"username": userInfo.Username,
"groups": userInfo.Groups,
"required_groups": groups,
})
}
}
}