dae7bd4cc6
implement GET /identity * GET /identity
1061 lines
34 KiB
Go
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: ×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 {
|
|
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: ×tamp,
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, response)
|
|
}
|