Anti-abuse hardening: hourly posting rate limit + captcha on facility registration
CI/CD / CI · dotnet build (push) Successful in 26s
CI/CD / Deploy · hamkadr (push) Successful in 40s

- SubmissionGuard.PostingRateExceededAsync: max 20 new listings (shifts+jobs) per account per rolling hour, enforced in PostJob + PostShift
- Captcha + spam-name screen added to /Employer/RegisterFacility

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 09:45:12 +03:30
parent 0587e040d9
commit 178e44c4da
5 changed files with 48 additions and 2 deletions
@@ -53,6 +53,18 @@ public class SubmissionGuard
return candidates.Any(c => Normalize(c) == t);
}
/// <summary>Max new listings (shifts + jobs) one account may post per rolling hour.</summary>
public const int MaxListingsPerHour = 20;
/// <summary>True if this owner has hit the hourly posting cap (flood protection).</summary>
public async Task<bool> PostingRateExceededAsync(int ownerUserId)
{
var since = DateTime.UtcNow.AddHours(-1);
var shifts = await _db.Shifts.CountAsync(s => s.Facility.OwnerUserId == ownerUserId && s.CreatedAt >= since);
var jobs = await _db.JobOpenings.CountAsync(j => j.Facility.OwnerUserId == ownerUserId && j.CreatedAt >= since);
return shifts + jobs >= MaxListingsPerHour;
}
/// <summary>Has this visitor already applied (Apply event) to this shift/job?</summary>
public Task<bool> AlreadyAppliedAsync(string visitorId, int? shiftId, int? jobId) =>
_db.InterestEvents.AnyAsync(e => e.VisitorId == visitorId