Skip to content

Commit

Permalink
Enable passing custom admin username and password, enable resetting … (
Browse files Browse the repository at this point in the history
…#499)

Close #484

---------

Co-authored-by: sykp241095 <[email protected]>
  • Loading branch information
sszgwdk and sykp241095 authored Dec 16, 2024
1 parent 4146511 commit bc20993
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 9 deletions.
29 changes: 27 additions & 2 deletions backend/app/auth/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
from fastapi_users.authentication.strategy import DatabaseStrategy
from fastapi_users_db_sqlmodel import SQLModelUserDatabaseAsync
from fastapi_users_db_sqlmodel.access_token import SQLModelAccessTokenDatabaseAsync
from fastapi_users.exceptions import UserAlreadyExists
from fastapi_users.exceptions import UserAlreadyExists, UserNotExists
from sqlmodel.ext.asyncio.session import AsyncSession

from app.core.config import settings, Environment
from app.core.db import get_db_async_session
from app.models import User, UserSession
from app.auth.db import get_user_db, get_user_session_db
from app.auth.api_keys import api_key_manager
from app.auth.schemas import UserCreate
from app.auth.schemas import UserCreate, UserUpdate

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -157,3 +157,28 @@ async def create_user(
except UserAlreadyExists:
logger.error(f"User {email} already exists")
raise

async def update_user_password(
session: AsyncSession,
user_id: uuid.UUID,
new_password: str,
) -> User:
try:
async with get_user_db_context(session) as user_db:
async with get_user_manager_context(user_db) as user_manager:
user = await user_manager.get(user_id)
if not user:
raise UserNotExists(f"User {user_id} does not exist")

user_update = UserUpdate(password=new_password)
await user_manager.update(user_update, user)
# verify
updated_user = await user_manager.get(user_id)
return updated_user

except UserNotExists as e:
logger.error(str(e))
raise
except Exception as e:
logger.error(f"Failed to update password for user {id}: {e}")
raise
36 changes: 29 additions & 7 deletions backend/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from app.models import User, ChatEngine


async def ensure_admin_user(session: AsyncSession, email: str | None = None, password: str | None = None) -> None:
async def ensure_admin_user(session: AsyncSession, email: str | None = None,
password: str | None = None) -> None:
result = await session.exec(select(User).where(User.is_superuser == True))
user = result.first()
if not user:
Expand All @@ -34,6 +35,25 @@ async def ensure_admin_user(session: AsyncSession, email: str | None = None, pas
else:
print(Fore.YELLOW + "Admin user already exists, skipping...")

async def reset_admin_password(session: AsyncSession, new_password: str | None = None) -> None:
result = await session.exec(select(User).where(User.is_superuser == True))
user = result.first()
if not user:
print(Fore.YELLOW + "Admin user does not exist, skipping reset password...")
else:
from app.auth.users import update_user_password

admin_password = new_password or secrets.token_urlsafe(16)
updated_user = await update_user_password(
session,
user_id=user.id,
new_password=admin_password,
)
print(
Fore.GREEN + "Admin user password reset SUCCESS!\n"
f"email: {updated_user.email} \n"
f"password: {admin_password}" + Style.RESET_ALL
)

async def ensure_default_chat_engine(session: AsyncSession) -> None:
result = await session.scalar(func.count(ChatEngine.id))
Expand All @@ -52,21 +72,23 @@ async def ensure_default_chat_engine(session: AsyncSession) -> None:
print(Fore.YELLOW + "Default chat engine already exists, skipping...")


async def bootstrap(email: str | None = None, password: str | None = None) -> None:
async def bootstrap(email: str | None = None, password: str | None = None,
reset_password: bool = False) -> None:
async with get_db_async_session_context() as session:
await ensure_admin_user(session, email, password)
await ensure_default_chat_engine(session)

if reset_password:
await reset_admin_password(session, password)

@click.command()
@click.option("--email", default=None, help="Admin user email, [email protected]")
@click.option("--password", default=None, help="Admin user password, default=random generated")
def main(email: str | None, password: str | None):
@click.option('--reset-password', '-r', is_flag=True, help='Reset admin user password.')
def main(email: str | None, password: str | None, reset_password: bool):
"""Bootstrap the application with optional admin credentials."""
print(Fore.GREEN + "Bootstrapping the application..." + Style.RESET_ALL)
asyncio.run(bootstrap(email, password))
asyncio.run(bootstrap(email, password, reset_password))
print(Fore.GREEN + "Bootstrapping completed." + Style.RESET_ALL)


if __name__ == "__main__":
main()
main()
8 changes: 8 additions & 0 deletions frontend/app/src/pages/docs/deploy-with-docker.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ This document provides instructions for deploying the entire Autoflow applicatio

Running the bootstrap script creates an admin user. You can find the username and password in the output.

```bash
# Reset admin password (random generated)
docker compose run backend /bin/sh -c "python bootstrap.py -r"

# Or specify a new password
docker compose run backend /bin/sh -c "python bootstrap.py -r --password <new_password>"
```

5. Start the services:

```bash
Expand Down

0 comments on commit bc20993

Please sign in to comment.