feat: enhance authentication logging with detailed error and info messages

- Added logging for various authentication events in the Adapter and its subclasses, including email validation, user existence checks, and password strength validation.
- Implemented error handling for GitHub OAuth email retrieval, ensuring proper logging of unexpected responses and missing primary emails.
- Updated logging configuration in local and production settings to include a dedicated logger for authentication events.
This commit is contained in:
pablohashescobar
2025-10-21 17:15:12 +05:30
parent 98b81d7ebb
commit 599e81592b
6 changed files with 57 additions and 1 deletions

View File

@@ -1,6 +1,7 @@
# Python imports
import os
import uuid
import logging
# Django imports
from django.utils import timezone
@@ -19,6 +20,7 @@ from plane.utils.host import base_host
from plane.utils.ip_address import get_client_ip
class Adapter:
"""Common interface for all auth providers"""
@@ -28,6 +30,7 @@ class Adapter:
self.callback = callback
self.token_data = None
self.user_data = None
self.logger = logging.getLogger("plane.authentication")
def get_user_token(self, data, headers=None):
raise NotImplementedError
@@ -50,6 +53,7 @@ class Adapter:
def sanitize_email(self, email):
# Check if email is present
if not email:
self.logger.error(f"Email is not present: {email}")
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"],
error_message="INVALID_EMAIL",
@@ -63,6 +67,7 @@ class Adapter:
try:
validate_email(email)
except ValidationError:
self.logger.warning(f"Email is not valid: {email}")
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"],
error_message="INVALID_EMAIL",
@@ -75,6 +80,7 @@ class Adapter:
"""Validate password strength"""
results = zxcvbn(self.code)
if results["score"] < 3:
self.logger.warning(f"Password is not strong enough: {email}")
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD"],
error_message="INVALID_PASSWORD",
@@ -92,6 +98,7 @@ class Adapter:
# Check if sign up is disabled and invite is present or not
if ENABLE_SIGNUP == "0" and not WorkspaceMemberInvite.objects.filter(email=email).exists():
self.logger.warning(f"Sign up is disabled and invite is not present: {email}")
# Raise exception
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["SIGNUP_DISABLED"],

View File

@@ -72,6 +72,10 @@ class OauthAdapter(Adapter):
response.raise_for_status()
return response.json()
except requests.RequestException:
self.logger.warning("Error getting user token", extra={
"data": data,
"headers": headers,
})
code = self.authentication_error_code()
raise AuthenticationException(error_code=AUTHENTICATION_ERROR_CODES[code], error_message=str(code))
@@ -82,6 +86,9 @@ class OauthAdapter(Adapter):
response.raise_for_status()
return response.json()
except requests.RequestException:
self.logger.warning("Error getting user response", extra={
"headers": headers,
})
code = self.authentication_error_code()
raise AuthenticationException(error_code=AUTHENTICATION_ERROR_CODES[code], error_message=str(code))

View File

@@ -39,6 +39,9 @@ class EmailProvider(CredentialAdapter):
if self.is_signup:
# Check if the user already exists
if User.objects.filter(email=self.key).exists():
self.logger.warning("User already exists", extra={
"email": self.key,
})
raise AuthenticationException(
error_message="USER_ALREADY_EXIST",
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
@@ -62,6 +65,9 @@ class EmailProvider(CredentialAdapter):
# User does not exists
if not user:
self.logger.warning("User does not exist", extra={
"email": self.key,
})
raise AuthenticationException(
error_message="USER_DOES_NOT_EXIST",
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
@@ -70,6 +76,9 @@ class EmailProvider(CredentialAdapter):
# Check user password
if not user.check_password(self.code):
self.logger.warning("Authentication failed", extra={
"email": self.key,
})
raise AuthenticationException(
error_message=(
"AUTHENTICATION_FAILED_SIGN_UP" if self.is_signup else "AUTHENTICATION_FAILED_SIGN_IN"

View File

@@ -2,7 +2,6 @@
import os
from datetime import datetime
from urllib.parse import urlencode
import pytz
import requests
@@ -109,9 +108,26 @@ class GitHubOAuthProvider(OauthAdapter):
# Github does not provide email in user response
emails_url = "https://api.github.com/user/emails"
emails_response = requests.get(emails_url, headers=headers).json()
# Ensure the response is a list before iterating
if not isinstance(emails_response, list):
self.logger.error(f"Unexpected response format from GitHub emails API: {emails_response}")
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["GITHUB_OAUTH_PROVIDER_ERROR"],
error_message="GITHUB_OAUTH_PROVIDER_ERROR",
)
email = next((email["email"] for email in emails_response if email["primary"]), None)
if not email:
self.logger.error(f"No primary email found for user: {emails_response}")
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["GITHUB_OAUTH_PROVIDER_ERROR"],
error_message="GITHUB_OAUTH_PROVIDER_ERROR",
)
return email
except requests.RequestException:
self.logger.warning("Error getting email from GitHub", extra={
"headers": headers,
"emails_response": emails_response,
})
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["GITHUB_OAUTH_PROVIDER_ERROR"],
error_message="GITHUB_OAUTH_PROVIDER_ERROR",
@@ -134,12 +150,19 @@ class GitHubOAuthProvider(OauthAdapter):
if self.organization_id:
if not self.is_user_in_organization(user_info_response.get("login")):
self.logger.warning("User is not in organization", extra={
"organization_id": self.organization_id,
"user_login": user_info_response.get("login"),
})
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["GITHUB_USER_NOT_IN_ORG"],
error_message="GITHUB_USER_NOT_IN_ORG",
)
email = self.__get_email(headers=headers)
self.logger.info("Email found", extra={
"email": email,
})
super().set_user_data(
{
"email": email,

View File

@@ -76,5 +76,10 @@ LOGGING = {
"handlers": ["console"],
"propagate": False,
},
"plane.authentication": {
"level": "INFO",
"handlers": ["console"],
"propagate": False,
},
},
}

View File

@@ -86,5 +86,10 @@ LOGGING = {
"handlers": ["console"],
"propagate": False,
},
"plane.authentication": {
"level": "INFO",
"handlers": ["console"],
"propagate": False,
},
},
}