Security: add rate limiting, input validation, and filter category

validation
Backend:
- Add rate limiting middleware (mw/ratelimit.go) - in-memory per-IP
  limiter
- Apply rate limits per endpoint group:
  - Public read-only: 120/min
  - Registration: 10/min
  - Portfolio filters: 60/min
  - Authenticated users: 120/min
  - Admin: none (trusted)
- Add 256 char input length validation on portfolio endpoints
- Validate filter categories exist in DB before querying
- Secure GetImage endpoint: only allow UUID or numeric timestamp (15-20
  digits)
- Remove pattern-based image lookup to prevent enumeration
- Add services validation: name (100), duration (1-480), patch test
  (0-168)
  Frontend:
- Add maxlength=256 to portfolio tag/search inputs
- Add maxlength to registration: name (50), email (255), phone (20),
  password (72)
- Add maxlength=100 to service name input
This commit is contained in:
2026-02-20 12:03:14 +00:00
parent f9eec94f2f
commit a5a2ffd83e
9 changed files with 153 additions and 31 deletions
@@ -469,6 +469,7 @@
<input
type="search"
maxlength={256}
enterkeyhint="done"
autocomplete="off"
autocorrect="off"
@@ -551,6 +551,7 @@
<Input
id="service-name"
type="text"
maxlength={100}
placeholder="e.g., Haircut, Color, Blowdry"
bind:value={newService.name}
onblur={validateNameField}
+5
View File
@@ -371,6 +371,7 @@
<Input
id="firstName"
placeholder="John"
maxlength={50}
bind:value={formData.firstName}
onblur={() => (formData.firstName = formData.firstName.trim())}
required
@@ -381,6 +382,7 @@
<Input
id="lastName"
placeholder="Doe"
maxlength={50}
bind:value={formData.lastName}
onblur={() => (formData.lastName = formData.lastName.trim())}
required
@@ -394,6 +396,7 @@
id="phone"
type="tel"
placeholder="07123 456789 or +44 7123 456789"
maxlength={20}
value={formData.phone}
oninput={handlePhoneInput}
onblur={() => validatePhone(formData.phone)}
@@ -428,6 +431,7 @@
id="email"
type="email"
placeholder="john@example.com"
maxlength={255}
bind:value={formData.email}
onblur={() => validateEmail(formData.email)}
required
@@ -443,6 +447,7 @@
id="password"
type="password"
placeholder="Enter your password"
maxlength={72}
bind:value={formData.password}
required
/>
@@ -599,6 +599,7 @@
<div class="flex gap-2 lg:border-l lg:pl-4">
<Input
placeholder="Search tags"
maxlength={256}
bind:value={searchQuery}
onkeydown={(e) => {
if ((e as KeyboardEvent).key === 'Enter') applySearch();