makeplane/plane · error · AuthenticationException
5019
5019
Error message
USER_ACCOUNT_DEACTIVATED
What it means
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.
Source
Thrown at apps/api/plane/authentication/adapter/base.py:327
def complete_login_or_signup(self):
# Get email
email = self.user_data.get("email")
# Sanitize email
email = self.sanitize_email(email)
# Check if the user is present
user = User.objects.filter(email=email).first()
# Reject explicitly-deactivated accounts (GHSA-rmmf-rj2q-3rrg).
# The deactivation endpoint always sets last_logout_time, so using it
# as the discriminator is more reliable than last_login_time: a
# provisioned account that was never deactivated has last_logout_time=None
# and is allowed through for its first login; an account deactivated via
# the API has last_logout_time set and is blocked regardless of whether
# it had previously logged in.
if user and not user.is_active and user.last_logout_time is not None:
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["USER_ACCOUNT_DEACTIVATED"],
error_message="USER_ACCOUNT_DEACTIVATED",
payload={"email": email},
)
# Reject bot service accounts (BOT_USER_LOGIN_FORBIDDEN). Bots (is_bot=True,
# e.g. the WORKSPACE_SEED bot) are internal identities that act only through
# API tokens; they must never be assumable via the interactive login/signup
# flow (email/password, magic code, or any OAuth provider). A brand-new
# signup can never be a bot — bots are provisioned internally, never through
# this path — so guarding on an existing `user` record is sufficient.
if user and user.is_bot:
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["BOT_USER_LOGIN_FORBIDDEN"],
error_message="BOT_USER_LOGIN_FORBIDDEN",
payload={"email": email},
)
View on GitHub (pinned to 1c8a60f858)
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.
Example fix
// before: user cannot log in after deactivation // after (reactivate in shell/manage.py): // python manage.py shell // >>> from plane.db.models import User // >>> u = User.objects.get(email='jane@x.com') // >>> u.is_active = True // >>> u.last_logout_time = None // >>> u.save()
Defensive patterns
Strategy: validation
Validate before calling
from plane.db.models import User
def can_attempt_login(email: str) -> bool:
u = User.objects.filter(email=email).first()
# False when deactivated with a recorded logout -> would raise 5019
return u is None or u.is_active or u.last_logout_time is None Type guard
def is_login_blocked_by_deactivation(user) -> bool:
return user is not None and not user.is_active and user.last_logout_time is not None Try / catch
from plane.authentication.adapter.error import AuthenticationException
try:
adapter_login(email, password)
except AuthenticationException as e:
if e.error_code == 5019:
show_reactivation_prompt(email=e.payload.get('email'))
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12).
Data as JSON: /api/errors/10af3841d5c9803d.
Report an issue: GitHub.