mirror of
https://github.com/makeplane/plane.git
synced 2025-12-16 11:57:56 +01:00
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:
@@ -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"],
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -76,5 +76,10 @@ LOGGING = {
|
||||
"handlers": ["console"],
|
||||
"propagate": False,
|
||||
},
|
||||
"plane.authentication": {
|
||||
"level": "INFO",
|
||||
"handlers": ["console"],
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -86,5 +86,10 @@ LOGGING = {
|
||||
"handlers": ["console"],
|
||||
"propagate": False,
|
||||
},
|
||||
"plane.authentication": {
|
||||
"level": "INFO",
|
||||
"handlers": ["console"],
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user