Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart with Orion/orion/ORION across 184 files. This includes database identifiers, email addresses, domain references, R2 bucket names, DNS prefixes, encryption salt, Celery app name, config defaults, Docker configs, CI configs, documentation, seed data, and templates. Renames homepage-wizamart.html template to homepage-orion.html. Fixes duplicate file_pattern key in api.yaml architecture rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
15 KiB
Store RBAC System - Complete Guide
Overview
The store dashboard implements a Role-Based Access Control (RBAC) system that distinguishes between Owners and Team Members, with granular permissions for team members.
User Types
1. Store Owner
Who: The user who created the store account.
Characteristics:
- Has ALL permissions automatically (no role needed)
- Cannot be removed or have permissions restricted
- Can invite team members
- Can create and manage roles
- Identified by
StoreUser.user_type = "owner" - Linked via
Store.owner_user_id → User.id
Database:
# StoreUser record for owner
{
"store_id": 1,
"user_id": 5,
"user_type": "owner", # ✓ Owner
"role_id": None, # No role needed
"is_active": True
}
Permissions:
- ✅ All 75 permissions (complete access)
- See full list below
2. Team Members
Who: Users invited by the store owner to help manage the store.
Characteristics:
- Have limited permissions based on assigned role
- Must be invited via email
- Invitation must be accepted before activation
- Can be assigned one of the pre-defined roles or custom role
- Identified by
StoreUser.user_type = "member" - Permissions come from
StoreUser.role_id → Role.permissions
Database:
# StoreUser record for team member
{
"store_id": 1,
"user_id": 7,
"user_type": "member", # ✓ Team member
"role_id": 3, # ✓ Role required
"is_active": True,
"invitation_token": None, # Accepted
"invitation_accepted_at": "2024-11-15 10:30:00"
}
# Role record
{
"id": 3,
"store_id": 1,
"name": "Manager",
"permissions": [
"dashboard.view",
"products.view",
"products.create",
"products.edit",
"orders.view",
...
]
}
Permissions:
- 🔒 Limited based on assigned role
- Can have between 0 and 75 permissions
- Common roles: Manager, Staff, Support, Viewer, Marketing
Permission System
All Available Permissions (75 total)
class StorePermissions(str, Enum):
# Dashboard (1)
DASHBOARD_VIEW = "dashboard.view"
# Products (6)
PRODUCTS_VIEW = "products.view"
PRODUCTS_CREATE = "products.create"
PRODUCTS_EDIT = "products.edit"
PRODUCTS_DELETE = "products.delete"
PRODUCTS_IMPORT = "products.import"
PRODUCTS_EXPORT = "products.export"
# Stock/Inventory (3)
STOCK_VIEW = "stock.view"
STOCK_EDIT = "stock.edit"
STOCK_TRANSFER = "stock.transfer"
# Orders (4)
ORDERS_VIEW = "orders.view"
ORDERS_EDIT = "orders.edit"
ORDERS_CANCEL = "orders.cancel"
ORDERS_REFUND = "orders.refund"
# Customers (4)
CUSTOMERS_VIEW = "customers.view"
CUSTOMERS_EDIT = "customers.edit"
CUSTOMERS_DELETE = "customers.delete"
CUSTOMERS_EXPORT = "customers.export"
# Marketing (3)
MARKETING_VIEW = "marketing.view"
MARKETING_CREATE = "marketing.create"
MARKETING_SEND = "marketing.send"
# Reports (3)
REPORTS_VIEW = "reports.view"
REPORTS_FINANCIAL = "reports.financial"
REPORTS_EXPORT = "reports.export"
# Settings (4)
SETTINGS_VIEW = "settings.view"
SETTINGS_EDIT = "settings.edit"
SETTINGS_THEME = "settings.theme"
SETTINGS_DOMAINS = "settings.domains"
# Team Management (4)
TEAM_VIEW = "team.view"
TEAM_INVITE = "team.invite"
TEAM_EDIT = "team.edit"
TEAM_REMOVE = "team.remove"
# Marketplace Imports (3)
IMPORTS_VIEW = "imports.view"
IMPORTS_CREATE = "imports.create"
IMPORTS_CANCEL = "imports.cancel"
Pre-Defined Roles
1. Owner (All 75 permissions)
Use case: Store owner (automatically assigned)
- ✅ Full access to everything
- ✅ Cannot be restricted
- ✅ No role record needed (permissions checked differently)
2. Manager (43 permissions)
Use case: Senior staff who manage most operations
Has access to:
- ✅ Dashboard, Products (all), Stock (all)
- ✅ Orders (all), Customers (view, edit, export)
- ✅ Marketing (all), Reports (all including financial)
- ✅ Settings (view, theme)
- ✅ Imports (all)
Does NOT have:
- ❌
customers.delete- Cannot delete customers - ❌
settings.edit- Cannot change core settings - ❌
settings.domains- Cannot manage domains - ❌
team.*- Cannot manage team members
3. Staff (10 permissions)
Use case: Daily operations staff
Has access to:
- ✅ Dashboard view
- ✅ Products (view, create, edit)
- ✅ Stock (view, edit)
- ✅ Orders (view, edit)
- ✅ Customers (view, edit)
Does NOT have:
- ❌ Delete anything
- ❌ Import/export
- ❌ Marketing
- ❌ Financial reports
- ❌ Settings
- ❌ Team management
4. Support (6 permissions)
Use case: Customer support team
Has access to:
- ✅ Dashboard view
- ✅ Products (view only)
- ✅ Orders (view, edit)
- ✅ Customers (view, edit)
Does NOT have:
- ❌ Create/delete products
- ❌ Stock management
- ❌ Marketing
- ❌ Reports
- ❌ Settings
- ❌ Team management
5. Viewer (6 permissions)
Use case: Read-only access for reporting/audit
Has access to:
- ✅ Dashboard (view)
- ✅ Products (view)
- ✅ Stock (view)
- ✅ Orders (view)
- ✅ Customers (view)
- ✅ Reports (view)
Does NOT have:
- ❌ Edit anything
- ❌ Create/delete anything
- ❌ Marketing
- ❌ Financial reports
- ❌ Settings
- ❌ Team management
6. Marketing (7 permissions)
Use case: Marketing team focused on campaigns
Has access to:
- ✅ Dashboard (view)
- ✅ Customers (view, export)
- ✅ Marketing (all)
- ✅ Reports (view)
Does NOT have:
- ❌ Products management
- ❌ Orders management
- ❌ Stock management
- ❌ Financial reports
- ❌ Settings
- ❌ Team management
Permission Checking Logic
How Permissions Are Checked
# In User model (models/database/user.py)
def has_store_permission(self, store_id: int, permission: str) -> bool:
"""Check if user has a specific permission in a store."""
# Step 1: Check if user is owner
if self.is_owner_of(store_id):
return True # ✅ Owners have ALL permissions
# Step 2: Check team member permissions
for vm in self.store_memberships:
if vm.store_id == store_id and vm.is_active:
if vm.role and permission in vm.role.permissions:
return True # ✅ Permission found in role
# No permission found
return False
Permission Checking Flow
Request → Middleware → Extract store from URL
↓
Check user authentication
↓
Check if user is owner
├── YES → ✅ Allow (all permissions)
└── NO ↓
Check if user is team member
├── NO → ❌ Deny
└── YES ↓
Check if membership is active
├── NO → ❌ Deny
└── YES ↓
Check if role has required permission
├── NO → ❌ Deny (403 Forbidden)
└── YES → ✅ Allow
Using Permissions in Code
1. Require Specific Permission
When to use: Endpoint needs one specific permission
from fastapi import APIRouter, Depends
from app.api.deps import require_store_permission
from app.core.permissions import StorePermissions
from models.database.user import User
router = APIRouter()
@router.post("/products")
def create_product(
product_data: ProductCreate,
user: User = Depends(
require_store_permission(StorePermissions.PRODUCTS_CREATE.value)
)
):
"""
Create a product.
Required permission: products.create
✅ Owner: Always allowed
✅ Manager: Allowed (has products.create)
✅ Staff: Allowed (has products.create)
❌ Support: Denied (no products.create)
❌ Viewer: Denied (no products.create)
❌ Marketing: Denied (no products.create)
"""
# Create product...
pass
2. Require ANY Permission
When to use: Endpoint can be accessed with any of several permissions
@router.get("/dashboard")
def view_dashboard(
user: User = Depends(
require_any_store_permission(
StorePermissions.DASHBOARD_VIEW.value,
StorePermissions.REPORTS_VIEW.value
)
)
):
"""
View dashboard.
Required: dashboard.view OR reports.view
✅ Owner: Always allowed
✅ Manager: Allowed (has both)
✅ Staff: Allowed (has dashboard.view)
✅ Support: Allowed (has dashboard.view)
✅ Viewer: Allowed (has both)
✅ Marketing: Allowed (has both)
"""
# Show dashboard...
pass
3. Require ALL Permissions
When to use: Endpoint needs multiple permissions
@router.post("/products/bulk-delete")
def bulk_delete_products(
user: User = Depends(
require_all_store_permissions(
StorePermissions.PRODUCTS_VIEW.value,
StorePermissions.PRODUCTS_DELETE.value
)
)
):
"""
Bulk delete products.
Required: products.view AND products.delete
✅ Owner: Always allowed
✅ Manager: Allowed (has both)
❌ Staff: Denied (no products.delete)
❌ Support: Denied (no products.delete)
❌ Viewer: Denied (no products.delete)
❌ Marketing: Denied (no products.delete)
"""
# Delete products...
pass
4. Require Owner Only
When to use: Endpoint is owner-only (team management, critical settings)
from app.api.deps import require_store_owner
@router.post("/team/invite")
def invite_team_member(
email: str,
role_id: int,
user: User = Depends(require_store_owner)
):
"""
Invite a team member.
Required: Must be store owner
✅ Owner: Allowed
❌ Manager: Denied (not owner)
❌ All team members: Denied (not owner)
"""
# Invite team member...
pass
5. Get User Permissions
When to use: Need to check permissions in business logic
from app.api.deps import get_user_permissions
@router.get("/my-permissions")
def list_my_permissions(
permissions: list = Depends(get_user_permissions)
):
"""
Get all permissions for current user.
Returns:
- Owner: All 75 permissions
- Team Member: Permissions from their role
"""
return {"permissions": permissions}
Database Schema
StoreUser Table
CREATE TABLE store_users (
id SERIAL PRIMARY KEY,
store_id INTEGER NOT NULL REFERENCES stores(id),
user_id INTEGER NOT NULL REFERENCES users(id),
user_type VARCHAR NOT NULL, -- 'owner' or 'member'
role_id INTEGER REFERENCES roles(id), -- NULL for owners
invited_by INTEGER REFERENCES users(id),
invitation_token VARCHAR,
invitation_sent_at TIMESTAMP,
invitation_accepted_at TIMESTAMP,
is_active BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
Role Table
CREATE TABLE roles (
id SERIAL PRIMARY KEY,
store_id INTEGER NOT NULL REFERENCES stores(id),
name VARCHAR(100) NOT NULL,
permissions JSON DEFAULT '[]', -- Array of permission strings
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
Team Member Lifecycle
1. Invitation
Owner invites user → StoreUser created:
{
"user_type": "member",
"is_active": False,
"invitation_token": "abc123...",
"invitation_sent_at": "2024-11-29 10:00:00",
"invitation_accepted_at": null
}
2. Acceptance
User accepts invitation → StoreUser updated:
{
"is_active": True,
"invitation_token": null,
"invitation_accepted_at": "2024-11-29 10:30:00"
}
3. Active Member
Member can now access store dashboard with role permissions
4. Deactivation
Owner deactivates member → StoreUser updated:
{
"is_active": False
}
Common Use Cases
Use Case 1: Dashboard Access
Q: Can all users access the dashboard?
A: Yes, if they have dashboard.view permission.
- ✅ Owner: Always
- ✅ Manager, Staff, Support, Viewer, Marketing: All have it
- ❌ Custom role without
dashboard.view: No
Use Case 2: Product Management
Q: Who can create products?
A: Users with products.create permission.
- ✅ Owner: Always
- ✅ Manager: Yes (has permission)
- ✅ Staff: Yes (has permission)
- ❌ Support, Viewer, Marketing: No
Use Case 3: Financial Reports
Q: Who can view financial reports?
A: Users with reports.financial permission.
- ✅ Owner: Always
- ✅ Manager: Yes (has permission)
- ❌ Staff, Support, Viewer, Marketing: No
Use Case 4: Team Management
Q: Who can invite team members?
A: Only the store owner.
- ✅ Owner: Yes (owner-only operation)
- ❌ All team members (including Manager): No
Use Case 5: Settings Changes
Q: Who can change store settings?
A: Users with settings.edit permission.
- ✅ Owner: Always
- ❌ Manager: No (doesn't have permission)
- ❌ All other roles: No
Error Responses
Missing Permission
HTTP 403 Forbidden
{
"error_code": "INSUFFICIENT_STORE_PERMISSIONS",
"message": "You don't have permission to perform this action",
"details": {
"required_permission": "products.delete",
"store_code": "orion"
}
}
Not Owner
HTTP 403 Forbidden
{
"error_code": "STORE_OWNER_ONLY",
"message": "This operation requires store owner privileges",
"details": {
"operation": "team management",
"store_code": "orion"
}
}
Inactive Membership
HTTP 403 Forbidden
{
"error_code": "INACTIVE_STORE_MEMBERSHIP",
"message": "Your store membership is inactive"
}
Summary
Owner vs Team Member
| Feature | Owner | Team Member |
|---|---|---|
| Permissions | All 75 (automatic) | Based on role (0-75) |
| Role Required | No | Yes |
| Can Be Removed | No | Yes |
| Team Management | ✅ Yes | ❌ No |
| Critical Settings | ✅ Yes | ❌ No (usually) |
| Invitation Required | No (creates store) | Yes |
Permission Hierarchy
Owner (75 permissions)
└─ Manager (43 permissions)
└─ Staff (10 permissions)
└─ Support (6 permissions)
└─ Viewer (6 permissions, read-only)
Marketing (7 permissions, specialized)
Best Practices
- Use Constants: Always use
StorePermissions.PERMISSION_NAME.value - Least Privilege: Give team members minimum permissions needed
- Owner Only: Keep sensitive operations owner-only
- Custom Roles: Create custom roles for specific needs
- Regular Audit: Review team member permissions regularly
This RBAC system provides flexible, secure access control for store dashboards with clear separation between owners and team members.