merge authconfig
This commit is contained in:
@@ -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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
+43
-43
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user