diff --git a/apiserver/plane/authentication/adapter/base.py b/apiserver/plane/authentication/adapter/base.py index 7b899e63cab..5876e934f30 100644 --- a/apiserver/plane/authentication/adapter/base.py +++ b/apiserver/plane/authentication/adapter/base.py @@ -4,6 +4,8 @@ # Django imports from django.utils import timezone +from django.core.validators import validate_email +from django.core.exceptions import ValidationError # Third party imports from zxcvbn import zxcvbn @@ -46,53 +48,115 @@ def create_update_account(self, user): def authenticate(self): raise NotImplementedError + def sanitize_email(self, email): + # Check if email is present + if not email: + raise AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"], + error_message="INVALID_EMAIL", + payload={"email": email}, + ) + + # Sanitize email + email = str(email).lower().strip() + + # validate email + try: + validate_email(email) + except ValidationError: + raise AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"], + error_message="INVALID_EMAIL", + payload={"email": email}, + ) + # Return email + return email + + def validate_password(self, email): + """Validate password strength""" + results = zxcvbn(self.code) + if results["score"] < 3: + raise AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD"], + error_message="INVALID_PASSWORD", + payload={"email": email}, + ) + return + + def __check_signup(self, email): + """Check if sign up is enabled or not and raise exception if not enabled""" + + # Get configuration value + (ENABLE_SIGNUP,) = get_configuration_value( + [ + { + "key": "ENABLE_SIGNUP", + "default": os.environ.get("ENABLE_SIGNUP", "1"), + }, + ] + ) + + # Check if sign up is disabled and invite is present or not + if ( + ENABLE_SIGNUP == "0" + and not WorkspaceMemberInvite.objects.filter( + email=email, + ).exists() + ): + # Raise exception + raise AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["SIGNUP_DISABLED"], + error_message="SIGNUP_DISABLED", + payload={"email": email}, + ) + + return True + + def save_user_data(self, user): + # Update user details + user.last_login_medium = self.provider + user.last_active = timezone.now() + user.last_login_time = timezone.now() + user.last_login_ip = self.request.META.get("REMOTE_ADDR") + user.last_login_uagent = self.request.META.get("HTTP_USER_AGENT") + user.token_updated_at = timezone.now() + user.save() + return user + 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() # Check if sign up case or login is_signup = bool(user) + # If user is not present, create a new user if not user: # New user - (ENABLE_SIGNUP,) = get_configuration_value( - [ - { - "key": "ENABLE_SIGNUP", - "default": os.environ.get("ENABLE_SIGNUP", "1"), - }, - ] - ) - if ( - ENABLE_SIGNUP == "0" - and not WorkspaceMemberInvite.objects.filter( - email=email, - ).exists() - ): - raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES["SIGNUP_DISABLED"], - error_message="SIGNUP_DISABLED", - payload={"email": email}, - ) + self.__check_signup(email) + + # Initialize user user = User(email=email, username=uuid.uuid4().hex) + # Check if password is autoset if self.user_data.get("user").get("is_password_autoset"): user.set_password(uuid.uuid4().hex) user.is_password_autoset = True user.is_email_verified = True + + # Validate password else: # Validate password - results = zxcvbn(self.code) - if results["score"] < 3: - raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "INVALID_PASSWORD" - ], - error_message="INVALID_PASSWORD", - payload={"email": email}, - ) - + self.validate_password(email) + # Set password user.set_password(self.code) user.is_password_autoset = False + # Set user details avatar = self.user_data.get("user", {}).get("avatar", "") first_name = self.user_data.get("user", {}).get("first_name", "") last_name = self.user_data.get("user", {}).get("last_name", "") @@ -100,6 +164,8 @@ def complete_login_or_signup(self): user.first_name = first_name if first_name else "" user.last_name = last_name if last_name else "" user.save() + + # Create profile Profile.objects.create(user=user) if not user.is_active: @@ -108,15 +174,10 @@ def complete_login_or_signup(self): error_message="USER_ACCOUNT_DEACTIVATED", ) - # Update user details - user.last_login_medium = self.provider - user.last_active = timezone.now() - user.last_login_time = timezone.now() - user.last_login_ip = self.request.META.get("REMOTE_ADDR") - user.last_login_uagent = self.request.META.get("HTTP_USER_AGENT") - user.token_updated_at = timezone.now() - user.save() + # Save user data + user = self.save_user_data(user=user) + # Call callback if present if self.callback: self.callback( user, @@ -124,7 +185,9 @@ def complete_login_or_signup(self): self.request, ) + # Create or update account if token data is present if self.token_data: self.create_update_account(user=user) + # Return user return user diff --git a/apiserver/plane/authentication/adapter/error.py b/apiserver/plane/authentication/adapter/error.py index 7b12db9456a..55ff1098820 100644 --- a/apiserver/plane/authentication/adapter/error.py +++ b/apiserver/plane/authentication/adapter/error.py @@ -58,6 +58,8 @@ "ADMIN_USER_DEACTIVATED": 5190, # Rate limit "RATE_LIMIT_EXCEEDED": 5900, + # Unknown + "AUTHENTICATION_FAILED": 5999, } diff --git a/apiserver/plane/authentication/adapter/oauth.py b/apiserver/plane/authentication/adapter/oauth.py index a917c002aef..b1a92e79e55 100644 --- a/apiserver/plane/authentication/adapter/oauth.py +++ b/apiserver/plane/authentication/adapter/oauth.py @@ -81,11 +81,11 @@ def get_user_response(self): response.raise_for_status() return response.json() except requests.RequestException: - code = ( - "GOOGLE_OAUTH_PROVIDER_ERROR" - if self.provider == "google" - else "GITHUB_OAUTH_PROVIDER_ERROR" - ) + if self.provider == "google": + code = "GOOGLE_OAUTH_PROVIDER_ERROR" + if self.provider == "github": + code = "GITHUB_OAUTH_PROVIDER_ERROR" + raise AuthenticationException( error_code=AUTHENTICATION_ERROR_CODES[code], error_message=str(code),