From e607e892671eabe5a59f32683066f16afb9100ed Mon Sep 17 00:00:00 2001 From: jay brown Date: Mon, 28 Apr 2025 12:35:18 -0700 Subject: [PATCH] in progress --- .../cognitoauth/cognitotest/auth_full_test.go | 392 ++++++++++++++++++ internal/cognitoauth/cognitotest/test.sh | 18 + 2 files changed, 410 insertions(+) create mode 100644 internal/cognitoauth/cognitotest/auth_full_test.go create mode 100755 internal/cognitoauth/cognitotest/test.sh diff --git a/internal/cognitoauth/cognitotest/auth_full_test.go b/internal/cognitoauth/cognitotest/auth_full_test.go new file mode 100644 index 00000000..b14eff6b --- /dev/null +++ b/internal/cognitoauth/cognitotest/auth_full_test.go @@ -0,0 +1,392 @@ +//go:build ignore && manual_auth_tests +// +build ignore,manual_auth_tests + +// This file is excluded from all builds. +// It contains integration tests that require external dependencies which +// shouldn't be included in the main project's go.mod. +// +// The "ignore" build tag ensures this file won't be considered by Go tools, +// including go mod tidy. + +package cognitotest + +// Standard imports that don't need special handling +import ( + "context" + "fmt" + "log" + "log/slog" + "net/http" + "os" + "strings" + "testing" + "time" + + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "github.com/stretchr/testify/assert" + + "queryorchestration/internal/cognitoauth" + "queryorchestration/internal/serviceconfig/auth" + + "github.com/chromedp/cdproto/network" + "github.com/chromedp/chromedp" +) + +// setupEnvironment ensures all required environment variables are set +func setupEnvironment(t *testing.T) { + // Define all required environment variables with test values + envVars := map[string]string{ + "USER": os.Getenv("USER"), + "PASSWORD": os.Getenv("PASSWORD"), + "COGNITO_DOMAIN": os.Getenv("COGNITO_DOMAIN"), + "AWS_REGION": os.Getenv("AWS_REGION"), + "COGNITO_CLIENT_SECRET": os.Getenv("COGNITO_CLIENT_SECRET"), + "COGNITO_USER_POOL_ID": os.Getenv("COGNITO_USER_POOL_ID"), + "COGNITO_CLIENT_ID": os.Getenv("COGNITO_CLIENT_ID"), + "DEBUG": "true", + "HEADLESS": os.Getenv("HEADLESS"), + } + + // Verify all required variables are set + for key, value := range envVars { + if value == "" { + // HEADLESS is optional with a default of "true" + if key == "HEADLESS" { + os.Setenv(key, "true") + continue + } + t.Fatalf("Required environment variable %s is not set", key) + } + // Set the environment variable for the test + os.Setenv(key, value) + } +} + +// setupTestServer creates and configures an Echo server for testing +func setupTestServer(t *testing.T) (*http.Server, *auth.CognitoConfig) { + // Create a new Echo instance + e := echo.New() + + // Add basic middleware + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + // Initialize logger + logHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelDebug, + }) + logger := slog.New(logHandler) + + // Create auth config with correct base URL that matches Cognito settings + baseURL := "http://localhost:8080" + config := auth.InitializeConfig(baseURL, logger) + + // Set route permissions based on test requirements + routePermissions := map[string][]string{ + "/query": {"exporters", "uploaders", "querybuilders"}, + "/norights": {"exporters"}, // Test user should not have this permission + } + config.SetAuthRoutePermissions(routePermissions) + + // Register Cognito auth routes and middleware + cognitoauth.RegisterRoutes(e, config) + + // Add test endpoints + e.GET("/home", func(c echo.Context) error { + // Check if user is authenticated + userInfo, authenticated := cognitoauth.GetUserInfo(c) + + response := map[string]interface{}{ + "authenticated": authenticated, + } + + if authenticated { + response["username"] = userInfo.Username + response["email"] = userInfo.Email + response["groups"] = userInfo.Groups + } + + return c.JSON(http.StatusOK, response) + }) + + e.GET("/query", func(c echo.Context) error { + return c.JSON(http.StatusOK, map[string]string{ + "status": "success", + "message": "You have access to the query endpoint", + }) + }) + + e.GET("/norights", func(c echo.Context) error { + return c.JSON(http.StatusOK, map[string]string{ + "status": "success", + "message": "You should not be able to access this endpoint", + }) + }) + + // Create server with fixed address to match Cognito config + server := &http.Server{ + Addr: ":8080", + Handler: e, + ReadHeaderTimeout: 30 * time.Second, // <-- Add this line + } + + // Log the redirect URL for debugging + logger.Info("Using redirect URI", "uri", config.GetAuthRedirectURI()) + + return server, config +} + +// TestCognitoAuthFlow runs the complete end-to-end test +func TestCognitoAuthFlow(t *testing.T) { + if os.Getenv("ENABLE_AUTH_TESTS") != "true" { + t.Skip("Skipping auth end to end tests") + } + + // Skip if running in CI environment or short test mode + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + // Setup test environment + setupEnvironment(t) + + // Get credentials from environment + username := os.Getenv("USER") + password := os.Getenv("PASSWORD") + + // Setup test server + server, config := setupTestServer(t) + + // Start the server in a goroutine + go func() { + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + t.Logf("Server error: %v", err) + } + }() + + // Ensure server shuts down at the end of the test + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := server.Shutdown(ctx); err != nil { + t.Logf("Server shutdown error: %v", err) + } + }() + + // Give the server time to start + time.Sleep(1 * time.Second) + + // Log important URLs for debugging + baseURL := "http://localhost:8080" + t.Logf("Server URL: %s", baseURL) + t.Logf("Login URL: %s%s", baseURL, config.GetAuthLoginPath()) + t.Logf("Callback URL: %s", config.GetAuthRedirectURI()) + + // Get headless mode from environment variable + headless := true + if os.Getenv("HEADLESS") == "false" { + headless = false + } + + // Setup Chrome options + opts := append(chromedp.DefaultExecAllocatorOptions[:], + // Headless mode controlled by HEADLESS environment variable + chromedp.Flag("headless", headless), + chromedp.Flag("disable-gpu", true), + chromedp.Flag("no-sandbox", true), + chromedp.Flag("disable-dev-shm-usage", true), + // Block Prometheus metrics requests that are interfering + chromedp.Flag("host-blocking-patterns", "*prometheus*"), + // Disable browser cache + chromedp.Flag("disable-cache", true), + ) + + allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) + defer cancel() + + // Create Chrome context with longer timeout for authentication flow + ctx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf)) + defer cancel() + ctx, cancel = context.WithTimeout(ctx, 5*time.Minute) // Extended timeout + defer cancel() + + // Enable request interception for debugging + if err := chromedp.Run(ctx, network.Enable()); err != nil { + t.Fatalf("Failed to enable network: %v", err) + } + + // Listen for events to track redirects + chromedp.ListenTarget(ctx, func(ev interface{}) { + switch e := ev.(type) { + case *network.EventRequestWillBeSent: + t.Logf("Request: %s %s", e.Request.Method, e.Request.URL) + case *network.EventResponseReceived: + t.Logf("Response: %s %s (status: %d)", e.Response.URL, e.Response.MimeType, e.Response.Status) + } + }) + + // Store cookies to verify JWT token later + var cookies []*network.Cookie + + // 1. Navigate to login page, which will redirect to Cognito + loginURL := fmt.Sprintf("%s%s", baseURL, config.GetAuthLoginPath()) + t.Run("Navigate to login page", func(t *testing.T) { + err := chromedp.Run(ctx, + chromedp.Navigate(loginURL), + chromedp.WaitVisible(`input[name="username"]`, chromedp.ByQuery), + ) + assert.NoError(t, err, "Failed to navigate to login page") + + // Log current URL to verify redirect + var currentURL string + err = chromedp.Run(ctx, chromedp.Location(¤tURL)) + assert.NoError(t, err) + t.Logf("Current URL after navigation: %s", currentURL) + }) + + // 2. Submit login credentials + t.Run("Submit login credentials", func(t *testing.T) { + // Wait for the username field and enter the username + err := chromedp.Run(ctx, + chromedp.WaitVisible(`input[name="username"]`, chromedp.ByQuery), + chromedp.Clear(`input[name="username"]`, chromedp.ByQuery), + chromedp.SendKeys(`input[name="username"]`, username, chromedp.ByQuery), + // Click the "Continue" button for username step + chromedp.Click(`button[type="submit"], button.amplify-button--primary, input[type="Submit"]`, chromedp.ByQuery), + ) + assert.NoError(t, err, "Failed to submit username") + + // Wait for password field to appear and enter password + err = chromedp.Run(ctx, + chromedp.WaitVisible(`input[name="password"]`, chromedp.ByQuery), + chromedp.Clear(`input[name="password"]`, chromedp.ByQuery), + chromedp.SendKeys(`input[name="password"]`, password, chromedp.ByQuery), + // Click the sign-in button for password step + chromedp.Sleep(1*time.Second), // Give a moment for button to be fully active + chromedp.Click(`button[type="submit"], button.amplify-button--primary, input[type="Submit"]`, chromedp.ByQuery), + ) + assert.NoError(t, err, "Failed to submit password") + + // Log that we submitted credentials + t.Logf("Submitted login credentials for user: %s", username) + }) + + // 3. Wait for redirection to home page after successful login + var homePageContent string + t.Run("Verify redirection to home page", func(t *testing.T) { + // Wait for the redirect to complete - we need to handle multiple redirects: + // 1. From Cognito to our callback endpoint with the authorization code + // 2. From callback to home page + err := chromedp.Run(ctx, chromedp.ActionFunc(func(ctx context.Context) error { + // Wait for up to 45 seconds + startTime := time.Now() + timeout := 45 * time.Second + + for { + if time.Since(startTime) > timeout { + return fmt.Errorf("timed out waiting for redirect") + } + + // Check current URL + var currentURL string + if err := chromedp.Location(¤tURL).Do(ctx); err != nil { + return err + } + + t.Logf("Current URL during redirect wait: %s", currentURL) + + // If we're at home page, we're done + if strings.Contains(currentURL, "/home") { + return nil + } + + // If we're at the callback page, wait a bit for the next redirect + if strings.Contains(currentURL, "/login-callback") { + t.Log("At callback page, waiting for processing...") + time.Sleep(2 * time.Second) + continue + } + + // Still waiting for redirect + time.Sleep(1 * time.Second) + } + })) + assert.NoError(t, err, "Failed to wait for redirect after login") + + // Get the final page content + err = chromedp.Run(ctx, + chromedp.Sleep(2*time.Second), // Give time for page to fully load + chromedp.OuterHTML("body", &homePageContent), + chromedp.ActionFunc(func(ctx context.Context) error { + cookies, err = network.GetCookies().Do(ctx) + return err + }), + ) + assert.NoError(t, err, "Failed to get home page content") + + // Log cookie names for debugging + cookieNames := make([]string, 0, len(cookies)) + for _, cookie := range cookies { + cookieNames = append(cookieNames, cookie.Name) + } + t.Logf("Found cookies: %v", cookieNames) + + // Verify we have the home page content with authentication info + assert.Contains(t, homePageContent, "authenticated", "Home page should show authenticated status") + }) + + // 4. Verify JWT token in cookies + var authToken string + t.Run("Verify JWT token cookie", func(t *testing.T) { + for _, cookie := range cookies { + if cookie.Name == "auth_token" { + authToken = cookie.Value + break + } + } + assert.NotEmpty(t, authToken, "JWT token cookie not found") + t.Logf("Found auth_token cookie") + }) + + // 5. Try accessing protected endpoints + t.Run("Access endpoints with correct token", func(t *testing.T) { + // First test /query endpoint (should succeed) + queryURL := fmt.Sprintf("%s/query", baseURL) + var queryResponseBody string + + err := chromedp.Run(ctx, + chromedp.Navigate(queryURL), + chromedp.Sleep(3*time.Second), // Give time for page to load + chromedp.OuterHTML("body", &queryResponseBody), + ) + assert.NoError(t, err, "Failed to access authorized endpoint") + t.Logf("Query endpoint response: %s", queryResponseBody) + assert.Contains(t, queryResponseBody, "success", "Should have access to /query endpoint") + + // Then test /norights endpoint (should fail) + noRightsURL := fmt.Sprintf("%s/norights", baseURL) + var noRightsResponseBody string + + err = chromedp.Run(ctx, + chromedp.Navigate(noRightsURL), + chromedp.Sleep(3*time.Second), // Give time for page to load + chromedp.OuterHTML("body", &noRightsResponseBody), + ) + assert.NoError(t, err, "Failed to navigate to unauthorized endpoint") + t.Logf("NoRights endpoint response: %s", noRightsResponseBody) + assert.Contains(t, noRightsResponseBody, "Insufficient permissions", + "Should NOT have access to /norights endpoint") + + // Test successful + t.Log("✅ All tests passed! Authentication flow verified successfully.") + }) +} + +// Helper function to decode JSON response bodies for verification +//func decodeJSONResponse(responseBody string) (map[string]interface{}, error) { +// var response map[string]interface{} +// decoder := json.NewDecoder(strings.NewReader(responseBody)) +// err := decoder.Decode(&response) +// return response, err +//} diff --git a/internal/cognitoauth/cognitotest/test.sh b/internal/cognitoauth/cognitotest/test.sh new file mode 100755 index 00000000..385d1f74 --- /dev/null +++ b/internal/cognitoauth/cognitotest/test.sh @@ -0,0 +1,18 @@ +export USER=betot75403@isorax.com +export PASSWORD=qqQQ11!! +export COGNITO_DOMAIN=https://us-east-21y6po8rr8.auth.us-east-2.amazoncognito.com +export AWS_REGION=us-east-2 +export COGNITO_CLIENT_SECRET=aaknqeq9ajr07qjch4tkq38ulghvn2i8v7tn3d2fcv44uevfemf +export COGNITO_USER_POOL_ID=us-east-2_1y6po8rR8 +export COGNITO_CLIENT_ID=552cqkf3640t39ncehkmgpce31 +export DEBUG=true +export HEADLESS=false +export ENABLE_AUTH_TESTS=true + +# run test without flags +#go test -v -timeout 3m + +# Run the test with the manual flag +go test -tags manual_auth_tests -v -timeout 3m + +