Files
query-orchestration/internal/cognitoauth/cognitotest/auth_full_test.go
T
Michael McGuinness 7ce7c9df4d Merged in feature/ecr (pull request #161)
Feature/ecr

* nosave

* repo

* awscli

* unzip

* ignore

* moreram

* 14k

* ref

* deployment

* 12k

* uselocal

* go

* dockercomd

* reorder

* iamgename

* installs

* tart

* cli

* clideps

* y

* dockerce

* nodock

* multi

* rmecr

* dev
2025-06-03 13:52:10 +00:00

382 lines
12 KiB
Go

package cognitotest
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(t.Context(), 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(t.Context(), 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(&currentURL))
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(&currentURL).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
//}