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.
Background
LoginAuthenticationConverterwires aRateLimiterOperatoragainst the existingauthenticationconfig (3 attempts per 1m, IP-keyed) inapplication.yaml:TotpCodeAuthenticationConverter(the converter forPOST /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-validationnamed limiter toapplication.yaml(e.g., 5 attempts / 5m) and wrapTotpCodeAuthenticationConverter#convertwithRateLimiterOperator.of(...)keyed on the SESSION cookie value (withIpAddressUtils.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
LoginAuthenticationConverterkeys on the IP returned byIpAddressUtils.getClientIp(...), which readsX-Forwarded-For/X-Real-IP/CF-Connecting-IP. For the TOTP step, the surroundingTwoFactorAuthenticationmarker 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.