auth: store-review test login + matchmaking no-hang/watchdog
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 56s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m9s
CI/CD / Deploy - local stack (db + server + web) (push) Successful in 1m7s

- OtpService: a designated test phone (default 09120000000 / code 453115,
  overridable via Sms__TestPhone/Sms__TestCode) skips real SMS and always
  verifies — for Google Play / Bazaar / Myket reviewers. Give them these creds.
- Matchmaking UX: tapping a league now navigates to the matchmaking screen
  BEFORE awaiting the SignalR handshake, so the button can't freeze. Added a
  watchdog hint after 28s ("connection took too long, cancel & retry") so it
  never spins forever when the hub doesn't connect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-15 16:40:01 +03:30
parent a35acea7e4
commit 76c4b68a74
5 changed files with 29 additions and 2 deletions
+15
View File
@@ -18,6 +18,14 @@ public sealed class SmsOptions
public string DevCode { get; set; } = "1234";
public int TtlSeconds { get; set; } = 120;
/// <summary>
/// A reviewer/test login (Google Play, Bazaar, Myket): this exact phone never
/// triggers a real SMS and always accepts <see cref="TestCode"/>. Give these
/// to the store's review team. Set TestPhone empty to disable.
/// </summary>
public string TestPhone { get; set; } = "09120000000";
public string TestCode { get; set; } = "453115";
/* --- Rate limiting (applies to real SMS sends only; dev mode is unlimited) --- */
/// <summary>Minimum seconds between two OTP sends to the same phone (resend cooldown).</summary>
public int ResendCooldownSeconds { get; set; } = 60;
@@ -54,12 +62,18 @@ public sealed class OtpService
/// <summary>Dev mode = explicitly on, or no API key configured.</summary>
public bool IsDev => _opts.DevMode || string.IsNullOrWhiteSpace(_opts.ApiKey);
private bool IsTestPhone(string normalizedPhone) =>
!string.IsNullOrWhiteSpace(_opts.TestPhone) && normalizedPhone == Normalize(_opts.TestPhone);
/// <summary>Generate a code, store it, and send the SMS. Returns devCode only in dev mode.</summary>
public async Task<OtpResult> Request(string phone)
{
phone = Normalize(phone);
if (string.IsNullOrWhiteSpace(phone)) return new OtpResult(false, null, "INVALID_PHONE", 0);
// Store review/test login: never send an SMS for the designated test number.
if (IsTestPhone(phone)) return new OtpResult(true, null, null, 0);
// Dev mode never sends an SMS (fixed code) → no cost, no rate limit.
if (IsDev)
{
@@ -131,6 +145,7 @@ public sealed class OtpService
public bool Verify(string phone, string code)
{
phone = Normalize(phone);
if (IsTestPhone(phone)) return code == _opts.TestCode; // store-review test login
if (IsDev && code == _opts.DevCode) return true;
if (!_codes.TryGetValue(phone, out var e)) return false;
if (DateTime.UtcNow > e.Expires) { _codes.TryRemove(phone, out _); return false; }