{"record":{"id":"10af3841d5c9803d","repo":"makeplane/plane","slug":"5019","errorCode":"5019","errorMessage":"USER_ACCOUNT_DEACTIVATED","messagePattern":"USER_ACCOUNT_DEACTIVATED","errorType":"exception","errorClass":"AuthenticationException","httpStatus":null,"severity":"error","filePath":"apps/api/plane/authentication/adapter/base.py","lineNumber":327,"sourceCode":"    def complete_login_or_signup(self):\n        # Get email\n        email = self.user_data.get(\"email\")\n\n        # Sanitize email\n        email = self.sanitize_email(email)\n\n        # Check if the user is present\n        user = User.objects.filter(email=email).first()\n\n        # Reject explicitly-deactivated accounts (GHSA-rmmf-rj2q-3rrg).\n        # The deactivation endpoint always sets last_logout_time, so using it\n        # as the discriminator is more reliable than last_login_time: a\n        # provisioned account that was never deactivated has last_logout_time=None\n        # and is allowed through for its first login; an account deactivated via\n        # the API has last_logout_time set and is blocked regardless of whether\n        # it had previously logged in.\n        if user and not user.is_active and user.last_logout_time is not None:\n            raise AuthenticationException(\n                error_code=AUTHENTICATION_ERROR_CODES[\"USER_ACCOUNT_DEACTIVATED\"],\n                error_message=\"USER_ACCOUNT_DEACTIVATED\",\n                payload={\"email\": email},\n            )\n\n        # Reject bot service accounts (BOT_USER_LOGIN_FORBIDDEN). Bots (is_bot=True,\n        # e.g. the WORKSPACE_SEED bot) are internal identities that act only through\n        # API tokens; they must never be assumable via the interactive login/signup\n        # flow (email/password, magic code, or any OAuth provider). A brand-new\n        # signup can never be a bot — bots are provisioned internally, never through\n        # this path — so guarding on an existing `user` record is sufficient.\n        if user and user.is_bot:\n            raise AuthenticationException(\n                error_code=AUTHENTICATION_ERROR_CODES[\"BOT_USER_LOGIN_FORBIDDEN\"],\n                error_message=\"BOT_USER_LOGIN_FORBIDDEN\",\n                payload={\"email\": email},\n            )\n","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/makeplane/plane/blob/1c8a60f858d8472aa56e29994ec1c7926da2c6ce/apps/api/plane/authentication/adapter/base.py#L309-L345","documentation":"Thrown at base.py:327 when an email matches a User row that is both inactive (is_active=False) and has a recorded last_logout_time. It is the explicit guard for GHSA-rmmf-rj2q-3rrg: an account deactivated via the deactivation API must never log back in through any interactive flow. Using last_logout_time (rather than last_login_time) as the discriminator lets a never-logged-in provisioned account still complete its first login.","triggerScenarios":"The adapter (base.py:~310) looks up User.objects.filter(email=email).first() during any sign-in/sign-up. If user.is_active is False AND user.last_logout_time is not None, AuthenticationException is raised with code 5019 and payload {email}. Reached from email/password, magic-code, and every OAuth provider because they all funnel through this shared pre-flight.","commonSituations":"User was deactivated by an admin or via the account-deactivation endpoint, then attempts to log in again before being reactivated. Also seen in test/staging environments that copy production users without resetting is_active/last_logout_time.","solutions":["Reactivate the account: set is_active=True and clear last_logout_time=None on the User (admin or Django shell), then retry login.","If reactivation is intentional and self-service, expose it through the password-reset/reactivation flow rather than manual DB edits.","Verify the deactivation was not accidental by auditing last_logout_time and the admin action log for that user."],"exampleFix":"// before: user cannot log in after deactivation\n// after (reactivate in shell/manage.py):\n// python manage.py shell\n// >>> from plane.db.models import User\n// >>> u = User.objects.get(email='jane@x.com')\n// >>> u.is_active = True\n// >>> u.last_logout_time = None\n// >>> u.save()","handlingStrategy":"validation","validationCode":"from plane.db.models import User\n\ndef can_attempt_login(email: str) -> bool:\n    u = User.objects.filter(email=email).first()\n    # False when deactivated with a recorded logout -> would raise 5019\n    return u is None or u.is_active or u.last_logout_time is None","typeGuard":"def is_login_blocked_by_deactivation(user) -> bool:\n    return user is not None and not user.is_active and user.last_logout_time is not None","tryCatchPattern":"from plane.authentication.adapter.error import AuthenticationException\n\ntry:\n    adapter_login(email, password)\nexcept AuthenticationException as e:\n    if e.error_code == 5019:\n        show_reactivation_prompt(email=e.payload.get('email'))\n    else:\n        raise","preventionTips":["Surface account reactivation as a first-class flow instead of letting users hit 5019 repeatedly.","When deactivating accounts, notify the user and invalidate outstanding sessions."],"tags":["authentication","account-deactivation","security","login","ghsa-rmmf-rj2q-3rrg"],"backgroundTag":null,"analyzedSha":"1c8a60f858d8472aa56e29994ec1c7926da2c6ce","analyzedAt":"2026-08-12T14:44:31.636Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}