feat: RBAC Phase 1 — consolidate user roles into 4-value enum
Some checks failed
Some checks failed
Consolidate User.role (2-value: admin/store) + User.is_super_admin (boolean) into a single 4-value UserRole enum: super_admin, platform_admin, merchant_owner, store_member. Drop stale StoreUser.user_type column. Fix role="user" bug in merchant creation. Key changes: - Expand UserRole enum from 2 to 4 values with computed properties (is_admin, is_super_admin, is_platform_admin, is_merchant_owner, is_store_user) - Add Alembic migration (tenancy_003) for data migration + column drops - Remove is_super_admin from JWT token payload - Update all auth dependencies, services, routes, templates, JS, and tests - Update all RBAC documentation 66 files changed, 1219 unit tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -57,7 +57,7 @@ def admin_login(
|
||||
login_result = auth_service.login_user(db=db, user_credentials=user_credentials)
|
||||
|
||||
# Verify user is admin
|
||||
if login_result["user"].role != "admin":
|
||||
if not login_result["user"].is_admin:
|
||||
logger.warning(
|
||||
f"Non-admin user attempted admin login: {user_credentials.email_or_username}"
|
||||
)
|
||||
|
||||
@@ -92,7 +92,7 @@ def get_all_users(
|
||||
store_id=su.store_id,
|
||||
store_code=su.store.store_code,
|
||||
store_name=su.store.name,
|
||||
user_type=su.user_type,
|
||||
role=su.user.role,
|
||||
is_active=su.is_active,
|
||||
)
|
||||
for su in (user.store_memberships or [])
|
||||
@@ -299,7 +299,7 @@ def get_user_details(
|
||||
store_id=su.store_id,
|
||||
store_code=su.store.store_code,
|
||||
store_name=su.store.name,
|
||||
user_type=su.user_type,
|
||||
role=su.user.role,
|
||||
is_active=su.is_active,
|
||||
)
|
||||
for su in (user.store_memberships or [])
|
||||
|
||||
@@ -57,7 +57,7 @@ class AdminUserResponse(BaseModel):
|
||||
first_name: str | None = None
|
||||
last_name: str | None = None
|
||||
is_active: bool
|
||||
is_super_admin: bool
|
||||
role: str
|
||||
platform_assignments: list[PlatformAssignmentResponse] = []
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
@@ -82,7 +82,7 @@ class CreateAdminUserRequest(BaseModel):
|
||||
password: str
|
||||
first_name: str | None = None
|
||||
last_name: str | None = None
|
||||
is_super_admin: bool = False
|
||||
role: str = "platform_admin"
|
||||
platform_ids: list[int] = []
|
||||
|
||||
|
||||
@@ -93,9 +93,9 @@ class AssignPlatformRequest(BaseModel):
|
||||
|
||||
|
||||
class ToggleSuperAdminRequest(BaseModel):
|
||||
"""Request to toggle super admin status."""
|
||||
"""Request to change admin role (super_admin or platform_admin)."""
|
||||
|
||||
is_super_admin: bool
|
||||
role: str # "super_admin" or "platform_admin"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -125,7 +125,7 @@ def _build_admin_response(admin: User) -> AdminUserResponse:
|
||||
first_name=admin.first_name,
|
||||
last_name=admin.last_name,
|
||||
is_active=admin.is_active,
|
||||
is_super_admin=admin.is_super_admin,
|
||||
role=admin.role,
|
||||
platform_assignments=assignments,
|
||||
created_at=admin.created_at,
|
||||
updated_at=admin.updated_at,
|
||||
@@ -174,14 +174,14 @@ def create_admin_user(
|
||||
|
||||
Super admin only.
|
||||
"""
|
||||
# Validate platform_ids required for non-super admin
|
||||
if not request.is_super_admin and not request.platform_ids:
|
||||
# Validate platform_ids required for platform admins
|
||||
if request.role != "super_admin" and not request.platform_ids:
|
||||
raise ValidationException(
|
||||
"Platform admins must be assigned to at least one platform",
|
||||
field="platform_ids",
|
||||
)
|
||||
|
||||
if request.is_super_admin:
|
||||
if request.role == "super_admin":
|
||||
# Create super admin using service
|
||||
user = admin_platform_service.create_super_admin(
|
||||
db=db,
|
||||
@@ -202,7 +202,7 @@ def create_admin_user(
|
||||
first_name=user.first_name,
|
||||
last_name=user.last_name,
|
||||
is_active=user.is_active,
|
||||
is_super_admin=user.is_super_admin,
|
||||
role=user.role,
|
||||
platform_assignments=[],
|
||||
)
|
||||
# Create platform admin with assignments using service
|
||||
@@ -306,17 +306,17 @@ def toggle_super_admin_status(
|
||||
user = admin_platform_service.toggle_super_admin(
|
||||
db=db,
|
||||
user_id=user_id,
|
||||
is_super_admin=request.is_super_admin,
|
||||
is_super_admin=(request.role == "super_admin"),
|
||||
current_admin_id=current_admin.id,
|
||||
)
|
||||
db.commit()
|
||||
|
||||
action = "promoted to" if request.is_super_admin else "demoted from"
|
||||
action = "promoted to" if request.role == "super_admin" else "demoted from"
|
||||
|
||||
return {
|
||||
"message": f"Admin {action} super admin successfully",
|
||||
"user_id": user_id,
|
||||
"is_super_admin": user.is_super_admin,
|
||||
"role": user.role,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ def store_login(
|
||||
user = login_result["user"]
|
||||
|
||||
# CRITICAL: Prevent admin users from using store login
|
||||
if user.role == "admin":
|
||||
if user.is_admin:
|
||||
logger.warning(f"Admin user attempted store login: {user.username}")
|
||||
raise InvalidCredentialsException(
|
||||
"Admins cannot access store portal. Please use admin portal."
|
||||
|
||||
Reference in New Issue
Block a user