fix: resolve pr remarks

This commit is contained in:
Simon 2026-01-23 14:00:18 +01:00
parent a92f503752
commit b4657cae23
Signed by: simon
GPG key ID: 40E7A58C4AA1EDB2
6 changed files with 139 additions and 83 deletions

View file

@ -778,9 +778,32 @@ defmodule MvWeb.MemberLive.Index do
|> Ash.Query.new()
|> Ash.Query.select(@overview_fields)
# Load custom field values for visible custom fields (based on user selection)
# Load custom field values for visible custom fields AND active boolean filters
# This ensures boolean filters work even when the custom field is not visible in overview
visible_custom_field_ids = socket.assigns[:visible_custom_field_ids] || []
query = load_custom_field_values(query, visible_custom_field_ids)
# Get IDs of active boolean filters (whitelisted against boolean_custom_fields)
# Convert boolean_custom_fields list to map for efficient lookup (consistent with maybe_update_boolean_filters)
boolean_custom_fields_map =
socket.assigns.boolean_custom_fields
|> Map.new(fn cf -> {to_string(cf.id), cf} end)
active_boolean_filter_ids =
socket.assigns.boolean_custom_field_filters
|> Map.keys()
|> Enum.filter(fn id_str ->
# Validate UUID format and check against whitelist
String.length(id_str) <= @max_uuid_length &&
match?({:ok, _}, Ecto.UUID.cast(id_str)) &&
Map.has_key?(boolean_custom_fields_map, id_str)
end)
# Union of visible IDs and active filter IDs
ids_to_load =
(visible_custom_field_ids ++ active_boolean_filter_ids)
|> Enum.uniq()
query = load_custom_field_values(query, ids_to_load)
# Load membership fee cycles for status display
query = MembershipFeeStatus.load_cycles_for_members(query, socket.assigns.show_current_cycle)
@ -1233,35 +1256,43 @@ defmodule MvWeb.MemberLive.Index do
|> Map.new(fn cf -> {to_string(cf.id), cf} end)
# Parse all boolean filter parameters
# Security: Use reduce_while to abort early after @max_boolean_filters to prevent DoS attacks
# This protects CPU/Parsing costs, not just memory/state
# We count processed parameters (not just valid filters) to protect against parsing DoS
prefix_length = String.length(@boolean_filter_prefix)
filters =
{filters, total_processed} =
params
|> Enum.filter(fn {key, _value} -> String.starts_with?(key, @boolean_filter_prefix) end)
|> Enum.reduce(%{}, fn {key, value_str}, acc ->
process_boolean_filter_param(
key,
value_str,
prefix_length,
boolean_custom_fields,
acc
)
|> Enum.reduce_while({%{}, 0}, fn {key, value_str}, {acc, count} ->
if count >= @max_boolean_filters do
Logger.warning(
"Too many boolean filter parameters in request (#{count} processed), limiting to #{@max_boolean_filters} to prevent DoS"
)
{:halt, {acc, count}}
else
new_acc =
process_boolean_filter_param(
key,
value_str,
prefix_length,
boolean_custom_fields,
acc
)
# Increment counter for each processed parameter (DoS protection)
# Note: We count processed params, not just valid filters, to protect parsing costs
{:cont, {new_acc, count + 1}}
end
end)
# Security: Limit number of filters to prevent DoS attacks
# Maximum @max_boolean_filters boolean filters allowed per request
filters =
if map_size(filters) > @max_boolean_filters do
Logger.warning(
"Too many boolean filters requested: #{map_size(filters)}, limiting to #{@max_boolean_filters}"
)
filters
|> Enum.take(@max_boolean_filters)
|> Enum.into(%{})
else
filters
end
# Log additional warning if we hit the limit
if total_processed >= @max_boolean_filters do
Logger.warning(
"Boolean filter limit reached: processed #{total_processed} parameters, accepted #{map_size(filters)} valid filters (max: #{@max_boolean_filters})"
)
end
assign(socket, :boolean_custom_field_filters, filters)
end
@ -1340,8 +1371,8 @@ defmodule MvWeb.MemberLive.Index do
# This ensures no raw user input is ever passed to filter functions.
#
# Returns:
# - `:true` for "true" string
# - `:false` for "false" string
# - `true` for "true" string
# - `false` for "false" string
# - `nil` for any other value
defp determine_boolean_filter("true"), do: true
defp determine_boolean_filter("false"), do: false