makeplane/plane · warning · AuthenticationException

5102

5102

Error message

EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP

What it means

Raised in MagicCodeProvider.initiate (magic_code.py:98) — the SIGN_UP counterpart of 51. Same trigger (data['current_attempt'] > 2 on the issue path) but raised when NO User exists for the email, signaling an unregistered email hitting its code-issue limit. Code 5102, payload {email: self.key} (note: uses self.key, not the cleaned email).

Source

Thrown at apps/api/plane/authentication/provider/credentials/magic_code.py:98

        key = "magic_" + str(self.key)

        # Check if the key already exists in python
        if ri.exists(key):
            data = json.loads(ri.get(key))

            current_attempt = data["current_attempt"] + 1

            if data["current_attempt"] > 2:
                email = str(self.key).replace("magic_", "", 1)
                if User.objects.filter(email=email).exists():
                    raise AuthenticationException(
                        error_code=AUTHENTICATION_ERROR_CODES["EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN"],
                        error_message="EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN",
                        payload={"email": str(email)},
                    )
                else:
                    raise AuthenticationException(
                        error_code=AUTHENTICATION_ERROR_CODES["EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP"],
                        error_message="EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP",
                        payload={"email": self.key},
                    )

            value = {
                "current_attempt": current_attempt,
                "email": str(self.key),
                "token": token,
            }
            expiry = 600
            ri.set(key, json.dumps(value), ex=expiry)
        else:
            value = {"current_attempt": 0, "email": self.key, "token": token}
            expiry = 600

            ri.set(key, json.dumps(value), ex=expiry)
        # Reset the verify-attempt counter so each newly issued token starts

View on GitHub (pinned to 1c8a60f858)

Solutions

  1. Wait for the 600s TTL to expire so the Redis key clears, then request a fresh code.
  2. Verify the email is spelled correctly before requesting (a typo creates a throwaway unregistered entry).
  3. Admin can delete the 'magic_<email>' Redis key to reset the counter immediately.

Example fix

# before: bot spamming signup codes for unknown emails -> 5102
# redis-cli DEL magic:user@example.com
# after: counter reset; a fresh code can be issued
Defensive patterns

Strategy: validation

Validate before calling

import json
from plane.settings.redis import redis_instance

def can_issue_magic_code_signup(email: str) -> bool:
    ri = redis_instance()
    key = 'magic_' + str(email)
    if not ri.exists(key):
        return True
    return json.loads(ri.get(key)).get('current_attempt', 0) <= 2

Try / catch

try:
    provider.initiate()
except AuthenticationException as e:
    if e.error_code == 5102:
        tell_user_to_wait_or_verify_email(email=e.payload.get('email'))
    else:
        raise

Prevention

When it happens

Trigger: Repeated initiate() for an email with no User row, within the 600s Redis TTL, pushes current_attempt past 2; the existence check fails and AuthenticationException code 5102 is raised.

Common situations: Someone repeatedly requesting a signup magic code without completing it; a bot enumerating emails; a user who keeps typoing their email and resending.

Related errors


AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12). Data as JSON: /api/errors/f826da60b37b14e9. Report an issue: GitHub.