After LOGIN_MAX_FAILURES consecutive failed attempts from the same source IP within LOGIN_WINDOW_SECONDS, POST /api/v1/auth/token returns HTTP 429 with a Retry-After header for LOGIN_COOLDOWN_SECONDS. A successful login resets the counter. Trusted upstream proxy IPs/CIDRs can be declared via LOGIN_TRUSTED_PROXY_IPS so X-Forwarded-For is honoured correctly behind nginx ingress or similar reverse proxies. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1.8 KiB
API Contract: Authentication
POST /api/v1/auth/token
Authenticates the owner and returns a JWT access token.
This endpoint is modified by feature 009 to enforce brute-force protection. All previous behaviour is preserved. One new response code (429) is added.
Request
POST /api/v1/auth/token
Content-Type: application/json
{
"username": "string",
"password": "string"
}
Responses
200 OK — Credentials accepted
{
"access_token": "<jwt>",
"token_type": "bearer",
"expires_in": 86400
}
Side effect: resets the failure counter for the caller's IP address.
401 Unauthorized — Credentials rejected
{
"detail": "Invalid credentials",
"code": "invalid_credentials"
}
Side effect: increments the failure counter for the caller's IP address. If the
counter reaches LOGIN_MAX_FAILURES, subsequent requests from this IP will receive
429 until the cooldown expires.
429 Too Many Requests — Source blocked after repeated failures
This response is new in feature 009.
HTTP/1.1 429 Too Many Requests
Retry-After: 900
Content-Type: application/json
{
"detail": "Too many failed login attempts. Please try again later.",
"code": "login_rate_limited"
}
The Retry-After header value is the configured cooldown duration in seconds (default: 900).
It reflects the maximum possible wait, not the exact remaining lockout time.
No credentials are verified when this response is returned — the request is rejected before authentication is attempted.
Notes
- The failure counter is per source IP address (TCP peer, not forwarded headers).
- Threshold values (
LOGIN_MAX_FAILURES,LOGIN_WINDOW_SECONDS,LOGIN_COOLDOWN_SECONDS) are not disclosed in any response. - Counters are in-memory and reset on process restart.