// adminHandlers.go implements the admin API handlers for user management operations. // These handlers provide REST API access to AWS Cognito and Permit.io user management functions. package queryapi import ( "context" "fmt" "net/http" "os" "time" "queryorchestration/internal/cognitoauth" "queryorchestration/internal/serviceconfig/aws" "queryorchestration/internal/usermanagement" "github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider" "github.com/labstack/echo/v4" "github.com/oapi-codegen/runtime/types" ) const ( adminCognitoListPageSize = 60 maxAdminCognitoListPages = 100 maxAdminCognitoListUsers = 6000 ) // getAdminUserForAudit retrieves the admin user information for audit logging. // When DISABLE_AUTH is set to "true", it returns a default system user for audit purposes. // Otherwise, it attempts to extract user info from the JWT token in the request. // // Parameters: // - ctx: The Echo context containing the HTTP request // // Returns: // - cognitoauth.UserInfo: The admin user information // - error: Error if user info cannot be retrieved and auth is not disabled func getAdminUserForAudit(ctx echo.Context) (cognitoauth.UserInfo, error) { // Try to get user info from JWT token adminUser, ok := cognitoauth.GetUserInfo(ctx) if ok { return adminUser, nil } // If auth is disabled, use a default system user for audit logging if os.Getenv("DISABLE_AUTH") == "true" { return cognitoauth.UserInfo{ Email: "system@test.local", Username: "system", Groups: []string{"admin"}, }, nil } // Auth is enabled but user info not found - return error return cognitoauth.UserInfo{}, fmt.Errorf("could not identify admin user") } // getPermitConfig creates a PermitConfig by reading environment variables. // This centralizes Permit.io configuration to avoid code duplication. // Note: ProjectID and EnvID are automatically derived from the API key scope // when first needed, so they do not need to be set here. // // Parameters: // - ctrl: The Controllers instance containing the API key from config // // Returns: // - *usermanagement.PermitConfig: The Permit.io configuration func (ctrl *Controllers) getPermitConfig() *usermanagement.PermitConfig { tenant := os.Getenv("PERMIT_IO_TENANT") if tenant == "" { tenant = "default" } baseURL := os.Getenv("PERMIT_IO_BASE_URL") if baseURL == "" { baseURL = "https://api.permit.io" } return &usermanagement.PermitConfig{ APIKey: ctrl.cfg.GetPermitIOAPIKey(), BaseURL: baseURL, DefaultTenant: tenant, } } // CreateAdminUser creates a new user in both AWS Cognito and Permit.io. // POST /admin/users func (ctrl *Controllers) CreateAdminUser(ctx echo.Context) error { // Parse request body var req AdminUserCreate if err := ctx.Bind(&req); err != nil { return ctx.JSON(http.StatusBadRequest, ErrorMessage{ Message: fmt.Sprintf("Invalid request body: %v", err), }) } // Get admin user email from JWT for audit logging adminUser, err := getAdminUserForAudit(ctx) if err != nil { return ctx.JSON(http.StatusUnauthorized, ErrorMessage{ Message: "Could not identify admin user", }) } httpClient := &http.Client{Timeout: 10 * time.Second} permitConfig := ctrl.getPermitConfig() if !canAdminModeCreateUser(ctrl.getAdminTeamMode(ctx, httpClient, permitConfig), string(req.Email), req.Roles) { return adminUserForbidden(ctx) } // Create AWS config and Cognito client awsCfg, err := aws.GetAWSConfig(context.Background()) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to load AWS config", "error", err) return ctx.JSON(http.StatusInternalServerError, ErrorMessage{ Message: "Failed to initialize AWS configuration", }) } cognitoClient := cognitoidentityprovider.NewFromConfig(awsCfg) // Prepare Cognito configuration cognitoConfig := &usermanagement.CognitoConfig{ UserPoolID: ctrl.cfg.GetAuthUserPoolID(), Region: ctrl.cfg.GetAuthRegion(), } // Prepare user attributes userAttrs := usermanagement.UserAttributes{ Email: string(req.Email), FirstName: req.FirstName, LastName: req.LastName, EnvironmentID: ctrl.getCognitoEnvironmentID(), } // Check if user already exists existingUser, err := usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, userAttrs.Email) if err == nil { // User exists - check if attributes match (idempotent) if existingUser.FirstName == userAttrs.FirstName && existingUser.LastName == userAttrs.LastName { // Return 200 OK for idempotent creation usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionCreate, usermanagement.SystemCognito, adminUser.Email, userAttrs.Email, existingUser.SubjectID, usermanagement.ResultSuccess, "User already exists with matching attributes (idempotent)", ctx.Response().Header().Get(echo.HeaderXRequestID), ) createdAt := time.Now() return ctx.JSON(http.StatusOK, AdminUserCreateResponse{ CognitoSubjectId: existingUser.SubjectID, Email: types.Email(existingUser.Email), FirstName: &existingUser.FirstName, LastName: &existingUser.LastName, Status: existingUser.Status, Enabled: &existingUser.Enabled, PermitSynced: true, CreatedAt: &createdAt, }) } // User exists but attributes differ - conflict usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionCreate, usermanagement.SystemCognito, adminUser.Email, userAttrs.Email, existingUser.SubjectID, usermanagement.ResultFailure, "User already exists with different attributes", ctx.Response().Header().Get(echo.HeaderXRequestID), ) return ctx.JSON(http.StatusConflict, ErrorMessage{ Message: "User already exists with different attributes", }) } // Create user in Cognito cognitoUser, err := usermanagement.CreateCognitoUser(context.Background(), cognitoClient, cognitoConfig, userAttrs) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to create user in Cognito", "error", err, "email", userAttrs.Email) usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionCreate, usermanagement.SystemCognito, adminUser.Email, userAttrs.Email, "", usermanagement.ResultFailure, fmt.Sprintf("Cognito creation failed: %v", err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) return ctx.JSON(http.StatusBadGateway, ErrorMessage{ Message: fmt.Sprintf("Failed to create user in Cognito: %v", err), }) } // Log successful Cognito creation usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionCreate, usermanagement.SystemCognito, adminUser.Email, userAttrs.Email, cognitoUser.SubjectID, usermanagement.ResultSuccess, "User created successfully in Cognito", ctx.Response().Header().Get(echo.HeaderXRequestID), ) // Create user in Permit.io permitSynced := false err = usermanagement.CreatePermitUser(httpClient, permitConfig, cognitoUser) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to create user in Permit.io", "error", err, "email", userAttrs.Email) usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionCreate, usermanagement.SystemPermit, adminUser.Email, userAttrs.Email, cognitoUser.SubjectID, usermanagement.ResultFailure, fmt.Sprintf("Permit.io creation failed: %v", err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) } else { permitSynced = true usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionCreate, usermanagement.SystemPermit, adminUser.Email, userAttrs.Email, cognitoUser.SubjectID, usermanagement.ResultSuccess, "User created successfully in Permit.io", ctx.Response().Header().Get(echo.HeaderXRequestID), ) } // Assign roles in Permit.io var rolesAssigned []string if permitSynced { for _, role := range req.Roles { err = usermanagement.AssignRole(httpClient, permitConfig, cognitoUser.SubjectID, role) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to assign role", "error", err, "role", role, "user", userAttrs.Email) usermanagement.LogRoleAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionAssignRole, adminUser.Email, userAttrs.Email, role, usermanagement.ResultFailure, fmt.Sprintf("Role assignment failed: %v", err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) } else { rolesAssigned = append(rolesAssigned, role) usermanagement.LogRoleAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionAssignRole, adminUser.Email, userAttrs.Email, role, usermanagement.ResultSuccess, "Role assigned successfully", ctx.Response().Header().Get(echo.HeaderXRequestID), ) } } } // Build response createdAt := time.Now() response := AdminUserCreateResponse{ CognitoSubjectId: cognitoUser.SubjectID, Email: types.Email(cognitoUser.Email), FirstName: &cognitoUser.FirstName, LastName: &cognitoUser.LastName, Status: cognitoUser.Status, Enabled: &cognitoUser.Enabled, PermitSynced: permitSynced, CreatedAt: &createdAt, } if len(rolesAssigned) > 0 { response.RolesAssigned = &rolesAssigned } return ctx.JSON(http.StatusCreated, response) } // GetAdminUser retrieves user details from both AWS Cognito and Permit.io. // GET /admin/users/{email} func (ctrl *Controllers) GetAdminUser(ctx echo.Context, email UserEmail) error { // Get admin user email from JWT for audit logging adminUser, err := getAdminUserForAudit(ctx) if err != nil { return ctx.JSON(http.StatusUnauthorized, ErrorMessage{ Message: "Could not identify admin user", }) } // Create AWS config and Cognito client awsCfg, err := aws.GetAWSConfig(context.Background()) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to load AWS config", "error", err) return ctx.JSON(http.StatusInternalServerError, ErrorMessage{ Message: "Failed to initialize AWS configuration", }) } cognitoClient := cognitoidentityprovider.NewFromConfig(awsCfg) // Prepare Cognito configuration cognitoConfig := &usermanagement.CognitoConfig{ UserPoolID: ctrl.cfg.GetAuthUserPoolID(), Region: ctrl.cfg.GetAuthRegion(), } // Get user from Cognito cognitoUser, err := usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email)) if err != nil { errInfo := usermanagement.ClassifyCognitoError(err, string(email)) ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email, "error_type", errInfo.ErrorType, "http_status", errInfo.HTTPStatus) usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionUpdate, usermanagement.SystemCognito, adminUser.Email, string(email), "", usermanagement.ResultFailure, fmt.Sprintf("Cognito error (%s): %v", errInfo.ErrorType, err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) errorType := string(errInfo.ErrorType) return ctx.JSON(errInfo.HTTPStatus, ErrorMessage{ Message: errInfo.Message, ErrorType: &errorType, Details: &errInfo.Details, }) } // Check environment ownership - return 404 for users in other environments if err := checkUserEnvironmentOwnership(cognitoUser, ctrl.getCognitoEnvironmentID()); err != nil { return ctx.JSON(http.StatusNotFound, ErrorMessage{ Message: fmt.Sprintf("User not found: %s", email), }) } targetAccess := ctrl.getAdminTargetAccess(ctx, email, cognitoUser) if !targetAccess.canRead(cognitoUser.Email) { return adminUserNotFound(ctx, string(email)) } roles := targetAccess.roles // Log successful retrieval usermanagement.LogUserListAction( ctrl.cfg.GetAuthLogger(), adminUser.Email, fmt.Sprintf("email=%s", email), 1, ctx.Response().Header().Get(echo.HeaderXRequestID), ) // Build response response := AdminUserDetails{ CognitoSubjectId: cognitoUser.SubjectID, Email: types.Email(cognitoUser.Email), FirstName: &cognitoUser.FirstName, LastName: &cognitoUser.LastName, Status: cognitoUser.Status, Enabled: cognitoUser.Enabled, } if len(roles) > 0 { response.Roles = &roles } return ctx.JSON(http.StatusOK, response) } // CheckAdminUserExists checks if a user exists in Cognito and/or Permit.io. // HEAD /admin/users/{email} func (ctrl *Controllers) CheckAdminUserExists(ctx echo.Context, email UserEmail) error { // Create AWS config and Cognito client awsCfg, err := aws.GetAWSConfig(context.Background()) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to load AWS config", "error", err) return ctx.NoContent(http.StatusInternalServerError) } cognitoClient := cognitoidentityprovider.NewFromConfig(awsCfg) // Prepare Cognito configuration cognitoConfig := &usermanagement.CognitoConfig{ UserPoolID: ctrl.cfg.GetAuthUserPoolID(), Region: ctrl.cfg.GetAuthRegion(), } // Check if user exists in Cognito (use GetCognitoUser to get detailed error info) cognitoUser, err := usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email)) if err != nil { errInfo := usermanagement.ClassifyCognitoError(err, string(email)) // For HEAD requests, we can only return status code and headers ctx.Response().Header().Set("X-Error-Type", string(errInfo.ErrorType)) ctx.Response().Header().Set("X-Error-Message", errInfo.Message) return ctx.NoContent(errInfo.HTTPStatus) } // Check environment ownership - return 404 for users in other environments if err := checkUserEnvironmentOwnership(cognitoUser, ctrl.getCognitoEnvironmentID()); err != nil { return ctx.NoContent(http.StatusNotFound) } targetAccess := ctrl.getAdminTargetAccess(ctx, email, cognitoUser) if !targetAccess.canRead(cognitoUser.Email) { return ctx.NoContent(http.StatusNotFound) } return ctx.NoContent(http.StatusOK) } // ListAdminUsers lists users from AWS Cognito with pagination, filtering, and sorting. // GET /admin/users func (ctrl *Controllers) ListAdminUsers(ctx echo.Context, params ListAdminUsersParams) error { // Get admin user email from JWT for audit logging adminUser, err := getAdminUserForAudit(ctx) if err != nil { return ctx.JSON(http.StatusUnauthorized, ErrorMessage{ Message: "Could not identify admin user", }) } // Create AWS config and Cognito client awsCfg, err := aws.GetAWSConfig(context.Background()) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to load AWS config", "error", err) return ctx.JSON(http.StatusInternalServerError, ErrorMessage{ Message: "Failed to initialize AWS configuration", }) } cognitoClient := cognitoidentityprovider.NewFromConfig(awsCfg) // Prepare Cognito configuration cognitoConfig := &usermanagement.CognitoConfig{ UserPoolID: ctrl.cfg.GetAuthUserPoolID(), Region: ctrl.cfg.GetAuthRegion(), } page, pageSize := adminUserListPagination(params) allUsers, err := listAllAdminCognitoUsers(cognitoClient, cognitoConfig.UserPoolID) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to list users from Cognito", "error", err) return ctx.JSON(http.StatusBadGateway, ErrorMessage{ Message: fmt.Sprintf("Failed to list users from Cognito: %v", err), }) } filteredUsers := usermanagement.FilterUsersByEnvironmentID(allUsers, ctrl.getCognitoEnvironmentID()) httpClient := &http.Client{Timeout: 10 * time.Second} permitConfig := ctrl.getPermitConfig() adminMode := ctrl.getAdminTeamMode(ctx, httpClient, permitConfig) if adminMode == adminTeamModeNone { return adminUserForbidden(ctx) } roleAssignments, err := usermanagement.ListAllPermitRoleAssignments(httpClient, permitConfig) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to list role assignments from Permit.io", "error", err) return ctx.JSON(http.StatusBadGateway, ErrorMessage{ Message: fmt.Sprintf("Failed to list role assignments from Permit.io: %v", err), }) } visibleUsers := ctrl.visibleAdminUsers(filteredUsers, adminMode, adminRoleAssignmentsByUser(roleAssignments)) users, hasMore := paginateAdminUsers(visibleUsers, page, pageSize) // Log list action usermanagement.LogUserListAction( ctrl.cfg.GetAuthLogger(), adminUser.Email, fmt.Sprintf("page=%d,page_size=%d", page, pageSize), len(users), ctx.Response().Header().Get(echo.HeaderXRequestID), ) // Build response total := int32(len(visibleUsers)) // #nosec G115 -- admin user lists are bounded by Cognito user pool size response := AdminUserList{ Users: users, Total: total, Page: page, PageSize: pageSize, HasMore: &hasMore, } return ctx.JSON(http.StatusOK, response) } func adminUserListPagination(params ListAdminUsersParams) (int32, int32) { page := int32(1) pageSize := int32(50) if params.Page != nil && *params.Page > 0 { page = *params.Page } if params.PageSize != nil && *params.PageSize > 0 && *params.PageSize <= 60 { pageSize = *params.PageSize } return page, pageSize } func listAllAdminCognitoUsers(cognitoClient *cognitoidentityprovider.Client, userPoolID string) ([]usermanagement.CognitoUserResponse, error) { return collectAdminCognitoUsers(func(pageSize int, paginationToken *string) (*usermanagement.ListCognitoUsersResult, error) { return usermanagement.ListCognitoUsers(context.Background(), cognitoClient, userPoolID, pageSize, paginationToken) }) } func collectAdminCognitoUsers(fetchPage func(pageSize int, paginationToken *string) (*usermanagement.ListCognitoUsersResult, error)) ([]usermanagement.CognitoUserResponse, error) { var users []usermanagement.CognitoUserResponse var paginationToken *string for page := 1; page <= maxAdminCognitoListPages; page++ { result, err := fetchPage(adminCognitoListPageSize, paginationToken) if err != nil { return nil, err } users = append(users, result.Users...) if len(users) > maxAdminCognitoListUsers { return nil, fmt.Errorf("admin Cognito user listing exceeded %d users", maxAdminCognitoListUsers) } if result.PaginationKey == nil || *result.PaginationKey == "" { return users, nil } paginationToken = result.PaginationKey } return nil, fmt.Errorf("admin Cognito user listing exceeded %d pages", maxAdminCognitoListPages) } func (ctrl *Controllers) visibleAdminUsers(users []usermanagement.CognitoUserResponse, mode adminTeamMode, rolesBySubject map[string][]string) []AdminUserDetails { visibleUsers := make([]AdminUserDetails, 0, len(users)) for _, cognitoUser := range users { roles, ok := rolesForAdminListUser(cognitoUser, mode, rolesBySubject) if !ok || !canAdminModeReadTarget(mode, cognitoUser.Email, roles) { continue } visibleUsers = append(visibleUsers, adminUserDetailsFromCognito(cognitoUser, roles)) } return visibleUsers } func rolesForAdminListUser(cognitoUser usermanagement.CognitoUserResponse, mode adminTeamMode, rolesBySubject map[string][]string) ([]string, bool) { roles, ok := rolesBySubject[cognitoUser.SubjectID] if !ok && mode == adminTeamModeUserAdmin { return nil, false } if roles == nil { roles = []string{} } return roles, true } func adminRoleAssignmentsByUser(assignments []usermanagement.RoleAssignment) map[string][]string { rolesBySubject := make(map[string][]string, len(assignments)) for _, assignment := range assignments { if assignment.User == "" || assignment.Role == "" { continue } rolesBySubject[assignment.User] = append(rolesBySubject[assignment.User], assignment.Role) } return rolesBySubject } func adminUserDetailsFromCognito(cognitoUser usermanagement.CognitoUserResponse, roles []string) AdminUserDetails { if roles == nil { roles = []string{} } userDetail := AdminUserDetails{ CognitoSubjectId: cognitoUser.SubjectID, Email: types.Email(cognitoUser.Email), FirstName: &cognitoUser.FirstName, LastName: &cognitoUser.LastName, Status: cognitoUser.Status, Enabled: cognitoUser.Enabled, } userDetail.Roles = &roles return userDetail } func paginateAdminUsers(visibleUsers []AdminUserDetails, page int32, pageSize int32) ([]AdminUserDetails, bool) { start := int((page - 1) * pageSize) // #nosec G115 -- page and pageSize are positive int32 request parameters end := start + int(pageSize) if start > len(visibleUsers) { start = len(visibleUsers) } if end > len(visibleUsers) { end = len(visibleUsers) } return visibleUsers[start:end], end < len(visibleUsers) } // updateUserAttributesInCognito updates user attributes in Cognito and logs the operation. func (ctrl *Controllers) updateUserAttributesInCognito(ctx echo.Context, cognitoClient *cognitoidentityprovider.Client, cognitoConfig *usermanagement.CognitoConfig, email UserEmail, req AdminUserUpdate, adminUser cognitoauth.UserInfo, cognitoUser *usermanagement.CognitoUserResponse) error { if req.FirstName == nil && req.LastName == nil { return nil } firstName := "" if req.FirstName != nil { firstName = *req.FirstName } lastName := "" if req.LastName != nil { lastName = *req.LastName } userAttrs := usermanagement.UserAttributes{ Email: string(email), FirstName: firstName, LastName: lastName, } err := usermanagement.UpdateCognitoUserAttributes(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email), userAttrs) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to update user in Cognito", "error", err, "email", email) usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionUpdate, usermanagement.SystemCognito, adminUser.Email, string(email), cognitoUser.SubjectID, usermanagement.ResultFailure, fmt.Sprintf("Cognito update failed: %v", err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) return err } usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionUpdate, usermanagement.SystemCognito, adminUser.Email, string(email), cognitoUser.SubjectID, usermanagement.ResultSuccess, "User attributes updated successfully in Cognito", ctx.Response().Header().Get(echo.HeaderXRequestID), ) if req.FirstName != nil { cognitoUser.FirstName = *req.FirstName } if req.LastName != nil { cognitoUser.LastName = *req.LastName } return nil } // updateUserRolesInPermit updates user roles in Permit.io and logs each operation. func (ctrl *Controllers) updateUserRolesInPermit(ctx echo.Context, email UserEmail, req AdminUserUpdate, adminUser cognitoauth.UserInfo, subjectID string, httpClient *http.Client, permitConfig *usermanagement.PermitConfig) { if req.Roles == nil { return } currentRoles, err := usermanagement.GetUserRoles(httpClient, permitConfig, subjectID) if err != nil { ctrl.cfg.GetAuthLogger().Warn("Failed to get current user roles from Permit.io", "error", err, "email", email) currentRoles = []string{} } requestedRoles := *req.Roles currentRolesMap := make(map[string]bool) for _, role := range currentRoles { currentRolesMap[role] = true } requestedRolesMap := make(map[string]bool) for _, role := range requestedRoles { requestedRolesMap[role] = true } // Add new roles for _, role := range requestedRoles { if !currentRolesMap[role] { err = usermanagement.AssignRole(httpClient, permitConfig, subjectID, role) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to assign role", "error", err, "role", role, "user", email) usermanagement.LogRoleAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionAssignRole, adminUser.Email, string(email), role, usermanagement.ResultFailure, fmt.Sprintf("Role assignment failed: %v", err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) } else { usermanagement.LogRoleAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionAssignRole, adminUser.Email, string(email), role, usermanagement.ResultSuccess, "Role assigned successfully", ctx.Response().Header().Get(echo.HeaderXRequestID), ) } } } // Remove old roles for _, role := range currentRoles { if !requestedRolesMap[role] { err = usermanagement.UnassignRole(httpClient, permitConfig, subjectID, role) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to unassign role", "error", err, "role", role, "user", email) usermanagement.LogRoleAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionUnassignRole, adminUser.Email, string(email), role, usermanagement.ResultFailure, fmt.Sprintf("Role unassignment failed: %v", err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) } else { usermanagement.LogRoleAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionUnassignRole, adminUser.Email, string(email), role, usermanagement.ResultSuccess, "Role unassigned successfully", ctx.Response().Header().Get(echo.HeaderXRequestID), ) } } } } // UpdateAdminUser updates user attributes in Cognito (first_name, last_name) and manages roles in Permit.io. // PATCH /admin/users/{email} func (ctrl *Controllers) UpdateAdminUser(ctx echo.Context, email UserEmail) error { // Parse request body var req AdminUserUpdate if err := ctx.Bind(&req); err != nil { return ctx.JSON(http.StatusBadRequest, ErrorMessage{ Message: fmt.Sprintf("Invalid request body: %v", err), }) } // Get admin user email from JWT for audit logging adminUser, err := getAdminUserForAudit(ctx) if err != nil { return ctx.JSON(http.StatusUnauthorized, ErrorMessage{ Message: "Could not identify admin user", }) } // Create AWS config and Cognito client awsCfg, err := aws.GetAWSConfig(context.Background()) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to load AWS config", "error", err) return ctx.JSON(http.StatusInternalServerError, ErrorMessage{ Message: "Failed to initialize AWS configuration", }) } cognitoClient := cognitoidentityprovider.NewFromConfig(awsCfg) // Prepare Cognito configuration cognitoConfig := &usermanagement.CognitoConfig{ UserPoolID: ctrl.cfg.GetAuthUserPoolID(), Region: ctrl.cfg.GetAuthRegion(), } // Get user from Cognito to verify existence and get Subject ID cognitoUser, err := usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email)) if err != nil { errInfo := usermanagement.ClassifyCognitoError(err, string(email)) ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email, "error_type", errInfo.ErrorType, "http_status", errInfo.HTTPStatus) usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionUpdate, usermanagement.SystemCognito, adminUser.Email, string(email), "", usermanagement.ResultFailure, fmt.Sprintf("Cognito error (%s): %v", errInfo.ErrorType, err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) errorType := string(errInfo.ErrorType) return ctx.JSON(errInfo.HTTPStatus, ErrorMessage{ Message: errInfo.Message, ErrorType: &errorType, Details: &errInfo.Details, }) } // Check environment ownership - return 404 for users in other environments if err := checkUserEnvironmentOwnership(cognitoUser, ctrl.getCognitoEnvironmentID()); err != nil { return ctx.JSON(http.StatusNotFound, ErrorMessage{ Message: fmt.Sprintf("User not found: %s", email), }) } targetAccess := ctrl.getAdminTargetAccess(ctx, email, cognitoUser) if !targetAccess.canMutate(cognitoUser.Email) { return adminUserForbidden(ctx) } if !canAdminModeAssignRequestedRoles(targetAccess.mode, req.Roles) { return adminUserForbidden(ctx) } httpClient := targetAccess.httpClient permitConfig := targetAccess.permitConfig // Update user attributes in Cognito if provided err = ctrl.updateUserAttributesInCognito(ctx, cognitoClient, cognitoConfig, email, req, adminUser, cognitoUser) if err != nil { return ctx.JSON(http.StatusBadGateway, ErrorMessage{ Message: fmt.Sprintf("Failed to update user in Cognito: %v", err), }) } // Handle role updates in Permit.io if provided ctrl.updateUserRolesInPermit(ctx, email, req, adminUser, cognitoUser.SubjectID, httpClient, permitConfig) // Get updated user from Cognito updatedUser, err := usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email)) if err != nil { ctrl.cfg.GetAuthLogger().Warn("Failed to get updated user from Cognito", "error", err, "email", email) updatedUser = cognitoUser } // Get updated roles updatedRoles, err := usermanagement.GetUserRoles(httpClient, permitConfig, cognitoUser.SubjectID) if err != nil { ctrl.cfg.GetAuthLogger().Warn("Failed to get updated user roles from Permit.io", "error", err, "email", email) } // Build response response := AdminUserDetails{ CognitoSubjectId: updatedUser.SubjectID, Email: types.Email(updatedUser.Email), FirstName: &updatedUser.FirstName, LastName: &updatedUser.LastName, Status: updatedUser.Status, Enabled: updatedUser.Enabled, } if len(updatedRoles) > 0 { response.Roles = &updatedRoles } return ctx.JSON(http.StatusOK, response) } // DeleteAdminUser permanently deletes a user from both AWS Cognito and Permit.io. // DELETE /admin/users/{email} func (ctrl *Controllers) DeleteAdminUser(ctx echo.Context, email UserEmail, params DeleteAdminUserParams) error { if !params.Confirm { return ctx.JSON(http.StatusBadRequest, ErrorMessage{ Message: "confirm=true query parameter is required to delete a user", }) } // Get admin user email from JWT for audit logging adminUser, err := getAdminUserForAudit(ctx) if err != nil { return ctx.JSON(http.StatusUnauthorized, ErrorMessage{ Message: "Could not identify admin user", }) } // Create AWS config and Cognito client awsCfg, err := aws.GetAWSConfig(context.Background()) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to load AWS config", "error", err) return ctx.JSON(http.StatusInternalServerError, ErrorMessage{ Message: "Failed to initialize AWS configuration", }) } cognitoClient := cognitoidentityprovider.NewFromConfig(awsCfg) // Prepare Cognito configuration cognitoConfig := &usermanagement.CognitoConfig{ UserPoolID: ctrl.cfg.GetAuthUserPoolID(), Region: ctrl.cfg.GetAuthRegion(), } // Get user from Cognito to verify existence and get Subject ID cognitoUser, err := usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email)) if err != nil { errInfo := usermanagement.ClassifyCognitoError(err, string(email)) ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email, "error_type", errInfo.ErrorType, "http_status", errInfo.HTTPStatus) usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionDelete, usermanagement.SystemCognito, adminUser.Email, string(email), "", usermanagement.ResultFailure, fmt.Sprintf("Cognito error (%s): %v", errInfo.ErrorType, err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) errorType := string(errInfo.ErrorType) return ctx.JSON(errInfo.HTTPStatus, ErrorMessage{ Message: errInfo.Message, ErrorType: &errorType, Details: &errInfo.Details, }) } // Check environment ownership - return 404 for users in other environments if err := checkUserEnvironmentOwnership(cognitoUser, ctrl.getCognitoEnvironmentID()); err != nil { return ctx.JSON(http.StatusNotFound, ErrorMessage{ Message: fmt.Sprintf("User not found: %s", email), }) } targetAccess := ctrl.getAdminTargetAccess(ctx, email, cognitoUser) if !targetAccess.canMutate(cognitoUser.Email) { return adminUserForbidden(ctx) } httpClient := targetAccess.httpClient permitConfig := targetAccess.permitConfig // Delete user from Cognito cognitoDeleted := false err = usermanagement.DeleteCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email)) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to delete user from Cognito", "error", err, "email", email) usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionDelete, usermanagement.SystemCognito, adminUser.Email, string(email), cognitoUser.SubjectID, usermanagement.ResultFailure, fmt.Sprintf("Cognito deletion failed: %v", err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) } else { cognitoDeleted = true usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionDelete, usermanagement.SystemCognito, adminUser.Email, string(email), cognitoUser.SubjectID, usermanagement.ResultSuccess, "User deleted successfully from Cognito", ctx.Response().Header().Get(echo.HeaderXRequestID), ) } // Delete user from Permit.io permitDeleted := false err = usermanagement.DeletePermitUser(httpClient, permitConfig, cognitoUser.SubjectID) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to delete user from Permit.io", "error", err, "email", email) usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionDelete, usermanagement.SystemPermit, adminUser.Email, string(email), cognitoUser.SubjectID, usermanagement.ResultFailure, fmt.Sprintf("Permit.io deletion failed: %v", err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) } else { permitDeleted = true usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionDelete, usermanagement.SystemPermit, adminUser.Email, string(email), cognitoUser.SubjectID, usermanagement.ResultSuccess, "User deleted successfully from Permit.io", ctx.Response().Header().Get(echo.HeaderXRequestID), ) } // Build response response := AdminUserDeleteResponse{ Success: cognitoDeleted, // Success based on Cognito deletion (primary auth system) CognitoSubjectId: &cognitoUser.SubjectID, Email: types.Email(string(email)), } response.DeletedFrom.Cognito = cognitoDeleted response.DeletedFrom.Permit = permitDeleted return ctx.JSON(http.StatusOK, response) } // DisableAdminUser disables a user in AWS Cognito, preventing authentication. // POST /admin/users/{email}/disable func (ctrl *Controllers) DisableAdminUser(ctx echo.Context, email UserEmail) error { // Get admin user email from JWT for audit logging adminUser, err := getAdminUserForAudit(ctx) if err != nil { return ctx.JSON(http.StatusUnauthorized, ErrorMessage{ Message: "Could not identify admin user", }) } // Create AWS config and Cognito client awsCfg, err := aws.GetAWSConfig(context.Background()) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to load AWS config", "error", err) return ctx.JSON(http.StatusInternalServerError, ErrorMessage{ Message: "Failed to initialize AWS configuration", }) } cognitoClient := cognitoidentityprovider.NewFromConfig(awsCfg) // Prepare Cognito configuration cognitoConfig := &usermanagement.CognitoConfig{ UserPoolID: ctrl.cfg.GetAuthUserPoolID(), Region: ctrl.cfg.GetAuthRegion(), } // Get user from Cognito to verify existence and get Subject ID cognitoUser, err := usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email)) if err != nil { errInfo := usermanagement.ClassifyCognitoError(err, string(email)) ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email, "error_type", errInfo.ErrorType, "http_status", errInfo.HTTPStatus) usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionDisable, usermanagement.SystemCognito, adminUser.Email, string(email), "", usermanagement.ResultFailure, fmt.Sprintf("Cognito error (%s): %v", errInfo.ErrorType, err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) errorType := string(errInfo.ErrorType) return ctx.JSON(errInfo.HTTPStatus, ErrorMessage{ Message: errInfo.Message, ErrorType: &errorType, Details: &errInfo.Details, }) } // Check environment ownership - return 404 for users in other environments if err := checkUserEnvironmentOwnership(cognitoUser, ctrl.getCognitoEnvironmentID()); err != nil { return ctx.JSON(http.StatusNotFound, ErrorMessage{ Message: fmt.Sprintf("User not found: %s", email), }) } targetAccess := ctrl.getAdminTargetAccess(ctx, email, cognitoUser) if !targetAccess.canMutate(cognitoUser.Email) { return adminUserForbidden(ctx) } // Disable user in Cognito err = usermanagement.DisableCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email)) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to disable user in Cognito", "error", err, "email", email) usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionDisable, usermanagement.SystemCognito, adminUser.Email, string(email), cognitoUser.SubjectID, usermanagement.ResultFailure, fmt.Sprintf("Cognito disable failed: %v", err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) return ctx.JSON(http.StatusBadGateway, ErrorMessage{ Message: fmt.Sprintf("Failed to disable user in Cognito: %v", err), }) } // Log successful disable usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionDisable, usermanagement.SystemCognito, adminUser.Email, string(email), cognitoUser.SubjectID, usermanagement.ResultSuccess, "User disabled successfully in Cognito", ctx.Response().Header().Get(echo.HeaderXRequestID), ) // Build response timestamp := time.Now() response := AdminUserActionResponse{ Action: Disable, CognitoSubjectId: &cognitoUser.SubjectID, Email: types.Email(string(email)), Success: true, Timestamp: ×tamp, } return ctx.JSON(http.StatusOK, response) } // EnableAdminUser re-enables a previously disabled user in AWS Cognito. // POST /admin/users/{email}/enable func (ctrl *Controllers) EnableAdminUser(ctx echo.Context, email UserEmail) error { // Get admin user email from JWT for audit logging adminUser, err := getAdminUserForAudit(ctx) if err != nil { return ctx.JSON(http.StatusUnauthorized, ErrorMessage{ Message: "Could not identify admin user", }) } // Create AWS config and Cognito client awsCfg, err := aws.GetAWSConfig(context.Background()) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to load AWS config", "error", err) return ctx.JSON(http.StatusInternalServerError, ErrorMessage{ Message: "Failed to initialize AWS configuration", }) } cognitoClient := cognitoidentityprovider.NewFromConfig(awsCfg) // Prepare Cognito configuration cognitoConfig := &usermanagement.CognitoConfig{ UserPoolID: ctrl.cfg.GetAuthUserPoolID(), Region: ctrl.cfg.GetAuthRegion(), } // Get user from Cognito to verify existence and get Subject ID cognitoUser, err := usermanagement.GetCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email)) if err != nil { errInfo := usermanagement.ClassifyCognitoError(err, string(email)) ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email, "error_type", errInfo.ErrorType, "http_status", errInfo.HTTPStatus) usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionEnable, usermanagement.SystemCognito, adminUser.Email, string(email), "", usermanagement.ResultFailure, fmt.Sprintf("Cognito error (%s): %v", errInfo.ErrorType, err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) errorType := string(errInfo.ErrorType) return ctx.JSON(errInfo.HTTPStatus, ErrorMessage{ Message: errInfo.Message, ErrorType: &errorType, Details: &errInfo.Details, }) } // Check environment ownership - return 404 for users in other environments if err := checkUserEnvironmentOwnership(cognitoUser, ctrl.getCognitoEnvironmentID()); err != nil { return ctx.JSON(http.StatusNotFound, ErrorMessage{ Message: fmt.Sprintf("User not found: %s", email), }) } targetAccess := ctrl.getAdminTargetAccess(ctx, email, cognitoUser) if !targetAccess.canMutate(cognitoUser.Email) { return adminUserForbidden(ctx) } // Enable user in Cognito err = usermanagement.EnableCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email)) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to enable user in Cognito", "error", err, "email", email) usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionEnable, usermanagement.SystemCognito, adminUser.Email, string(email), cognitoUser.SubjectID, usermanagement.ResultFailure, fmt.Sprintf("Cognito enable failed: %v", err), ctx.Response().Header().Get(echo.HeaderXRequestID), ) return ctx.JSON(http.StatusBadGateway, ErrorMessage{ Message: fmt.Sprintf("Failed to enable user in Cognito: %v", err), }) } // Log successful enable usermanagement.LogUserAdminAction( ctrl.cfg.GetAuthLogger(), usermanagement.ActionEnable, usermanagement.SystemCognito, adminUser.Email, string(email), cognitoUser.SubjectID, usermanagement.ResultSuccess, "User enabled successfully in Cognito", ctx.Response().Header().Get(echo.HeaderXRequestID), ) // Build response timestamp := time.Now() response := AdminUserActionResponse{ Action: Enable, CognitoSubjectId: &cognitoUser.SubjectID, Email: types.Email(string(email)), Success: true, Timestamp: ×tamp, } return ctx.JSON(http.StatusOK, response) }