{"record":{"id":"5a7e1392c4d0a105","repo":"makeplane/plane","slug":"5100","errorCode":"5100","errorMessage":"EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN","messagePattern":"EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN","errorType":"exception","errorClass":"AuthenticationException","httpStatus":null,"severity":"warning","filePath":"apps/api/plane/authentication/provider/credentials/magic_code.py","lineNumber":92,"sourceCode":"\n    def initiate(self):\n        ## Generate a random token\n        token = str(secrets.randbelow(900000) + 100000)\n\n        ri = redis_instance()\n\n        key = \"magic_\" + str(self.key)\n\n        # Check if the key already exists in python\n        if ri.exists(key):\n            data = json.loads(ri.get(key))\n\n            current_attempt = data[\"current_attempt\"] + 1\n\n            if data[\"current_attempt\"] > 2:\n                email = str(self.key).replace(\"magic_\", \"\", 1)\n                if User.objects.filter(email=email).exists():\n                    raise AuthenticationException(\n                        error_code=AUTHENTICATION_ERROR_CODES[\"EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN\"],\n                        error_message=\"EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN\",\n                        payload={\"email\": str(email)},\n                    )\n                else:\n                    raise AuthenticationException(\n                        error_code=AUTHENTICATION_ERROR_CODES[\"EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP\"],\n                        error_message=\"EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP\",\n                        payload={\"email\": self.key},\n                    )\n\n            value = {\n                \"current_attempt\": current_attempt,\n                \"email\": str(self.key),\n                \"token\": token,\n            }\n            expiry = 600\n            ri.set(key, json.dumps(value), ex=expiry)","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/makeplane/plane/blob/1c8a60f858d8472aa56e29994ec1c7926da2c6ce/apps/api/plane/authentication/provider/credentials/magic_code.py#L74-L110","documentation":"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.","triggerScenarios":"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.","commonSituations":"User spamming 'resend code', a misbehaving client auto-retrying, or an attacker probing the magic-code flow for a known email.","solutions":["Wait for the Redis key to expire (TTL 600s) or have the user slow down resend requests.","Throttle the client-side 'resend' button (e.g., 60s cooldown) to stay under the 3-issue budget.","If legitimately locked, an admin can delete the 'magic_<email>' Redis key to reset the counter."],"exampleFix":"// before: client auto-resends on every focus -> current_attempt > 2 -> 5100\n// after: client enforces a 60s resend cooldown and stops at 3 sends","handlingStrategy":"validation","validationCode":"import json\nfrom plane.settings.redis import redis_instance\n\ndef can_issue_magic_code(email: str) -> bool:\n    ri = redis_instance()\n    key = 'magic_' + str(email)\n    if not ri.exists(key):\n        return True\n    return json.loads(ri.get(key)).get('current_attempt', 0) <= 2","typeGuard":null,"tryCatchPattern":"try:\n    provider.initiate()\nexcept AuthenticationException as e:\n    if e.error_code == 5100:\n        tell_user_to_wait_then_retry(email=e.payload.get('email'))\n    else:\n        raise","preventionTips":["Enforce a client-side resend cooldown (>=60s).","Cap resend attempts to stay within the 3-issue budget per 600s window."],"tags":["authentication","magic-code","rate-limit","redis","signin"],"backgroundTag":null,"analyzedSha":"1c8a60f858d8472aa56e29994ec1c7926da2c6ce","analyzedAt":"2026-08-12T14:44:31.636Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}