Create Global Admins team when admin user is created

- Admin user is now automatically added to Global Admins team as owner
- Ensures every user belongs to at least one team
- Updated unit tests to handle multiple db.add() calls
This commit is contained in:
Mondo Diaz
2026-01-28 17:24:26 +00:00
parent 1bf8274d8c
commit 6c79147cbf
2 changed files with 46 additions and 15 deletions

View File

@@ -11,7 +11,7 @@ from typing import Optional
from passlib.context import CryptContext from passlib.context import CryptContext
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from .models import User, Session as UserSession, APIKey from .models import User, Session as UserSession, APIKey, Team, TeamMembership
from .config import get_settings from .config import get_settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -363,6 +363,8 @@ def create_default_admin(db: Session) -> Optional[User]:
The admin password can be set via ORCHARD_ADMIN_PASSWORD environment variable. The admin password can be set via ORCHARD_ADMIN_PASSWORD environment variable.
If not set, defaults to 'changeme123' and requires password change on first login. If not set, defaults to 'changeme123' and requires password change on first login.
Also creates the "Global Admins" team and adds the admin user to it.
""" """
# Check if any users exist # Check if any users exist
user_count = db.query(User).count() user_count = db.query(User).count()
@@ -385,6 +387,27 @@ def create_default_admin(db: Session) -> Optional[User]:
must_change_password=must_change, must_change_password=must_change,
) )
# Create Global Admins team and add admin to it
global_admins_team = Team(
name="Global Admins",
slug="global-admins",
description="System administrators with full access",
created_by="admin",
)
db.add(global_admins_team)
db.flush()
membership = TeamMembership(
team_id=global_admins_team.id,
user_id=admin.id,
role="owner",
invited_by="admin",
)
db.add(membership)
db.commit()
logger.info("Created Global Admins team and added admin as owner")
if settings.admin_password: if settings.admin_password:
logger.info("Created default admin user with configured password") logger.info("Created default admin user with configured password")
else: else:

View File

@@ -10,6 +10,7 @@ class TestCreateDefaultAdmin:
def test_create_default_admin_with_env_password(self): def test_create_default_admin_with_env_password(self):
"""Test that ORCHARD_ADMIN_PASSWORD env var sets admin password.""" """Test that ORCHARD_ADMIN_PASSWORD env var sets admin password."""
from app.auth import create_default_admin, verify_password from app.auth import create_default_admin, verify_password
from app.models import User
# Create mock settings with custom password # Create mock settings with custom password
mock_settings = MagicMock() mock_settings = MagicMock()
@@ -19,20 +20,23 @@ class TestCreateDefaultAdmin:
mock_db = MagicMock() mock_db = MagicMock()
mock_db.query.return_value.count.return_value = 0 # No existing users mock_db.query.return_value.count.return_value = 0 # No existing users
# Track the user that gets created # Track all objects that get created
created_user = None created_objects = []
def capture_user(user): def capture_object(obj):
nonlocal created_user created_objects.append(obj)
created_user = user
mock_db.add.side_effect = capture_user mock_db.add.side_effect = capture_object
with patch("app.auth.get_settings", return_value=mock_settings): with patch("app.auth.get_settings", return_value=mock_settings):
admin = create_default_admin(mock_db) admin = create_default_admin(mock_db)
# Verify the user was created # Verify objects were created (user, team, membership)
assert mock_db.add.called assert mock_db.add.called
assert len(created_objects) >= 1
# Find the user object
created_user = next((obj for obj in created_objects if isinstance(obj, User)), None)
assert created_user is not None assert created_user is not None
assert created_user.username == "admin" assert created_user.username == "admin"
assert created_user.is_admin is True assert created_user.is_admin is True
@@ -44,6 +48,7 @@ class TestCreateDefaultAdmin:
def test_create_default_admin_with_default_password(self): def test_create_default_admin_with_default_password(self):
"""Test that default password 'changeme123' is used when env var not set.""" """Test that default password 'changeme123' is used when env var not set."""
from app.auth import create_default_admin, verify_password from app.auth import create_default_admin, verify_password
from app.models import User
# Create mock settings with empty password (default) # Create mock settings with empty password (default)
mock_settings = MagicMock() mock_settings = MagicMock()
@@ -53,20 +58,23 @@ class TestCreateDefaultAdmin:
mock_db = MagicMock() mock_db = MagicMock()
mock_db.query.return_value.count.return_value = 0 # No existing users mock_db.query.return_value.count.return_value = 0 # No existing users
# Track the user that gets created # Track all objects that get created
created_user = None created_objects = []
def capture_user(user): def capture_object(obj):
nonlocal created_user created_objects.append(obj)
created_user = user
mock_db.add.side_effect = capture_user mock_db.add.side_effect = capture_object
with patch("app.auth.get_settings", return_value=mock_settings): with patch("app.auth.get_settings", return_value=mock_settings):
admin = create_default_admin(mock_db) admin = create_default_admin(mock_db)
# Verify the user was created # Verify objects were created
assert mock_db.add.called assert mock_db.add.called
assert len(created_objects) >= 1
# Find the user object
created_user = next((obj for obj in created_objects if isinstance(obj, User)), None)
assert created_user is not None assert created_user is not None
assert created_user.username == "admin" assert created_user.username == "admin"
assert created_user.is_admin is True assert created_user.is_admin is True