fix(admin): admin OTP login always failed — rate-limit key clobbered the OTP
CI/CD / CI · API (dotnet build + test) (push) Successful in 47s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 33s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m10s
CI/CD / CI · Admin Web (tsc) (push) Successful in 40s
CI/CD / CI · Website (tsc) (push) Successful in 46s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Successful in 1m36s
CI/CD / CI · API (dotnet build + test) (push) Successful in 47s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 33s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m10s
CI/CD / CI · Admin Web (tsc) (push) Successful in 40s
CI/CD / CI · Website (tsc) (push) Successful in 46s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Successful in 1m36s
The admin send-otp used the SAME Redis key ("otp:admin:{phone}") for both the
OTP value and the per-hour attempts counter. After storing the code and SMSing
it, the rate-limit StringIncrementAsync ran on that same key, turning the stored
value into code+1 (e.g. SMS said 337835, Redis held 337836). verify-otp then
compared the entered code to the incremented value, never matched, and returned
INVALID_OTP → 400. Admin OTP login could never succeed.
Give the attempts counter its own key ("otp:admin:attempts:{phone}"), exactly
like the main API (otp:{phone} vs otp:attempts:{phone}). Password login was
unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -77,7 +77,10 @@ public class AdminAuthService : IAdminAuthService
|
|||||||
|
|
||||||
var redis = _redis.GetDatabase();
|
var redis = _redis.GetDatabase();
|
||||||
var maxAttempts = _configuration.GetValue("Auth:MaxOtpAttemptsPerHour", DefaultMaxOtpAttemptsPerHour);
|
var maxAttempts = _configuration.GetValue("Auth:MaxOtpAttemptsPerHour", DefaultMaxOtpAttemptsPerHour);
|
||||||
var attemptsKey = $"otp:admin:{phone}";
|
// MUST differ from the OTP value key ($"otp:admin:{phone}") — sharing one
|
||||||
|
// key made the rate-limit INCR overwrite the stored OTP (337835 → 337836),
|
||||||
|
// so every admin OTP verification failed. Mirror the main API's split keys.
|
||||||
|
var attemptsKey = $"otp:admin:attempts:{phone}";
|
||||||
if (maxAttempts > 0)
|
if (maxAttempts > 0)
|
||||||
{
|
{
|
||||||
var attempts = await redis.StringGetAsync(attemptsKey);
|
var attempts = await redis.StringGetAsync(attemptsKey);
|
||||||
|
|||||||
Reference in New Issue
Block a user