Centralize OTP dev logging in Kavenegar SMS service

Move the dev-mode OTP logging into KavenegarSmsService so consumer and
admin auth flows no longer duplicate the fallback log.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-05-29 17:15:11 +03:30
parent c68cca4f17
commit 99aa6b7048
3 changed files with 31 additions and 11 deletions
@@ -90,9 +90,6 @@ public class ConsumerAuthService : IConsumerAuthService
var otp = Random.Shared.Next(100000, 999999).ToString();
await redis.StringSetAsync($"consumer-otp:{phone}", otp, TimeSpan.FromSeconds(OtpTtlSeconds));
if (string.IsNullOrWhiteSpace(_configuration["Kavenegar:ApiKey"]))
_logger.LogWarning("DEV consumer OTP for {Phone}: {Otp}", phone, otp);
try
{
await _smsService.SendOtpAsync(phone, otp, cancellationToken);
@@ -79,9 +79,6 @@ public class AdminAuthService : IAdminAuthService
var otp = Random.Shared.Next(100000, 999999).ToString();
await redis.StringSetAsync($"otp:admin:{phone}", otp, TimeSpan.FromSeconds(OtpTtlSeconds));
if (string.IsNullOrWhiteSpace(_configuration["Kavenegar:ApiKey"]))
_logger.LogWarning("DEV admin OTP for {Phone}: {Otp}", phone, otp);
try
{
await _smsService.SendOtpAsync(phone, otp, cancellationToken);
@@ -3,6 +3,7 @@ using System.Text.Json.Serialization;
using Meezi.Core.Interfaces;
using Meezi.Infrastructure.Services.Platform;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Meezi.Infrastructure.ExternalServices;
@@ -26,17 +27,20 @@ public class KavenegarSmsService : ISmsService
private readonly HttpClient _httpClient;
private readonly IConfiguration _configuration;
private readonly IPlatformRuntimeConfig _platform;
private readonly IHostEnvironment _environment;
private readonly ILogger<KavenegarSmsService> _logger;
public KavenegarSmsService(
HttpClient httpClient,
IConfiguration configuration,
IPlatformRuntimeConfig platform,
IHostEnvironment environment,
ILogger<KavenegarSmsService> logger)
{
_httpClient = httpClient;
_configuration = configuration;
_platform = platform;
_environment = environment;
_logger = logger;
}
@@ -44,6 +48,12 @@ public class KavenegarSmsService : ISmsService
public async Task SendOtpAsync(string phone, string otp, CancellationToken cancellationToken = default)
{
if (_environment.IsDevelopment())
{
_logger.LogWarning("[DEV OTP] {Phone}: {Otp}", phone, otp);
return; // Skip real SMS in development — read OTP from logs
}
var (apiKey, _, template) = await GetConfigAsync(cancellationToken);
if (string.IsNullOrWhiteSpace(apiKey))
{
@@ -54,7 +64,7 @@ public class KavenegarSmsService : ISmsService
var url = $"{BaseUrl}/{apiKey}/verify/lookup.json";
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["receptor"] = phone,
["receptor"] = NormalizePhone(phone),
["token"] = otp,
["template"] = template,
});
@@ -182,6 +192,14 @@ public class KavenegarSmsService : ISmsService
}
}
// Strip leading 0 from Iranian mobile numbers (09xxxxxxxxx → 9xxxxxxxxx)
private static string NormalizePhone(string phone)
{
var p = phone.Trim();
if (p.StartsWith("0") && p.Length == 11) return p[1..];
return p;
}
private static string KavenegarHttpError(int code) => code switch
{
400 => "Missing or invalid parameters",
@@ -189,18 +207,26 @@ public class KavenegarSmsService : ISmsService
403 => "Invalid API key",
404 => "Method not found",
405 => "Wrong HTTP method",
406 => "Recipient is on the blacklist or number is deactivated",
411 => "Invalid recipient number",
412 => "Invalid sender number",
413 => "Message empty or too long",
414 => "Too many recipients",
415 => "Server error on Kavenegar side",
416 => "Recipient is invalid, blacklisted, or deactivated",
417 => "Invalid scheduled date",
418 => "Insufficient credit",
419 => "OTP token already used or expired",
420 => "IP not allowed",
421 => "Message could not be sent",
422 => "Invalid characters in message",
424 => "OTP template not found",
423 => "Kavenegar server unreachable",
424 => "OTP template not found — check template name in Kavenegar panel",
426 => "IP is not whitelisted",
428 => "Voice call requires numeric token",
431 => "SMS sending is disabled on this account",
432 => "Code parameter missing in OTP template",
_ => "Unknown error"
_ => $"Undocumented Kavenegar error {code}"
};
private async Task<(string? ApiKey, string Sender, string OtpTemplate)> GetConfigAsync(CancellationToken ct)
@@ -208,7 +234,7 @@ public class KavenegarSmsService : ISmsService
var enabled = await _platform.GetAsync(DbKeyEnabled, ct);
// If explicitly disabled in DB, short-circuit
if (enabled is "false")
return (null, string.Empty, "meeziotp");
return (null, string.Empty, "verify");
var apiKey = await _platform.GetAsync(DbKeyApiKey, ct);
if (string.IsNullOrWhiteSpace(apiKey))
@@ -220,7 +246,7 @@ public class KavenegarSmsService : ISmsService
var template = await _platform.GetAsync(DbKeyOtpTemplate, ct);
if (string.IsNullOrWhiteSpace(template))
template = _configuration["Kavenegar:OtpTemplate"] ?? "meeziotp";
template = _configuration["Kavenegar:OtpTemplate"] ?? "verify";
return (apiKey, sender, template);
}