Laravel authorization policies defining access control rules in ShelfWise
ShelfWise uses Laravel Policies for all authorization checks, implementing a hierarchical role-based access control (RBAC) system. Policies enforce tenant isolation and shop-level access restrictions.
public function view(User $user, Product $product): bool{ // Tenant isolation if ($user->tenant_id !== $product->tenant_id) { return false; } // Cross-store roles can view any product if (in_array($user->role->value, [ UserRole::OWNER->value, UserRole::GENERAL_MANAGER->value ])) { return true; } // Other roles must be assigned to product's shop return $user->shops() ->where('shops.id', $product->shop_id) ->exists();}
Authorization Logic
Tenant Check: User must belong to same tenant as product
Role Check: Owner and General Manager have cross-store access
Shop Assignment: Other roles must be assigned to product’s shop
public function delete(User $user, Product $product): bool{ if ($user->tenant_id !== $product->tenant_id) { return false; } // Only Owner and General Manager can delete return in_array($user->role->value, [ UserRole::OWNER->value, UserRole::GENERAL_MANAGER->value, ]);}
use Illuminate\Support\Facades\Gate;use App\Models\Product;// Check if user can view productsif (Gate::allows('viewAny', Product::class)) { $products = Product::query()->get();}// Check if user can view specific productif (Gate::allows('view', $product)) { return view('products.show', compact('product'));}// Authorize or throw exceptionGate::authorize('update', $product);// Check multiple abilitiesif (Gate::any(['update', 'delete'], $product)) { // Show edit/delete buttons}
public function update(User $user, Order $order): bool{ if ($user->tenant_id !== $order->tenant_id) { return false; } // Can only edit pending or confirmed orders if (!in_array($order->status, ['pending', 'confirmed'])) { return false; } return $user->role->hasPermission('manage_orders');}
Edit Restrictions
Orders can only be edited when in:
PENDING status - Before confirmation
CONFIRMED status - After confirmation but before processing
Once an order enters PROCESSING or later stages, it cannot be edited.
public function cancel(User $user, Order $order): bool{ if ($user->tenant_id !== $order->tenant_id) { return false; } // Cannot cancel completed or already cancelled orders if (in_array($order->status, ['completed', 'cancelled'])) { return false; } // Only Store Manager and above can cancel orders if ($user->role->level() < UserRole::STORE_MANAGER->level()) { return false; } return $user->role->hasPermission('manage_orders');}
public function refund(User $user, Order $order): bool{ if ($user->tenant_id !== $order->tenant_id) { return false; } // Can only refund completed orders if ($order->status !== 'completed') { return false; } // Only General Manager and above can process refunds if ($user->role->level() < UserRole::GENERAL_MANAGER->level()) { return false; } return $user->role->hasPermission('manage_orders');}
public function fulfill(User $user, Order $order): bool{ if ($user->tenant_id !== $order->tenant_id) { return false; } // Can only fulfill confirmed orders if ($order->status !== 'confirmed') { return false; } return $user->role->hasPermission('manage_orders');}
Always check tenant isolation before any other logic:
public function view(User $user, Product $product): bool{ // ALWAYS first check if ($user->tenant_id !== $product->tenant_id) { return false; } // Then check role/permissions return $user->role->hasPermission('view_products');}
public function update(User $user, Order $order): bool{ // Cannot edit completed orders if ($order->status->isFinal()) { return false; } return $user->role->hasPermission('manage_orders');}
Permission-Based Checks
Use permission system for granular control:
public function export(User $user): bool{ return $user->role->hasPermission('export_data') || $user->role->hasPermission('export_reports');}