MOON
Server: Apache
System: Linux ip-208-109-13-31.ip.secureserver.net 3.10.0-1160.119.1.el7.tuxcare.els4.x86_64 #1 SMP Sat Aug 31 06:58:57 UTC 2024 x86_64
User: durgeshpandey215 (1013)
PHP: 8.1.29
Disabled: NONE
Upload Files
File: /home/durgeshpandey215/public_html/bharatmatabackend.skilladders.com/DB/UAC.txt
CREATE TABLE `users` (
  `id` bigint(20) UNSIGNED NOT NULL,
  `role_id` bigint(20) UNSIGNED DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `phone` varchar(15) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `otp` varchar(10) DEFAULT NULL,
  `otp_expires_at` datetime DEFAULT NULL,
  `is_kyc_completed` tinyint(1) DEFAULT 0,
  `is_active` tinyint(1) DEFAULT 1,
  `remember_token` varchar(100) DEFAULT NULL,
  `phone_verified_at` timestamp(1) NULL DEFAULT NULL,
  `email_verified_at` timestamp NULL DEFAULT NULL,
  `created_by` bigint(20) UNSIGNED DEFAULT NULL,
  `updated_by` bigint(20) UNSIGNED DEFAULT NULL,
  `deleted_by` bigint(20) UNSIGNED DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT current_timestamp(),
  `updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  `deleted_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

create table roles (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE,
    alias VARCHAR(50) NOT NULL,
    description TEXT NULL,
    guard_name VARCHAR(50) NOT NULL DEFAULT 'web, api',
    is_active TINYINT DEFAULT 1 COMMENT '1=Active, 0=Inactive',
    created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL DEFAULT NULL
) ENGINE = InnoDB;

create table permissions (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL UNIQUE,
    description TEXT NULL,
    guard_name VARCHAR(50) NOT NULL DEFAULT 'web, api',
    group_name VARCHAR(50) NULL,
    is_active TINYINT DEFAULT 1 COMMENT '1=Active, 0=Inactive',
    created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL DEFAULT NULL
) ENGINE = InnoDB;

create table role_has_permissions (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    role_id BIGINT UNSIGNED NOT NULL,
    permission_id BIGINT UNSIGNED NOT NULL,
    is_active TINYINT DEFAULT 1 COMMENT '1=Active, 0=Inactive',
    created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL DEFAULT NULL,
    UNIQUE KEY uniq_role_permission (role_id, permission_id),
    INDEX idx_role_id (role_id),
    INDEX idx_permission_id (permission_id),
    CONSTRAINT fk_rpm_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
    CONSTRAINT fk_rpm_permission FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
) ENGINE = InnoDB;

All Models  are inside 
Models/Uac

UAC implementation
This is the complete **User Access Control (UAC)** implementation, organized into a clear step-by-step workflow based on your specific SQL schema and model directory structure.

---

## Step 1: Define the Models
Place these in `app/Models/Uac/` (except the User model).

**`app/Models/Uac/Permission.php`**
```php
namespace App\Models\Uac;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Permission extends Model
{
    use SoftDeletes;
    protected $table = 'permissions';
    protected $fillable = ['name', 'description', 'guard_name', 'group_name', 'is_active'];

    public function roles()
    {
        return $this->belongsToMany(Role::class, 'role_has_permissions', 'permission_id', 'role_id');
    }
}
```

**`app/Models/Uac/Role.php`**
```php
namespace App\Models\Uac;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Role extends Model
{
    use SoftDeletes;
    protected $table = 'roles';
    protected $fillable = ['name', 'alias', 'description', 'guard_name', 'is_active'];

    public function permissions(): BelongsToMany
    {
        return $this->belongsToMany(Permission::class, 'role_has_permissions', 'role_id', 'permission_id')
            ->withPivot('id', 'is_active')
            ->withTimestamps()
            ->wherePivot('deleted_at', null);
    }
}
```

**`app/Models/User.php`**
```php
namespace App\Models;

use App\Models\Uac\Role;
use App\Traits\HasUac;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class User extends Authenticatable
{
    use SoftDeletes, HasUac; // HasUac Trait added here

    protected $fillable = ['role_id', 'name', 'email', 'password', 'is_active'];

    public function role(): BelongsTo
    {
        return $this->belongsTo(Role::class, 'role_id');
    }
}
```

---

## Step 2: Create the Core Trait
This handles the logic and caches permissions during the request to save database hits.

**`app/Traits/HasUac.php`**
```php
namespace App\Traits;

trait HasUac
{
    protected $permCache = null;

    public function hasPermission(string $permission): bool
    {
        // Fail if user or role is inactive
        if (!$this->is_active || !$this->role?->is_active) return false;

        $this->loadPermissions();
        return in_array($permission, $this->permCache['names'] ?? []);
    }

    public function hasGroupAccess(string $groupName): bool
    {
        if (!$this->is_active || !$this->role?->is_active) return false;

        $this->loadPermissions();
        return in_array($groupName, $this->permCache['groups'] ?? []);
    }

    protected function loadPermissions(): void
    {
        if ($this->permCache !== null) return;

        // Fetch active permissions linked via active role_has_permissions
        $perms = $this->role->permissions()
            ->where('permissions.is_active', 1)
            ->wherePivot('is_active', 1)
            ->get();

        $this->permCache = [
            'names'  => $perms->pluck('name')->unique()->toArray(),
            'groups' => $perms->pluck('group_name')->unique()->toArray()
        ];
    }
}
```

---

## Step 3: Middleware Registration
Create the middleware to intercept requests.

**`app/Http/Middleware/CheckPermission.php`**
```php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class CheckPermission
{
    public function handle(Request $request, Closure $next, $value = 'auto')
    {
        if (!$request->user()) return abort(401);

        // If 'auto' is passed, it uses the current Route Name (e.g., 'zones.index')
        $permissionToCheck = ($value === 'auto') ? $request->route()->getName() : $value;

        if (!$request->user()->hasPermission($permissionToCheck)) {
            return $request->expectsJson() 
                ? response()->json(['message' => 'Permission Denied'], 403) 
                : abort(403, 'Unauthorized Action');
        }

        return $next($request);
    }
}
```

**Register in `bootstrap/app.php`**:
```php
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'uac' => \App\Http\Middleware\CheckPermission::class,
    ]);
})
```

---

## Step 4: Security Gates
Ensure Laravel's built-in `@can` and `Gate::` helpers work with your custom system.

**`app/Providers/AppServiceProvider.php`**
```php
use Illuminate\Support\Facades\Gate;
use App\Models\User;

public function boot(): void
{
    // 1. Super Admin bypass
    Gate::before(function (User $user, $ability) {
        if ($user->role?->name === 'super_admin') return true;
    });

    // 2. Map all Gate checks to our HasUac trait
    Gate::after(function ($user, $ability, $result) {
        if ($result === null) {
            return $user->hasPermission($ability);
        }
    });
}
```

---

## Step 5: Real-World Use Case (Practical Example)

### Scenario: Inventory Management
Imagine a system where you have an **Inventory Module** (Group) and specific actions like **Delete Item** (Permission).

#### 1. Route Protection
```php
// Protect an entire module (checks group_name)
Route::middleware(['auth:api', 'uac:inventory,group'])->prefix('inventory')->group(function () {
    
    Route::get('/list', [InventoryController::class, 'index']);

    // Protect a specific sensitive action (checks name)
    Route::delete('/delete/{id}', [InventoryController::class, 'destroy'])
         ->middleware('uac:inventory_delete');
});
```

#### 2. Logic in Controllers
```php
public function destroy($id) {
    // Manual check if needed
    if (auth()->user()->hasPermission('inventory_delete')) {
        // Proceed with deletion
    }
}
```

#### 3. Frontend (Blade)
```html
@if(Auth::user()->hasGroupAccess('inventory'))
    <li>Inventory Dashboard</li>
@endif

@can('inventory_delete')
    <button class="btn-danger">Delete Stock</button>
@endcan
```

---

### Step 6: Initial Data (Seeder)
Run `php artisan make:seeder UacSeeder` and use this to test:

```php
public function run()
{
    $role = Role::create(['name' => 'manager', 'alias' => 'Store Manager']);
    
    $p1 = Permission::create(['name' => 'view_stock', 'group_name' => 'inventory']);
    $p2 = Permission::create(['name' => 'inventory_delete', 'group_name' => 'inventory']);

    // Attach permissions to role
    $role->permissions()->attach([$p1->id, $p2->id]);
}
```

In a professional application, your sidebar or navigation menu should dynamically adapt based on the user's permissions. This prevents "Unauthorized" errors by simply not showing links the user can't access.

Based on your schema, here is how you implement the **Side Nav Use Case** effectively.

---

### 1. Simple Permission-Based Link
If a link represents a single specific action (like viewing a user list), use the `@can` directive. This works because we registered the `Gate::after` logic in your `AppServiceProvider`.

```html
<ul class="nav-links">
    @can('view_users')
        <li>
            <a href="{{ route('users.index') }}">
                <i class="fa fa-users"></i> User Management
            </a>
        </li>
    @endcan
</ul>
```

---

### 2. Group-Based Module (Parent Menu)
If you have a dropdown or a section that contains multiple sub-links (like "Inventory"), you should check for **Group Access**. This is where your `group_name` column in the `permissions` table becomes very powerful.

```html
@if(Auth::user()->hasGroupAccess('inventory'))
    <li class="nav-item dropdown">
        <a href="#">Inventory Module</a>
        
        <ul class="dropdown-menu">
            @can('add_inventory')
                <li><a href="{{ route('inventory.create') }}">Add New Stock</a></li>
            @endcan

            @can('view_inventory_reports')
                <li><a href="{{ route('inventory.reports') }}">Reports</a></li>
            @endcan
        </ul>
    </li>
@endif
```

---

### 3. Handling "Super Admin" logic
In your `AppServiceProvider`, we added a `Gate::before` check. This means if a user has the role `super_admin`, the nav logic above will **automatically** show every link without you having to write extra `if` statements for the admin.

---

### 4. Implementation Strategy: The "Active" State
To make your UI robust, combine the UAC check with an "active" class check to highlight the current page.

```html
<li class="nav-item {{ request()->is('inventory*') ? 'active' : '' }}">
    @if(Auth::user()->hasGroupAccess('inventory'))
        <a href="{{ route('inventory.index') }}">
            <i class="fa fa-boxes"></i> Inventory
        </a>
    @endif
</li>
```

Route::group(['middleware' => ['auth:web', PreventBackHistory::class]], function () {
    
    Route::post('logout', [AuthController::class, 'masterLogout'])->name('admin.logout');
    Route::get('dashboard', [AdminLandingPageController::class, 'index'])->name('dashboard');

    // WRAP ALL PROTECTED MODULES IN THIS GROUP
    Route::group(['middleware' => 'uac:auto'], function () {

        // Zones (Checked against 'zones.index', 'zones.create', etc.)
        Route::get('/zones', [ZoneController::class, 'index'])->name('zones.index');
        Route::get('/zones/create', [ZoneController::class, 'create'])->name('zones.create');
        Route::post('/zones/{id}/delete', [ZoneController::class, 'destroy'])->name('zones.destroy');

        // Zonal Areas (Checked against 'zonal.areas.index', etc.)
        Route::get('/zonalareas', [ZonalAreaController::class, 'index'])->name('zonal.areas.index');

        // Categories (Checked against 'categories.index', etc.)
        Route::get('/categories', [CategoryController::class, 'index'])->name('categories.index');

        // ... All your other routes like Districts, Cities, Vendor Verifications ...
        
        // UAC (Checked against 'roles.index', etc.)
        Route::get('/roles', [RoleController::class, 'index'])->name('roles.index');
        Route::get('/permissions', [PermissionController::class, 'index'])->name('permissions.index');

    });
});


<nav class="sidebar-nav">
    <ul>
        {{-- MASTER SECTION --}}
        @if(Auth::user()->hasGroupAccess('Product & Category'))
        <li class="nav-item nav-item-has-children">
            <a href="#0" data-bs-toggle="collapse" data-bs-target="#master_menu" class="collapsed">
                <span class="icon"><i class="fas fa-database"></i></span>
                <span class="text">Master</span>
            </a>
            <ul id="master_menu" class="collapse dropdown-nav">
                @can('categories.index')
                <li><a href="{{ route('categories.index') }}">Categories</a></li>
                @endcan
            </ul>
        </li>
        @endif

        {{-- LOCATION SECTION --}}
        @if(Auth::user()->hasGroupAccess('Location Management'))
        <li class="nav-item nav-item-has-children">
            <a href="#0" data-bs-toggle="collapse" data-bs-target="#location_menu" class="collapsed">
                <span class="icon"><i class="fas fa-map-marker-alt"></i></span>
                <span class="text">Location</span>
            </a>
            <ul id="location_menu" class="collapse dropdown-nav">
                @can('zones.index')
                <li><a href="{{ route('zones.index') }}">Zones</a></li>
                @endcan
            </ul>
        </li>
        @endif
    </ul>
</nav>

TEST

-- 1. Create Permissions
INSERT INTO `permissions` (`name`, `group_name`, `guard_name`) VALUES 
('zones.index', 'Location Management', 'web'),
('zones.destroy', 'Location Management', 'web'),
('categories.index', 'Product & Category', 'web');

-- 2. Link to Super Admin (ID 1) - Gets everything
INSERT INTO `role_has_permissions` (`role_id`, `permission_id`) VALUES (1, 1), (1, 2), (1, 3);

-- 3. Link to Area Admin (ID 2) - Gets only Zones View
INSERT INTO `role_has_permissions` (`role_id`, `permission_id`) VALUES (2, 1);
**Would you like me to help you create a Vue or Blade-based UI to manage these permissions (assigning them to roles)?**