Clean up some permissions

This commit is contained in:
2025-08-30 17:28:43 -04:00
parent 075e7253a0
commit 5dcd19e7f3
5 changed files with 269 additions and 136 deletions

View File

@@ -7,12 +7,13 @@ This document outlines the permission system implemented in the Inventory Manage
Permissions follow this naming convention: Permissions follow this naming convention:
- Page access: `access:{page_name}` - Page access: `access:{page_name}`
- Actions: `{action}:{resource}` - Settings sections: `settings:{section_name}`
- Admin features: `admin:{feature}`
Examples: Examples:
- `access:products` - Can access the Products page - `access:products` - Can access the Products page
- `create:products` - Can create new products - `settings:user_management` - Can access User Management settings
- `edit:users` - Can edit user accounts - `admin:debug` - Can see debug information
## Permission Components ## Permission Components
@@ -22,10 +23,10 @@ The core component that conditionally renders content based on permissions.
```tsx ```tsx
<PermissionGuard <PermissionGuard
permission="create:products" permission="settings:user_management"
fallback={<p>No permission</p>} fallback={<p>No permission</p>}
> >
<button>Create Product</button> <button>Manage Users</button>
</PermissionGuard> </PermissionGuard>
``` ```
@@ -81,7 +82,7 @@ Specific component for settings with built-in permission checks.
<SettingsSection <SettingsSection
title="System Settings" title="System Settings"
description="Configure global settings" description="Configure global settings"
permission="edit:system_settings" permission="settings:global"
> >
{/* Settings content */} {/* Settings content */}
</SettingsSection> </SettingsSection>
@@ -95,8 +96,8 @@ Core hook for checking any permission.
```tsx ```tsx
const { hasPermission, hasPageAccess, isAdmin } = usePermissions(); const { hasPermission, hasPageAccess, isAdmin } = usePermissions();
if (hasPermission('delete:products')) { if (hasPermission('settings:user_management')) {
// Can delete products // Can access user management
} }
``` ```
@@ -106,8 +107,8 @@ Specialized hook for page-level permissions.
```tsx ```tsx
const { canView, canCreate, canEdit, canDelete } = usePagePermission('products'); const { canView, canCreate, canEdit, canDelete } = usePagePermission('products');
if (canEdit()) { if (canView()) {
// Can edit products // Can view products
} }
``` ```
@@ -119,18 +120,43 @@ Permissions are stored in the database:
Admin users automatically have all permissions. Admin users automatically have all permissions.
## Common Permission Codes ## Implemented Permission Codes
### Page Access Permissions
| Code | Description | | Code | Description |
|------|-------------| |------|-------------|
| `access:dashboard` | Access to Dashboard page | | `access:dashboard` | Access to Dashboard page |
| `access:overview` | Access to Overview page |
| `access:products` | Access to Products page | | `access:products` | Access to Products page |
| `create:products` | Create new products | | `access:categories` | Access to Categories page |
| `edit:products` | Edit existing products | | `access:brands` | Access to Brands page |
| `delete:products` | Delete products | | `access:vendors` | Access to Vendors page |
| `view:users` | View user accounts | | `access:purchase_orders` | Access to Purchase Orders page |
| `edit:users` | Edit user accounts | | `access:analytics` | Access to Analytics page |
| `manage:permissions` | Assign permissions to users | | `access:forecasting` | Access to Forecasting page |
| `access:import` | Access to Import page |
| `access:settings` | Access to Settings page |
| `access:chat` | Access to Chat Archive page |
### Settings Permissions
| Code | Description |
|------|-------------|
| `settings:global` | Access to Global Settings section |
| `settings:products` | Access to Product Settings section |
| `settings:vendors` | Access to Vendor Settings section |
| `settings:data_management` | Access to Data Management settings |
| `settings:calculation_settings` | Access to Calculation Settings |
| `settings:library_management` | Access to Image Library Management |
| `settings:performance_metrics` | Access to Performance Metrics |
| `settings:prompt_management` | Access to AI Prompt Management |
| `settings:stock_management` | Access to Stock Management |
| `settings:templates` | Access to Template Management |
| `settings:user_management` | Access to User Management |
### Admin Permissions
| Code | Description |
|------|-------------|
| `admin:debug` | Can see debug information and features |
## Implementation Examples ## Implementation Examples
@@ -148,25 +174,31 @@ In `App.tsx`:
### Component Level Protection ### Component Level Protection
```tsx ```tsx
const { canEdit } = usePagePermission('products'); const { hasPermission } = usePermissions();
function handleEdit() { function handleAction() {
if (!canEdit()) { if (!hasPermission('settings:user_management')) {
toast.error("You don't have permission"); toast.error("You don't have permission");
return; return;
} }
// Edit logic // Action logic
} }
``` ```
### UI Element Protection ### UI Element Protection
```tsx ```tsx
<PermissionButton <PermissionGuard permission="settings:user_management">
page="products" <button onClick={handleManageUsers}>
action="delete" Manage Users
onClick={handleDelete} </button>
> </PermissionGuard>
Delete
</PermissionButton>
``` ```
## Notes
- **Page Access**: These permissions control which pages a user can navigate to
- **Settings Access**: These permissions control access to different sections within the Settings page
- **Admin Features**: Special permissions for administrative functions
- **CRUD Operations**: The application currently focuses on viewing and managing data rather than creating/editing/deleting individual records
- **User Management**: User CRUD operations are handled through the settings interface rather than dedicated user management pages

View File

@@ -103,14 +103,7 @@ function App() {
}> }>
{/* Core inventory app routes - will be lazy loaded */} {/* Core inventory app routes - will be lazy loaded */}
<Route index element={ <Route index element={
<Protected page="dashboard" fallback={<FirstAccessiblePage />}> <Protected page="overview" fallback={<FirstAccessiblePage />}>
<Suspense fallback={<PageLoading />}>
<Overview />
</Suspense>
</Protected>
} />
<Route path="/" element={
<Protected page="dashboard">
<Suspense fallback={<PageLoading />}> <Suspense fallback={<PageLoading />}>
<Overview /> <Overview />
</Suspense> </Suspense>

View File

@@ -1,67 +1,162 @@
# Permission System Documentation # Permission System Documentation
This document outlines the simplified permission system implemented in the Inventory Manager application. This document outlines the permission system implemented in the Inventory Manager application.
## Permission Structure ## Permission Structure
Permissions follow this naming convention: Permissions follow this naming convention:
- Page access: `access:{page_name}` - Page access: `access:{page_name}`
- Actions: `{action}:{resource}` - Settings sections: `settings:{section_name}`
- Admin features: `admin:{feature}`
Examples: Examples:
- `access:products` - Can access the Products page - `access:products` - Can access the Products page
- `create:products` - Can create new products - `settings:user_management` - Can access User Management settings
- `edit:users` - Can edit user accounts - `admin:debug` - Can see debug information
## Permission Component ## Permission Components
### Protected ### PermissionGuard
The core component that conditionally renders content based on permissions. The core component that conditionally renders content based on permissions.
```tsx ```tsx
<Protected <PermissionGuard
permission="create:products" permission="settings:user_management"
fallback={<p>No permission</p>} fallback={<p>No permission</p>}
> >
<button>Create Product</button> <button>Manage Users</button>
</Protected> </PermissionGuard>
``` ```
Options: Options:
- `permission`: Single permission code (e.g., "create:products") - `permission`: Single permission code
- `page`: Page name (checks `access:{page}` permission) - `anyPermissions`: Array of permissions (ANY match grants access)
- `resource` + `action`: Resource and action (checks `{action}:{resource}` permission) - `allPermissions`: Array of permissions (ALL required)
- `adminOnly`: For admin-only sections - `adminOnly`: For admin-only sections
- `page`: Page name (checks `access:{page}` permission)
- `fallback`: Content to show if permission check fails - `fallback`: Content to show if permission check fails
### RequireAuth ### PermissionProtectedRoute
Used for basic authentication checks (is user logged in?). Protects entire pages based on page access permissions.
```tsx ```tsx
<Route element={ <Route path="/products" element={
<RequireAuth> <PermissionProtectedRoute page="products">
<MainLayout /> <Products />
</RequireAuth> </PermissionProtectedRoute>
}> } />
{/* Protected routes */}
</Route>
``` ```
## Common Permission Codes ### ProtectedSection
Protects sections within a page based on action permissions.
```tsx
<ProtectedSection page="products" action="create">
<button>Add Product</button>
</ProtectedSection>
```
### PermissionButton
Button that automatically handles permissions.
```tsx
<PermissionButton
page="products"
action="create"
onClick={handleCreateProduct}
>
Add Product
</PermissionButton>
```
### SettingsSection
Specific component for settings with built-in permission checks.
```tsx
<SettingsSection
title="System Settings"
description="Configure global settings"
permission="settings:global"
>
{/* Settings content */}
</SettingsSection>
```
## Permission Hooks
### usePermissions
Core hook for checking any permission.
```tsx
const { hasPermission, hasPageAccess, isAdmin } = usePermissions();
if (hasPermission('settings:user_management')) {
// Can access user management
}
```
### usePagePermission
Specialized hook for page-level permissions.
```tsx
const { canView, canCreate, canEdit, canDelete } = usePagePermission('products');
if (canView()) {
// Can view products
}
```
## Database Schema
Permissions are stored in the database:
- `permissions` table: Stores all available permissions
- `user_permissions` junction table: Maps permissions to users
Admin users automatically have all permissions.
## Implemented Permission Codes
### Page Access Permissions
| Code | Description | | Code | Description |
|------|-------------| |------|-------------|
| `access:dashboard` | Access to Dashboard page | | `access:dashboard` | Access to Dashboard page |
| `access:overview` | Access to Overview page |
| `access:products` | Access to Products page | | `access:products` | Access to Products page |
| `create:products` | Create new products | | `access:categories` | Access to Categories page |
| `edit:products` | Edit existing products | | `access:brands` | Access to Brands page |
| `delete:products` | Delete products | | `access:vendors` | Access to Vendors page |
| `view:users` | View user accounts | | `access:purchase_orders` | Access to Purchase Orders page |
| `edit:users` | Edit user accounts | | `access:analytics` | Access to Analytics page |
| `manage:permissions` | Assign permissions to users | | `access:forecasting` | Access to Forecasting page |
| `access:import` | Access to Import page |
| `access:settings` | Access to Settings page |
| `access:chat` | Access to Chat Archive page |
### Settings Permissions
| Code | Description |
|------|-------------|
| `settings:global` | Access to Global Settings section |
| `settings:products` | Access to Product Settings section |
| `settings:vendors` | Access to Vendor Settings section |
| `settings:data_management` | Access to Data Management settings |
| `settings:calculation_settings` | Access to Calculation Settings |
| `settings:library_management` | Access to Image Library Management |
| `settings:performance_metrics` | Access to Performance Metrics |
| `settings:prompt_management` | Access to AI Prompt Management |
| `settings:stock_management` | Access to Stock Management |
| `settings:templates` | Access to Template Management |
| `settings:user_management` | Access to User Management |
### Admin Permissions
| Code | Description |
|------|-------------|
| `admin:debug` | Can see debug information and features |
## Implementation Examples ## Implementation Examples
@@ -70,35 +165,40 @@ Used for basic authentication checks (is user logged in?).
In `App.tsx`: In `App.tsx`:
```tsx ```tsx
<Route path="/products" element={ <Route path="/products" element={
<Protected page="products" fallback={<Navigate to="/" />}> <PermissionProtectedRoute page="products">
<Products /> <Products />
</Protected> </PermissionProtectedRoute>
} /> } />
``` ```
### Component Level Protection ### Component Level Protection
```tsx ```tsx
<Protected permission="edit:products"> const { hasPermission } = usePermissions();
<form>
{/* Form fields */} function handleAction() {
<button type="submit">Save Changes</button> if (!hasPermission('settings:user_management')) {
</form> toast.error("You don't have permission");
</Protected> return;
}
// Action logic
}
``` ```
### Button Protection ### UI Element Protection
```tsx ```tsx
<Button <PermissionGuard permission="settings:user_management">
onClick={handleDelete} <button onClick={handleManageUsers}>
disabled={!hasPermission('delete:products')} Manage Users
> </button>
Delete </PermissionGuard>
</Button>
// With Protected component
<Protected permission="delete:products" fallback={null}>
<Button onClick={handleDelete}>Delete</Button>
</Protected>
``` ```
## Notes
- **Page Access**: These permissions control which pages a user can navigate to
- **Settings Access**: These permissions control access to different sections within the Settings page
- **Admin Features**: Special permissions for administrative functions
- **CRUD Operations**: The application currently focuses on viewing and managing data rather than creating/editing/deleting individual records
- **User Management**: User CRUD operations are handled through the settings interface rather than dedicated user management pages

View File

@@ -29,6 +29,8 @@ import {
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { useLocation, useNavigate, Link } from "react-router-dom"; import { useLocation, useNavigate, Link } from "react-router-dom";
import { Protected } from "@/components/auth/Protected"; import { Protected } from "@/components/auth/Protected";
import { useContext } from "react";
import { AuthContext } from "@/contexts/AuthContext";
const dashboardItems = [ const dashboardItems = [
{ {
@@ -112,6 +114,7 @@ export function AppSidebar() {
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
useSidebar(); useSidebar();
const { user } = useContext(AuthContext);
const handleLogout = () => { const handleLogout = () => {
localStorage.removeItem('token'); localStorage.removeItem('token');
@@ -119,6 +122,12 @@ export function AppSidebar() {
navigate('/login'); navigate('/login');
}; };
// Check if user has access to any items in a section
const hasAccessToSection = (items: typeof inventoryItems): boolean => {
if (user?.is_admin) return true;
return items.some(item => user?.permissions?.includes(item.permission));
};
const renderMenuItems = (items: typeof inventoryItems) => { const renderMenuItems = (items: typeof inventoryItems) => {
return items.map((item) => { return items.map((item) => {
const isActive = const isActive =
@@ -180,58 +189,58 @@ export function AppSidebar() {
<SidebarSeparator /> <SidebarSeparator />
<SidebarContent> <SidebarContent>
{/* Dashboard Section */} {/* Dashboard Section */}
<SidebarGroup> {hasAccessToSection(dashboardItems) && (
<SidebarGroupLabel>Dashboard</SidebarGroupLabel> <SidebarGroup>
<SidebarGroupContent> <SidebarGroupLabel>Dashboard</SidebarGroupLabel>
<SidebarMenu> <SidebarGroupContent>
{renderMenuItems(dashboardItems)} <SidebarMenu>
</SidebarMenu> {renderMenuItems(dashboardItems)}
</SidebarGroupContent> </SidebarMenu>
</SidebarGroup> </SidebarGroupContent>
</SidebarGroup>
)}
{/* Inventory Section */} {/* Inventory Section */}
<SidebarGroup> {hasAccessToSection(inventoryItems) && (
<SidebarGroupLabel>Inventory</SidebarGroupLabel> <SidebarGroup>
<SidebarGroupContent> <SidebarGroupLabel>Inventory</SidebarGroupLabel>
<SidebarMenu> <SidebarGroupContent>
{renderMenuItems(inventoryItems)} <SidebarMenu>
</SidebarMenu> {renderMenuItems(inventoryItems)}
</SidebarGroupContent> </SidebarMenu>
</SidebarGroup> </SidebarGroupContent>
</SidebarGroup>
)}
{/* Product Setup Section */} {/* Product Setup Section */}
<SidebarGroup> {hasAccessToSection(productSetupItems) && (
<SidebarGroupLabel>Product Setup</SidebarGroupLabel> <SidebarGroup>
<SidebarGroupContent> <SidebarGroupLabel>Product Setup</SidebarGroupLabel>
<SidebarMenu> <SidebarGroupContent>
{renderMenuItems(productSetupItems)} <SidebarMenu>
</SidebarMenu> {renderMenuItems(productSetupItems)}
</SidebarGroupContent> </SidebarMenu>
</SidebarGroup> </SidebarGroupContent>
</SidebarGroup>
)}
{/* Chat Section */} {/* Chat Section */}
<SidebarGroup> {hasAccessToSection(chatItems) && (
<SidebarGroupLabel>Chat</SidebarGroupLabel> <SidebarGroup>
<SidebarGroupContent> <SidebarGroupLabel>Chat</SidebarGroupLabel>
<SidebarMenu> <SidebarGroupContent>
{renderMenuItems(chatItems)} <SidebarMenu>
</SidebarMenu> {renderMenuItems(chatItems)}
</SidebarGroupContent> </SidebarMenu>
</SidebarGroup> </SidebarGroupContent>
<SidebarSeparator /> </SidebarGroup>
)}
{/* Settings Section */} {/* Settings Section */}
<SidebarGroup> <Protected permission="access:settings" fallback={null}>
<SidebarGroup>
<SidebarGroupContent> <SidebarGroupContent>
<SidebarMenu> <SidebarMenu>
<Protected
permission="access:settings"
fallback={null}
>
<SidebarMenuItem> <SidebarMenuItem>
<SidebarMenuButton <SidebarMenuButton
asChild asChild
@@ -246,10 +255,10 @@ export function AppSidebar() {
</Link> </Link>
</SidebarMenuButton> </SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>
</Protected> </SidebarMenu>
</SidebarMenu> </SidebarGroupContent>
</SidebarGroupContent> </SidebarGroup>
</SidebarGroup> </Protected>
</SidebarContent> </SidebarContent>
<SidebarSeparator /> <SidebarSeparator />
<SidebarFooter> <SidebarFooter>

View File

@@ -291,8 +291,7 @@ export function UserForm({ user, permissions, onSave, onCancel }: UserFormProps)
<> <>
{form.watch("is_admin") ? ( {form.watch("is_admin") ? (
<div className="space-y-4"> <div className="space-y-4">
<h3 className="text-lg font-medium">Permissions</h3> <Alert variant="destructive">
<Alert>
<AlertDescription> <AlertDescription>
Administrators have access to all permissions by default. Individual permissions cannot be edited for admin users. Administrators have access to all permissions by default. Individual permissions cannot be edited for admin users.
</AlertDescription> </AlertDescription>