Skip to content

Apply rate-limit operator to TOTP authentication path (parity with login path) #9972

@eddieran

Description

@eddieran

Background

LoginAuthenticationConverter wires a RateLimiterOperator against the existing authentication config (3 attempts per 1m, IP-keyed) in application.yaml:

// application/src/main/java/run/halo/app/security/authentication/login/LoginAuthenticationConverter.java
.transformDeferred(createIpBasedRateLimiter(exchange))
.onErrorMap(RequestNotPermitted.class, TooManyRequestsException::new);

TotpCodeAuthenticationConverter (the converter for POST /challenges/two-factor/totp) does not apply an analogous operator. The two converters in the same authentication pipeline therefore behave inconsistently under repeated client requests.

Proposal

Add a totp-validation named limiter to application.yaml (e.g., 5 attempts / 5m) and wrap TotpCodeAuthenticationConverter#convert with RateLimiterOperator.of(...) keyed on the SESSION cookie value (with IpAddressUtils.getClientIp(...) as fallback). Session-id keying matches the logical scope of the TOTP step (which is itself session-bound).

Why session-id and not IP-only

LoginAuthenticationConverter keys on the IP returned by IpAddressUtils.getClientIp(...), which reads X-Forwarded-For / X-Real-IP / CF-Connecting-IP. For the TOTP step, the surrounding TwoFactorAuthentication marker is already session-scoped, so session-id is the natural key for that step (and avoids the IP being re-used across logically separate session-attached attempts).

Out of scope for this issue

Changes to IpAddressUtils (e.g., adding a trusted-proxies allow-list to bound spoofable headers) are deliberately out of scope here — that touches several other call sites and warrants its own discussion.

PR

Will open a small PR that closes this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions