Files
query-orchestration/api/queryAPI/adminHandlers.go
T
Jay Brown dae7bd4cc6 Merged in feature/textExtractionsPart4 (pull request #197)
implement GET /identity

* GET /identity
2025-12-16 04:26:06 +00:00

1061 lines
34 KiB
Go

// 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"
)
// 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@localhost",
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",
})
}
// 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,
}
// 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
httpClient := &http.Client{Timeout: 10 * time.Second}
permitConfig := ctrl.getPermitConfig()
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 {
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email)
usermanagement.LogUserAdminAction(
ctrl.cfg.GetAuthLogger(),
usermanagement.ActionUpdate,
usermanagement.SystemCognito,
adminUser.Email,
string(email),
"",
usermanagement.ResultFailure,
fmt.Sprintf("User not found in Cognito: %v", err),
ctx.Response().Header().Get(echo.HeaderXRequestID),
)
return ctx.JSON(http.StatusNotFound, ErrorMessage{
Message: fmt.Sprintf("User not found: %s", email),
})
}
// Get user roles from Permit.io
httpClient := &http.Client{Timeout: 10 * time.Second}
permitConfig := ctrl.getPermitConfig()
roles, err := usermanagement.GetUserRoles(httpClient, permitConfig, cognitoUser.SubjectID)
if err != nil {
ctrl.cfg.GetAuthLogger().Warn("Failed to get user roles from Permit.io", "error", err, "email", email)
}
// 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
exists := usermanagement.UserExistsInCognito(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email))
if !exists {
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(),
}
// Set default pagination parameters
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
}
// List users from Cognito (note: Cognito doesn't support traditional page numbers, only pagination tokens)
// For simplicity, we'll just return the first page
result, err := usermanagement.ListCognitoUsers(context.Background(), cognitoClient, cognitoConfig.UserPoolID, int(pageSize), nil)
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),
})
}
// Convert to AdminUserDetails format
users := make([]AdminUserDetails, 0, len(result.Users))
for _, cognitoUser := range result.Users {
userDetail := AdminUserDetails{
CognitoSubjectId: cognitoUser.SubjectID,
Email: types.Email(cognitoUser.Email),
FirstName: &cognitoUser.FirstName,
LastName: &cognitoUser.LastName,
Status: cognitoUser.Status,
Enabled: cognitoUser.Enabled,
}
users = append(users, userDetail)
}
// 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
hasMore := result.PaginationKey != nil && *result.PaginationKey != ""
// len(users) is bounded by pageSize (max 60), so this conversion is safe
total := int32(len(users)) // #nosec G115 -- len(users) is bounded by pageSize which is max 60
response := AdminUserList{
Users: users,
Total: total,
Page: page,
PageSize: pageSize,
HasMore: &hasMore,
}
return ctx.JSON(http.StatusOK, response)
}
// 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 || len(*req.Roles) == 0 {
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 {
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email)
usermanagement.LogUserAdminAction(
ctrl.cfg.GetAuthLogger(),
usermanagement.ActionUpdate,
usermanagement.SystemCognito,
adminUser.Email,
string(email),
"",
usermanagement.ResultFailure,
fmt.Sprintf("User not found in Cognito: %v", err),
ctx.Response().Header().Get(echo.HeaderXRequestID),
)
return ctx.JSON(http.StatusNotFound, ErrorMessage{
Message: fmt.Sprintf("User not found: %s", email),
})
}
// 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
httpClient := &http.Client{Timeout: 10 * time.Second}
permitConfig := ctrl.getPermitConfig()
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 {
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email)
usermanagement.LogUserAdminAction(
ctrl.cfg.GetAuthLogger(),
usermanagement.ActionDelete,
usermanagement.SystemCognito,
adminUser.Email,
string(email),
"",
usermanagement.ResultFailure,
fmt.Sprintf("User not found in Cognito: %v", err),
ctx.Response().Header().Get(echo.HeaderXRequestID),
)
return ctx.JSON(http.StatusNotFound, ErrorMessage{
Message: fmt.Sprintf("User not found: %s", email),
})
}
// 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
httpClient := &http.Client{Timeout: 10 * time.Second}
permitConfig := ctrl.getPermitConfig()
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 {
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email)
usermanagement.LogUserAdminAction(
ctrl.cfg.GetAuthLogger(),
usermanagement.ActionDisable,
usermanagement.SystemCognito,
adminUser.Email,
string(email),
"",
usermanagement.ResultFailure,
fmt.Sprintf("User not found in Cognito: %v", err),
ctx.Response().Header().Get(echo.HeaderXRequestID),
)
return ctx.JSON(http.StatusNotFound, ErrorMessage{
Message: fmt.Sprintf("User not found: %s", email),
})
}
// 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: &timestamp,
}
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 {
ctrl.cfg.GetAuthLogger().Error("Failed to get user from Cognito", "error", err, "email", email)
usermanagement.LogUserAdminAction(
ctrl.cfg.GetAuthLogger(),
usermanagement.ActionEnable,
usermanagement.SystemCognito,
adminUser.Email,
string(email),
"",
usermanagement.ResultFailure,
fmt.Sprintf("User not found in Cognito: %v", err),
ctx.Response().Header().Get(echo.HeaderXRequestID),
)
return ctx.JSON(http.StatusNotFound, ErrorMessage{
Message: fmt.Sprintf("User not found: %s", email),
})
}
// 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: &timestamp,
}
return ctx.JSON(http.StatusOK, response)
}