7ce7c9df4d
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
382 lines
12 KiB
Go
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(¤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
|
|
//}
|