package cognitoauth import ( "fmt" "log/slog" "net/http" "os" "strings" "time" ) // ValidatePermitIOConfiguration validates both PDP connectivity and API key at startup // This function should be called during application startup to ensure configuration is correct // before processing any real authorization requests. // // It performs two checks: // 1. PDP Health Check: Tests connectivity to the PDP URL using the /healthy endpoint // 2. API Key Validation: Makes a dummy permission check to verify the API key is valid // // If DISABLE_AUTH=true, validation is skipped and function returns success. // Returns an error if either check fails, which should cause the application to halt startup. func ValidatePermitIOConfiguration() error { // Skip validation if auth is disabled disableAuth := os.Getenv("DISABLE_AUTH") if strings.ToLower(disableAuth) == "true" { fmt.Println("✓ Permit.io validation skipped (DISABLE_AUTH=true)") return nil } pdpURL := os.Getenv("PERMIT_IO_PDP_URL") apiKey := os.Getenv("PERMIT_IO_API_KEY") // Apply default for PDP URL if not set if pdpURL == "" { pdpURL = "http://localhost:7766" } if apiKey == "" { return fmt.Errorf("PERMIT_IO_API_KEY environment variable must be set") } // 1. Test PDP connectivity with /healthy endpoint client := &http.Client{Timeout: 10 * time.Second} healthURL := strings.TrimSuffix(pdpURL, "/") + "/healthy" req, err := http.NewRequest("GET", healthURL, nil) if err != nil { return fmt.Errorf("failed to create health check request: %v", err) } resp, err := client.Do(req) if err != nil { return fmt.Errorf("Permit.io PDP connectivity failed with PDP_URL=%s: %v", pdpURL, err) } defer resp.Body.Close() if resp.StatusCode != 200 { return fmt.Errorf("Permit.io PDP health check failed with PDP_URL=%s: HTTP %d", pdpURL, resp.StatusCode) } // 2. Test API key validity with a dummy permission check logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelWarn})) permitClient := NewPermitIOClient(apiKey, pdpURL, logger) // Use dummy test data that will likely be denied but should work with valid API key testUser := "startup-validation-user-12345" testAction := "read" testResource := "startup-validation-resource" _, err = permitClient.CheckPermission(testUser, testAction, testResource) if err != nil { // Check for specific error patterns to distinguish error types errStr := err.Error() if strings.Contains(errStr, "UnexpectedError") && (strings.Contains(errStr, "connection refused") || strings.Contains(errStr, "no such host")) { return fmt.Errorf("Permit.io PDP connectivity failed with PDP_URL=%s: %v", pdpURL, err) } if strings.Contains(errStr, "Unauthorized") || strings.Contains(errStr, "Invalid PDP token") || strings.Contains(errStr, "api_error") { return fmt.Errorf("Permit.io API key validation failed with API_KEY=%s and PDP_URL=%s: %v", maskAPIKey(apiKey), pdpURL, err) } if strings.Contains(errStr, "UnprocessableEntityError") { return fmt.Errorf("Permit.io API configuration error with API_KEY=%s and PDP_URL=%s: %v", maskAPIKey(apiKey), pdpURL, err) } // Any other error is also a configuration problem return fmt.Errorf("Permit.io validation failed with API_KEY=%s and PDP_URL=%s: %v", maskAPIKey(apiKey), pdpURL, err) } // Success - both PDP connectivity and API key are valid fmt.Printf("✓ Permit.io configuration validated successfully (PDP_URL=%s, API_KEY=%s)\n", pdpURL, maskAPIKey(apiKey)) return nil } // maskAPIKey masks the API key for logging, showing only the first 8 characters func maskAPIKey(apiKey string) string { if len(apiKey) <= 8 { return "***masked***" } return apiKey[:8] + "..." }