diff --git a/cmd/cognito_test/cognito.auth.harness/main.go b/cmd/cognito_test/cognito.auth.harness/main.go index 30e0ce9f..016f19d8 100644 --- a/cmd/cognito_test/cognito.auth.harness/main.go +++ b/cmd/cognito_test/cognito.auth.harness/main.go @@ -6,6 +6,8 @@ import ( "os" "time" + "queryorchestration/internal/serviceconfig/auth" + "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" @@ -34,7 +36,7 @@ func main() { logger := slog.New(logHandler) // Create auth config from environment variables - config := cognitoauth.NewConfigFromEnv("http://localhost:8080", logger) + config := auth.InitializeConfig("http://localhost:8080", logger) // Set route permissions routePermissions := map[string][]string{ diff --git a/internal/cognitoauth/auth.go b/internal/cognitoauth/auth.go index ef2969a2..f835da71 100644 --- a/internal/cognitoauth/auth.go +++ b/internal/cognitoauth/auth.go @@ -4,6 +4,9 @@ import ( "context" "fmt" "net/http" + + "queryorchestration/internal/serviceconfig/auth" + "strings" "github.com/labstack/echo/v4" @@ -11,15 +14,15 @@ import ( ) // RegisterRoutes registers all authentication-related routes to the Echo engine -func RegisterRoutes(e *echo.Echo, config *CognitoConfig) { +func RegisterRoutes(e *echo.Echo, config *auth.CognitoConfig) { // Register the login route - the middleware will handle this - e.GET(config.LoginPath, func(c echo.Context) error { + e.GET(config.AuthLoginPath, func(c echo.Context) error { // This is handled by the middleware return nil }) // Register callback route - the middleware will handle this - e.GET(config.CallbackPath, func(c echo.Context) error { + e.GET(config.AuthCallbackPath, 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) @@ -36,12 +39,12 @@ func RegisterRoutes(e *echo.Echo, config *CognitoConfig) { }) // Register logout route - e.GET(config.LogoutPath, func(c echo.Context) error { + e.GET(config.AuthLogoutPath, func(c echo.Context) error { return LogoutHandler(c, config) }) // Default home page handler if requested - e.GET(config.HomePath, func(c echo.Context) error { + e.GET(config.AuthHomePath, func(c echo.Context) error { return HomeHandler(c, config) }) @@ -53,20 +56,20 @@ func RegisterRoutes(e *echo.Echo, config *CognitoConfig) { } // HomeHandler implements a simple home page that shows auth status and available endpoints -func HomeHandler(c echo.Context, config *CognitoConfig) error { +func HomeHandler(c echo.Context, config *auth.CognitoConfig) error { // Create a list of all registered routes var endpoints []string // Add the auth routes endpoints = append(endpoints, []string{ - config.LoginPath, - config.CallbackPath, - config.HomePath, - config.LogoutPath, + config.AuthLoginPath, + config.AuthCallbackPath, + config.AuthHomePath, + config.AuthLogoutPath, }...) // Add routes from the permission map - for route := range config.RoutePermissions { + for route := range config.AuthRoutePermissions { // Skip routes that are already in the list alreadyAdded := false for _, endpoint := range endpoints { diff --git a/internal/cognitoauth/config.go b/internal/cognitoauth/config.go deleted file mode 100644 index 08053a32..00000000 --- a/internal/cognitoauth/config.go +++ /dev/null @@ -1,146 +0,0 @@ -package cognitoauth - -import ( - "fmt" - "log" - "log/slog" - "os" - "strings" -) - -// CognitoConfig holds the configuration for AWS Cognito -// Contains all necessary parameters to interact with Cognito endpoints -type CognitoConfig struct { - RedirectURI string // URL where Cognito redirects after authentication - TokenURL string // Cognito endpoint for token operations - JwksURL string // URL for JSON Web Key Set (for token verification) - AuthURL string // Cognito authorization endpoint for initiating login - LoginPath string // Path for initiating login - CallbackPath string // Path for OAuth callback - HomePath string // Path for home page - LogoutPath string // Path for logout - Logger *slog.Logger // Logger instance - RoutePermissions map[string][]string // Map of routes to required permissions - - // annotated vars - Region string `env:"COGNITO_REGION" envDefault:"us-east-2"` - UserPoolID string `env:"COGNITO_USER_POOL_ID" required:"true"` - ClientSecret string `env:"COGNITO_CLIENT_SECRET" required:"true"` - Domain string `env:"COGNITO_DOMAIN" required:"true"` - ClientID string `env:"COGNITO_CLIENT_ID" required:"true"` -} - -// NewConfigFromEnv creates a new CognitoConfig from environment variables -func NewConfigFromEnv(baseURL string, logger *slog.Logger) *CognitoConfig { - // Set default logger if not provided - if logger == nil { - var logLevel slog.Level - if os.Getenv("DEBUG") == "true" { - logLevel = slog.LevelDebug - } else { - logLevel = slog.LevelInfo - } - - logHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ - Level: logLevel, - }) - logger = slog.New(logHandler) - } - - // Set paths with defaults - loginPath := os.Getenv("COGNITO_LOGIN_PATH") - if loginPath == "" { - loginPath = "/login" - } - - callbackPath := os.Getenv("COGNITO_CALLBACK_PATH") - if callbackPath == "" { - callbackPath = "/login-callback" - } - - homePath := os.Getenv("COGNITO_HOME_PATH") - if homePath == "" { - homePath = "/home" - } - - logoutPath := os.Getenv("COGNITO_LOGOUT_PATH") - if logoutPath == "" { - logoutPath = "/logout" - } - - // Get AWS region with fallback - region := os.Getenv("AWS_REGION") - if region == "" { - region = "us-east-2" // Fallback to default value - } - - // Get User Pool ID with fallback - userPoolID := os.Getenv("COGNITO_USER_POOL_ID") - if userPoolID == "" { - userPoolID = "" // No default, will need to be provided - logger.Warn("COGNITO_USER_POOL_ID environment variable is not set") - } - - // Get Cognito domain - domain := os.Getenv("COGNITO_DOMAIN") - if domain == "" { - //logger.Warn("COGNITO_DOMAIN environment variable is not set, using default") - //// Use the user pool ID directly as part of the domain - //domain = fmt.Sprintf("%s.auth.%s.amazoncognito.com", userPoolID, region) - log.Fatalf("COGNITO_DOMAIN environment variable is not set") - } - - // Ensure domain format is correct (no protocol prefix) - domain = strings.TrimPrefix(domain, "https://") - domain = strings.TrimPrefix(domain, "http://") - - // Sanitize base URL (remove trailing slash) - baseURL = strings.TrimSuffix(baseURL, "/") - - config := &CognitoConfig{ - ClientID: os.Getenv("COGNITO_CLIENT_ID"), - ClientSecret: os.Getenv("COGNITO_CLIENT_SECRET"), - RedirectURI: baseURL + callbackPath, - TokenURL: fmt.Sprintf("https://%s/oauth2/token", domain), - AuthURL: fmt.Sprintf("https://%s/oauth2/authorize", domain), - JwksURL: fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", region, userPoolID), - UserPoolID: userPoolID, - Region: region, - LoginPath: loginPath, - CallbackPath: callbackPath, - HomePath: homePath, - LogoutPath: logoutPath, - Logger: logger, - RoutePermissions: make(map[string][]string), - } - - config.PrettyPrint() - return config -} - -func (c *CognitoConfig) PrettyPrint() { - fmt.Printf("CognitoConfig:\n") - fmt.Printf(" ClientID: %s\n", c.ClientID) - fmt.Printf(" ClientSecret: %s\n", c.ClientSecret) - fmt.Printf(" RedirectURI: %s\n", c.RedirectURI) - fmt.Printf(" TokenURL: %s\n", c.TokenURL) - fmt.Printf(" AuthURL: %s\n", c.AuthURL) - fmt.Printf(" JwksURL: %s\n", c.JwksURL) - fmt.Printf(" UserPoolID: %s\n", c.UserPoolID) - fmt.Printf(" Region: %s\n", c.Region) - fmt.Printf(" LoginPath: %s\n", c.LoginPath) - fmt.Printf(" CallbackPath: %s\n", c.CallbackPath) - fmt.Printf(" HomePath: %s\n", c.HomePath) - fmt.Printf(" LogoutPath: %s\n", c.LogoutPath) - - // pretty print the route permissions map - for route, permissions := range c.RoutePermissions { - fmt.Printf(" Route: %s, Permissions: %v\n", route, permissions) - } - -} - -// SetRoutePermissions sets the route permissions map -func (c *CognitoConfig) SetRoutePermissions(permissions map[string][]string) { - c.RoutePermissions = permissions -} diff --git a/internal/cognitoauth/handler.go b/internal/cognitoauth/handler.go index a1114855..b65ad63a 100644 --- a/internal/cognitoauth/handler.go +++ b/internal/cognitoauth/handler.go @@ -7,6 +7,9 @@ import ( "net/http" "net/url" "os" + + "queryorchestration/internal/serviceconfig/auth" + "strings" "time" @@ -15,18 +18,18 @@ import ( // initiateLoginWithPKCE generates PKCE code challenge and redirects to Cognito // This function starts the OAuth authorization flow with PKCE -func initiateLoginWithPKCE(c echo.Context, config *CognitoConfig) error { +func initiateLoginWithPKCE(c echo.Context, config *auth.CognitoConfig) error { // Generate random state parameter to prevent CSRF state, err := generateRandomString(32) if err != nil { - config.Logger.Error("Failed to generate state parameter", "error", err) + config.AuthLogger.Error("Failed to generate state parameter", "error", err) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Authentication initialization failed"}) } // Generate code verifier (random string between 43-128 chars) codeVerifier, err := generateRandomString(64) if err != nil { - config.Logger.Error("Failed to generate code verifier", "error", err) + config.AuthLogger.Error("Failed to generate code verifier", "error", err) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Authentication initialization failed"}) } @@ -34,13 +37,13 @@ func initiateLoginWithPKCE(c echo.Context, config *CognitoConfig) error { codeChallenge := createCodeChallenge(codeVerifier) // Store in session for later verification (with expiration time) - storePKCESession(state, codeVerifier, config.Logger) + storePKCESession(state, codeVerifier, config.AuthLogger) // Build authorization URL with PKCE parameters params := url.Values{} - params.Set("client_id", config.ClientID) + params.Set("client_id", config.AuthClientID) params.Set("response_type", "code") - params.Set("redirect_uri", config.RedirectURI) + params.Set("redirect_uri", config.AuthRedirectURI) params.Set("scope", "openid email profile") params.Set("state", state) params.Set("code_challenge", codeChallenge) @@ -49,11 +52,11 @@ func initiateLoginWithPKCE(c echo.Context, config *CognitoConfig) error { authURL := fmt.Sprintf("%s?%s", config.AuthURL, params.Encode()) if os.Getenv("DEBUG") == "true" { - config.Logger.Debug("Initiating login with PKCE", + config.AuthLogger.Debug("Initiating login with PKCE", "code_verifier", codeVerifier, "code_challenge", codeChallenge, "state", state, - "redirect_uri", config.RedirectURI) + "redirect_uri", config.AuthRedirectURI) } // Redirect user to Cognito login page @@ -63,20 +66,20 @@ func initiateLoginWithPKCE(c echo.Context, config *CognitoConfig) error { // exchangeCodeForTokensWithPKCE exchanges the authorization code for tokens using PKCE // Called during the OAuth callback flow to get tokens from the authorization code // Makes an HTTP request to Cognito's token endpoint to perform this exchange -func exchangeCodeForTokensWithPKCE(authCode, codeVerifier string, config CognitoConfig) (*TokenResponse, error) { +func exchangeCodeForTokensWithPKCE(authCode, codeVerifier string, config auth.CognitoConfig) (*TokenResponse, error) { data := url.Values{} data.Set("grant_type", "authorization_code") - data.Set("client_id", config.ClientID) + data.Set("client_id", config.AuthClientID) data.Set("code", authCode) - data.Set("redirect_uri", config.RedirectURI) + data.Set("redirect_uri", config.AuthRedirectURI) data.Set("code_verifier", codeVerifier) // Include code verifier for PKCE - config.Logger.Debug("Token request details", - "url", config.TokenURL, - "client_id", config.ClientID, - "redirect_uri", config.RedirectURI) + config.AuthLogger.Debug("Token request details", + "url", config.AuthTokenURL, + "client_id", config.AuthClientID, + "redirect_uri", config.AuthRedirectURI) - req, err := http.NewRequest("POST", config.TokenURL, strings.NewReader(data.Encode())) + req, err := http.NewRequest("POST", config.AuthTokenURL, strings.NewReader(data.Encode())) if err != nil { return nil, fmt.Errorf("failed to create token request: %w", err) } @@ -84,11 +87,11 @@ func exchangeCodeForTokensWithPKCE(authCode, codeVerifier string, config Cognito req.Header.Add("Content-Type", "application/x-www-form-urlencoded") // Add Authorization header if client secret is provided - if config.ClientSecret != "" { - req.SetBasicAuth(config.ClientID, config.ClientSecret) - config.Logger.Debug("Using Basic Auth authentication") + if config.AuthClientSecret != "" { + req.SetBasicAuth(config.AuthClientID, config.AuthClientSecret) + config.AuthLogger.Debug("Using Basic Auth authentication") } else { - config.Logger.Debug("Using public client authentication (no secret)") + config.AuthLogger.Debug("Using public client authentication (no secret)") } client := &http.Client{Timeout: 10 * time.Second} @@ -118,7 +121,7 @@ func exchangeCodeForTokensWithPKCE(authCode, codeVerifier string, config Cognito // handleOAuthCallback handles the OAuth 2.0 authorization code flow callback with PKCE // Called when Cognito redirects back to our application with an authorization code // Exchanges the code for tokens, verifies them, and redirects to home page with token in cookie -func handleOAuthCallback(c echo.Context, config *CognitoConfig) error { +func handleOAuthCallback(c echo.Context, config *auth.CognitoConfig) error { // Extract the authorization code and state code := c.QueryParam("code") if code == "" { @@ -141,52 +144,52 @@ func handleOAuthCallback(c echo.Context, config *CognitoConfig) error { } // Retrieve stored code verifier - codeVerifier, err := getCodeVerifier(state, config.Logger) + codeVerifier, err := getCodeVerifier(state, config.AuthLogger) if err != nil { - config.Logger.Error("Failed to retrieve code verifier", "error", err, "state", state) + config.AuthLogger.Error("Failed to retrieve code verifier", "error", err, "state", state) return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid or expired session"}) } // Exchange the code for tokens using PKCE tokens, err := exchangeCodeForTokensWithPKCE(code, codeVerifier, *config) if err != nil { - config.Logger.Error("Failed to exchange code for tokens", "error", err) + config.AuthLogger.Error("Failed to exchange code for tokens", "error", err) return c.JSON(http.StatusUnauthorized, map[string]string{"error": err.Error()}) } // Get the JWKS for token verification - keySet, err := GetJWKS(config.JwksURL, config.Logger) + keySet, err := GetJWKS(config.AuthJwksURL, config.AuthLogger) if err != nil { - config.Logger.Error("Failed to fetch JWKS", "error", err) + config.AuthLogger.Error("Failed to fetch JWKS", "error", err) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to verify token"}) } // Verify ID token idTokenClaims, err := verifyToken(tokens.IDToken, keySet, *config) if err != nil { - config.Logger.Error("Failed to verify ID token", "error", err) + config.AuthLogger.Error("Failed to verify ID token", "error", err) return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid token"}) } // Extract user groups userGroups, err := GetUserGroups(idTokenClaims) if err != nil { - config.Logger.Warn("Failed to extract user groups", "error", err) + config.AuthLogger.Warn("Failed to extract user groups", "error", err) userGroups = []string{} // Initialize as empty array } // Debug logging for tokens if os.Getenv("DEBUG") == "true" { // Log decoded token for debugging - config.Logger.Info("ID Token Claims", "claims", idTokenClaims) - config.Logger.Info("Raw ID Token", "token", tokens.IDToken) - config.Logger.Info("Raw Access Token", "token", tokens.AccessToken) + config.AuthLogger.Info("ID Token Claims", "claims", idTokenClaims) + config.AuthLogger.Info("Raw ID Token", "token", tokens.IDToken) + config.AuthLogger.Info("Raw Access Token", "token", tokens.AccessToken) } // Check authorization - authorized, requiredGroups := checkPermissions(config.CallbackPath, userGroups, config.RoutePermissions, config.Logger) + authorized, requiredGroups := checkPermissions(config.AuthCallbackPath, userGroups, config.AuthRoutePermissions, config.AuthLogger) if !authorized { - config.Logger.Warn("OAuth callback - access denied", + config.AuthLogger.Warn("OAuth callback - access denied", "user", idTokenClaims["cognito:username"], "groups", userGroups, "required", requiredGroups) @@ -221,14 +224,14 @@ func handleOAuthCallback(c echo.Context, config *CognitoConfig) error { // Extract username for display purposes (optional) username, _ := idTokenClaims["cognito:username"].(string) - config.Logger.Info("User authenticated successfully", "username", username, "groups", userGroups) + config.AuthLogger.Info("User authenticated successfully", "username", username, "groups", userGroups) // Redirect to home page - return c.Redirect(http.StatusFound, config.HomePath) + return c.Redirect(http.StatusFound, config.AuthHomePath) } // LogoutHandler handles the logout process by clearing the auth cookie -func LogoutHandler(c echo.Context, config *CognitoConfig) error { +func LogoutHandler(c echo.Context, config *auth.CognitoConfig) error { // Clear the token cookie cookie := new(http.Cookie) cookie.Name = "auth_token" @@ -239,5 +242,5 @@ func LogoutHandler(c echo.Context, config *CognitoConfig) error { c.SetCookie(cookie) // Redirect to home page - return c.Redirect(http.StatusFound, config.HomePath) + return c.Redirect(http.StatusFound, config.AuthHomePath) } diff --git a/internal/cognitoauth/middleware.go b/internal/cognitoauth/middleware.go index 555f4e41..1bbb1fc1 100644 --- a/internal/cognitoauth/middleware.go +++ b/internal/cognitoauth/middleware.go @@ -2,6 +2,9 @@ package cognitoauth import ( "net/http" + + "queryorchestration/internal/serviceconfig/auth" + "strings" "github.com/labstack/echo/v4" @@ -10,24 +13,24 @@ import ( // TokenValidationMiddleware verifies JWT tokens efficiently // This is the primary middleware that handles both authentication and authorization // Applied to all routes to enforce security requirements -func TokenValidationMiddleware(config *CognitoConfig) echo.MiddlewareFunc { +func TokenValidationMiddleware(config *auth.CognitoConfig) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { requestPath := c.Request().URL.Path - config.Logger.Debug("Processing request", "path", requestPath, "method", c.Request().Method) + config.AuthLogger.Debug("Processing request", "path", requestPath, "method", c.Request().Method) // Skip token validation for specified public paths - publicPaths := []string{config.HomePath, config.LogoutPath} + publicPaths := []string{config.AuthHomePath, config.AuthLogoutPath} for _, path := range publicPaths { if requestPath == path { - config.Logger.Debug("Skipping token validation for public path", "path", requestPath) + config.AuthLogger.Debug("Skipping token validation for public path", "path", requestPath) return next(c) } } // Special case for the OAuth callback path - if requestPath == config.CallbackPath && (c.QueryParam("code") != "" || c.QueryParam("error") != "") { - config.Logger.Debug("Handling OAuth callback", + if requestPath == config.AuthCallbackPath && (c.QueryParam("code") != "" || c.QueryParam("error") != "") { + config.AuthLogger.Debug("Handling OAuth callback", "has_code", c.QueryParam("code") != "", "has_error", c.QueryParam("error") != "") // Skip token validation for OAuth callback - it will be handled by the callback handler @@ -35,14 +38,14 @@ func TokenValidationMiddleware(config *CognitoConfig) echo.MiddlewareFunc { } // Initialize login flow if this is a login request - if requestPath == config.LoginPath { + if requestPath == config.AuthLoginPath { return initiateLoginWithPKCE(c, config) } // Get authorization header authHeader := c.Request().Header.Get("Authorization") if authHeader == "" { - config.Logger.Warn("No authorization header provided") + config.AuthLogger.Warn("No authorization header provided") return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Authorization required"}) } @@ -53,23 +56,23 @@ func TokenValidationMiddleware(config *CognitoConfig) echo.MiddlewareFunc { } // Get JWKS (cached if possible) - keySet, err := GetJWKS(config.JwksURL, config.Logger) + keySet, err := GetJWKS(config.AuthJwksURL, config.AuthLogger) if err != nil { - config.Logger.Error("Failed to get JWKS", "error", err) + config.AuthLogger.Error("Failed to get JWKS", "error", err) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to verify token"}) } // Verify token claims, err := verifyToken(tokenStr, keySet, *config) if err != nil { - config.Logger.Warn("Token verification failed", "error", err) + config.AuthLogger.Warn("Token verification failed", "error", err) return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid token"}) } // Extract user groups userGroups, err := GetUserGroups(claims) if err != nil { - config.Logger.Warn("Failed to extract user groups", "error", err) + config.AuthLogger.Warn("Failed to extract user groups", "error", err) userGroups = []string{} // Empty array if no groups found } @@ -82,9 +85,9 @@ func TokenValidationMiddleware(config *CognitoConfig) echo.MiddlewareFunc { c.Set("user_info", userInfo) // Check authorization - authorized, requiredGroups := checkPermissions(requestPath, userGroups, config.RoutePermissions, config.Logger) + authorized, requiredGroups := checkPermissions(requestPath, userGroups, config.AuthRoutePermissions, config.AuthLogger) if !authorized { - config.Logger.Warn("Access denied", + config.AuthLogger.Warn("Access denied", "path", requestPath, "user", claims["cognito:username"], "groups", userGroups, @@ -106,16 +109,16 @@ func TokenValidationMiddleware(config *CognitoConfig) echo.MiddlewareFunc { } // JWTAuthMiddleware checks for JWT token in cookies and adds it to the Authorization header -func JWTAuthMiddleware(config *CognitoConfig) echo.MiddlewareFunc { +func JWTAuthMiddleware(config *auth.CognitoConfig) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { // Skip for login and callback paths - if c.Path() == config.LoginPath || c.Path() == config.CallbackPath { + if c.Path() == config.AuthLoginPath || c.Path() == config.AuthCallbackPath { return next(c) } // Skip for home and logout as they should be accessible without auth - if c.Path() == config.HomePath || c.Path() == config.LogoutPath { + if c.Path() == config.AuthHomePath || c.Path() == config.AuthLogoutPath { return next(c) } @@ -130,7 +133,7 @@ func JWTAuthMiddleware(config *CognitoConfig) echo.MiddlewareFunc { tokenCookie, err := c.Cookie("auth_token") if err != nil || tokenCookie.Value == "" { // No token cookie found - return c.Redirect(http.StatusFound, config.LoginPath) + return c.Redirect(http.StatusFound, config.AuthLoginPath) } // Add token to Authorization header diff --git a/internal/cognitoauth/token.go b/internal/cognitoauth/token.go index 002767d1..c6cfc45b 100644 --- a/internal/cognitoauth/token.go +++ b/internal/cognitoauth/token.go @@ -10,6 +10,8 @@ import ( "log/slog" "time" + "queryorchestration/internal/serviceconfig/auth" + "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jwt" ) @@ -17,9 +19,9 @@ import ( // verifyToken verifies the JWT token and returns its claims // Used during both OAuth callback and subsequent API requests with bearer token // Verifies signature, expiration, issuer, and other JWT claims -func verifyToken(tokenString string, keySet jwk.Set, config CognitoConfig) (map[string]interface{}, error) { - region := config.Region - issuer := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s", region, config.UserPoolID) +func verifyToken(tokenString string, keySet jwk.Set, config auth.CognitoConfig) (map[string]interface{}, error) { + region := config.AuthRegion + issuer := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s", region, config.AuthUserPoolID) // Verify the token with the keySet verifiedToken, err := jwt.Parse( diff --git a/internal/rbac/cognito.go b/internal/rbac/cognito.go index 5fb66c5e..1f4a9ce2 100644 --- a/internal/rbac/cognito.go +++ b/internal/rbac/cognito.go @@ -138,7 +138,7 @@ func (ckp *CognitoKeyProvider) GetPublicKey(kid string) (*rsa.PublicKey, error) // Fetch JWKS from Cognito (old way) // baseURL := "https://cognito-idp.amazonaws.com" - // jwksPath := fmt.Sprintf("/%s/%s/.well-known/jwks.json", ckp.Region, ckp.UserPoolID) + // jwksPath := fmt.Sprintf("/%s/%s/.well-known/jwks.json", ckp.AuthRegion, ckp.AuthUserPoolID) // https://cognito-idp.us-east-2.amazonaws.com/us-east-2_1y6po8rR8/.well-known/jwks.json jwksURL := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", ckp.Region, ckp.UserPoolID) diff --git a/internal/rbac/cognito_test.go b/internal/rbac/cognito_test.go index a99a6714..7cd786b0 100644 --- a/internal/rbac/cognito_test.go +++ b/internal/rbac/cognito_test.go @@ -533,8 +533,8 @@ func TestCognitoKeyProviderGetPublicKey(t *testing.T) { // Instead of trying to patch the URL (which is difficult with reflection), // we'll verify that the provider was created correctly - regionField := reflect.Indirect(providerValue).FieldByName("Region") - userPoolIDField := reflect.Indirect(providerValue).FieldByName("UserPoolID") + regionField := reflect.Indirect(providerValue).FieldByName("AuthRegion") + userPoolIDField := reflect.Indirect(providerValue).FieldByName("AuthUserPoolID") assert.Equal(t, "us-east-1", regionField.String()) assert.Equal(t, "test-pool-id", userPoolIDField.String()) @@ -552,10 +552,10 @@ func TestNewCognitoKeyProvider(t *testing.T) { // Use reflection to check the internal fields providerValue := reflect.ValueOf(provider).Elem() - regionField := providerValue.FieldByName("Region") + regionField := providerValue.FieldByName("AuthRegion") assert.Equal(t, region, regionField.String()) - userPoolIDField := providerValue.FieldByName("UserPoolID") + userPoolIDField := providerValue.FieldByName("AuthUserPoolID") assert.Equal(t, userPoolID, userPoolIDField.String()) jwksCacheField := providerValue.FieldByName("JwksCache") diff --git a/internal/serviceconfig/cognitoauth/config.go b/internal/serviceconfig/auth/config.go similarity index 50% rename from internal/serviceconfig/cognitoauth/config.go rename to internal/serviceconfig/auth/config.go index 4000b10e..e9b085d4 100644 --- a/internal/serviceconfig/cognitoauth/config.go +++ b/internal/serviceconfig/auth/config.go @@ -1,4 +1,4 @@ -package cognitoauth +package auth import ( "fmt" @@ -12,22 +12,22 @@ import ( // Contains all necessary parameters to interact with Cognito endpoints type CognitoConfig struct { // annotated vars - Region string `env:"COGNITO_REGION" envDefault:"us-east-2"` - UserPoolID string `env:"COGNITO_USER_POOL_ID,required,notEmpty"` - ClientSecret string `env:"COGNITO_CLIENT_SECRET,required,notEmpty"` - Domain string `env:"COGNITO_DOMAIN,required,notEmpty"` - ClientID string `env:"COGNITO_CLIENT_ID,required,notEmpty"` + AuthRegion string `env:"COGNITO_REGION" envDefault:"us-east-2"` + AuthUserPoolID string `env:"COGNITO_USER_POOL_ID,required,notEmpty"` + AuthClientSecret string `env:"COGNITO_CLIENT_SECRET,required,notEmpty"` + AuthDomain string `env:"COGNITO_DOMAIN,required,notEmpty"` + AuthClientID string `env:"COGNITO_CLIENT_ID,required,notEmpty"` - RedirectURI string // URL where Cognito redirects after authentication - TokenURL string // Cognito endpoint for token operations - JwksURL string // URL for JSON Web Key Set (for token verification) - AuthURL string // Cognito authorization endpoint for initiating login - LoginPath string // Path for initiating login - CallbackPath string // Path for OAuth callback - HomePath string // Path for home page - LogoutPath string // Path for logout - AuthLogger *slog.Logger // Logger instance ( normally just the same logger as main) - RoutePermissions map[string][]string // Map of routes to required permissions + AuthRedirectURI string // URL where Cognito redirects after authentication + AuthTokenURL string // Cognito endpoint for token operations + AuthJwksURL string // URL for JSON Web Key Set (for token verification) + AuthURL string // Cognito authorization endpoint for initiating login + AuthLoginPath string // Path for initiating login + AuthCallbackPath string // Path for OAuth callback + AuthHomePath string // Path for home page + AuthLogoutPath string // Path for logout + AuthLogger *slog.Logger // Logger instance ( normally just the same logger as main) + AuthRoutePermissions map[string][]string // Map of routes to required permissions } // InitializeConfig creates a new CognitoConfig from environment variables @@ -99,20 +99,20 @@ func InitializeConfig(baseURL string, logger *slog.Logger) *CognitoConfig { baseURL = strings.TrimSuffix(baseURL, "/") config := &CognitoConfig{ - ClientID: os.Getenv("COGNITO_CLIENT_ID"), - ClientSecret: os.Getenv("COGNITO_CLIENT_SECRET"), - RedirectURI: baseURL + callbackPath, - TokenURL: fmt.Sprintf("https://%s/oauth2/token", domain), - AuthURL: fmt.Sprintf("https://%s/oauth2/authorize", domain), - JwksURL: fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", region, userPoolID), - UserPoolID: userPoolID, - Region: region, - LoginPath: loginPath, - CallbackPath: callbackPath, - HomePath: homePath, - LogoutPath: logoutPath, - AuthLogger: logger, - RoutePermissions: make(map[string][]string), + AuthClientID: os.Getenv("COGNITO_CLIENT_ID"), + AuthClientSecret: os.Getenv("COGNITO_CLIENT_SECRET"), + AuthRedirectURI: baseURL + callbackPath, + AuthTokenURL: fmt.Sprintf("https://%s/oauth2/token", domain), + AuthURL: fmt.Sprintf("https://%s/oauth2/authorize", domain), + AuthJwksURL: fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", region, userPoolID), + AuthUserPoolID: userPoolID, + AuthRegion: region, + AuthLoginPath: loginPath, + AuthCallbackPath: callbackPath, + AuthHomePath: homePath, + AuthLogoutPath: logoutPath, + AuthLogger: logger, + AuthRoutePermissions: make(map[string][]string), } config.PrettyPrint() @@ -121,21 +121,21 @@ func InitializeConfig(baseURL string, logger *slog.Logger) *CognitoConfig { func (c *CognitoConfig) PrettyPrint() { fmt.Printf("CognitoConfig:\n") - fmt.Printf(" ClientID: %s\n", c.ClientID) - fmt.Printf(" ClientSecret: %s\n", c.ClientSecret) - fmt.Printf(" RedirectURI: %s\n", c.RedirectURI) - fmt.Printf(" TokenURL: %s\n", c.TokenURL) + fmt.Printf(" AuthClientID: %s\n", c.AuthClientID) + fmt.Printf(" AuthClientSecret: %s\n", c.AuthClientSecret) + fmt.Printf(" AuthRedirectURI: %s\n", c.AuthRedirectURI) + fmt.Printf(" AuthTokenURL: %s\n", c.AuthTokenURL) fmt.Printf(" AuthURL: %s\n", c.AuthURL) - fmt.Printf(" JwksURL: %s\n", c.JwksURL) - fmt.Printf(" UserPoolID: %s\n", c.UserPoolID) - fmt.Printf(" Region: %s\n", c.Region) - fmt.Printf(" LoginPath: %s\n", c.LoginPath) - fmt.Printf(" CallbackPath: %s\n", c.CallbackPath) - fmt.Printf(" HomePath: %s\n", c.HomePath) - fmt.Printf(" LogoutPath: %s\n", c.LogoutPath) + fmt.Printf(" AuthJwksURL: %s\n", c.AuthJwksURL) + fmt.Printf(" AuthUserPoolID: %s\n", c.AuthUserPoolID) + fmt.Printf(" AuthRegion: %s\n", c.AuthRegion) + fmt.Printf(" AuthLoginPath: %s\n", c.AuthLoginPath) + fmt.Printf(" AuthCallbackPath: %s\n", c.AuthCallbackPath) + fmt.Printf(" AuthHomePath: %s\n", c.AuthHomePath) + fmt.Printf(" AuthLogoutPath: %s\n", c.AuthLogoutPath) // pretty print the route permissions map - for route, permissions := range c.RoutePermissions { + for route, permissions := range c.AuthRoutePermissions { fmt.Printf(" Route: %s, Permissions: %v\n", route, permissions) } @@ -143,5 +143,5 @@ func (c *CognitoConfig) PrettyPrint() { // SetRoutePermissions sets the route permissions map func (c *CognitoConfig) SetRoutePermissions(permissions map[string][]string) { - c.RoutePermissions = permissions + c.AuthRoutePermissions = permissions } diff --git a/internal/serviceconfig/common.go b/internal/serviceconfig/common.go index 60d35acc..4766db70 100644 --- a/internal/serviceconfig/common.go +++ b/internal/serviceconfig/common.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "log/slog" - "queryorchestration/internal/serviceconfig/cognitoauth" + + "queryorchestration/internal/serviceconfig/auth" + "reflect" "strings" "sync" @@ -33,7 +35,7 @@ type BaseConfig struct { Pwd string `env:"PWD,required,notEmpty"` //rbac.AuthConfig - cognitoauth.CognitoConfig + auth.CognitoConfig logger.LogConfig observability.ObsConfig database.DBConfig