[WEB - 1742] chore: user activation and deactivation workflow (#4944)

* chore: user deactivation workflow

* dev: activation deactivation template
This commit is contained in:
Nikhil 2024-06-27 20:44:16 +05:30 committed by GitHub
parent 90339b1c62
commit 1a37c1542d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 3343 additions and 114 deletions

View file

@ -35,6 +35,8 @@ from plane.license.models import Instance, InstanceAdmin
from plane.utils.cache import cache_response, invalidate_cache
from plane.utils.paginator import BasePaginator
from plane.authentication.utils.host import user_ip
from plane.bgtasks.user_deactivation_email_task import user_deactivation_email
from plane.utils.host import base_host
class UserEndpoint(BaseViewSet):
@ -192,6 +194,11 @@ class UserEndpoint(BaseViewSet):
user.last_logout_time = timezone.now()
user.save()
# Send an email to the user
user_deactivation_email.delay(
base_host(request=request, is_app=True), user.id
)
# Logout the user
logout(request)
return Response(status=status.HTTP_204_NO_CONTENT)

View file

@ -18,6 +18,8 @@ from plane.db.models import (
)
from plane.license.utils.instance_value import get_configuration_value
from .error import AuthenticationException, AUTHENTICATION_ERROR_CODES
from plane.bgtasks.user_activation_email_task import user_activation_email
from plane.authentication.utils.host import base_host
class Adapter:
@ -120,6 +122,13 @@ class Adapter:
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()
# If user is not active, send the activation email and set the user as active
if not user.is_active:
user_activation_email.delay(
base_host(request=self.request), user.id
)
# Set user as active
user.is_active = True
user.save()
return user
@ -168,12 +177,6 @@ class Adapter:
# Create profile
Profile.objects.create(user=user)
if not user.is_active:
raise AuthenticationException(
AUTHENTICATION_ERROR_CODES["USER_ACCOUNT_DEACTIVATED"],
error_message="USER_ACCOUNT_DEACTIVATED",
)
# Save user data
user = self.save_user_data(user=user)

View file

@ -95,17 +95,7 @@ class EmailCheckEndpoint(APIView):
# If existing user
if existing_user:
if not existing_user.is_active:
exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES[
"USER_ACCOUNT_DEACTIVATED"
],
error_message="USER_ACCOUNT_DEACTIVATED",
)
return Response(
exc.get_error_dict(), status=status.HTTP_400_BAD_REQUEST
)
# Return response
return Response(
{
"existing": True,

View file

@ -107,22 +107,6 @@ class SignInAuthEndpoint(View):
)
return HttpResponseRedirect(url)
if not existing_user.is_active:
exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES[
"USER_ACCOUNT_DEACTIVATED"
],
error_message="USER_ACCOUNT_DEACTIVATED",
)
params = exc.get_error_dict()
if next_path:
params["next_path"] = str(next_path)
url = urljoin(
base_host(request=request, is_app=True),
"sign-in?" + urlencode(params),
)
return HttpResponseRedirect(url)
try:
provider = EmailProvider(
request=request,
@ -222,22 +206,6 @@ class SignUpAuthEndpoint(View):
if existing_user:
# Existing User
if not existing_user.is_active:
exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES[
"USER_ACCOUNT_DEACTIVATED"
],
error_message="USER_ACCOUNT_DEACTIVATED",
)
params = exc.get_error_dict()
if next_path:
params["next_path"] = str(next_path)
url = urljoin(
base_host(request=request, is_app=True),
"?" + urlencode(params),
)
return HttpResponseRedirect(url)
exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
error_message="USER_ALREADY_EXIST",

View file

@ -112,22 +112,6 @@ class MagicSignInEndpoint(View):
)
return HttpResponseRedirect(url)
if not existing_user.is_active:
exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES[
"USER_ACCOUNT_DEACTIVATED"
],
error_message="USER_ACCOUNT_DEACTIVATED",
)
params = exc.get_error_dict()
if next_path:
params["next_path"] = str(next_path)
url = urljoin(
base_host(request=request, is_app=True),
"sign-in?" + urlencode(params),
)
return HttpResponseRedirect(url)
try:
provider = MagicCodeProvider(
request=request,

View file

@ -93,17 +93,7 @@ class EmailCheckSpaceEndpoint(APIView):
# If existing user
if existing_user:
if not existing_user.is_active:
exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES[
"USER_ACCOUNT_DEACTIVATED"
],
error_message="USER_ACCOUNT_DEACTIVATED",
)
return Response(
exc.get_error_dict(), status=status.HTTP_400_BAD_REQUEST
)
# Return response
return Response(
{
"existing": True,

View file

@ -89,19 +89,6 @@ class SignInAuthSpaceEndpoint(View):
url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}"
return HttpResponseRedirect(url)
if not existing_user.is_active:
exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES[
"USER_ACCOUNT_DEACTIVATED"
],
error_message="USER_ACCOUNT_DEACTIVATED",
)
params = exc.get_error_dict()
if next_path:
params["next_path"] = str(next_path)
url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}"
return HttpResponseRedirect(url)
try:
provider = EmailProvider(
request=request, key=email, code=password, is_signup=False
@ -178,19 +165,6 @@ class SignUpAuthSpaceEndpoint(View):
existing_user = User.objects.filter(email=email).first()
if existing_user:
if not existing_user.is_active:
exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES[
"USER_ACCOUNT_DEACTIVATED"
],
error_message="USER_ACCOUNT_DEACTIVATED",
)
params = exc.get_error_dict()
if next_path:
params["next_path"] = str(next_path)
url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}"
return HttpResponseRedirect(url)
exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
error_message="USER_ALREADY_EXIST",

View file

@ -101,18 +101,6 @@ class MagicSignInSpaceEndpoint(View):
return HttpResponseRedirect(url)
# Active User
if not existing_user.is_active:
exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES[
"USER_ACCOUNT_DEACTIVATED"
],
error_message="USER_ACCOUNT_DEACTIVATED",
)
params = exc.get_error_dict()
if next_path:
params["next_path"] = str(next_path)
url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}"
return HttpResponseRedirect(url)
try:
provider = MagicCodeProvider(
request=request, key=f"magic_{email}", code=code

View file

@ -0,0 +1,70 @@
# Python imports
import logging
# Django imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from django.utils.html import strip_tags
# Third party imports
from celery import shared_task
# Module imports
from plane.db.models import User
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.exception_logger import log_exception
@shared_task
def user_activation_email(current_site, user_id):
try:
# Send email to user when account is activated
user = User.objects.get(id=user_id)
subject = f"{user.first_name or user.display_name or user.email} has been activated on Plane"
context = {
"email": str(user.email),
"profile_url": current_site + "/profile",
}
# Send email to user
html_content = render_to_string(
"emails/user/user_activation.html", context
)
text_content = strip_tags(html_content)
# Configure email connection from the database
(
EMAIL_HOST,
EMAIL_HOST_USER,
EMAIL_HOST_PASSWORD,
EMAIL_PORT,
EMAIL_USE_TLS,
EMAIL_USE_SSL,
EMAIL_FROM,
) = get_email_configuration()
connection = get_connection(
host=EMAIL_HOST,
port=int(EMAIL_PORT),
username=EMAIL_HOST_USER,
password=EMAIL_HOST_PASSWORD,
use_tls=EMAIL_USE_TLS == "1",
use_ssl=EMAIL_USE_SSL == "1",
)
msg = EmailMultiAlternatives(
subject=subject,
body=text_content,
from_email=EMAIL_FROM,
to=[user.email],
connection=connection,
)
msg.attach_alternative(html_content, "text/html")
msg.send()
logging.getLogger("plane").info("Email sent successfully.")
return
except Exception as e:
log_exception(e)
return

View file

@ -0,0 +1,72 @@
# Python imports
import logging
# Django imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from django.utils.html import strip_tags
# Third party imports
from celery import shared_task
# Module imports
from plane.db.models import User
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.exception_logger import log_exception
@shared_task
def user_deactivation_email(current_site, user_id):
try:
# Send email to user when account is deactivated
user = User.objects.get(id=user_id)
subject = f"{user.first_name or user.display_name or user.email} has been deactivated on Plane"
context = {
"email": str(user.email),
"login_url": current_site + "/login",
}
# Send email to user
html_content = render_to_string(
"emails/user/user_deactivation.html", context
)
text_content = strip_tags(html_content)
# Configure email connection from the database
(
EMAIL_HOST,
EMAIL_HOST_USER,
EMAIL_HOST_PASSWORD,
EMAIL_PORT,
EMAIL_USE_TLS,
EMAIL_USE_SSL,
EMAIL_FROM,
) = get_email_configuration()
connection = get_connection(
host=EMAIL_HOST,
port=int(EMAIL_PORT),
username=EMAIL_HOST_USER,
password=EMAIL_HOST_PASSWORD,
use_tls=EMAIL_USE_TLS == "1",
use_ssl=EMAIL_USE_SSL == "1",
)
# Send email
msg = EmailMultiAlternatives(
subject=subject,
body=text_content,
from_email=EMAIL_FROM,
to=[user.email],
connection=connection,
)
# Attach HTML content
msg.attach_alternative(html_content, "text/html")
msg.send()
logging.getLogger("plane").info("Email sent successfully.")
return
except Exception as e:
log_exception(e)
return

View file

@ -0,0 +1,42 @@
# Python imports
from urllib.parse import urlsplit
# Django imports
from django.conf import settings
def base_host(request, is_admin=False, is_space=False, is_app=False):
"""Utility function to return host / origin from the request"""
# Calculate the base origin from request
base_origin = str(
request.META.get("HTTP_ORIGIN")
or f"{urlsplit(request.META.get('HTTP_REFERER')).scheme}://{urlsplit(request.META.get('HTTP_REFERER')).netloc}"
or f"""{"https" if request.is_secure() else "http"}://{request.get_host()}"""
)
# Admin redirections
if is_admin:
if settings.ADMIN_BASE_URL:
return settings.ADMIN_BASE_URL
else:
return base_origin + "/god-mode/"
# Space redirections
if is_space:
if settings.SPACE_BASE_URL:
return settings.SPACE_BASE_URL
else:
return base_origin + "/spaces/"
# App Redirection
if is_app:
if settings.APP_BASE_URL:
return settings.APP_BASE_URL
else:
return base_origin
return base_origin
def user_ip(request):
return str(request.META.get("REMOTE_ADDR"))

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff