Skip to content

fix(accounts): return 400 instead of 500 on malformed login requests#2512

Open
jptamayo76 wants to merge 1 commit into
amidaware:developfrom
jptamayo76:fix/loginview-500-on-malformed-request
Open

fix(accounts): return 400 instead of 500 on malformed login requests#2512
jptamayo76 wants to merge 1 commit into
amidaware:developfrom
jptamayo76:fix/loginview-500-on-malformed-request

Conversation

@jptamayo76

Copy link
Copy Markdown

What

Make LoginViewV2 return a clean 400 Bad credentials instead of an unhandled HTTP 500 when it receives a malformed login request.

Thanks again for the project! I hit a couple of 500s in the API logs coming from the v2 login endpoint while testing some edge cases, and both turned out to be unhandled exceptions on inputs that should just be rejected as bad credentials. Small hardening fix below.

The problem

In accounts.views.LoginViewV2.post, after the credentials serializer validates, the code does:

token = request.data["twofactor"]
totp = pyotp.TOTP(user.totp_key)
...
elif totp.verify(token, valid_window=10):
    valid = True

Two inputs make this raise instead of returning a normal response:

  1. User with valid username/password but no 2FA set up. Such a user has an empty totp_key, so pyotp.TOTP("").verify(...) raises (base64 decode of an empty secret) → the request 500s instead of being rejected.
  2. Request body without a twofactor field. request.data["twofactor"] raises KeyError → 500.

Neither is a server error — they're just malformed/incomplete login attempts that should get the same Bad credentials response every other failure path already returns. As-is they produce noisy 500s (and stack traces in the logs) that a client can trigger with a crafted POST.

The fix

Guard both cases so they fall through to the existing clean 400:

# empty totp_key -> reject instead of crashing pyotp
if not user.totp_key:
    return notify_error("Bad credentials")

# missing "twofactor" -> "" fails verification -> clean 400, no KeyError
token = request.data.get("twofactor", "")
totp = pyotp.TOTP(user.totp_key)

No behavior change for valid logins, and every rejection path still returns the identical Bad credentials message (so this doesn't leak whether the username/password was correct). No new imports.

Testing

  • POST valid username/password with no twofactor field → now 400 Bad credentials (was 500).
  • POST for a user that hasn't completed TOTP enrollment → now 400 Bad credentials (was 500).
  • Normal 2FA login with a correct token still succeeds; a wrong token still returns 400 Bad credentials.
  • python -m py_compile on the module is clean.

LoginViewV2 crashed with an unhandled HTTP 500 in two cases:

- a user who never finished 2FA setup has an empty totp_key, so
  pyotp.TOTP("").verify(...) raised; and
- request.data["twofactor"] raised KeyError when the field was absent.

Guard the empty totp_key and read twofactor with .get(), so both paths
fall through to the existing clean "Bad credentials" 400 response.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant