fix: validate product URLs in offer edits (#137) and reorder email validation before rate-limit (#143)#178
Conversation
… email validation before rate-limit (profullstack#143) profullstack#137: Add isValidUrl validation to PATCH /api/affiliates/offers/[id] - Import isValidUrl from validation module - Validate product_url before saving, rejecting non-http(s) schemes - Rejects javascript:, ftp:, data:, and missing-scheme URLs with 400 - Allows empty string to clear the field profullstack#143: Reorder email validation and rate-limit checks in POST /api/referrals - Move email syntax validation BEFORE rate-limit checks - Only valid emails count toward throttle limits - Invalid email batches now return 400 instead of misleading 429 - Replace duplicate validation with dedup filter for already-invited emails Both fixes include regression tests.
Greptile SummaryThis PR fixes two validation gaps: PATCH offers now rejects non-http(s)
Confidence Score: 3/5The offers URL validation is safe to merge, but the referrals change introduces a normalization regression that breaks the already-invited deduplication guard for mixed-case email inputs. The referrals refactor moved email validation earlier (the intended fix), but lost the normalization step that the old code applied up-front. src/app/api/referrals/route.ts — the Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[POST /api/referrals] --> B{auth?}
B -- no --> Z1[401 Unauthorized]
B -- yes --> C{emails array valid?}
C -- no --> Z2[400 Bad Request]
C -- yes --> D["Filter valid emails (regex)\nvalidEmails = emails.filter(...)"]
D --> E{validEmails.length == 0?}
E -- yes --> Z3[400 No valid emails]
E -- no --> F[Query hourly count from DB]
F --> G{hourlyCount + validEmails.length > 10?}
G -- yes --> Z4[429 Too many invites]
G -- no --> H[Query daily count from DB]
H --> I{dailyCount + validEmails.length > 50?}
I -- yes --> Z5[429 Daily limit]
I -- no --> J["Query existing invites\nnormalizedEmails = emails.map(normalize)"]
J --> K["newValidEmails = validEmails.filter(not alreadyInvited)\n⚠️ normalization mismatch here"]
K --> L{newValidEmails.length == 0?}
L -- yes --> Z6[400 Already invited]
L -- no --> M[Insert referral rows]
M --> N[Send invite emails]
N --> O[200 Success]
|
| const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | ||
| const validEmails = emails.filter((e: string) => typeof e === "string" && emailRegex.test(e.trim().toLowerCase())); |
There was a problem hiding this comment.
Email normalization mismatch in duplicate check — the filter on line 141 compares original-casing values from
validEmails against the lowercased values in alreadyInvited (which come from the DB). For example, if "User@Test.Com" was already invited as "user@test.com", alreadyInvited.has("User@Test.Com") returns false, bypassing the guard and attempting a duplicate insert that will hit a DB constraint error. The old code normalized all emails at the top with .map(e => e.trim().toLowerCase()) before any filtering; the new code lost that normalization for validEmails.
| const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | |
| const validEmails = emails.filter((e: string) => typeof e === "string" && emailRegex.test(e.trim().toLowerCase())); | |
| const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | |
| const validEmails = emails | |
| .filter((e: string) => typeof e === "string" && emailRegex.test(e.trim().toLowerCase())) | |
| .map((e: string) => e.trim().toLowerCase()); |
Summary
Fixes two related validation issues on a single branch.
#137 — Affiliate offer edits accept invalid product URLs
Bug: The
PATCH /api/affiliates/offers/[id]endpoint accepted malformed product URLs (missing scheme,ftp://,javascript:,data:, etc.) without validation.Fix:
isValidUrlfrom@/lib/affiliates/validation(already exists)product_urlbefore saving: reject non-http(s)schemes with 400null)#143 — Referral invite validation reports rate limits for invalid email batches
Bug:
POST /api/referralsapplied rate-limit checks usingemails.length(raw input count) before validating email syntax. Invalid emails counted against throttle limits, producing misleading 429 errors instead of 400 validation errors.Fix:
validEmails.lengthcounts toward throttle limits (not rawemails.length)"No valid email addresses provided"instead of 429newValidEmails(valid + not-already-invited)Regression Tests
javascript:,ftp:,data:, missing scheme)Files Changed
src/app/api/affiliates/offers/[id]/route.ts— importisValidUrl, add product_url validationsrc/app/api/affiliates/offers/[id]/route.test.ts— add PATCH regression testssrc/app/api/referrals/route.ts— reorder email validation before rate-limitsrc/app/api/referrals/route.test.ts— add rate-limit order regression testsCloses #137
Closes #143