Access Control Update Summary
Issue
The chart widget endpoints were using role-based checks (role: "admin") but the JWT token contains role_id: "admin". This caused a 403 error even for valid admin users.
Solution
Updated the access control system to use permission-based authentication instead of role-based checks, following the same pattern as TMS (Transaction Management Service).
Changes Made
1. Updated AccessID Enum (app/dependencies/auth.py)
Added new permission identifiers for widgets:
class AccessID(Enum):
# ... existing permissions ...
# Chart Widgets
VIEW_CHART_WIDGETS = "view_charts"
VIEW_REVENUE_CHARTS = "view_charts"
VIEW_MARGIN_CHARTS = "view_charts"
VIEW_INVENTORY_CHARTS = "view_charts"
VIEW_SALES_CHARTS = "view_charts"
VIEW_STAFF_CHARTS = "view_charts"
VIEW_PERSONAL_CHARTS = "view_charts"
# Table Widgets
VIEW_TABLE_WIDGETS = "view_tables"
VIEW_ORDER_TABLES = "view_tables"
VIEW_INVENTORY_TABLES = "view_tables"
VIEW_CUSTOMER_TABLES = "view_tables"
# KPI Widgets
VIEW_KPI_WIDGETS = "view_kpis"
2. Updated Chart Widget Router (app/routers/chart_widget_router.py)
Before (Role-based):
async def require_manager_or_admin(current_user: dict = Depends(get_current_user)):
"""Require manager or admin role."""
if current_user.get("role") not in ["admin", "manager"]:
raise HTTPException(status_code=403, detail="Manager or admin access required")
return current_user
@router.post("/revenue-trend")
async def get_revenue_trend_chart(
current_user: dict = Depends(require_manager_or_admin)
):
...
After (Permission-based):
async def require_view_charts_permission(current_user: dict = Depends(get_current_user)):
"""Require permission to view chart widgets."""
return await require_permission(AccessID.VIEW_CHART_WIDGETS.value, current_user)
@router.post("/revenue-trend")
async def get_revenue_trend_chart(
current_user: dict = Depends(require_view_charts_permission)
):
...
3. Updated All Chart Endpoints
All 9 chart widget endpoints now use permission-based access:
| Endpoint | Old Access | New Access |
|---|---|---|
/revenue-trend |
Admin, Manager roles | view_charts permission |
/gross-margin-trend |
Admin role | view_charts permission |
/channel-mix |
Admin, Manager roles | view_charts permission |
/top-skus |
Admin, Manager roles | view_charts permission |
/inventory-status |
All roles | view_charts permission |
/top-selling-products |
Manager, Associate roles | view_charts permission |
/staff-performance |
Manager role | view_charts permission |
/my-sales-trend |
Associate role | view_personal_charts permission |
/my-top-products |
Associate role | view_personal_charts permission |
4. Simplified Universal Endpoint
The /widget endpoint now:
- Uses permission-based access control
- Removed role checking logic
- Simplified handler mapping
How It Works
Permission Check Flow
User makes request with JWT token containing:
{ "merchant_id": "IN-NATUR-CHEANN-7D2B-O9BP1", "associate_id": "AST011", "role_id": "admin", "branch_id": "hq" }Permission dependency calls
require_permission():await require_permission("view_charts", current_user)MongoDB lookup checks
access_rolescollection:{ "merchant_id": "IN-NATUR-CHEANN-7D2B-O9BP1", "role_id": "admin", "permissions": { "charts": ["view", "create", "update"] } }Permission granted if role has the required permission
Required MongoDB Structure
The access_roles collection should have documents like:
{
"_id": ObjectId("..."),
"merchant_id": "IN-NATUR-CHEANN-7D2B-O9BP1",
"role_id": "admin",
"permissions": {
"charts": ["view", "create", "update", "delete"],
"tables": ["view", "create", "update", "delete"],
"kpis": ["view"],
"analytics": ["view", "create"],
"reports": ["view", "create", "update"]
}
}
{
"_id": ObjectId("..."),
"merchant_id": "IN-NATUR-CHEANN-7D2B-O9BP1",
"role_id": "manager",
"permissions": {
"charts": ["view"],
"tables": ["view"],
"kpis": ["view"],
"orders": ["view", "create", "update"]
}
}
{
"_id": ObjectId("..."),
"merchant_id": "IN-NATUR-CHEANN-7D2B-O9BP1",
"role_id": "associate",
"permissions": {
"charts": ["view"],
"tables": ["view"],
"sales": ["view", "create"]
}
}
Benefits
- Flexible Access Control: Permissions can be configured per merchant and role
- Consistent with TMS: Uses the same permission system as other services
- Database-Driven: Permissions stored in MongoDB, easy to update
- Fine-Grained: Can control access at resource and action level
- Scalable: Easy to add new permissions without code changes
Testing
Test with cURL
curl -X 'POST' \
'https://your-domain/api/v1/charts/revenue-trend?use_cache=true' \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"widget_id": "wid_revenue_trend_12m_001",
"months": 12,
"chart_type": "line"
}'
Expected Response
Success (200):
{
"success": true,
"message": "Revenue trend chart retrieved successfully",
"data": {
"widget_id": "wid_revenue_trend_12m_001",
"type": "chart",
"title": "Revenue Trend (12 Months)",
...
}
}
Permission Denied (403):
{
"detail": "Forbidden"
}
Migration Steps
For Existing Deployments
- Create access_roles collection in MongoDB
- Add permission documents for each role
- Deploy updated code
- Test with different roles
Sample Permission Setup Script
// MongoDB script to set up permissions
db.access_roles.insertMany([
{
merchant_id: "IN-NATUR-CHEANN-7D2B-O9BP1",
role_id: "admin",
permissions: {
charts: ["view", "create", "update", "delete"],
tables: ["view", "create", "update", "delete"],
kpis: ["view"],
analytics: ["view", "create", "update"],
reports: ["view", "create", "update", "delete"],
dashboard: ["view", "create", "update", "delete"]
}
},
{
merchant_id: "IN-NATUR-CHEANN-7D2B-O9BP1",
role_id: "manager",
permissions: {
charts: ["view"],
tables: ["view"],
kpis: ["view"],
analytics: ["view"],
reports: ["view", "create"]
}
},
{
merchant_id: "IN-NATUR-CHEANN-7D2B-O9BP1",
role_id: "associate",
permissions: {
charts: ["view"],
tables: ["view"]
}
}
]);
Files Modified
- β
app/dependencies/auth.py- Added widget permissions to AccessID enum - β
app/routers/chart_widget_router.py- Updated all endpoints to use permission-based access
Verification
β No syntax errors β All endpoints updated β Permission dependencies implemented β Consistent with TMS pattern β Ready for testing
Next Steps
- Set up
access_rolescollection in MongoDB - Add permission documents for all roles
- Test with different user roles
- Update table_widget_router.py similarly
- Update kpi_widget_router.py similarly
Summary
Successfully migrated from role-based to permission-based access control, fixing the 403 error and providing a more flexible, database-driven permission system that's consistent with other services.