From 4afab4400a0d085aeaf840750be454ce63f7cade Mon Sep 17 00:00:00 2001
From: ali-zipstack <muhammad.ali@zipstack.com>
Date: Mon, 1 Jul 2024 14:45:01 +0530
Subject: [PATCH 1/2] version 2 of user/organization account module

---
 backend/account_v2/ReadMe.md                  |  26 +
 backend/account_v2/__init__.py                |   0
 backend/account_v2/admin.py                   |   5 +
 backend/account_v2/apps.py                    |   6 +
 .../account_v2/authentication_controller.py   | 489 ++++++++++++++++++
 backend/account_v2/authentication_helper.py   | 121 +++++
 .../authentication_plugin_registry.py         |  96 ++++
 backend/account_v2/authentication_service.py  | 395 ++++++++++++++
 backend/account_v2/constants.py               |  89 ++++
 backend/account_v2/custom_auth_middleware.py  |  51 ++
 backend/account_v2/custom_authentication.py   |  13 +
 backend/account_v2/custom_cache.py            |  12 +
 backend/account_v2/custom_exceptions.py       |  60 +++
 backend/account_v2/dto.py                     | 131 +++++
 backend/account_v2/enums.py                   |   6 +
 backend/account_v2/exceptions.py              |  31 ++
 backend/account_v2/migrations/__init__.py     |   0
 backend/account_v2/models.py                  | 144 ++++++
 backend/account_v2/organization.py            |  45 ++
 backend/account_v2/serializer.py              | 117 +++++
 backend/account_v2/subscription_loader.py     |  77 +++
 backend/account_v2/templates/index.html       |  11 +
 backend/account_v2/templates/login.html       | 134 +++++
 backend/account_v2/tests.py                   |   1 +
 backend/account_v2/urls.py                    |  22 +
 backend/account_v2/user.py                    |  55 ++
 backend/account_v2/views.py                   | 169 ++++++
 27 files changed, 2306 insertions(+)
 create mode 100644 backend/account_v2/ReadMe.md
 create mode 100644 backend/account_v2/__init__.py
 create mode 100644 backend/account_v2/admin.py
 create mode 100644 backend/account_v2/apps.py
 create mode 100644 backend/account_v2/authentication_controller.py
 create mode 100644 backend/account_v2/authentication_helper.py
 create mode 100644 backend/account_v2/authentication_plugin_registry.py
 create mode 100644 backend/account_v2/authentication_service.py
 create mode 100644 backend/account_v2/constants.py
 create mode 100644 backend/account_v2/custom_auth_middleware.py
 create mode 100644 backend/account_v2/custom_authentication.py
 create mode 100644 backend/account_v2/custom_cache.py
 create mode 100644 backend/account_v2/custom_exceptions.py
 create mode 100644 backend/account_v2/dto.py
 create mode 100644 backend/account_v2/enums.py
 create mode 100644 backend/account_v2/exceptions.py
 create mode 100644 backend/account_v2/migrations/__init__.py
 create mode 100644 backend/account_v2/models.py
 create mode 100644 backend/account_v2/organization.py
 create mode 100644 backend/account_v2/serializer.py
 create mode 100644 backend/account_v2/subscription_loader.py
 create mode 100644 backend/account_v2/templates/index.html
 create mode 100644 backend/account_v2/templates/login.html
 create mode 100644 backend/account_v2/tests.py
 create mode 100644 backend/account_v2/urls.py
 create mode 100644 backend/account_v2/user.py
 create mode 100644 backend/account_v2/views.py

diff --git a/backend/account_v2/ReadMe.md b/backend/account_v2/ReadMe.md
new file mode 100644
index 0000000000..35695cfb52
--- /dev/null
+++ b/backend/account_v2/ReadMe.md
@@ -0,0 +1,26 @@
+# Basic WorkFlow
+
+`We can Add Workflows Here`
+
+## Login
+
+### Step
+
+1. Login
+2. Get Organizations
+3. Set Organization
+4. Use organizational APIs /unstract/<org_id>/
+
+## Switch organization
+
+1. Get Organizations
+2. Set Organization
+3. Use organizational APIs /unstract/<org_id>/
+
+## Get current user and Organization data
+
+- Use Get User Profile and  Get Organization Info APIs
+
+## Signout
+
+1.signout APi
diff --git a/backend/account_v2/__init__.py b/backend/account_v2/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/backend/account_v2/admin.py b/backend/account_v2/admin.py
new file mode 100644
index 0000000000..e0b96cce89
--- /dev/null
+++ b/backend/account_v2/admin.py
@@ -0,0 +1,5 @@
+from django.contrib import admin
+
+from .models import Organization, User
+
+admin.site.register([Organization, User])
diff --git a/backend/account_v2/apps.py b/backend/account_v2/apps.py
new file mode 100644
index 0000000000..65343132cf
--- /dev/null
+++ b/backend/account_v2/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class AccountConfig(AppConfig):
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "account_v2"
diff --git a/backend/account_v2/authentication_controller.py b/backend/account_v2/authentication_controller.py
new file mode 100644
index 0000000000..0aec22294d
--- /dev/null
+++ b/backend/account_v2/authentication_controller.py
@@ -0,0 +1,489 @@
+import logging
+from typing import Any, Optional, Union
+
+from account_v2.authentication_helper import AuthenticationHelper
+from account_v2.authentication_plugin_registry import AuthenticationPluginRegistry
+from account_v2.authentication_service import AuthenticationService
+from account_v2.constants import (
+    AuthorizationErrorCode,
+    Common,
+    Cookie,
+    ErrorMessage,
+    OrganizationMemberModel,
+)
+from account_v2.custom_exceptions import (
+    DuplicateData,
+    Forbidden,
+    MethodNotImplemented,
+    UserNotExistError,
+)
+from account_v2.dto import (
+    MemberInvitation,
+    OrganizationData,
+    UserInfo,
+    UserInviteResponse,
+    UserRoleData,
+)
+from account_v2.exceptions import OrganizationNotExist
+from account_v2.models import Organization, User
+from account_v2.organization import OrganizationService
+from account_v2.serializer import (
+    GetOrganizationsResponseSerializer,
+    OrganizationSerializer,
+    SetOrganizationsResponseSerializer,
+)
+from account_v2.user import UserService
+from django.conf import settings
+from django.contrib.auth import logout as django_logout
+from django.db.utils import IntegrityError
+from django.middleware import csrf
+from django.shortcuts import redirect
+from rest_framework import status
+from rest_framework.request import Request
+from rest_framework.response import Response
+from tenant_account_v2.models import OrganizationMember as OrganizationMember
+from tenant_account_v2.organization_member_service import OrganizationMemberService
+from utils.cache_service import CacheService
+from utils.local_context import StateStore
+from utils.user_context import UserContext
+from utils.user_session import UserSessionUtils
+
+logger = logging.getLogger(__name__)
+
+
+class AuthenticationController:
+    """Authentication Controller This controller class manages user
+    authentication processes."""
+
+    def __init__(self) -> None:
+        """This method initializes the controller by selecting the appropriate
+        authentication plugin based on availability."""
+        self.authentication_helper = AuthenticationHelper()
+        if AuthenticationPluginRegistry.is_plugin_available():
+            self.auth_service: AuthenticationService = (
+                AuthenticationPluginRegistry.get_plugin()
+            )
+        else:
+            self.auth_service = AuthenticationService()
+
+    def user_login(
+        self,
+        request: Request,
+    ) -> Any:
+        return self.auth_service.user_login(request)
+
+    def user_signup(self, request: Request) -> Any:
+        return self.auth_service.user_signup(request)
+
+    def authorization_callback(
+        self, request: Request, backend: str = settings.DEFAULT_MODEL_BACKEND
+    ) -> Any:
+        """Handle authorization callback.
+
+        This function processes the authorization callback from
+        an external service.
+
+        Args:
+            request (Request): Request instance
+            backend (str, optional): backend used to use login.
+                Defaults: settings.DEFAULT_MODEL_BACKEND.
+
+        Returns:
+            Any: Redirect response
+        """
+        try:
+            return self.auth_service.handle_authorization_callback(
+                request=request, backend=backend
+            )
+        except Exception as ex:
+            logger.error(f"Error while handling authorization callback: {ex}")
+            return redirect(f"{settings.ERROR_URL}")
+
+    def user_organizations(self, request: Request) -> Any:
+        """List a user's organizations.
+
+        Args:
+            user (User): User instance
+            z_code (str): _description_
+
+        Returns:
+            list[OrganizationData]: _description_
+        """
+
+        try:
+            organizations = self.auth_service.user_organizations(request)
+        except Exception as ex:
+            #
+            self.user_logout(request)
+
+            response = Response(
+                status=status.HTTP_412_PRECONDITION_FAILED,
+            )
+            if hasattr(ex, "code") and ex.code in {
+                AuthorizationErrorCode.USF,
+                AuthorizationErrorCode.USR,
+                AuthorizationErrorCode.INE001,
+                AuthorizationErrorCode.INE002,
+            }:  # type: ignore
+                response.data = ({"domain": ex.data.get("domain"), "code": ex.code},)
+                return response
+            # Return in case even if missed unknown exception in
+            # self.auth_service.user_organizations(request)
+            return response
+
+        user: User = request.user
+        org_ids = {org.id for org in organizations}
+
+        CacheService.set_user_organizations(user.user_id, list(org_ids))
+
+        serialized_organizations = GetOrganizationsResponseSerializer(
+            organizations, many=True
+        ).data
+        response = Response(
+            status=status.HTTP_200_OK,
+            data={
+                "message": "success",
+                "organizations": serialized_organizations,
+            },
+        )
+        if Cookie.CSRFTOKEN not in request.COOKIES:
+            csrf_token = csrf.get_token(request)
+            response.set_cookie(Cookie.CSRFTOKEN, csrf_token)
+
+        return response
+
+    def set_user_organization(self, request: Request, organization_id: str) -> Response:
+        user: User = request.user
+        new_organization = False
+        organization_ids = CacheService.get_user_organizations(user.user_id)
+        if not organization_ids:
+            z_organizations: list[OrganizationData] = (
+                self.auth_service.get_organizations_by_user_id(user.user_id)
+            )
+            organization_ids = {org.id for org in z_organizations}
+        if organization_id and organization_id in organization_ids:
+            # Set organization in user context
+            UserContext.set_organization_identifier(organization_id)
+            organization = OrganizationService.get_organization_by_org_id(
+                organization_id
+            )
+            if not organization:
+                try:
+                    organization_data: OrganizationData = (
+                        self.auth_service.get_organization_by_org_id(organization_id)
+                    )
+                except ValueError:
+                    raise OrganizationNotExist()
+                try:
+                    organization = OrganizationService.create_organization(
+                        organization_data.name,
+                        organization_data.display_name,
+                        organization_data.id,
+                    )
+                    new_organization = True
+                except IntegrityError:
+                    raise DuplicateData(
+                        f"{ErrorMessage.ORGANIZATION_EXIST}, \
+                            {ErrorMessage.DUPLICATE_API}"
+                    )
+            self.create_tenant_user(organization=organization, user=user)
+
+            if new_organization:
+                try:
+                    self.auth_service.hubspot_signup_api(request=request)
+                except MethodNotImplemented:
+                    logger.info("hubspot_signup_api not implemented")
+
+                try:
+                    self.auth_service.frictionless_onboarding(
+                        organization=organization, user=user
+                    )
+                except MethodNotImplemented:
+                    logger.info("frictionless_onboarding not implemented")
+
+                self.authentication_helper.create_initial_platform_key(
+                    user=user, organization=organization
+                )
+                logger.info(
+                    f"New organization created with Id {organization_id}",
+                )
+
+            user_info: Optional[UserInfo] = self.get_user_info(request)
+            serialized_user_info = SetOrganizationsResponseSerializer(user_info).data
+            organization_info = OrganizationSerializer(organization).data
+            response: Response = Response(
+                status=status.HTTP_200_OK,
+                data={
+                    "user": serialized_user_info,
+                    "organization": organization_info,
+                    f"{Common.LOG_EVENTS_ID}": StateStore.get(Common.LOG_EVENTS_ID),
+                },
+            )
+            current_organization_id = UserSessionUtils.get_organization_id(request)
+            if current_organization_id:
+                OrganizationMemberService.remove_user_membership_in_organization_cache(
+                    user_id=user.user_id,
+                    organization_id=current_organization_id,
+                )
+            UserSessionUtils.set_organization_id(request, organization_id)
+            OrganizationMemberService.set_user_membership_in_organization_cache(
+                user_id=user.user_id, organization_id=organization_id
+            )
+            return response
+        return Response(status=status.HTTP_403_FORBIDDEN)
+
+    def get_user_info(self, request: Request) -> Optional[UserInfo]:
+        return self.auth_service.get_user_info(request)
+
+    def is_admin_by_role(self, role: str) -> bool:
+        """Check the role is act as admin in the context of authentication
+        plugin.
+
+        Args:
+            role (str): role
+
+        Returns:
+            bool: _description_
+        """
+        return self.auth_service.is_admin_by_role(role=role)
+
+    def get_organization_info(self, org_id: str) -> Optional[Organization]:
+        organization = OrganizationService.get_organization_by_org_id(org_id=org_id)
+        return organization
+
+    def make_organization_and_add_member(
+        self,
+        user_id: str,
+        user_name: str,
+        organization_name: Optional[str] = None,
+        display_name: Optional[str] = None,
+    ) -> Optional[OrganizationData]:
+        return self.auth_service.make_organization_and_add_member(
+            user_id, user_name, organization_name, display_name
+        )
+
+    def make_user_organization_name(self) -> str:
+        return self.auth_service.make_user_organization_name()
+
+    def make_user_organization_display_name(self, user_name: str) -> str:
+        return self.auth_service.make_user_organization_display_name(user_name)
+
+    def user_logout(self, request: Request) -> Response:
+        response = self.auth_service.user_logout(request=request)
+        organization_id = UserSessionUtils.get_organization_id(request)
+        user_id = UserSessionUtils.get_user_id(request)
+        if organization_id:
+            OrganizationMemberService.remove_user_membership_in_organization_cache(
+                user_id=user_id, organization_id=organization_id
+            )
+        django_logout(request)
+        return response
+
+    def get_organization_members_by_org_id(
+        self, organization_id: Optional[str] = None
+    ) -> list[OrganizationMember]:
+        members: list[OrganizationMember] = OrganizationMemberService.get_members()
+        return members
+
+    def get_organization_members_by_user(self, user: User) -> OrganizationMember:
+        """Get organization member by user. This method will return
+        organization member object for given user.
+
+        Args:
+            user (User): UserEntity
+
+        Returns:
+            OrganizationMember: OrganizationMemberEntity
+        """
+        member: OrganizationMember = OrganizationMemberService.get_user_by_id(
+            id=user.id
+        )
+        return member
+
+    def get_user_roles(self) -> list[UserRoleData]:
+        return self.auth_service.get_roles()
+
+    def get_user_invitations(self, organization_id: str) -> list[MemberInvitation]:
+        return self.auth_service.get_invitations(organization_id=organization_id)
+
+    def delete_user_invitation(self, organization_id: str, invitation_id: str) -> bool:
+        return self.auth_service.delete_invitation(
+            organization_id=organization_id, invitation_id=invitation_id
+        )
+
+    def reset_user_password(self, user: User) -> Response:
+        return self.auth_service.reset_user_password(user)
+
+    def invite_user(
+        self,
+        admin: User,
+        org_id: str,
+        user_list: list[dict[str, Union[str, None]]],
+    ) -> list[UserInviteResponse]:
+        """Invites users to join an organization.
+
+        Args:
+            admin (User): Admin user initiating the invitation.
+            org_id (str): ID of the organization to which users are invited.
+            user_list (list[dict[str, Union[str, None]]]):
+                List of user details for invitation.
+        Returns:
+            list[UserInviteResponse]: List of responses for each
+                user invitation.
+        """
+        admin_user = OrganizationMemberService.get_user_by_id(id=admin.id)
+        if not self.auth_service.is_organization_admin(admin_user):
+            raise Forbidden()
+        response = []
+        for user_item in user_list:
+            email = user_item.get("email")
+            role = user_item.get("role")
+            if email:
+                user = OrganizationMemberService.get_user_by_email(email=email)
+                user_response = {}
+                user_response["email"] = email
+                status = False
+                message = "User is already part of current organization"
+                # Check if user is already part of current organization
+                if not user:
+                    status = self.auth_service.invite_user(
+                        admin_user, org_id, email, role=role
+                    )
+                    message = "User invitation successful."
+
+                response.append(
+                    UserInviteResponse(
+                        email=email,
+                        status="success" if status else "failed",
+                        message=message,
+                    )
+                )
+        return response
+
+    def remove_users_from_organization(
+        self, admin: User, organization_id: str, user_emails: list[str]
+    ) -> bool:
+        admin_user = OrganizationMemberService.get_user_by_id(id=admin.id)
+        user_ids = OrganizationMemberService.get_members_by_user_email(
+            user_emails=user_emails,
+            values_list_fields=[
+                OrganizationMemberModel.USER_ID,
+                OrganizationMemberModel.ID,
+            ],
+        )
+        user_ids_list: list[str] = []
+        pk_list: list[str] = []
+        for user in user_ids:
+            user_ids_list.append(user[0])
+            pk_list.append(user[1])
+        if len(user_ids_list) > 0:
+            is_removed = self.auth_service.remove_users_from_organization(
+                admin=admin_user,
+                organization_id=organization_id,
+                user_ids=user_ids_list,
+            )
+        else:
+            is_removed = False
+        if is_removed:
+            AuthenticationHelper.remove_users_from_organization_by_pks(pk_list)
+            for user_id in user_ids_list:
+                OrganizationMemberService.remove_user_membership_in_organization_cache(
+                    user_id, organization_id
+                )
+
+        return is_removed
+
+    def add_user_role(
+        self, admin: User, org_id: str, email: str, role: str
+    ) -> Optional[str]:
+        admin_user = OrganizationMemberService.get_user_by_id(id=admin.id)
+        user = OrganizationMemberService.get_user_by_email(email=email)
+        if user:
+            current_roles = self.auth_service.add_organization_user_role(
+                admin_user, org_id, user.user.user_id, [role]
+            )
+            if current_roles:
+                self.save_organization_user_role(
+                    user_uid=user.user.user.id, role=current_roles[0]
+                )
+            return current_roles[0]
+        else:
+            return None
+
+    def remove_user_role(
+        self, admin: User, org_id: str, email: str, role: str
+    ) -> Optional[str]:
+        admin_user = OrganizationMemberService.get_user_by_id(id=admin.id)
+        organization_member = OrganizationMemberService.get_user_by_email(email=email)
+        if organization_member:
+            current_roles = self.auth_service.remove_organization_user_role(
+                admin_user, org_id, organization_member.user.user_id, [role]
+            )
+            if current_roles:
+                self.save_organization_user_role(
+                    user_uid=organization_member.user.id,
+                    role=current_roles[0],
+                )
+            return current_roles[0]
+        else:
+            return None
+
+    def save_organization_user_role(self, user_uid: str, role: str) -> None:
+        organization_user = OrganizationMemberService.get_user_by_id(id=user_uid)
+        if organization_user:
+            # consider single role
+            organization_user.role = role
+            organization_user.save()
+
+    def create_tenant_user(self, organization: Organization, user: User) -> None:
+        existing_tenant_user = OrganizationMemberService.get_user_by_id(id=user.id)
+
+        if existing_tenant_user:
+            return None
+
+        account_user = self.get_or_create_user(user=user)
+        if not account_user:
+            raise UserNotExistError()
+
+        logger.info(f"Creating account for {user.email}")
+        user_roles = self.auth_service.get_organization_role_of_user(
+            user_id=account_user.user_id,
+            organization_id=organization.organization_id,
+        )
+        user_role = user_roles[0]
+        try:
+            tenant_user: OrganizationMember = OrganizationMember(
+                user=user,
+                role=user_role,
+                is_login_onboarding_msg=False,
+                is_prompt_studio_onboarding_msg=False,
+            )
+            tenant_user.save()
+            logger.info(
+                f"{tenant_user.user.email} added in to the organization "
+                f"{organization.organization_id}"
+            )
+        except IntegrityError:
+            logger.warning(f"Account already exists for {user.email}")
+
+    def get_or_create_user(
+        self, user: User
+    ) -> Optional[Union[User, OrganizationMember]]:
+        user_service = UserService()
+        if user.id:
+            account_user: Optional[User] = user_service.get_user_by_id(user.id)
+            if account_user:
+                return account_user
+            elif user.email:
+                account_user = user_service.get_user_by_email(email=user.email)
+                if account_user:
+                    return account_user
+                if user.user_id:
+                    user.save()
+                    return user
+        elif user.email and user.user_id:
+            account_user = user_service.create_user(
+                email=user.email, user_id=user.user_id
+            )
+            return account_user
+        return None
diff --git a/backend/account_v2/authentication_helper.py b/backend/account_v2/authentication_helper.py
new file mode 100644
index 0000000000..d2159a0f4d
--- /dev/null
+++ b/backend/account_v2/authentication_helper.py
@@ -0,0 +1,121 @@
+import logging
+from typing import Any
+
+from account_v2.dto import MemberData
+from account_v2.models import Organization, User
+from account_v2.user import UserService
+from platform_settings_v2.platform_auth_service import PlatformAuthenticationService
+from tenant_account_v2.organization_member_service import OrganizationMemberService
+
+logger = logging.getLogger(__name__)
+
+
+class AuthenticationHelper:
+    def __init__(self) -> None:
+        pass
+
+    def list_of_members_from_user_model(
+        self, model_data: list[Any]
+    ) -> list[MemberData]:
+        members: list[MemberData] = []
+        for data in model_data:
+            user_id = data.user_id
+            email = data.email
+            name = data.username
+
+            members.append(MemberData(user_id=user_id, email=email, name=name))
+
+        return members
+
+    @staticmethod
+    def get_or_create_user_by_email(user_id: str, email: str) -> User:
+        """Get or create a user with the given email.
+
+        If a user with the given email already exists, return that user.
+        Otherwise, create a new user with the given email and return it.
+
+        Parameters:
+            user_id (str): The ID of the user.
+            email (str): The email of the user.
+
+        Returns:
+            User: The user with the given email.
+        """
+        user_service = UserService()
+        user = user_service.get_user_by_email(email)
+        if user and not user.user_id:
+            user = user_service.update_user(user, user_id)
+        if not user:
+            user = user_service.create_user(email, user_id)
+        return user
+
+    def create_initial_platform_key(
+        self, user: User, organization: Organization
+    ) -> None:
+        """Create an initial platform key for the given user and organization.
+
+        This method generates a new platform key with the specified parameters
+        and saves it to the database. The generated key is set as active and
+        assigned the name "Key #1". The key is associated with the provided
+        user and organization.
+
+        Parameters:
+            user (User): The user for whom the platform key is being created.
+            organization (Organization):
+                The organization to which the platform key belongs.
+
+        Raises:
+            Exception: If an error occurs while generating the platform key.
+
+        Returns:
+            None
+        """
+        try:
+            PlatformAuthenticationService.generate_platform_key(
+                is_active=True,
+                key_name="Key #1",
+                user=user,
+                organization=organization,
+            )
+        except Exception:
+            logger.error(
+                "Failed to create default platform key for "
+                f"organization {organization.organization_id}"
+            )
+
+    @staticmethod
+    def remove_users_from_organization_by_pks(
+        user_pks: list[str],
+    ) -> None:
+        """Remove users from an organization by their primary keys.
+
+        Parameters:
+            user_pks (list[str]): The primary keys of the users to remove.
+        """
+        # removing user from organization
+        OrganizationMemberService.remove_users_by_user_pks(user_pks)
+        # removing user m2m relations , while removing user
+        for user_pk in user_pks:
+            User.objects.get(pk=user_pk).shared_exported_tools.clear()
+            User.objects.get(pk=user_pk).shared_custom_tool.clear()
+            User.objects.get(pk=user_pk).shared_adapters.clear()
+
+    @staticmethod
+    def remove_user_from_organization_by_user_id(
+        user_id: str, organization_id: str
+    ) -> None:
+        """Remove users from an organization by their user_id.
+
+        Parameters:
+            user_id (str): The user_id of the users to remove.
+        """
+        # removing user from organization
+        OrganizationMemberService.remove_user_by_user_id(user_id)
+        # removing user m2m relations , while removing user
+        User.objects.get(user_id=user_id).shared_exported_tools.clear()
+        User.objects.get(user_id=user_id).shared_custom_tool.clear()
+        User.objects.get(user_id=user_id).shared_adapters.clear()
+        # removing user from organization cache
+        OrganizationMemberService.remove_user_membership_in_organization_cache(
+            user_id=user_id, organization_id=organization_id
+        )
diff --git a/backend/account_v2/authentication_plugin_registry.py b/backend/account_v2/authentication_plugin_registry.py
new file mode 100644
index 0000000000..4521af1cb5
--- /dev/null
+++ b/backend/account_v2/authentication_plugin_registry.py
@@ -0,0 +1,96 @@
+import logging
+import os
+from importlib import import_module
+from typing import Any
+
+from account_v2.constants import PluginConfig
+from django.apps import apps
+
+logger = logging.getLogger(__name__)
+
+
+def _load_plugins() -> dict[str, dict[str, Any]]:
+    """Iterating through the Authentication plugins and register their
+    metadata."""
+    auth_app = apps.get_app_config(PluginConfig.PLUGINS_APP)
+    auth_package_path = auth_app.module.__package__
+    auth_dir = os.path.join(auth_app.path, PluginConfig.AUTH_PLUGIN_DIR)
+    auth_package_path = f"{auth_package_path}.{PluginConfig.AUTH_PLUGIN_DIR}"
+    auth_modules = {}
+
+    for item in os.listdir(auth_dir):
+        # Loads a plugin only if name starts with `auth`.
+        if not item.startswith(PluginConfig.AUTH_MODULE_PREFIX):
+            continue
+        # Loads a plugin if it is in a directory.
+        if os.path.isdir(os.path.join(auth_dir, item)):
+            auth_module_name = item
+        # Loads a plugin if it is a shared library.
+        # Module name is extracted from shared library name.
+        # `auth.platform_architecture.so` will be file name and
+        # `auth` will be the module name.
+        elif item.endswith(".so"):
+            auth_module_name = item.split(".")[0]
+        else:
+            continue
+        try:
+            full_module_path = f"{auth_package_path}.{auth_module_name}"
+            module = import_module(full_module_path)
+            metadata = getattr(module, PluginConfig.AUTH_METADATA, {})
+            if metadata.get(PluginConfig.METADATA_IS_ACTIVE, False):
+                auth_modules[auth_module_name] = {
+                    PluginConfig.AUTH_MODULE: module,
+                    PluginConfig.AUTH_METADATA: module.metadata,
+                }
+                logger.info(
+                    "Loaded auth plugin: %s, is_active: %s",
+                    module.metadata["name"],
+                    module.metadata["is_active"],
+                )
+            else:
+                logger.warning(
+                    "Metadata is not active for %s authentication module.",
+                    auth_module_name,
+                )
+        except ModuleNotFoundError as exception:
+            logger.error(
+                "Error while importing authentication module : %s",
+                exception,
+            )
+
+    if len(auth_modules) > 1:
+        raise ValueError(
+            "Multiple authentication modules found."
+            "Only one authentication method is allowed."
+        )
+    elif len(auth_modules) == 0:
+        logger.warning(
+            "No authentication modules found."
+            "Application will start without authentication module"
+        )
+    return auth_modules
+
+
+class AuthenticationPluginRegistry:
+    auth_modules: dict[str, dict[str, Any]] = _load_plugins()
+
+    @classmethod
+    def is_plugin_available(cls) -> bool:
+        """Check if any authentication plugin is available.
+
+        Returns:
+            bool: True if a plugin is available, False otherwise.
+        """
+        return len(cls.auth_modules) > 0
+
+    @classmethod
+    def get_plugin(cls) -> Any:
+        """Get the selected authentication plugin.
+
+        Returns:
+            AuthenticationService: Selected authentication plugin instance.
+        """
+        chosen_auth_module = next(iter(cls.auth_modules.values()))
+        chosen_metadata = chosen_auth_module[PluginConfig.AUTH_METADATA]
+        service_class_name = chosen_metadata[PluginConfig.METADATA_SERVICE_CLASS]
+        return service_class_name()
diff --git a/backend/account_v2/authentication_service.py b/backend/account_v2/authentication_service.py
new file mode 100644
index 0000000000..273ce4fdbc
--- /dev/null
+++ b/backend/account_v2/authentication_service.py
@@ -0,0 +1,395 @@
+import logging
+import uuid
+from typing import Any, Optional
+
+from account_v2.authentication_helper import AuthenticationHelper
+from account_v2.constants import DefaultOrg, ErrorMessage, UserLoginTemplate
+from account_v2.custom_exceptions import Forbidden, MethodNotImplemented
+from account_v2.dto import (
+    CallbackData,
+    MemberData,
+    MemberInvitation,
+    OrganizationData,
+    ResetUserPasswordDto,
+    UserInfo,
+    UserRoleData,
+)
+from account_v2.enums import UserRole
+from account_v2.models import Organization, User
+from account_v2.organization import OrganizationService
+from account_v2.serializer import LoginRequestSerializer
+from django.conf import settings
+from django.contrib.auth import authenticate, login, logout
+from django.contrib.auth.hashers import make_password
+from django.http import HttpRequest
+from django.shortcuts import redirect, render
+from rest_framework.request import Request
+from rest_framework.response import Response
+from tenant_account_v2.models import OrganizationMember as OrganizationMember
+from tenant_account_v2.organization_member_service import OrganizationMemberService
+
+Logger = logging.getLogger(__name__)
+
+
+class AuthenticationService:
+    def __init__(self) -> None:
+        self.authentication_helper = AuthenticationHelper()
+        self.default_organization: Organization = self.user_organization()
+
+    def user_login(self, request: Request) -> Any:
+        """Authenticate and log in a user.
+
+        Args:
+            request (Request): The HTTP request object.
+
+        Returns:
+            Any: The response object.
+
+        Raises:
+            ValueError: If there is an error in the login credentials.
+        """
+        if request.method == "GET":
+            return self.render_login_page(request)
+        try:
+            validated_data = self.validate_login_credentials(request)
+            username = validated_data.get("username")
+            password = validated_data.get("password")
+        except ValueError as e:
+            return render(
+                request,
+                UserLoginTemplate.TEMPLATE,
+                {UserLoginTemplate.ERROR_PLACE_HOLDER: str(e)},
+            )
+        if self.authenticate_and_login(request, username, password):
+            return redirect(settings.WEB_APP_ORIGIN_URL)
+
+        return self.render_login_page_with_error(request, ErrorMessage.USER_LOGIN_ERROR)
+
+    def is_authenticated(self, request: HttpRequest) -> bool:
+        """Check if the user is authenticated.
+
+        Args:
+            request (Request): The HTTP request object.
+
+        Returns:
+            bool: True if the user is authenticated, False otherwise.
+        """
+        return request.user.is_authenticated
+
+    def authenticate_and_login(
+        self, request: Request, username: str, password: str
+    ) -> bool:
+        """Authenticate and log in a user.
+
+        Args:
+            request (Request): The HTTP request object.
+            username (str): The username of the user.
+            password (str): The password of the user.
+
+        Returns:
+            bool: True if the user is successfully authenticated and logged in,
+                False otherwise.
+        """
+        user = authenticate(request, username=username, password=password)
+        if user:
+            # To avoid conflicts with django superuser
+            if user.is_superuser:
+                return False
+            login(request, user)
+            return True
+        # Attempt to initiate default user and authenticate again
+        if self.set_default_user(username, password):
+            user = authenticate(request, username=username, password=password)
+            if user:
+                login(request, user)
+                return True
+        return False
+
+    def render_login_page(self, request: Request) -> Any:
+        return render(request, UserLoginTemplate.TEMPLATE)
+
+    def render_login_page_with_error(self, request: Request, error_message: str) -> Any:
+        return render(
+            request,
+            UserLoginTemplate.TEMPLATE,
+            {UserLoginTemplate.ERROR_PLACE_HOLDER: error_message},
+        )
+
+    def validate_login_credentials(self, request: Request) -> Any:
+        """Validate the login credentials.
+
+        Args:
+            request (Request): The HTTP request object.
+
+        Returns:
+            dict: The validated login credentials.
+
+        Raises:
+            ValueError: If the login credentials are invalid.
+        """
+        serializer = LoginRequestSerializer(data=request.POST)
+        if not serializer.is_valid():
+            error_messages = {
+                field: errors[0] for field, errors in serializer.errors.items()
+            }
+            first_error_message = list(error_messages.values())[0]
+            raise ValueError(first_error_message)
+        return serializer.validated_data
+
+    def user_signup(self, request: HttpRequest) -> Any:
+        raise MethodNotImplemented()
+
+    def is_admin_by_role(self, role: str) -> bool:
+        """Check the role with actual admin Role.
+
+        Args:
+            role (str): input string
+
+        Returns:
+            bool: _description_
+        """
+        try:
+            return UserRole(role.lower()) == UserRole.ADMIN
+        except ValueError:
+            return False
+
+    def get_callback_data(self, request: Request) -> CallbackData:
+        return CallbackData(
+            user_id=request.user.user_id,
+            email=request.user.email,
+            token="",
+        )
+
+    def user_organization(self) -> Organization:
+        return Organization(
+            name=DefaultOrg.ORGANIZATION_NAME,
+            display_name=DefaultOrg.ORGANIZATION_NAME,
+            organization_id=DefaultOrg.ORGANIZATION_NAME,
+            schema_name=DefaultOrg.ORGANIZATION_NAME,
+        )
+
+    def handle_invited_user_while_callback(
+        self, request: Request, user: User
+    ) -> MemberData:
+        member_data: MemberData = MemberData(
+            user_id=user.user_id,
+            organization_id=self.default_organization.organization_id,
+            role=[UserRole.ADMIN.value],
+        )
+
+        return member_data
+
+    def handle_authorization_callback(self, request: Request, backend: str) -> Response:
+        raise MethodNotImplemented()
+
+    def add_to_organization(
+        self,
+        request: Request,
+        user: User,
+        data: Optional[dict[str, Any]] = None,
+    ) -> MemberData:
+        member_data: MemberData = MemberData(
+            user_id=user.user_id,
+            organization_id=self.default_organization.organization_id,
+        )
+
+        return member_data
+
+    def remove_users_from_organization(
+        self,
+        admin: OrganizationMember,
+        organization_id: str,
+        user_ids: list[str],
+    ) -> bool:
+        raise MethodNotImplemented()
+
+    def user_organizations(self, request: Request) -> list[OrganizationData]:
+        organizationData: OrganizationData = OrganizationData(
+            id=self.default_organization.organization_id,
+            display_name=self.default_organization.display_name,
+            name=self.default_organization.name,
+        )
+        return [organizationData]
+
+    def get_organizations_by_user_id(self, id: str) -> list[OrganizationData]:
+        organizationData: OrganizationData = OrganizationData(
+            id=self.default_organization.organization_id,
+            display_name=self.default_organization.display_name,
+            name=self.default_organization.name,
+        )
+        return [organizationData]
+
+    def get_organization_role_of_user(
+        self, user_id: str, organization_id: str
+    ) -> list[str]:
+        return [UserRole.ADMIN.value]
+
+    def is_organization_admin(self, member: OrganizationMember) -> bool:
+        """Check if the organization member has administrative privileges.
+
+        Args:
+            member (OrganizationMember): The organization member to check.
+
+        Returns:
+            bool: True if the user has administrative privileges,
+                False otherwise.
+        """
+        try:
+            return UserRole(member.role) == UserRole.ADMIN
+        except ValueError:
+            return False
+
+    def check_user_organization_association(self, user_email: str) -> None:
+        """Check if the user is already associated with any organizations.
+
+        Raises:
+        - UserAlreadyAssociatedException:
+            If the user is already associated with organizations.
+        """
+        return None
+
+    def get_roles(self) -> list[UserRoleData]:
+        return [
+            UserRoleData(name=UserRole.ADMIN.value),
+            UserRoleData(name=UserRole.USER.value),
+        ]
+
+    def get_invitations(self, organization_id: str) -> list[MemberInvitation]:
+        raise MethodNotImplemented()
+
+    def frictionless_onboarding(self, organization: Organization, user: User) -> None:
+        raise MethodNotImplemented()
+
+    def hubspot_signup_api(self, request: Request) -> None:
+        raise MethodNotImplemented()
+
+    def delete_invitation(self, organization_id: str, invitation_id: str) -> bool:
+        raise MethodNotImplemented()
+
+    def add_organization_user_role(
+        self,
+        admin: User,
+        organization_id: str,
+        user_id: str,
+        role_ids: list[str],
+    ) -> list[str]:
+        if admin.role == UserRole.ADMIN.value:
+            return role_ids
+        raise Forbidden
+
+    def remove_organization_user_role(
+        self,
+        admin: User,
+        organization_id: str,
+        user_id: str,
+        role_ids: list[str],
+    ) -> list[str]:
+        if admin.role == UserRole.ADMIN.value:
+            return role_ids
+        raise Forbidden
+
+    def get_organization_by_org_id(self, id: str) -> OrganizationData:
+        organizationData: OrganizationData = OrganizationData(
+            id=DefaultOrg.ORGANIZATION_NAME,
+            display_name=DefaultOrg.ORGANIZATION_NAME,
+            name=DefaultOrg.ORGANIZATION_NAME,
+        )
+        return organizationData
+
+    def set_default_user(self, username: str, password: str) -> bool:
+        """Set the default user for authentication.
+
+        This method creates a default user with the provided username and
+        password if the username and password match the default values defined
+        in the 'DefaultOrg' class. The default user is saved in the database.
+
+        Args:
+            username (str): The username of the default user.
+            password (str): The password of the default user.
+
+        Returns:
+            bool: True if the default user is successfully created and saved,
+                False otherwise.
+        """
+        if (
+            username != DefaultOrg.MOCK_USER
+            or password != DefaultOrg.MOCK_USER_PASSWORD
+        ):
+            return False
+
+        user, created = User.objects.get_or_create(username=DefaultOrg.MOCK_USER)
+        if created:
+            user.password = make_password(DefaultOrg.MOCK_USER_PASSWORD)
+        else:
+            user.user_id = DefaultOrg.MOCK_USER_ID
+            user.email = DefaultOrg.MOCK_USER_EMAIL
+            user.password = make_password(DefaultOrg.MOCK_USER_PASSWORD)
+        user.save()
+        return True
+
+    def get_user_info(self, request: Request) -> Optional[UserInfo]:
+        user: User = request.user
+        if user:
+            return UserInfo(
+                id=user.id,
+                user_id=user.user_id,
+                name=user.username,
+                display_name=user.username,
+                email=user.email,
+            )
+        else:
+            return None
+
+    def get_organization_info(self, org_id: str) -> Optional[Organization]:
+        return OrganizationService.get_organization_by_org_id(org_id=org_id)
+
+    def make_organization_and_add_member(
+        self,
+        user_id: str,
+        user_name: str,
+        organization_name: Optional[str] = None,
+        display_name: Optional[str] = None,
+    ) -> Optional[OrganizationData]:
+        organization: OrganizationData = OrganizationData(
+            id=str(uuid.uuid4()),
+            display_name=DefaultOrg.MOCK_ORG,
+            name=DefaultOrg.MOCK_ORG,
+        )
+        return organization
+
+    def make_user_organization_name(self) -> str:
+        return str(uuid.uuid4())
+
+    def make_user_organization_display_name(self, user_name: str) -> str:
+        name = f"{user_name}'s" if user_name else "Your"
+        return f"{name} organization"
+
+    def user_logout(self, request: HttpRequest) -> Response:
+        """Log out the user.
+
+        Args:
+            request (HttpRequest): The HTTP request object.
+
+        Returns:
+            Response: The redirect response to the web app origin URL.
+        """
+        logout(request)
+        return redirect(settings.WEB_APP_ORIGIN_URL)
+
+    def get_organization_members_by_org_id(
+        self, organization_id: str
+    ) -> list[MemberData]:
+        users: list[OrganizationMember] = OrganizationMemberService.get_members()
+        return self.authentication_helper.list_of_members_from_user_model(users)
+
+    def reset_user_password(self, user: User) -> ResetUserPasswordDto:
+        raise MethodNotImplemented()
+
+    def invite_user(
+        self,
+        admin: OrganizationMember,
+        org_id: str,
+        email: str,
+        role: Optional[str] = None,
+    ) -> bool:
+        raise MethodNotImplemented()
diff --git a/backend/account_v2/constants.py b/backend/account_v2/constants.py
new file mode 100644
index 0000000000..15c1137903
--- /dev/null
+++ b/backend/account_v2/constants.py
@@ -0,0 +1,89 @@
+from django.conf import settings
+
+
+class LoginConstant:
+    INVITATION = "invitation"
+    ORGANIZATION = "organization"
+    ORGANIZATION_NAME = "organization_name"
+
+
+class Common:
+    NEXT_URL_VARIABLE = "next"
+    PUBLIC_SCHEMA_NAME = "public"
+    ID = "id"
+    USER_ID = "user_id"
+    USER_EMAIL = "email"
+    USER_EMAILS = "emails"
+    USER_IDS = "user_ids"
+    USER_ROLE = "role"
+    MAX_EMAIL_IN_REQUEST = 10
+    LOG_EVENTS_ID = "log_events_id"
+
+
+class UserModel:
+    USER_ID = "user_id"
+    ID = "id"
+
+
+class OrganizationMemberModel:
+    USER_ID = "user__user_id"
+    ID = "user__id"
+
+
+class Cookie:
+    ORG_ID = "org_id"
+    Z_CODE = "z_code"
+    CSRFTOKEN = "csrftoken"
+
+
+class ErrorMessage:
+    ORGANIZATION_EXIST = "Organization already exists"
+    DUPLICATE_API = "It appears that a duplicate call may have been made."
+    USER_LOGIN_ERROR = "Invalid username or password. Please try again."
+
+
+class DefaultOrg:
+    ORGANIZATION_NAME = "mock_org"
+    MOCK_ORG = "mock_org"
+    MOCK_USER = settings.DEFAULT_AUTH_USERNAME
+    MOCK_USER_ID = "mock_user_id"
+    MOCK_USER_EMAIL = "email@mock.com"
+    MOCK_USER_PASSWORD = settings.DEFAULT_AUTH_PASSWORD
+
+
+class UserLoginTemplate:
+    TEMPLATE = "login.html"
+    ERROR_PLACE_HOLDER = "error_message"
+
+
+class PluginConfig:
+    PLUGINS_APP = "plugins"
+    AUTH_MODULE_PREFIX = "auth"
+    AUTH_PLUGIN_DIR = "authentication"
+    AUTH_MODULE = "module"
+    AUTH_METADATA = "metadata"
+    METADATA_SERVICE_CLASS = "service_class"
+    METADATA_IS_ACTIVE = "is_active"
+
+
+class AuthorizationErrorCode:
+    """Error codes
+    IDM: INVITATION DENIED MESSAGE (Unauthorized invitation)
+    INF: INVITATION NOT FOUND (Invitation is either invalid or has expired)
+    UMM: USER MEMBERSHIP MISCONDUCT
+    USF: USER FOUND (User Account Already Exists for Organization)
+    INE001: INVALID EMAIL Exception code when an invalid email address is used
+    like disposable.
+    INE002: INVALID EMAIL Exception code when an invalid email address format.
+
+    Error code reference :
+        frontend/src/components/error/GenericError/GenericError.jsx.
+    """
+
+    IDM = "IDM"
+    UMM = "UMM"
+    INF = "INF"
+    USF = "USF"
+    USR = "USR"
+    INE001 = "INE001"
+    INE002 = "INE002"
diff --git a/backend/account_v2/custom_auth_middleware.py b/backend/account_v2/custom_auth_middleware.py
new file mode 100644
index 0000000000..db49d7352c
--- /dev/null
+++ b/backend/account_v2/custom_auth_middleware.py
@@ -0,0 +1,51 @@
+from account_v2.authentication_plugin_registry import AuthenticationPluginRegistry
+from account_v2.authentication_service import AuthenticationService
+from account_v2.constants import Common
+from django.conf import settings
+from django.http import HttpRequest, HttpResponse, JsonResponse
+from utils.constants import Account
+from utils.local_context import StateStore
+from utils.user_session import UserSessionUtils
+
+from backend.constants import RequestHeader
+
+
+class CustomAuthMiddleware:
+    def __init__(self, get_response: HttpResponse):
+        self.get_response = get_response
+        # One-time configuration and initialization.
+
+    def __call__(self, request: HttpRequest) -> HttpResponse:
+        # Returns result without authenticated if added in whitelisted paths
+        if any(request.path.startswith(path) for path in settings.WHITELISTED_PATHS):
+            return self.get_response(request)
+
+        # Authenticating With API_KEY
+        x_api_key = request.headers.get(RequestHeader.X_API_KEY)
+        if (
+            settings.INTERNAL_SERVICE_API_KEY
+            and x_api_key == settings.INTERNAL_SERVICE_API_KEY
+        ):  # Should API Key be in settings or just env alone?
+            return self.get_response(request)
+
+        if AuthenticationPluginRegistry.is_plugin_available():
+            auth_service: AuthenticationService = (
+                AuthenticationPluginRegistry.get_plugin()
+            )
+        else:
+            auth_service = AuthenticationService()
+
+        is_authenticated = auth_service.is_authenticated(request)
+
+        if is_authenticated:
+            StateStore.set(Common.LOG_EVENTS_ID, request.session.session_key)
+            StateStore.set(
+                Account.ORGANIZATION_ID,
+                UserSessionUtils.get_organization_id(request=request),
+            )
+            response = self.get_response(request)
+            StateStore.clear(Account.ORGANIZATION_ID)
+            StateStore.clear(Common.LOG_EVENTS_ID)
+
+            return response
+        return JsonResponse({"message": "Unauthorized"}, status=401)
diff --git a/backend/account_v2/custom_authentication.py b/backend/account_v2/custom_authentication.py
new file mode 100644
index 0000000000..1f7cdcb6e3
--- /dev/null
+++ b/backend/account_v2/custom_authentication.py
@@ -0,0 +1,13 @@
+from typing import Any
+
+from django.http import HttpRequest
+from rest_framework.exceptions import AuthenticationFailed
+
+
+def api_login_required(view_func: Any) -> Any:
+    def wrapper(request: HttpRequest, *args: Any, **kwargs: Any) -> Any:
+        if request.user and request.session and "user" in request.session:
+            return view_func(request, *args, **kwargs)
+        raise AuthenticationFailed("Unauthorized")
+
+    return wrapper
diff --git a/backend/account_v2/custom_cache.py b/backend/account_v2/custom_cache.py
new file mode 100644
index 0000000000..182f980f5b
--- /dev/null
+++ b/backend/account_v2/custom_cache.py
@@ -0,0 +1,12 @@
+from django_redis import get_redis_connection
+
+
+class CustomCache:
+    def __init__(self) -> None:
+        self.cache = get_redis_connection("default")
+
+    def rpush(self, key: str, value: str) -> None:
+        self.cache.rpush(key, value)
+
+    def lrem(self, key: str, value: str) -> None:
+        self.cache.lrem(key, value)
diff --git a/backend/account_v2/custom_exceptions.py b/backend/account_v2/custom_exceptions.py
new file mode 100644
index 0000000000..bec24e16c1
--- /dev/null
+++ b/backend/account_v2/custom_exceptions.py
@@ -0,0 +1,60 @@
+from typing import Optional
+
+from rest_framework.exceptions import APIException
+
+
+class ConflictError(Exception):
+    def __init__(self, message: str) -> None:
+        self.message = message
+        super().__init__(self.message)
+
+
+class MethodNotImplemented(APIException):
+    status_code = 501
+    default_detail = "Method Not Implemented"
+
+
+class DuplicateData(APIException):
+    status_code = 400
+    default_detail = "Duplicate Data"
+
+    def __init__(self, detail: Optional[str] = None, code: Optional[int] = None):
+        if detail is not None:
+            self.detail = detail
+        if code is not None:
+            self.code = code
+        super().__init__(detail, code)
+
+
+class TableNotExistError(APIException):
+    status_code = 400
+    default_detail = "Unknown Table"
+
+    def __init__(self, detail: Optional[str] = None, code: Optional[int] = None):
+        if detail is not None:
+            self.detail = detail
+        if code is not None:
+            self.code = code
+        super().__init__()
+
+
+class UserNotExistError(APIException):
+    status_code = 400
+    default_detail = "Unknown User"
+
+    def __init__(self, detail: Optional[str] = None, code: Optional[int] = None):
+        if detail is not None:
+            self.detail = detail
+        if code is not None:
+            self.code = code
+        super().__init__()
+
+
+class Forbidden(APIException):
+    status_code = 403
+    default_detail = "Do not have permission to perform this action."
+
+
+class UserAlreadyAssociatedException(APIException):
+    status_code = 400
+    default_detail = "User is already associated with one organization."
diff --git a/backend/account_v2/dto.py b/backend/account_v2/dto.py
new file mode 100644
index 0000000000..1554901fb6
--- /dev/null
+++ b/backend/account_v2/dto.py
@@ -0,0 +1,131 @@
+from dataclasses import dataclass
+from typing import Any, Optional
+
+
+@dataclass
+class MemberData:
+    user_id: str
+    email: Optional[str] = None
+    name: Optional[str] = None
+    picture: Optional[str] = None
+    role: Optional[list[str]] = None
+    organization_id: Optional[str] = None
+
+
+@dataclass
+class OrganizationData:
+    id: str
+    display_name: str
+    name: str
+
+
+@dataclass
+class CallbackData:
+    user_id: str
+    email: str
+    token: Any
+
+
+@dataclass
+class OrganizationSignupRequestBody:
+    name: str
+    display_name: str
+    organization_id: str
+
+
+@dataclass
+class OrganizationSignupResponse:
+    name: str
+    display_name: str
+    organization_id: str
+    created_at: str
+
+
+@dataclass
+class UserInfo:
+    email: str
+    user_id: str
+    id: Optional[str] = None
+    name: Optional[str] = None
+    display_name: Optional[str] = None
+    family_name: Optional[str] = None
+    picture: Optional[str] = None
+
+
+@dataclass
+class UserSessionInfo:
+    id: str
+    user_id: str
+    email: str
+    organization_id: str
+    user: UserInfo
+
+    @staticmethod
+    def from_dict(data: dict[str, Any]) -> "UserSessionInfo":
+        return UserSessionInfo(
+            id=data["id"],
+            user_id=data["user_id"],
+            email=data["email"],
+            organization_id=data["organization_id"],
+        )
+
+    def to_dict(self) -> Any:
+        return {
+            "id": self.id,
+            "user_id": self.user_id,
+            "email": self.email,
+            "organization_id": self.organization_id,
+        }
+
+
+@dataclass
+class GetUserReposne:
+    user: UserInfo
+    organizations: list[OrganizationData]
+
+
+@dataclass
+class ResetUserPasswordDto:
+    status: bool
+    message: str
+
+
+@dataclass
+class UserInviteResponse:
+    email: str
+    status: str
+    message: Optional[str] = None
+
+
+@dataclass
+class UserRoleData:
+    name: str
+    id: Optional[str] = None
+    description: Optional[str] = None
+
+
+@dataclass
+class MemberInvitation:
+    """Represents an invitation to join an organization.
+
+    Attributes:
+        id (str): The unique identifier for the invitation.
+        email (str): The user email.
+        roles (List[str]): The roles assigned to the invitee.
+        created_at (Optional[str]): The timestamp when the invitation
+            was created.
+        expires_at (Optional[str]): The timestamp when the invitation expires.
+    """
+
+    id: str
+    email: str
+    roles: list[str]
+    created_at: Optional[str] = None
+    expires_at: Optional[str] = None
+
+
+@dataclass
+class UserOrganizationRole:
+    user_id: str
+    role: UserRoleData
+    organization_id: str
diff --git a/backend/account_v2/enums.py b/backend/account_v2/enums.py
new file mode 100644
index 0000000000..d8209ec2d3
--- /dev/null
+++ b/backend/account_v2/enums.py
@@ -0,0 +1,6 @@
+from enum import Enum
+
+
+class UserRole(Enum):
+    USER = "user"
+    ADMIN = "admin"
diff --git a/backend/account_v2/exceptions.py b/backend/account_v2/exceptions.py
new file mode 100644
index 0000000000..2f5d34a84d
--- /dev/null
+++ b/backend/account_v2/exceptions.py
@@ -0,0 +1,31 @@
+from rest_framework.exceptions import APIException
+
+
+class UserIdNotExist(APIException):
+    status_code = 404
+    default_detail = "User ID does not exist"
+
+
+class UserAlreadyExistInOrganization(APIException):
+    status_code = 403
+    default_detail = "User allready exist in the organization"
+
+
+class OrganizationNotExist(APIException):
+    status_code = 404
+    default_detail = "Organization does not exist"
+
+
+class UnknownException(APIException):
+    status_code = 500
+    default_detail = "An unexpected error occurred"
+
+
+class BadRequestException(APIException):
+    status_code = 400
+    default_detail = "Bad Request"
+
+
+class Unauthorized(APIException):
+    status_code = 401
+    default_detail = "Unauthorized"
diff --git a/backend/account_v2/migrations/__init__.py b/backend/account_v2/migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/backend/account_v2/models.py b/backend/account_v2/models.py
new file mode 100644
index 0000000000..61428bda01
--- /dev/null
+++ b/backend/account_v2/models.py
@@ -0,0 +1,144 @@
+import uuid
+
+from django.contrib.auth.models import AbstractUser, Group, Permission
+from django.db import models
+
+from backend.constants import FieldLengthConstants as FieldLength
+
+NAME_SIZE = 64
+KEY_SIZE = 64
+
+
+class Organization(models.Model):
+    """Stores data related to an organization.
+
+    The fields created_by and modified_by is updated after a
+    :model:`account.User` is created.
+    """
+
+    name = models.CharField(max_length=NAME_SIZE)
+    display_name = models.CharField(max_length=NAME_SIZE)
+    organization_id = models.CharField(
+        max_length=FieldLength.ORG_NAME_SIZE, unique=True
+    )
+    created_by = models.ForeignKey(
+        "User",
+        on_delete=models.SET_NULL,
+        related_name="orgs_created",
+        null=True,
+        blank=True,
+    )
+    modified_by = models.ForeignKey(
+        "User",
+        on_delete=models.SET_NULL,
+        related_name="orgs_modified",
+        null=True,
+        blank=True,
+    )
+    modified_at = models.DateTimeField(auto_now=True)
+    created_at = models.DateTimeField(auto_now=True)
+    allowed_token_limit = models.IntegerField(
+        default=-1,
+        db_comment="token limit set in case of frition less onbaoarded org",
+    )
+
+    class Meta:
+        verbose_name = "Organization"
+        verbose_name_plural = "Organizations"
+        db_table = "organization_v2"
+
+
+class User(AbstractUser):
+    """Stores data related to a user belonging to any organization.
+
+    Every org, user is assumed to be unique.
+    """
+
+    # Third Party Authentication User ID
+    user_id = models.CharField()
+    project_storage_created = models.BooleanField(default=False)
+    created_by = models.ForeignKey(
+        "User",
+        on_delete=models.SET_NULL,
+        related_name="users_created",
+        null=True,
+        blank=True,
+    )
+    modified_by = models.ForeignKey(
+        "User",
+        on_delete=models.SET_NULL,
+        related_name="users_modified",
+        null=True,
+        blank=True,
+    )
+    modified_at = models.DateTimeField(auto_now=True)
+    created_at = models.DateTimeField(auto_now_add=True)
+
+    # Specify a unique related_name for the groups field
+    groups = models.ManyToManyField(
+        Group,
+        related_name="users",
+        related_query_name="user",
+        blank=True,
+    )
+
+    # Specify a unique related_name for the user_permissions field
+    user_permissions = models.ManyToManyField(
+        Permission,
+        related_name="users",
+        related_query_name="user",
+        blank=True,
+    )
+
+    def __str__(self):  # type: ignore
+        return f"User({self.id}, email: {self.email}, userId: {self.user_id})"
+
+    class Meta:
+        verbose_name = "User"
+        verbose_name_plural = "Users"
+        db_table = "user_v2"
+
+
+class PlatformKey(models.Model):
+    """Model to hold details of Platform keys.
+
+    Only users with admin role are allowed to perform any operation
+    related keys.
+    """
+
+    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
+    key = models.UUIDField(default=uuid.uuid4)
+    key_name = models.CharField(max_length=KEY_SIZE, null=False, blank=True, default="")
+    is_active = models.BooleanField(default=False)
+    organization = models.ForeignKey(
+        "Organization",
+        on_delete=models.SET_NULL,
+        related_name="platform_keys",
+        null=True,
+        blank=True,
+    )
+    created_by = models.ForeignKey(
+        "User",
+        on_delete=models.SET_NULL,
+        related_name="platform_keys_created",
+        null=True,
+        blank=True,
+    )
+    modified_by = models.ForeignKey(
+        "User",
+        on_delete=models.SET_NULL,
+        related_name="platform_keys_modified",
+        null=True,
+        blank=True,
+    )
+
+    class Meta:
+        verbose_name = "Platform Key"
+        verbose_name_plural = "Platform Keys"
+        db_table = "platform_key_v2"
+        constraints = [
+            models.UniqueConstraint(
+                fields=["key_name", "organization"],
+                name="unique_key_name_organization",
+            ),
+        ]
diff --git a/backend/account_v2/organization.py b/backend/account_v2/organization.py
new file mode 100644
index 0000000000..72ef69200d
--- /dev/null
+++ b/backend/account_v2/organization.py
@@ -0,0 +1,45 @@
+import logging
+from typing import Optional
+
+from account_v2.models import Organization
+from account_v2.subscription_loader import SubscriptionConfig, load_plugins
+from django.db import IntegrityError
+
+Logger = logging.getLogger(__name__)
+
+subscription_loader = load_plugins()
+
+
+class OrganizationService:
+    def __init__(self):  # type: ignore
+        pass
+
+    @staticmethod
+    def get_organization_by_org_id(org_id: str) -> Optional[Organization]:
+        try:
+            return Organization.objects.get(organization_id=org_id)  # type: ignore
+        except Organization.DoesNotExist:
+            return None
+
+    @staticmethod
+    def create_organization(
+        name: str, display_name: str, organization_id: str
+    ) -> Organization:
+        try:
+            organization: Organization = Organization(
+                name=name,
+                display_name=display_name,
+                organization_id=organization_id,
+            )
+            organization.save()
+
+            for subscription_plugin in subscription_loader:
+                cls = subscription_plugin[SubscriptionConfig.METADATA][
+                    SubscriptionConfig.METADATA_SERVICE_CLASS
+                ]
+                cls.add(organization_id=organization_id)
+
+        except IntegrityError as error:
+            Logger.info(f"[Duplicate Id] Failed to create Organization Error: {error}")
+            raise error
+        return organization
diff --git a/backend/account_v2/serializer.py b/backend/account_v2/serializer.py
new file mode 100644
index 0000000000..87c531a4a9
--- /dev/null
+++ b/backend/account_v2/serializer.py
@@ -0,0 +1,117 @@
+import re
+from typing import Optional
+
+from account_v2.models import Organization, User
+from rest_framework import serializers
+
+
+class OrganizationSignupSerializer(serializers.Serializer):
+    name = serializers.CharField(required=True, max_length=150)
+    display_name = serializers.CharField(required=True, max_length=150)
+    organization_id = serializers.CharField(required=True, max_length=30)
+
+    def validate_organization_id(self, value):  # type: ignore
+        if not re.match(r"^[a-z0-9_-]+$", value):
+            raise serializers.ValidationError(
+                "organization_code should only contain "
+                "alphanumeric characters,_ and -."
+            )
+        return value
+
+
+class OrganizationCallbackSerializer(serializers.Serializer):
+    id = serializers.CharField(required=False)
+
+
+class GetOrganizationsResponseSerializer(serializers.Serializer):
+    id = serializers.CharField()
+    display_name = serializers.CharField()
+    name = serializers.CharField()
+    # Add more fields as needed
+
+    def to_representation(self, instance):  # type: ignore
+        data = super().to_representation(instance)
+        # Modify the representation if needed
+        return data
+
+
+class GetOrganizationMembersResponseSerializer(serializers.Serializer):
+    user_id = serializers.CharField()
+    email = serializers.CharField()
+    name = serializers.CharField()
+    picture = serializers.CharField()
+    # Add more fields as needed
+
+    def to_representation(self, instance):  # type: ignore
+        data = super().to_representation(instance)
+        # Modify the representation if needed
+        return data
+
+
+class OrganizationSerializer(serializers.Serializer):
+    name = serializers.CharField()
+    organization_id = serializers.CharField()
+
+
+class SetOrganizationsResponseSerializer(serializers.Serializer):
+    id = serializers.CharField()
+    email = serializers.CharField()
+    name = serializers.CharField()
+    display_name = serializers.CharField()
+    family_name = serializers.CharField()
+    picture = serializers.CharField()
+    # Add more fields as needed
+
+    def to_representation(self, instance):  # type: ignore
+        data = super().to_representation(instance)
+        # Modify the representation if needed
+        return data
+
+
+class ModelTenantSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Organization
+        fields = fields = ("name", "created_on")
+
+
+class UserSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = User
+        fields = ("id", "username")
+
+
+class OrganizationSignupResponseSerializer(serializers.Serializer):
+    name = serializers.CharField()
+    display_name = serializers.CharField()
+    organization_id = serializers.CharField()
+    created_at = serializers.CharField()
+
+
+class LoginRequestSerializer(serializers.Serializer):
+    username = serializers.CharField(required=True)
+    password = serializers.CharField(required=True)
+
+    def validate_username(self, value: Optional[str]) -> str:
+        """Check that the username is not empty and has at least 3
+        characters."""
+        if not value or len(value) < 3:
+            raise serializers.ValidationError(
+                "Username must be at least 3 characters long."
+            )
+        return value
+
+    def validate_password(self, value: Optional[str]) -> str:
+        """Check that the password is not empty and has at least 3
+        characters."""
+        if not value or len(value) < 3:
+            raise serializers.ValidationError(
+                "Password must be at least 3 characters long."
+            )
+        return value
+
+
+class UserSessionResponseSerializer(serializers.Serializer):
+    id = serializers.IntegerField()
+    user_id = serializers.CharField()
+    email = serializers.CharField()
+    organization_id = serializers.CharField()
diff --git a/backend/account_v2/subscription_loader.py b/backend/account_v2/subscription_loader.py
new file mode 100644
index 0000000000..d380ed19dd
--- /dev/null
+++ b/backend/account_v2/subscription_loader.py
@@ -0,0 +1,77 @@
+import logging
+import os
+from importlib import import_module
+from typing import Any
+
+from django.apps import apps
+
+logger = logging.getLogger(__name__)
+
+
+class SubscriptionConfig:
+    """Loader config for subscription plugins."""
+
+    PLUGINS_APP = "plugins"
+    PLUGIN_DIR = "subscription"
+    MODULE = "module"
+    METADATA = "metadata"
+    METADATA_NAME = "name"
+    METADATA_SERVICE_CLASS = "service_class"
+    METADATA_IS_ACTIVE = "is_active"
+
+
+def load_plugins() -> list[Any]:
+    """Iterate through the subscription plugins and register them."""
+    plugins_app = apps.get_app_config(SubscriptionConfig.PLUGINS_APP)
+    package_path = plugins_app.module.__package__
+    subscription_dir = os.path.join(plugins_app.path, SubscriptionConfig.PLUGIN_DIR)
+    subscription_package_path = f"{package_path}.{SubscriptionConfig.PLUGIN_DIR}"
+    subscription_plugins: list[Any] = []
+
+    if not os.path.exists(subscription_dir):
+        return subscription_plugins
+
+    for item in os.listdir(subscription_dir):
+        # Loads a plugin if it is in a directory.
+        if os.path.isdir(os.path.join(subscription_dir, item)):
+            subscription_module_name = item
+        # Loads a plugin if it is a shared library.
+        # Module name is extracted from shared library name.
+        # `subscription.platform_architecture.so` will be file name and
+        # `subscription` will be the module name.
+        elif item.endswith(".so"):
+            subscription_module_name = item.split(".")[0]
+        else:
+            continue
+        try:
+            full_module_path = f"{subscription_package_path}.{subscription_module_name}"
+            module = import_module(full_module_path)
+            metadata = getattr(module, SubscriptionConfig.METADATA, {})
+
+            if metadata.get(SubscriptionConfig.METADATA_IS_ACTIVE, False):
+                subscription_plugins.append(
+                    {
+                        SubscriptionConfig.MODULE: module,
+                        SubscriptionConfig.METADATA: module.metadata,
+                    }
+                )
+                logger.info(
+                    "Loaded subscription plugin: %s, is_active: %s",
+                    module.metadata[SubscriptionConfig.METADATA_NAME],
+                    module.metadata[SubscriptionConfig.METADATA_IS_ACTIVE],
+                )
+            else:
+                logger.info(
+                    "subscription plugin %s is not active.",
+                    subscription_module_name,
+                )
+        except ModuleNotFoundError as exception:
+            logger.error(
+                "Error while importing subscription plugin: %s",
+                exception,
+            )
+
+    if len(subscription_plugins) == 0:
+        logger.info("No subscription plugins found.")
+
+    return subscription_plugins
diff --git a/backend/account_v2/templates/index.html b/backend/account_v2/templates/index.html
new file mode 100644
index 0000000000..ffa0b6085c
--- /dev/null
+++ b/backend/account_v2/templates/index.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en" xml:lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <title>ZipstackID Django App Example</title>
+  </head>
+  <body>
+    <h1 id="profileDropDown">Welcome Guest</h1>
+    <p><a href="{% url 'login' %}" id="qsLoginBtn">Login</a></p>
+  </body>
+</html>
diff --git a/backend/account_v2/templates/login.html b/backend/account_v2/templates/login.html
new file mode 100644
index 0000000000..4edf8e3f1e
--- /dev/null
+++ b/backend/account_v2/templates/login.html
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Login</title>
+    <style>
+        /* Centering the form */
+        body {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            height: 100vh;
+            margin: 0;
+            font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif;
+        }
+        form {
+            width: 300px; /* Adjust as needed */
+            padding: 20px;
+            border: 1px solid #ccc;
+            border-radius: 8px;
+            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+            position: relative; /* Add this to position spinner relative to the form */
+        }
+        /* Custom styles */
+        h2 {
+            text-align: center;
+            margin-top: 0; /* Add this to remove default margin */
+        }
+        label {
+            display: block;
+            margin-bottom: 5px;
+        }
+        input[type="text"],
+        input[type="password"] {
+            width: 100%;
+            padding: 8px;
+            margin-bottom: 10px;
+            border: 1px solid #ccc;
+            border-radius: 4px;
+            box-sizing: border-box;
+        }
+        input[type="submit"] {
+            width: 100%;
+            padding: 10px;
+            background: #2F93F6;
+            color: white;
+            border: none;
+            border-radius: 4px;
+            cursor: pointer;
+        }
+        input[type="submit"]:hover {
+            background: #2F93F6;
+        }
+        .error-message {
+            color: red;
+            text-align: center;
+            margin: 10px 0; /* Add margin for spacing */
+            font-weight: initial;
+            font-size: 12px;
+        }
+        /* Spinner styles */
+        .spinner {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            display: none; /* Initially hidden */
+        }
+        .lds-dual-ring {
+            display: inline-block;
+            width: 64px;
+            height: 64px;
+        }
+        .lds-dual-ring:after {
+            content: " ";
+            display: block;
+            width: 46px;
+            height: 46px;
+            margin: 1px;
+            border-radius: 50%;
+            border: 5px solid #00a6de;
+            border-color: #00a6de transparent #00a6de transparent;
+            animation: lds-dual-ring 1.2s linear infinite;
+        }
+        @keyframes lds-dual-ring {
+            0% {
+                transform: rotate(0deg);
+            }
+            100% {
+                transform: rotate(360deg);
+            }
+        }
+        .logo-box{
+            width: 100%;
+            text-align: center;
+            margin-bottom: 20px;
+            margin-top: 20px;
+        }
+        .login-heading{
+            font-size: 24px;
+            font-weight: initial;
+        }
+    </style>
+</head>
+<body>
+    <form method="post" action="{% url 'login' %}">
+        <div class="spinner">
+            <!-- Spinner animation -->
+            <div class="lds-dual-ring"></div>
+        </div>
+        {% load static %}
+        <div class="logo-box">
+            <img src="{% static 'logo.svg' %}" alt="My image">
+        </div>
+        <h2 class="login-heading">Login</h2>
+        {% if error_message %}
+            <p class="error-message">{{ error_message }}</p>
+        {% endif %}
+        {% csrf_token %}
+        <label for="username">Username</label>
+        <input type="text" id="username" name="username" required>
+        <label for="password">Password</label>
+        <input type="password" id="password" name="password" required><br><br>
+        <input type="submit" value="Login">
+    </form>
+    <script>
+        // Function to show spinner on form submit
+        document.querySelector('form').addEventListener('submit', function() {
+            document.querySelector('.spinner').style.display = 'block';
+        });
+    </script>
+</body>
+</html>
diff --git a/backend/account_v2/tests.py b/backend/account_v2/tests.py
new file mode 100644
index 0000000000..a39b155ac3
--- /dev/null
+++ b/backend/account_v2/tests.py
@@ -0,0 +1 @@
+# Create your tests here.
diff --git a/backend/account_v2/urls.py b/backend/account_v2/urls.py
new file mode 100644
index 0000000000..767be8aba9
--- /dev/null
+++ b/backend/account_v2/urls.py
@@ -0,0 +1,22 @@
+from account_v2.views import (
+    callback,
+    create_organization,
+    get_organizations,
+    get_session_data,
+    login,
+    logout,
+    set_organization,
+    signup,
+)
+from django.urls import path
+
+urlpatterns = [
+    path("login", login, name="login"),
+    path("signup", signup, name="signup"),
+    path("logout", logout, name="logout"),
+    path("callback", callback, name="callback"),
+    path("session", get_session_data, name="session"),
+    path("organizations", get_organizations, name="get_organizations"),
+    path("organization/<str:id>/set", set_organization, name="set_organization"),
+    path("organization/create", create_organization, name="create_organization"),
+]
diff --git a/backend/account_v2/user.py b/backend/account_v2/user.py
new file mode 100644
index 0000000000..7967521e9d
--- /dev/null
+++ b/backend/account_v2/user.py
@@ -0,0 +1,55 @@
+import logging
+from typing import Any, Optional
+
+from account_v2.models import User
+from django.db import IntegrityError
+
+Logger = logging.getLogger(__name__)
+
+
+class UserService:
+    def __init__(
+        self,
+    ) -> None:
+        pass
+
+    def create_user(self, email: str, user_id: str) -> User:
+        try:
+            user: User = User(email=email, user_id=user_id, username=email)
+            user.save()
+        except IntegrityError as error:
+            Logger.info(f"[Duplicate Id] Failed to create User Error: {error}")
+            raise error
+        return user
+
+    def update_user(self, user: User, user_id: str) -> User:
+        user.user_id = user_id
+        user.save()
+        return user
+
+    def get_user_by_email(self, email: str) -> Optional[User]:
+        try:
+            user: User = User.objects.get(email=email)
+            return user
+        except User.DoesNotExist:
+            return None
+
+    def get_user_by_user_id(self, user_id: str) -> Any:
+        try:
+            return User.objects.get(user_id=user_id)
+        except User.DoesNotExist:
+            return None
+
+    def get_user_by_id(self, id: str) -> Any:
+        """Retrieve a user by their ID, taking into account the schema context.
+
+        Args:
+            id (str): The ID of the user.
+
+        Returns:
+            Any: The user object if found, or None if not found.
+        """
+        try:
+            return User.objects.get(id=id)
+        except User.DoesNotExist:
+            return None
diff --git a/backend/account_v2/views.py b/backend/account_v2/views.py
new file mode 100644
index 0000000000..b73add92a4
--- /dev/null
+++ b/backend/account_v2/views.py
@@ -0,0 +1,169 @@
+import logging
+from typing import Any
+
+from account_v2.authentication_controller import AuthenticationController
+from account_v2.dto import (
+    OrganizationSignupRequestBody,
+    OrganizationSignupResponse,
+    UserSessionInfo,
+)
+from account_v2.models import Organization
+from account_v2.organization import OrganizationService
+from account_v2.serializer import (
+    OrganizationSignupResponseSerializer,
+    OrganizationSignupSerializer,
+    UserSessionResponseSerializer,
+)
+from rest_framework import status
+from rest_framework.decorators import api_view
+from rest_framework.request import Request
+from rest_framework.response import Response
+from utils.user_session import UserSessionUtils
+
+Logger = logging.getLogger(__name__)
+
+
+@api_view(["POST"])
+def create_organization(request: Request) -> Response:
+    serializer = OrganizationSignupSerializer(data=request.data)
+    serializer.is_valid(raise_exception=True)
+    try:
+        requestBody: OrganizationSignupRequestBody = makeSignupRequestParams(serializer)
+
+        organization: Organization = OrganizationService.create_organization(
+            requestBody.name,
+            requestBody.display_name,
+            requestBody.organization_id,
+        )
+        response = makeSignupResponse(organization)
+        return Response(
+            status=status.HTTP_201_CREATED,
+            data={"message": "success", "tenant": response},
+        )
+    except Exception as error:
+        Logger.error(error)
+        return Response(
+            status=status.HTTP_500_INTERNAL_SERVER_ERROR, data="Unknown Error"
+        )
+
+
+@api_view(["GET"])
+def callback(request: Request) -> Response:
+    auth_controller = AuthenticationController()
+    return auth_controller.authorization_callback(request)
+
+
+@api_view(["GET", "POST"])
+def login(request: Request) -> Response:
+    auth_controller = AuthenticationController()
+    return auth_controller.user_login(request)
+
+
+@api_view(["GET"])
+def signup(request: Request) -> Response:
+    auth_controller = AuthenticationController()
+    return auth_controller.user_signup(request)
+
+
+@api_view(["GET"])
+def logout(request: Request) -> Response:
+    auth_controller = AuthenticationController()
+    return auth_controller.user_logout(request)
+
+
+@api_view(["GET"])
+def get_organizations(request: Request) -> Response:
+    """get_organizations.
+
+    Retrieve the list of organizations to which the user belongs.
+    Args:
+        request (HttpRequest): _description_
+
+    Returns:
+        Response: A list of organizations with associated information.
+    """
+    auth_controller = AuthenticationController()
+    return auth_controller.user_organizations(request)
+
+
+@api_view(["POST"])
+def set_organization(request: Request, id: str) -> Response:
+    """set_organization.
+
+    Set the current organization to use.
+    Args:
+        request (HttpRequest): _description_
+        id (String): organization Id
+
+    Returns:
+        Response: Contains the User and Current organization details.
+    """
+
+    auth_controller = AuthenticationController()
+    return auth_controller.set_user_organization(request, id)
+
+
+@api_view(["GET"])
+def get_session_data(request: Request) -> Response:
+    """get_session_data.
+
+    Retrieve the current session data.
+    Args:
+        request (HttpRequest): _description_
+
+    Returns:
+        Response: Contains the User and Current organization details.
+    """
+    response = make_session_response(request)
+
+    return Response(
+        status=status.HTTP_201_CREATED,
+        data=response,
+    )
+
+
+def make_session_response(
+    request: Request,
+) -> Any:
+    """make_session_response.
+
+    Make the current session data.
+    Args:
+        request (HttpRequest): _description_
+
+    Returns:
+        User and Current organization details.
+    """
+    auth_controller = AuthenticationController()
+    return UserSessionResponseSerializer(
+        UserSessionInfo(
+            id=request.user.id,
+            user_id=request.user.user_id,
+            email=request.user.email,
+            user=auth_controller.get_user_info(request),
+            organization_id=UserSessionUtils.get_organization_id(request),
+        )
+    ).data
+
+
+def makeSignupRequestParams(
+    serializer: OrganizationSignupSerializer,
+) -> OrganizationSignupRequestBody:
+    return OrganizationSignupRequestBody(
+        serializer.validated_data["name"],
+        serializer.validated_data["display_name"],
+        serializer.validated_data["organization_id"],
+    )
+
+
+def makeSignupResponse(
+    organization: Organization,
+) -> Any:
+    return OrganizationSignupResponseSerializer(
+        OrganizationSignupResponse(
+            organization.name,
+            organization.display_name,
+            organization.organization_id,
+            organization.created_at,
+        )
+    ).data

From 8b250d8e4b9cd6064af39ca2e2cbec58a3590d74 Mon Sep 17 00:00:00 2001
From: ali-zipstack <muhammad.ali@zipstack.com>
Date: Thu, 11 Jul 2024 12:00:24 +0530
Subject: [PATCH 2/2] updated reverse foreign keys

---
 backend/account_v2/authentication_helper.py | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/backend/account_v2/authentication_helper.py b/backend/account_v2/authentication_helper.py
index d2159a0f4d..e303445fcc 100644
--- a/backend/account_v2/authentication_helper.py
+++ b/backend/account_v2/authentication_helper.py
@@ -96,9 +96,9 @@ def remove_users_from_organization_by_pks(
         OrganizationMemberService.remove_users_by_user_pks(user_pks)
         # removing user m2m relations , while removing user
         for user_pk in user_pks:
-            User.objects.get(pk=user_pk).shared_exported_tools.clear()
-            User.objects.get(pk=user_pk).shared_custom_tool.clear()
-            User.objects.get(pk=user_pk).shared_adapters.clear()
+            User.objects.get(pk=user_pk).prompt_registries.clear()
+            User.objects.get(pk=user_pk).shared_custom_tools.clear()
+            User.objects.get(pk=user_pk).shared_adapters_instance.clear()
 
     @staticmethod
     def remove_user_from_organization_by_user_id(
@@ -112,9 +112,9 @@ def remove_user_from_organization_by_user_id(
         # removing user from organization
         OrganizationMemberService.remove_user_by_user_id(user_id)
         # removing user m2m relations , while removing user
-        User.objects.get(user_id=user_id).shared_exported_tools.clear()
-        User.objects.get(user_id=user_id).shared_custom_tool.clear()
-        User.objects.get(user_id=user_id).shared_adapters.clear()
+        User.objects.get(user_id=user_id).prompt_registries.clear()
+        User.objects.get(user_id=user_id).shared_custom_tools.clear()
+        User.objects.get(user_id=user_id).shared_adapters_instance.clear()
         # removing user from organization cache
         OrganizationMemberService.remove_user_membership_in_organization_cache(
             user_id=user_id, organization_id=organization_id
