Compare commits

...
Author SHA1 Message Date
Raphael Michel 137059dd4e Customer account login: Add rate limit (Z#23252382) (#6760)
* Customer account login: Add rate limit

* Apply suggestion from @raphaelm
2026-11-11 10:29:51 +01:00
Raphael Michel bd0b55bdd3 Reusable media API: Limit data exposed on cross-organizer requests (Z#23253153) (#6761) 2026-11-11 10:29:02 +01:00
4 changed files with 32 additions and 3 deletions

No files matched your search

+2
View File
@@ -197,6 +197,8 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
'linked_giftcard',
'info',
'notes',
# When adding fields, check if they should be hidden in ReusableMediaViewSet
# for cross-organizer lookups
)
+11 -2
View File
@@ -183,9 +183,18 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
type=s.validated_data["type"],
identifier=s.validated_data["identifier"],
)
m.customer = None # not relevant for cross-organizer
s = self.get_serializer(m)
return Response({"result": s.data})
data = s.data
# Remove fields that are not relevant for cross-organizer usage
data["customer"] = None
data["linked_orderposition"] = None
data["linked_orderpositions"] = []
data["info"] = {}
data["notes"] = None
data["claim_token"] = None
data["label"] = None
return Response({"result": data})
except ReusableMedium.DoesNotExist:
mt = MEDIA_TYPES.get(s.validated_data["type"])
if mt:
+7
View File
@@ -75,6 +75,7 @@ class AuthenticationForm(forms.Form):
'unverified': _("You have not yet activated your account and set a password. Please click the link in the "
"email we sent you. In case you cannot find it, click \"Forgot your password?\" to receive "
"a new email."),
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
}
def __init__(self, request=None, *args, **kwargs):
@@ -93,6 +94,12 @@ class AuthenticationForm(forms.Form):
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')
if rate_limit("customer_login", include_ip_from_request=self.request, max_num=10, expire_time=300):
raise forms.ValidationError(
self.error_messages['rate_limit'],
code='rate_limit',
)
if email is not None and password:
try:
u = self.request.organizer.customers.get(email=email.lower(), provider__isnull=True)
+12 -1
View File
@@ -696,7 +696,7 @@ def test_medium_autocreate_giftcard(token_client, organizer, team):
@pytest.mark.django_db
def test_medium_lookup_cross_organizer(token_client, organizer, organizer2, org2_event, medium2, giftcard2):
def test_medium_lookup_cross_organizer(token_client, organizer, customer, organizer2, org2_event, medium2, giftcard2):
with scopes_disabled():
o = Order.objects.create(
code='FOO', event=org2_event, email='dummy@dummy.test',
@@ -708,7 +708,12 @@ def test_medium_lookup_cross_organizer(token_client, organizer, organizer2, org2
personalized=True)
op = o.positions.create(item=ticket, price=Decimal("14"))
medium2.linked_orderpositions.add(op)
medium2.customer = customer
medium2.linked_giftcard = giftcard2
medium2.claim_token = "1234"
medium2.info = {"foo": "bar"}
medium2.notes = "Internal notes"
medium2.label = "Max Muster"
medium2.save()
resp = token_client.post(
@@ -755,3 +760,9 @@ def test_medium_lookup_cross_organizer(token_client, organizer, organizer2, org2
assert resp.data["result"]["organizer"] == "partner"
assert resp.data["result"]["linked_giftcard"] is not None
assert resp.data["result"]["linked_orderposition"] is None
assert resp.data["result"]["linked_orderpositions"] == []
assert resp.data["result"]["customer"] is None
assert resp.data["result"]["info"] == {}
assert resp.data["result"]["notes"] is None
assert resp.data["result"]["claim_token"] is None
assert resp.data["result"]["label"] is None