2026-06-04 10:27:21 +03:30
|
|
|
|
using JobsMedical.Web.Services.Scraping;
|
2026-06-03 01:43:55 +03:30
|
|
|
|
using Microsoft.Extensions.Caching.Memory;
|
2026-06-04 19:21:47 +03:30
|
|
|
|
using Microsoft.Extensions.Hosting;
|
2026-06-03 01:43:55 +03:30
|
|
|
|
|
|
|
|
|
|
namespace JobsMedical.Web.Services;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-06-04 10:27:21 +03:30
|
|
|
|
/// One-time-code issuing/verification. Codes live in memory for 5 minutes. When SMS is configured
|
|
|
|
|
|
/// (admin settings) the code is sent via the gateway and NOT returned; otherwise it's returned so
|
|
|
|
|
|
/// the dev login page can display it.
|
2026-06-03 01:43:55 +03:30
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class OtpService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly IMemoryCache _cache;
|
2026-06-04 10:27:21 +03:30
|
|
|
|
private readonly ISmsSender _sms;
|
|
|
|
|
|
private readonly SettingsService _settings;
|
2026-06-04 19:21:47 +03:30
|
|
|
|
private readonly IHostEnvironment _env;
|
2026-06-04 10:27:21 +03:30
|
|
|
|
|
2026-06-04 19:21:47 +03:30
|
|
|
|
public OtpService(IMemoryCache cache, ISmsSender sms, SettingsService settings, IHostEnvironment env)
|
2026-06-04 10:27:21 +03:30
|
|
|
|
{
|
|
|
|
|
|
_cache = cache;
|
|
|
|
|
|
_sms = sms;
|
|
|
|
|
|
_settings = settings;
|
2026-06-04 19:21:47 +03:30
|
|
|
|
_env = env;
|
2026-06-04 10:27:21 +03:30
|
|
|
|
}
|
2026-06-03 01:43:55 +03:30
|
|
|
|
|
|
|
|
|
|
private static string Key(string phone) => $"otp:{Normalize(phone)}";
|
|
|
|
|
|
|
2026-06-04 10:27:21 +03:30
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Generate + store a 5-digit code. If SMS is enabled, send it and return null (don't reveal);
|
|
|
|
|
|
/// otherwise return the code so the dev login screen can show it.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<string?> IssueAsync(string phone)
|
2026-06-03 01:43:55 +03:30
|
|
|
|
{
|
|
|
|
|
|
var code = Random.Shared.Next(10000, 100000).ToString();
|
|
|
|
|
|
_cache.Set(Key(phone), code, TimeSpan.FromMinutes(5));
|
2026-06-04 10:27:21 +03:30
|
|
|
|
|
|
|
|
|
|
var settings = await _settings.GetAsync();
|
2026-06-04 19:21:47 +03:30
|
|
|
|
// In Development (local Docker / dotnet run) never call the SMS gateway — always show the
|
|
|
|
|
|
// code on the login screen. In Production, send via SMS only when it's enabled.
|
|
|
|
|
|
if (settings.SmsEnabled && !_env.IsDevelopment())
|
2026-06-04 10:27:21 +03:30
|
|
|
|
{
|
|
|
|
|
|
await _sms.SendOtpAsync(phone, code, settings);
|
2026-06-04 19:21:47 +03:30
|
|
|
|
return null; // sent via SMS — don't reveal it
|
2026-06-04 10:27:21 +03:30
|
|
|
|
}
|
2026-06-04 19:21:47 +03:30
|
|
|
|
return code; // local/dev (or SMS off): surface it on screen
|
2026-06-03 01:43:55 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool Verify(string phone, string code)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_cache.TryGetValue(Key(phone), out string? stored) && stored == code?.Trim())
|
|
|
|
|
|
{
|
|
|
|
|
|
_cache.Remove(Key(phone));
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Normalize Iranian mobile numbers (Persian digits → Latin, strip spaces).</summary>
|
|
|
|
|
|
public static string Normalize(string phone)
|
|
|
|
|
|
{
|
|
|
|
|
|
var chars = phone.Trim().ToCharArray();
|
|
|
|
|
|
for (var i = 0; i < chars.Length; i++)
|
|
|
|
|
|
if (chars[i] >= '۰' && chars[i] <= '۹') chars[i] = (char)('0' + (chars[i] - '۰'));
|
|
|
|
|
|
return new string(chars).Replace(" ", "").Replace("-", "");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|