fix: store login crash and dashboard misrouted as storefront
Some checks failed
Some checks failed
- Seed default RBAC roles per store and assign role_id to StoreUser records (was never implemented after RBAC Phase 1 cleanup) - Handle nullable role in auth_service find_user_store and get_user_store_role to prevent NoneType crash on login - Use platform_clean_path instead of clean_path in FrontendTypeMiddleware so /store/X/dashboard is detected as STORE, not STOREFRONT Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -131,7 +131,8 @@ class AuthService:
|
||||
)
|
||||
|
||||
if store_user:
|
||||
return True, store_user.role.name
|
||||
role_name = store_user.role.name if store_user.role else "staff"
|
||||
return True, role_name
|
||||
|
||||
return False, None
|
||||
|
||||
@@ -213,7 +214,8 @@ class AuthService:
|
||||
(vm for vm in user.store_memberships if vm.is_active), None
|
||||
)
|
||||
if active_membership:
|
||||
return active_membership.store, active_membership.role.name
|
||||
role_name = active_membership.role.name if active_membership.role else "staff"
|
||||
return active_membership.store, role_name
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
@@ -43,8 +43,11 @@ class FrontendTypeMiddleware(BaseHTTPMiddleware):
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
"""Detect frontend type and inject into request state."""
|
||||
host = request.headers.get("host", "")
|
||||
# Use clean_path if available (from store_context_middleware), else original path
|
||||
path = getattr(request.state, "clean_path", None) or request.url.path
|
||||
# Use platform_clean_path (platform prefix stripped, store prefix retained)
|
||||
# so FrontendDetector can distinguish /store/ from /storefront/.
|
||||
# Do NOT use clean_path here — it has the store prefix stripped too,
|
||||
# which makes /store/X/dashboard indistinguishable from /storefront/X/products.
|
||||
path = getattr(request.state, "platform_clean_path", None) or request.url.path
|
||||
|
||||
# Check if store context exists (set by StoreContextMiddleware)
|
||||
has_store_context = (
|
||||
|
||||
@@ -221,6 +221,7 @@ DEMO_SUBSCRIPTIONS = [
|
||||
]
|
||||
|
||||
# Demo team members (linked to merchants by index, assigned to stores by store_code)
|
||||
# Role must be one of: manager, staff, support, viewer, marketing (see ROLE_PRESETS)
|
||||
DEMO_TEAM_MEMBERS = [
|
||||
# WizaCorp team
|
||||
{
|
||||
@@ -229,6 +230,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
"password": "password123", # noqa: SEC001
|
||||
"first_name": "Alice",
|
||||
"last_name": "Manager",
|
||||
"role": "manager",
|
||||
"store_codes": ["WIZATECH", "WIZAGADGETS"], # manages two stores
|
||||
},
|
||||
{
|
||||
@@ -237,6 +239,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
"password": "password123", # noqa: SEC001
|
||||
"first_name": "Charlie",
|
||||
"last_name": "Staff",
|
||||
"role": "staff",
|
||||
"store_codes": ["WIZAHOME"],
|
||||
},
|
||||
# Fashion Group team
|
||||
@@ -246,6 +249,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
"password": "password123", # noqa: SEC001
|
||||
"first_name": "Diana",
|
||||
"last_name": "Stylist",
|
||||
"role": "manager",
|
||||
"store_codes": ["FASHIONHUB", "FASHIONOUTLET"],
|
||||
},
|
||||
{
|
||||
@@ -254,6 +258,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
"password": "password123", # noqa: SEC001
|
||||
"first_name": "Eric",
|
||||
"last_name": "Sales",
|
||||
"role": "staff",
|
||||
"store_codes": ["FASHIONOUTLET"],
|
||||
},
|
||||
# BookWorld team
|
||||
@@ -263,6 +268,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
"password": "password123", # noqa: SEC001
|
||||
"first_name": "Fiona",
|
||||
"last_name": "Editor",
|
||||
"role": "manager",
|
||||
"store_codes": ["BOOKSTORE", "BOOKDIGITAL"],
|
||||
},
|
||||
]
|
||||
@@ -853,10 +859,37 @@ def create_demo_stores(
|
||||
return stores
|
||||
|
||||
|
||||
def _ensure_store_roles(db: Session, store: Store) -> dict[str, Role]:
|
||||
"""Ensure default roles exist for a store, return name→Role lookup."""
|
||||
from app.modules.tenancy.services.permission_discovery_service import (
|
||||
permission_discovery_service,
|
||||
)
|
||||
|
||||
existing = db.query(Role).filter(Role.store_id == store.id).all()
|
||||
if existing:
|
||||
return {r.name: r for r in existing}
|
||||
|
||||
role_names = ["manager", "staff", "support", "viewer", "marketing"]
|
||||
roles = {}
|
||||
for name in role_names:
|
||||
permissions = list(permission_discovery_service.get_preset_permissions(name))
|
||||
role = Role(
|
||||
store_id=store.id,
|
||||
name=name,
|
||||
permissions=permissions,
|
||||
)
|
||||
db.add(role) # noqa: PERF006
|
||||
roles[name] = role
|
||||
|
||||
db.flush()
|
||||
print_success(f" Created default roles for {store.name}: {', '.join(role_names)}")
|
||||
return roles
|
||||
|
||||
|
||||
def create_demo_team_members(
|
||||
db: Session, stores: list[Store], auth_manager: AuthManager
|
||||
) -> list[User]:
|
||||
"""Create demo team member users and assign them to stores."""
|
||||
"""Create demo team member users and assign them to stores with roles."""
|
||||
|
||||
if SEED_MODE == "minimal":
|
||||
return []
|
||||
@@ -865,6 +898,15 @@ def create_demo_team_members(
|
||||
# Build a store_code → Store lookup from the created stores
|
||||
store_lookup = {s.store_code: s for s in stores}
|
||||
|
||||
# Pre-create default roles for all stores that team members will be assigned to
|
||||
store_roles: dict[str, dict[str, Role]] = {}
|
||||
for member_data in DEMO_TEAM_MEMBERS:
|
||||
for store_code in member_data["store_codes"]:
|
||||
if store_code not in store_roles:
|
||||
store = store_lookup.get(store_code)
|
||||
if store:
|
||||
store_roles[store_code] = _ensure_store_roles(db, store)
|
||||
|
||||
for member_data in DEMO_TEAM_MEMBERS:
|
||||
# Check if user already exists
|
||||
user = db.execute(
|
||||
@@ -894,7 +936,8 @@ def create_demo_team_members(
|
||||
|
||||
team_users.append(user)
|
||||
|
||||
# Assign user to stores
|
||||
# Assign user to stores with role
|
||||
role_name = member_data["role"]
|
||||
for store_code in member_data["store_codes"]:
|
||||
store = store_lookup.get(store_code)
|
||||
if not store:
|
||||
@@ -912,14 +955,20 @@ def create_demo_team_members(
|
||||
if existing_link:
|
||||
continue
|
||||
|
||||
# Look up role for this store
|
||||
role = store_roles.get(store_code, {}).get(role_name)
|
||||
|
||||
store_user = StoreUser(
|
||||
store_id=store.id,
|
||||
user_id=user.id,
|
||||
role_id=role.id if role else None,
|
||||
is_active=True,
|
||||
created_at=datetime.now(UTC),
|
||||
)
|
||||
db.add(store_user) # noqa: PERF006
|
||||
print_success(f" Assigned {user.first_name} to {store.name} as team member")
|
||||
print_success(
|
||||
f" Assigned {user.first_name} to {store.name} as {role_name}"
|
||||
)
|
||||
|
||||
db.flush()
|
||||
return team_users
|
||||
|
||||
Reference in New Issue
Block a user