Customers & accounts
Role-Based Access Control
Generated from docs/plan/core/role-based-access-control/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
Role-Based Access Control (RBAC) enforces permissions based on staff roles, letting merchants grant only the access each team member needs.
The problem
Merchants can’t trust employees with full access; they need to limit who can see orders, refunds, or edit prices.
What it does
- Pre-defined roles: Admin, Editor, Viewer, Fulfillment
- Custom roles with cherry-picked permissions
- Permission checks on every API endpoint (middleware)
- Audit trail logs user role at time of action
- Deny access to protected endpoints for unauthorized roles
- Settings for default role on new user invite
- Dashboard: permission matrix showing who can do what
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- Attribute-based access control (ABAC; conditional logic) — role-based only
- Time-based access (9am-5pm only) — temporal rules out of scope
- IP-based restrictions — belongs in security-hardening paid pack
- Delegation — flat hierarchy
- API key scoping per role (API keys are global) — keys separate from staff roles
Data model
Role: id, name, store_id, permissions (JSON array: orders.read, orders.write, products.read, etc.), is_default, created_at. No new migration; uses Role table from admin-user-management.
API
- GET /admin/roles
- POST /admin/roles
- PATCH /admin/roles/{role_id}
- DELETE /admin/roles/{role_id}
- GET /admin/permissions
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Permission matrix UI: rows = roles, columns = permissions, checkboxes to toggle. Role editor: pick-list of permissions. Staff assignment: show role description and affected permissions. Audit link: click role to see recent changes by users with that role.
The seam — why this is core
Core owns: permission list definition, permission-check middleware on every endpoint, enforcement at route level. Paid module owns: nothing; core infrastructure.
Core owns the interface + honest permission mapper; role management is infrastructure, not a credential or support commitment.
Dependencies
- admin-user-management
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- User with Viewer role attempts GET /admin/orders; response 200 OK
- Same user attempts PATCH /admin/orders/{id}; response 403
- Custom role InventoryOnly with [inventory.read, inventory.write] assigned; user cannot see orders (returns 403)
- Deleting built-in role returns 400 Bad Request
- Updating role to remove orders.read; user with that role loses order access immediately
- Audit log shows staff role at time of action
Risks
Permission list bloat; every new endpoint needs permission string. Cached permissions from old role used. Default role set to Admin gives full access on invite. Permission denial marks past actions as unauthorized. SQL injection if not parameterized.
Commercial context
| Suggested price | free (core) |
| Rival anchor | Shopify: included; Magento: included |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and ../../AI-GUIDE.md for how to work on this repository.