package auth import ( "context" "fmt" "time" "github.com/jackc/pgx/v5/pgxpool" ) // RoleManager handles role-based access control type RoleManager struct { db *pgxpool.Pool } // NewRoleManager creates a new role manager func NewRoleManager(db *pgxpool.Pool) *RoleManager { return &RoleManager{db: db} } // UserRole represents a user's role and track assignment type UserRole struct { Address string Track int Roles []string Approved bool ApprovedBy string ApprovedAt time.Time } // AssignTrack assigns a track level to a user address func (r *RoleManager) AssignTrack(ctx context.Context, address string, track int, approvedBy string) error { if track < 1 || track > 4 { return fmt.Errorf("invalid track level: %d (must be 1-4)", track) } query := ` INSERT INTO operator_roles (address, track_level, approved, approved_by, approved_at) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (address) DO UPDATE SET track_level = EXCLUDED.track_level, approved = EXCLUDED.approved, approved_by = EXCLUDED.approved_by, approved_at = EXCLUDED.approved_at, updated_at = NOW() ` _, err := r.db.Exec(ctx, query, address, track, true, approvedBy, time.Now()) if err != nil { return fmt.Errorf("failed to assign track: %w", err) } return nil } // GetUserRole gets the role and track for a user address func (r *RoleManager) GetUserRole(ctx context.Context, address string) (*UserRole, error) { var role UserRole query := ` SELECT address, track_level, roles, approved, approved_by, approved_at FROM operator_roles WHERE address = $1 ` err := r.db.QueryRow(ctx, query, address).Scan( &role.Address, &role.Track, &role.Roles, &role.Approved, &role.ApprovedBy, &role.ApprovedAt, ) if err != nil { // User not found, return default Track 1 return &UserRole{ Address: address, Track: 1, Roles: []string{}, Approved: false, }, nil } return &role, nil } // ApproveUser approves a user for their assigned track func (r *RoleManager) ApproveUser(ctx context.Context, address string, approvedBy string) error { query := ` UPDATE operator_roles SET approved = TRUE, approved_by = $2, approved_at = NOW(), updated_at = NOW() WHERE address = $1 ` result, err := r.db.Exec(ctx, query, address, approvedBy) if err != nil { return fmt.Errorf("failed to approve user: %w", err) } if result.RowsAffected() == 0 { return fmt.Errorf("user not found") } return nil } // RevokeUser revokes a user's approval func (r *RoleManager) RevokeUser(ctx context.Context, address string) error { query := ` UPDATE operator_roles SET approved = FALSE, approved_at = NULL, updated_at = NOW() WHERE address = $1 ` result, err := r.db.Exec(ctx, query, address) if err != nil { return fmt.Errorf("failed to revoke user: %w", err) } if result.RowsAffected() == 0 { return fmt.Errorf("user not found") } return nil } // AddIPWhitelist adds an IP address to the whitelist for an operator func (r *RoleManager) AddIPWhitelist(ctx context.Context, operatorAddress string, ipAddress string, description string) error { query := ` INSERT INTO operator_ip_whitelist (operator_address, ip_address, description) VALUES ($1, $2, $3) ON CONFLICT (operator_address, ip_address) DO UPDATE SET description = EXCLUDED.description ` _, err := r.db.Exec(ctx, query, operatorAddress, ipAddress, description) if err != nil { return fmt.Errorf("failed to add IP to whitelist: %w", err) } return nil } // IsIPWhitelisted checks if an IP address is whitelisted for an operator func (r *RoleManager) IsIPWhitelisted(ctx context.Context, operatorAddress string, ipAddress string) (bool, error) { var count int query := ` SELECT COUNT(*) FROM operator_ip_whitelist WHERE operator_address = $1 AND ip_address = $2 ` err := r.db.QueryRow(ctx, query, operatorAddress, ipAddress).Scan(&count) if err != nil { return false, fmt.Errorf("failed to check IP whitelist: %w", err) } return count > 0, nil } // LogOperatorEvent logs an operator event for audit purposes func (r *RoleManager) LogOperatorEvent(ctx context.Context, eventType string, chainID *int, operatorAddress string, targetResource string, action string, details map[string]interface{}, ipAddress string, userAgent string) error { query := ` INSERT INTO operator_events (event_type, chain_id, operator_address, target_resource, action, details, ip_address, user_agent) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ` // Convert details map to JSONB detailsJSON := map[string]interface{}(details) _, err := r.db.Exec(ctx, query, eventType, chainID, operatorAddress, targetResource, action, detailsJSON, ipAddress, userAgent) if err != nil { return fmt.Errorf("failed to log operator event: %w", err) } return nil }