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, }) } } }