updates on the otp updates on the customer app

This commit is contained in:
2026-09-15 11:50:12 +05:30
parent e8f4c0a593
commit 89321c9e06
17 changed files with 1072 additions and 69 deletions

View File

@@ -201,14 +201,38 @@ func issueCxOtp(cfg *config.Config, identifier, kind string) (resendAfter int, e
db.Rdb.Del(ctx, cxOtpTriesKey(identifier))
db.Rdb.Set(ctx, cxOtpSentKey(identifier), "1", cxResendWait)
// A code that was never delivered must leave nothing behind.
//
// The stored code, the resend cooldown and the rate-limit slot are all
// written BEFORE delivery is attempted, because they have to be — the code
// has to exist before it can be sent. But when sending fails, keeping them
// punishes the customer for the gateway's failure: they are told something
// went wrong, cannot resend until the cooldown expires, and have spent one
// of their five hourly codes — while a valid code they never received sits
// live in Redis for its full TTL.
//
// So unwind all three on failure. The customer can retry immediately, and
// nothing usable is left in Redis.
rollback := func() {
rctx, rcancel := context.WithTimeout(context.Background(), 3*time.Second)
defer rcancel()
db.Rdb.Del(rctx, cxOtpKey(identifier))
db.Rdb.Del(rctx, cxOtpSentKey(identifier))
// Give the slot back rather than deleting the window: DECR keeps the
// hourly window honest for codes that DID go out.
db.Rdb.Decr(rctx, cxOtpRateKey(identifier))
}
if kind == "email" {
if merr := mail.SendOTPEmail(cfg, identifier, code); merr != nil {
utils.Warn("cx auth: failed to send OTP email", "error", merr)
utils.Warn("cx auth: failed to send OTP email — rolling back the stored code", "error", merr)
rollback()
return 0, merr
}
} else {
if serr := sms.SendOTP(identifier, code); serr != nil {
utils.Warn("cx auth: failed to send OTP sms", "error", serr)
utils.Warn("cx auth: failed to send OTP sms — rolling back the stored code", "error", serr)
rollback()
return 0, serr
}
}
@@ -368,18 +392,37 @@ func CxVerifyOtp(cfg *config.Config) fiber.Handler {
var req struct {
Identifier string `json:"identifier"`
Code string `json:"code"`
Name string `json:"name"`
// Otp is a DEPRECATED alias for Code, and the only reason sign-in
// works for anyone on an already-installed build.
//
// The customer app was written against a spec that named this
// field "otp". The server only ever read "code", so req.Code was
// always empty, the empty-code branch below always fired, and
// EVERY sign-in failed with a 400 — a correct code failed exactly
// like a wrong one. Fixing the app alone would have left every
// customer locked out until they updated; accepting both keys
// fixes them all without a release.
//
// "code" stays the documented field. Remove this once the install
// base has moved on.
Otp string `json:"otp"`
Name string `json:"name"`
}
if err := c.BodyParser(&req); err != nil {
return utils.CxBadRequest(c, "We could not read that request")
}
code := strings.TrimSpace(req.Code)
if code == "" {
code = strings.TrimSpace(req.Otp)
}
identifier, kind, ok := normalizeIdentifier(req.Identifier)
if !ok || strings.TrimSpace(req.Code) == "" {
if !ok || code == "" {
return utils.CxBadRequest(c, "Enter the code we sent you")
}
if !consumeCxOtp(identifier, strings.TrimSpace(req.Code)) {
if !consumeCxOtp(identifier, code) {
return utils.CxFail(c, fiber.StatusUnauthorized, utils.CxErrInvalidOtp, "That code did not match")
}