Skip to content

Commit

Permalink
changes in user crdential updations and readme (#863)
Browse files Browse the repository at this point in the history
* changes in user crdential updations and readme

* update man Read me by adding a linnk of Authentication of  backend readme

* Update README.md

Co-authored-by: Chandrasekharan M <[email protected]>
Signed-off-by: ali <[email protected]>

* Update README.md

Co-authored-by: Chandrasekharan M <[email protected]>
Signed-off-by: ali <[email protected]>

* Update backend/README.md

Co-authored-by: Hari John Kuriakose <[email protected]>
Signed-off-by: ali <[email protected]>

* minor type and logs fixes

---------

Signed-off-by: ali <[email protected]>
Co-authored-by: Chandrasekharan M <[email protected]>
Co-authored-by: Hari John Kuriakose <[email protected]>
  • Loading branch information
3 people authored Dec 5, 2024
1 parent 1a7c794 commit 9f27bc5
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 30 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,15 @@ Next, either download a release or clone this repo and do the following:

`./run-platform.sh`<br>
✅ Now visit [http://frontend.unstract.localhost](http://frontend.unstract.localhost) in your browser <br>
✅ 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

Expand Down
24 changes: 17 additions & 7 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
122 changes: 101 additions & 21 deletions backend/account_v2/authentication_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions backend/tenant_account_v2/organization_member_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit 9f27bc5

Please sign in to comment.