Disable sensitive challenge rate limit for test

This commit is contained in:
richjohnrambo
2026-09-02 10:51:15 +08:00
parent ac45223e39
commit 7f6995d8fe
2 changed files with 46 additions and 44 deletions
+38 -35
View File
@@ -57,19 +57,20 @@ const (
) )
const ( const (
imVerificationCodeLength = 6 imVerificationCodeLength = 6
imVerificationExpiresInSeconds = 300 imVerificationExpiresInSeconds = 300
imVerificationResendAfterSeconds = 60 imVerificationResendAfterSeconds = 60
imVerificationMaxAttempts = 5 imVerificationMaxAttempts = 5
imVerificationCreateRateWindow = time.Hour imVerificationCreateRateWindow = time.Hour
imActionTokenExpiresInSeconds = 300 imSensitiveChallengeRateLimitEnabled = false
imIndependentPasswordMaxAttempts = 5 imActionTokenExpiresInSeconds = 300
imIndependentPasswordLockSeconds = 300 imIndependentPasswordMaxAttempts = 5
imQRCodeDefaultExpiresInSeconds = 86400 imIndependentPasswordLockSeconds = 300
imQRCodeMaxExpiresInSeconds = 7 * 86400 imQRCodeDefaultExpiresInSeconds = 86400
imPublicUploadMaxBytes = 10 << 20 imQRCodeMaxExpiresInSeconds = 7 * 86400
groupMaxAdminCount = 10 imPublicUploadMaxBytes = 10 << 20
reportPreviewMaxRunes = 120 groupMaxAdminCount = 10
reportPreviewMaxRunes = 120
) )
type imHTTPResponse struct { type imHTTPResponse struct {
@@ -6245,29 +6246,31 @@ func currentUserPhone(tenantID types.TenantID, uid types.Uid) (string, error) {
func createSensitiveChallenge(wrt http.ResponseWriter, req *http.Request, principal *imPrincipal, action, phone string) { func createSensitiveChallenge(wrt http.ResponseWriter, req *http.Request, principal *imPrincipal, action, phone string) {
now := time.Now().UTC().Round(time.Millisecond) now := time.Now().UTC().Round(time.Millisecond)
phoneHash := hashIMSecret(principal.TenantID, "sensitive_action", phone, "phone") phoneHash := hashIMSecret(principal.TenantID, "sensitive_action", phone, "phone")
active, err := store.Verifications.GetActive(principal.TenantID, "sensitive_action", phoneHash, now) if imSensitiveChallengeRateLimitEnabled {
if err != nil && !errors.Is(err, types.ErrNotFound) { active, err := store.Verifications.GetActive(principal.TenantID, "sensitive_action", phoneHash, now)
writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil) if err != nil && !errors.Is(err, types.ErrNotFound) {
return writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil)
} return
if active != nil && now.Before(active.ResendAfter) { }
writeIMError(wrt, req, http.StatusTooManyRequests, 30020, imReasonVerificationRate, "verification rate limited", map[string]any{ if active != nil && now.Before(active.ResendAfter) {
"challenge_id": active.ChallengeID, writeIMError(wrt, req, http.StatusTooManyRequests, 30020, imReasonVerificationRate, "verification rate limited", map[string]any{
"retry_after_seconds": int(time.Until(active.ResendAfter).Seconds()) + 1, "challenge_id": active.ChallengeID,
}) "retry_after_seconds": int(time.Until(active.ResendAfter).Seconds()) + 1,
return })
} return
if allowed, _, ttl, err := store.AllowRate(principal.TenantID, "verification:create:sensitive_action:"+phoneHash, imVerificationMaxAttempts, imVerificationCreateRateWindow); err != nil { }
logs.Warn.Println("im sensitive verification create: redis rate limit failed", principal.TenantID, err) if allowed, _, ttl, err := store.AllowRate(principal.TenantID, "verification:create:sensitive_action:"+phoneHash, imVerificationMaxAttempts, imVerificationCreateRateWindow); err != nil {
} else if !allowed { logs.Warn.Println("im sensitive verification create: redis rate limit failed", principal.TenantID, err)
retryAfter := int(ttl.Seconds()) } else if !allowed {
if retryAfter < 1 { retryAfter := int(ttl.Seconds())
retryAfter = int(imVerificationCreateRateWindow.Seconds()) if retryAfter < 1 {
retryAfter = int(imVerificationCreateRateWindow.Seconds())
}
writeIMError(wrt, req, http.StatusTooManyRequests, 30020, imReasonVerificationRate, "verification rate limited", map[string]any{
"retry_after_seconds": retryAfter,
})
return
} }
writeIMError(wrt, req, http.StatusTooManyRequests, 30020, imReasonVerificationRate, "verification rate limited", map[string]any{
"retry_after_seconds": retryAfter,
})
return
} }
challengeID, err := randomIMToken(18) challengeID, err := randomIMToken(18)
if err != nil { if err != nil {
+8 -9
View File
@@ -2194,12 +2194,13 @@ func TestServeIMSecurityChallengeVerifyIssuesActionToken(t *testing.T) {
} }
} }
func TestServeIMSecurityChallengeCreateReturnsActiveChallengeBeforeRateLimit(t *testing.T) { func TestServeIMSecurityChallengeCreateSkipsRateLimitWhenDisabled(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
expectIMBearer(t, ctrl, "token-1", 7, 42) expectIMBearer(t, ctrl, "token-1", 7, 42)
oldUsers := store.Users oldUsers := store.Users
oldVerifications := store.Verifications oldVerifications := store.Verifications
oldSender := sendIMVerificationCode
now := time.Now().UTC() now := time.Now().UTC()
users := mock_store.NewMockUsersPersistenceInterface(ctrl) users := mock_store.NewMockUsersPersistenceInterface(ctrl)
users.EXPECT().Get(types.TenantID(7), types.Uid(42)).Return(&types.User{ users.EXPECT().Get(types.TenantID(7), types.Uid(42)).Return(&types.User{
@@ -2220,9 +2221,11 @@ func TestServeIMSecurityChallengeCreateReturnsActiveChallengeBeforeRateLimit(t *
}} }}
store.Users = users store.Users = users
store.Verifications = verifications store.Verifications = verifications
sendIMVerificationCode = func(_ types.TenantID, _, _, _, _ string) error { return nil }
defer func() { defer func() {
store.Users = oldUsers store.Users = oldUsers
store.Verifications = oldVerifications store.Verifications = oldVerifications
sendIMVerificationCode = oldSender
}() }()
req := httptest.NewRequest(http.MethodPost, "/v1/im/security/challenges", req := httptest.NewRequest(http.MethodPost, "/v1/im/security/challenges",
@@ -2232,15 +2235,11 @@ func TestServeIMSecurityChallengeCreateReturnsActiveChallengeBeforeRateLimit(t *
serveIMSecurityChallenges(rec, req) serveIMSecurityChallenges(rec, req)
if rec.Code != http.StatusTooManyRequests { if rec.Code != http.StatusOK {
t.Fatalf("status: got %d, want %d; body=%s", rec.Code, http.StatusTooManyRequests, rec.Body.String()) t.Fatalf("status: got %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
} }
data := decodeIMHTTPResponse(t, rec).Details.(map[string]any) if verifications.created == nil || verifications.created.Action != "close_youth_mode" {
if data["challenge_id"] != "challenge-active" { t.Fatalf("expected a new challenge while sensitive challenge rate limit is disabled: %#v", verifications.created)
t.Fatalf("expected active challenge id, got %#v", data)
}
if verifications.created != nil {
t.Fatalf("should not create a new challenge while resend is cooling down: %#v", verifications.created)
} }
} }