diff --git a/README.md b/README.md
index 2d3dde6bb..6d75357cb 100644
--- a/README.md
+++ b/README.md
@@ -54,12 +54,15 @@ Next, either download a release or clone this repo and do the following:
✅ `./run-platform.sh`
✅ Now visit [http://frontend.unstract.localhost](http://frontend.unstract.localhost) in your browser
-✅ Use user name and password `unstract` to login
+✅ Use username and password `unstract` to login
+
That's all there is to it!
+Follow [these steps](backend/README.md#authentication) to change the default username and password.
+
See [user guide](https://docs.unstract.com/unstract/unstract_platform/user_guides/run_platform) for more details on managing the platform.
-Another really quick way to experience Unstract is by signing up for our [hosted version](https://us-central.unstract.com/).
+Another really quick way to experience Unstract is by signing up for our [hosted version](https://us-central.unstract.com/). It comes with a 14 day free trial!
## ⏩ Quick Start Guide
diff --git a/backend/README.md b/backend/README.md
index a048efec8..8f8cbc330 100644
--- a/backend/README.md
+++ b/backend/README.md
@@ -74,20 +74,30 @@ python manage.py runserver localhost:8000
The default username is `unstract` and the default password is `unstract`.
+### Initial Setup
+
To customize the username or password:
1. Navigate to `/backend/.env` created from [/backend/sample.env](/backend/sample.env)
-1. Update the values for `DEFAULT_AUTH_USERNAME` and `DEFAULT_AUTH_PASSWORD` with strong, unique credentials of your choosing
-1. Save the `/backend/.env` file and restart the server to apply changes
+2. Update the values for `DEFAULT_AUTH_USERNAME` and `DEFAULT_AUTH_PASSWORD` with strong, unique credentials of your choosing
+3. Save the `/backend/.env` file and restart the server to apply changes
+
+### Updating Credentials
+
+To update the username or password after initial setup:
-> **NOTE**: The username `admin` is reserved for Django admin, hence cannot be used
+1. Modify the credentials in `/backend/.env`
+ - **DEFAULT_AUTH_USERNAME**=`your_new_username`
+ - **DEFAULT_AUTH_PASSWORD**=`your_new_password`
+2. Save changes and restart `backend` service
-To update the username or password after it's been set:
+Now you can login with the new credentials.
-1. Modify the username and password in the same `/backend/.env`
-1. Restart server to apply updates
-1. Login with the new credentials
+### Important Notes
+- **DEFAULT_AUTH_USERNAME** must not match the `username` of any `Django superuser` or `admin` account. Keeping them distinct ensures security and avoids potential conflicts.
+- Use strong and unique credentials to protect your system.
+- The authentication system validates credentials against the values specified in the `/backend/.env` file.
## Asynchronous Execution
diff --git a/backend/account_v2/authentication_service.py b/backend/account_v2/authentication_service.py
index 59be49c44..7f2f746cf 100644
--- a/backend/account_v2/authentication_service.py
+++ b/backend/account_v2/authentication_service.py
@@ -27,8 +27,9 @@
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.user_context import UserContext
-Logger = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
class AuthenticationService:
@@ -90,6 +91,13 @@ def authenticate_and_login(
bool: True if the user is successfully authenticated and logged in,
False otherwise.
"""
+ # Validation of user credentials
+ if (
+ username != DefaultOrg.MOCK_USER
+ or password != DefaultOrg.MOCK_USER_PASSWORD
+ ):
+ return False
+
user = authenticate(request, username=username, password=password)
if user:
# To avoid conflicts with django superuser
@@ -98,7 +106,7 @@ def authenticate_and_login(
login(request, user)
return True
# Attempt to initiate default user and authenticate again
- if self.set_default_user(username, password):
+ if self._set_default_user():
user = authenticate(request, username=username, password=password)
if user:
login(request, user)
@@ -295,36 +303,108 @@ def get_organization_by_org_id(self, id: str) -> OrganizationData:
)
return organizationData
- def set_default_user(self, username: str, password: str) -> bool:
+ def _set_default_user(self) -> 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.
+ Creates or updates a default user with predefined credentials.
+
+ Returns:
+ bool: True if the default user is successfully created/updated.
+ """
+ try:
+ UserContext.set_organization_identifier(DefaultOrg.ORGANIZATION_NAME)
+ organization = UserContext.get_organization()
+
+ user = self._get_or_create_user(organization)
+ self._update_user_credentials(user)
+
+ return True
+
+ except Exception as e:
+ logger.error(f"Failed to set default user: {str(e)}")
+ return False
+
+ def _get_or_create_user(self, organization: Optional[Organization]) -> User:
+ """Get existing user or create a new one based on organization context.
Args:
- username (str): The username of the default user.
- password (str): The password of the default user.
+ organization: The organization context
Returns:
- bool: True if the default user is successfully created and saved,
- False otherwise.
+ User: The retrieved or created user
"""
- if (
- username != DefaultOrg.MOCK_USER
- or password != DefaultOrg.MOCK_USER_PASSWORD
- ):
- return False
+ if not organization:
+ return self._create_mock_user()
+ return self._get_or_create_organization_user()
+
+ def _create_mock_user(self) -> User:
+ """Create a new mock user if it doesn't exist.
+
+ Returns:
+ User: The created or existing mock user
+ """
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)
+ logger.info(f"Created new user with username {DefaultOrg.MOCK_USER}")
+ return user
+
+ def _get_or_create_organization_user(self) -> User:
+ """Get or create an organization user with admin privileges.
+
+ Returns:
+ User: The organization user with admin role
+ """
+ admin_user = self._get_admin_user()
+ if admin_user:
+ return admin_user
+
+ member = self._promote_first_member_to_admin()
+ if member:
+ return member.user
+
+ return self._create_mock_user()
+
+ def _get_admin_user(self) -> Optional[User]:
+ """Get the first admin user from the organization.
+
+ Returns:
+ Optional[User]: The admin user if exists, None otherwise
+ """
+ admin_members = OrganizationMemberService.get_members_by_role(
+ role=UserRole.ADMIN.value
+ )
+ return admin_members[0].user if admin_members else None
+
+ def _promote_first_member_to_admin(self) -> Optional[OrganizationMember]:
+ """Promote the first organization member to admin role.
+
+ Returns:
+ Optional[OrganizationMember]: The promoted member if exists, None otherwise
+ """
+ members = OrganizationMemberService.get_members()
+ if not members:
+ logger.warning("No organization member found")
+ return None
+
+ first_member = members[0]
+ OrganizationMemberService.set_member_role(
+ member_id=first_member.member_id, role=UserRole.ADMIN.value
+ )
+ return first_member
+
+ def _update_user_credentials(self, user: User) -> None:
+ """Update user with default credentials.
+
+ Args:
+ user (User): The user to update
+ """
+ user.username = DefaultOrg.MOCK_USER
+ 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
+ logger.info(f"Updated user {user} with username {DefaultOrg.MOCK_USER}")
def get_user_info(self, request: Request) -> Optional[UserInfo]:
user: User = request.user
diff --git a/backend/tenant_account_v2/organization_member_service.py b/backend/tenant_account_v2/organization_member_service.py
index ae9dd085c..71fa4f215 100644
--- a/backend/tenant_account_v2/organization_member_service.py
+++ b/backend/tenant_account_v2/organization_member_service.py
@@ -31,6 +31,30 @@ def get_user_by_id(id: str) -> Optional[OrganizationMember]:
def get_members() -> list[OrganizationMember]:
return OrganizationMember.objects.all()
+ @staticmethod
+ def get_members_by_role(role: str) -> list[OrganizationMember]:
+ """It return members in the order of member_id
+
+ Args:
+ role (str): user role
+
+ Returns:
+ list[OrganizationMember]: list of members
+ """
+ return OrganizationMember.objects.filter(role=role).order_by("member_id")
+
+ @staticmethod
+ def set_member_role(member_id: int, role: str) -> None:
+ """Set the role of a member.
+
+ Parameters:
+ role (str): The role to set.
+ """
+ # Get and update member
+ member = OrganizationMember.objects.get(member_id=member_id)
+ member.role = role.lower()
+ member.save()
+
@staticmethod
def get_members_by_user_email(
user_emails: list[str], values_list_fields: list[str]