makeplane/plane · warning · AuthenticationException

5100

5100

Error message

EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN

What it means

Raised in MagicCodeProvider.initiate (magic_code.py:92) when the rate limit on issuing magic codes is exceeded for an email. The Redis key 'magic_'+email holds {current_attempt, email, token}. When an existing key has data['current_attempt'] > 2 AND a User with that email exists, code 5100 (SIGN_IN variant) is raised with payload {email}. This guards the issue path, not the verify path.

Source

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

    def initiate(self):
        ## Generate a random token
        token = str(secrets.randbelow(900000) + 100000)

        ri = redis_instance()

        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)

View on GitHub (pinned to 1c8a60f858)

Solutions

  1. Wait for the Redis key to expire (TTL 600s) or have the user slow down resend requests.
  2. Throttle the client-side 'resend' button (e.g., 60s cooldown) to stay under the 3-issue budget.
  3. If legitimately locked, an admin can delete the 'magic_<email>' Redis key to reset the counter.

Example fix

// before: client auto-resends on every focus -> current_attempt > 2 -> 5100
// after: client enforces a 60s resend cooldown and stops at 3 sends
Defensive patterns

Strategy: validation

Validate before calling

import json
from plane.settings.redis import redis_instance

def can_issue_magic_code(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 == 5100:
        tell_user_to_wait_then_retry(email=e.payload.get('email'))
    else:
        raise

Prevention

When it happens

Trigger: Calling initiate() repeatedly for the same email: each call increments current_attempt. Once it exceeds 2 (i.e., the 4th+ request within the 600s TTL), the existence check decides SIGN_IN vs SIGN_UP. Existing user -> 5100. Note the off-by-one: the check uses the pre-increment value, so the limit triggers a step later than the counter name implies.

Common situations: User spamming 'resend code', a misbehaving client auto-retrying, or an attacker probing the magic-code flow for a known email.

Related errors


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