Some checks failed
- Add admin store roles page with merchant→store cascading for superadmin and store-only selection for platform admin - Add permission catalog API with translated labels/descriptions (en/fr/de/lb) - Add permission translations to all 15 module locale files (60 files total) - Add info icon tooltips for permission descriptions in role editor - Add store roles menu item and admin menu item in module definition - Fix store-selector.js URL construction bug when apiEndpoint has query params - Add admin store roles API (CRUD + platform scoping) - Add integration tests for admin store roles and permission catalog Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
274 lines
15 KiB
Markdown
274 lines
15 KiB
Markdown
# Complete Access Control Stack
|
|
|
|
The Orion platform enforces access control through a **4-layer stack**. Each layer filters at a different level, and they compose together to determine what a user can see and do.
|
|
|
|
> **Think of it as:** subscription controls **WHAT** you can do, modules control **WHERE** it exists, menu config controls **WHAT's shown**, permissions control **WHO** can do it.
|
|
|
|
## Overview
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ INCOMING REQUEST │
|
|
└──────────────────────────────────┬──────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ LAYER 1: SUBSCRIPTION GATING "What you can do" │
|
|
│ │
|
|
│ TierFeatureLimit → binary/quantitative caps per subscription tier │
|
|
│ MerchantFeatureOverride → per-merchant exceptions │
|
|
│ FeatureService.check_resource_limit() → enforcement point │
|
|
│ │
|
|
│ Example: Free tier → 50 products max. Pro tier → unlimited. │
|
|
│ Example: Binary feature "advanced_analytics" → on/off per tier. │
|
|
└──────────────────────────────────┬──────────────────────────────────────────┘
|
|
│ allowed
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ LAYER 2: MODULE ENABLEMENT "Where it exists" │
|
|
│ │
|
|
│ PlatformModule table → per-platform module on/off │
|
|
│ Core modules always enabled; optional modules toggled per platform │
|
|
│ Auto-discovered from app/modules/*/definition.py │
|
|
│ │
|
|
│ Example: OMS platform has catalog + orders enabled. │
|
|
│ Example: Loyalty platform has loyalty + analytics but no inventory. │
|
|
└──────────────────────────────────┬──────────────────────────────────────────┘
|
|
│ module enabled
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ LAYER 3: MENU VISIBILITY "What's shown" │
|
|
│ │
|
|
│ AdminMenuConfig → opt-out model (only hidden items stored) │
|
|
│ Platform scope → applies to all users on platform │
|
|
│ User scope → personal preference (super admins only) │
|
|
│ MenuDiscoveryService → filtering pipeline │
|
|
│ │
|
|
│ Example: Platform admin hides "code-quality" from store sidebar. │
|
|
│ Example: Mandatory items (dashboard) cannot be hidden. │
|
|
└──────────────────────────────────┬──────────────────────────────────────────┘
|
|
│ visible
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ LAYER 4: ROLE PERMISSIONS "Who can do it" │
|
|
│ │
|
|
│ Module-declared permissions via PermissionDefinition │
|
|
│ Role presets: owner, manager, staff, support, viewer, marketing │
|
|
│ Per-store roles via StoreUser.role_id → Role.permissions │
|
|
│ Owner bypass: Merchant.owner_user_id gets all permissions │
|
|
│ │
|
|
│ Example: "viewer" role → can see products but not edit them. │
|
|
│ Example: Owner sees everything, no role record needed. │
|
|
└──────────────────────────────────┬──────────────────────────────────────────┘
|
|
│ permitted
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ USER SEES / DOES THE THING │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Layer 1: Subscription Gating
|
|
|
|
Subscription gating controls **what a store can do** based on its merchant's subscription tier.
|
|
|
|
### How It Works
|
|
|
|
1. Each subscription tier (Free, Starter, Pro, Enterprise) has a set of **feature limits** defined in the `TierFeatureLimit` table.
|
|
2. Features are either **binary** (on/off) or **quantitative** (numeric cap).
|
|
3. Modules declare their billable features via `FeatureProviderProtocol` — a cross-module interface that lets each module own its feature definitions.
|
|
4. Admins can override limits per-merchant using `MerchantFeatureOverride`.
|
|
|
|
### Enforcement
|
|
|
|
```python
|
|
from app.modules.billing.services.feature_service import feature_service
|
|
|
|
# Check before allowing an action
|
|
allowed, message = feature_service.check_resource_limit(
|
|
db=db,
|
|
feature_code="products",
|
|
store_id=store.id,
|
|
)
|
|
if not allowed:
|
|
raise TierLimitExceededException(message)
|
|
```
|
|
|
|
### Key Models
|
|
|
|
| Model | Table | Purpose |
|
|
|-------|-------|---------|
|
|
| `TierFeatureLimit` | `tier_feature_limits` | Per-tier feature caps (binary/quantitative) |
|
|
| `MerchantFeatureOverride` | `merchant_feature_overrides` | Per-merchant exceptions to tier limits |
|
|
|
|
### Resolution Order
|
|
|
|
```
|
|
MerchantFeatureOverride (if exists) → TierFeatureLimit → denied
|
|
```
|
|
|
|
If a merchant has an override for a feature, it takes precedence over the tier default.
|
|
|
|
## Layer 2: Module Enablement
|
|
|
|
Module enablement controls **where functionality exists** at the platform level.
|
|
|
|
### How It Works
|
|
|
|
1. Modules are auto-discovered from `app/modules/*/definition.py`.
|
|
2. **Core modules** (core, tenancy, cms, customers, billing, payments, messaging, contracts) are always enabled.
|
|
3. **Optional modules** (catalog, orders, inventory, analytics, etc.) can be enabled/disabled per platform via the `PlatformModule` table.
|
|
4. When a module is disabled, its routes, menu items, and features are excluded from the platform.
|
|
|
|
### Key Model
|
|
|
|
| Model | Table | Purpose |
|
|
|-------|-------|---------|
|
|
| `PlatformModule` | `platform_modules` | Junction table: which modules are enabled per platform |
|
|
|
|
### Impact on Other Layers
|
|
|
|
- **Menu items** from disabled modules are automatically excluded by `MenuDiscoveryService`
|
|
- **Features** from disabled modules are not available for subscription gating
|
|
- **Permissions** from disabled modules are not shown in role management
|
|
|
|
## Layer 3: Menu Visibility
|
|
|
|
Menu visibility controls **what's shown** in the sidebar for admin and store interfaces.
|
|
|
|
### How It Works
|
|
|
|
1. Each module defines its menu items in `definition.py` using `MenuSectionDefinition` and `MenuItemDefinition`.
|
|
2. `MenuDiscoveryService` aggregates items from all enabled modules and applies filtering.
|
|
3. `AdminMenuConfig` stores **visibility overrides** using an opt-out model: all items visible by default, only hidden items stored in the database.
|
|
4. Mandatory items (`is_mandatory=True`) cannot be hidden.
|
|
|
|
### Filtering Pipeline
|
|
|
|
```
|
|
All Module Menu Items
|
|
→ Remove items from disabled modules (Layer 2)
|
|
→ Remove super_admin_only items for non-super-admins
|
|
→ Remove items hidden via AdminMenuConfig
|
|
→ Remove items requiring permissions the user lacks (Layer 4)
|
|
= Final visible menu
|
|
```
|
|
|
|
### Scope
|
|
|
|
| Scope | Who it affects | Frontend |
|
|
|-------|----------------|----------|
|
|
| Platform | All platform admins and stores on that platform | Admin + Store |
|
|
| User | Individual super admin | Admin only |
|
|
|
|
### Key Model
|
|
|
|
| Model | Table | Purpose |
|
|
|-------|-------|---------|
|
|
| `AdminMenuConfig` | `admin_menu_configs` | Visibility overrides per platform or user |
|
|
|
|
## Layer 4: Role Permissions
|
|
|
|
Role permissions control **who can do what** at the store level.
|
|
|
|
### How It Works
|
|
|
|
1. Each module declares its permissions in `definition.py` using `PermissionDefinition`:
|
|
|
|
```python
|
|
PermissionDefinition(
|
|
id="products.view",
|
|
label_key="catalog.permission.products_view",
|
|
description_key="catalog.permission.products_view_desc",
|
|
category="products",
|
|
)
|
|
```
|
|
|
|
2. `PermissionDiscoveryService` aggregates permissions from all modules.
|
|
3. Roles are collections of permission IDs, stored per-store in the `roles` table.
|
|
4. Store team members are linked to roles via `StoreUser.role_id`.
|
|
|
|
### Owner Bypass
|
|
|
|
Store owners (`Merchant.owner_user_id`) automatically receive **all permissions** without needing a role record. Ownership is checked via `User.is_owner_of(store_id)`.
|
|
|
|
### Role Presets
|
|
|
|
The system provides 5 preset roles with predefined permission sets:
|
|
|
|
| Preset | Description | Permission Count |
|
|
|--------|-------------|-----------------|
|
|
| `manager` | Full operational access | ~23 |
|
|
| `staff` | Day-to-day operations | ~10 |
|
|
| `support` | Customer service focus | ~6 |
|
|
| `viewer` | Read-only access | ~6 |
|
|
| `marketing` | Marketing and customer data | ~7 |
|
|
|
|
Preset roles are created automatically on first access. Store owners can also create custom roles with any combination of the available permissions via the role editor UI at `/store/{store_code}/team/roles`.
|
|
|
|
### Custom Role Management
|
|
|
|
Store owners can create, edit, and delete custom roles via:
|
|
- **Store UI:** `/store/{store_code}/team/roles` (Alpine.js permission matrix)
|
|
- **Store API:** `POST/PUT/DELETE /api/v1/store/team/roles`
|
|
- **Admin UI:** `/admin/store-roles` (with Tom Select store picker)
|
|
- **Admin API:** `GET/POST/PUT/DELETE /api/v1/admin/store-roles?store_id=X`
|
|
|
|
The **Permission Catalog API** (`GET /api/v1/store/team/permissions/catalog`) returns all permissions grouped by category with labels and descriptions for the UI.
|
|
|
|
Admin access is scoped: **super admins** can manage any store, while **platform admins** can only manage stores within their assigned platforms (validated via `StorePlatform` table).
|
|
|
|
### Enforcement Points
|
|
|
|
| Where | How | Pattern |
|
|
|-------|-----|---------|
|
|
| **API routes** | `require_store_permission("products.view")` | FastAPI `Depends()` — returns 403 JSON |
|
|
| **Page routes** | `require_store_page_permission("products.view")` | FastAPI `Depends()` — redirects to no-access page |
|
|
| **Sidebar menu** | `MenuItemDefinition.requires_permission` | Items hidden if user lacks permission |
|
|
| **Template UI** | `window.USER_PERMISSIONS` | Alpine.js `x-show` for button/element hiding |
|
|
|
|
### Key Models
|
|
|
|
| Model | Table | Purpose |
|
|
|-------|-------|---------|
|
|
| `Role` | `roles` | Permission set per store (preset or custom) |
|
|
| `StoreUser` | `store_users` | Links user to store with role assignment |
|
|
|
|
### Per-Store Flexibility
|
|
|
|
A user can have **different roles in different stores**. For example, a user might be a `manager` in Store A but a `viewer` in Store B, because `StoreUser` records are per-store.
|
|
|
|
## How the Layers Interact
|
|
|
|
### Example: Store team member tries to view products
|
|
|
|
```
|
|
1. SUBSCRIPTION: Does the merchant's tier include "products" feature?
|
|
→ Yes (tier allows up to 200 products) → Continue
|
|
→ No → "Upgrade your plan to access products"
|
|
|
|
2. MODULE: Is the catalog module enabled on this platform?
|
|
→ Yes (OMS platform has catalog enabled) → Continue
|
|
→ No → Products section doesn't exist at all
|
|
|
|
3. MENU VISIBILITY: Is the products menu item visible?
|
|
→ Yes (not hidden in AdminMenuConfig) → Show in sidebar
|
|
→ No → Item hidden from sidebar (but URL still works)
|
|
|
|
4. PERMISSIONS: Does the user's role include "products.view"?
|
|
→ Yes (user has "staff" role with products.view) → Allow access
|
|
→ No → 403 Forbidden / redirect to no-access page
|
|
```
|
|
|
|
### Key Distinction
|
|
|
|
- **Layers 1-3** are about **platform/store configuration** — they define the environment
|
|
- **Layer 4** is about **individual user authorization** — it defines what each person can do within that environment
|
|
|
|
## Related Documentation
|
|
|
|
- [Authentication & RBAC](auth-rbac.md) — JWT auth, user roles, enforcement methods
|
|
- [Store RBAC](../backend/store-rbac.md) — Custom role CRUD, permission catalog API, admin role management
|
|
- [Menu Management](menu-management.md) — Menu discovery, visibility config, AdminMenuConfig
|
|
- [Module System](module-system.md) — Module architecture, auto-discovery, classification
|
|
- [Feature Gating](../implementation/feature-gating-system.md) — Tier-based feature limits
|