Files
hamkadr/src/JobsMedical.Web/Pages/Employer/RegisterFacility.cshtml.cs
T
soroush.asadi 178e44c4da
CI/CD / CI · dotnet build (push) Successful in 26s
CI/CD / Deploy · hamkadr (push) Successful in 40s
Anti-abuse hardening: hourly posting rate limit + captcha on facility registration
- 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>
2026-06-04 09:45:12 +03:30

105 lines
3.7 KiB
C#

using System.Security.Claims;
using JobsMedical.Web.Data;
using JobsMedical.Web.Models;
using JobsMedical.Web.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace JobsMedical.Web.Pages.Employer;
[Authorize]
public class RegisterFacilityModel : PageModel
{
private readonly AppDbContext _db;
private readonly CaptchaService _captcha;
public RegisterFacilityModel(AppDbContext db, CaptchaService captcha)
{
_db = db;
_captcha = captcha;
}
public List<City> Cities { get; private set; } = new();
public List<District> Districts { get; private set; } = new();
public string CaptchaQuestion { get; private set; } = "";
[BindProperty] public string? CaptchaToken { get; set; }
[BindProperty] public string? CaptchaAnswer { get; set; }
[BindProperty] public string Name { get; set; } = "";
[BindProperty] public FacilityType Type { get; set; }
[BindProperty] public int CityId { get; set; }
[BindProperty] public int? DistrictId { get; set; }
[BindProperty] public string? Address { get; set; }
[BindProperty] public string? Phone { get; set; }
[BindProperty] public string? BaleId { get; set; }
[BindProperty] public double? Lat { get; set; }
[BindProperty] public double? Lng { get; set; }
public string? Error { get; private set; }
public async Task OnGetAsync() { await LoadListsAsync(); NewCaptcha(); }
public async Task<IActionResult> OnPostAsync()
{
await LoadListsAsync();
if (!_captcha.Verify(CaptchaToken, CaptchaAnswer))
{ Error = "پاسخ سؤال امنیتی نادرست است."; NewCaptcha(); return Page(); }
if (string.IsNullOrWhiteSpace(Name) || CityId == 0)
{
Error = "نام مرکز و شهر الزامی است.";
NewCaptcha();
return Page();
}
if (SubmissionGuard.ContainsSpam(Name))
{ Error = "نام مرکز نامعتبر به‌نظر می‌رسد."; NewCaptcha(); return Page(); }
var userId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
var facility = new Facility
{
Name = Name.Trim(),
Type = Type,
CityId = CityId,
DistrictId = DistrictId,
Address = Address?.Trim(),
Phone = Phone?.Trim(),
BaleId = BaleId?.Trim(),
Lat = Lat,
Lng = Lng,
OwnerUserId = userId,
IsVerified = false, // platform verifies later
};
_db.Facilities.Add(facility);
// Promote the user to FacilityAdmin (keep Admin if already admin) and refresh the cookie.
var user = await _db.Users.FindAsync(userId);
if (user is not null && user.Role == UserRole.Doctor)
{
user.Role = UserRole.FacilityAdmin;
await _db.SaveChangesAsync();
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
AuthHelper.BuildPrincipal(user));
}
else
{
await _db.SaveChangesAsync();
}
return RedirectToPage("/Employer/Index");
}
private async Task LoadListsAsync()
{
Cities = await _db.Cities.OrderByDescending(c => c.IsActive).ThenBy(c => c.Name).ToListAsync();
Districts = await _db.Districts.Where(d => d.IsActive).OrderBy(d => d.Name).ToListAsync();
}
private void NewCaptcha()
{
var (q, token) = _captcha.Create();
CaptchaQuestion = q;
CaptchaToken = token;
}
}