├── CONTRIBUTING.md ├── pint.json ├── src ├── Contracts │ ├── Permission.php │ ├── Role.php │ └── User.php ├── Facades │ └── Guard.php ├── HasPermissions.php ├── Models │ ├── Permission.php │ └── Role.php ├── Guard.php ├── HasRoles.php ├── Commands │ ├── CreateRole.php │ └── CreatePermission.php └── GuardServiceProvider.php ├── config └── guard.php ├── CHANGELOG.md ├── LICENSE.md ├── database └── migrations │ ├── create_permissions_table.php.stub │ └── create_roles_table.php.stub ├── composer.json └── README.md /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | You are most welcome to Pull Request to `guard-laravel` Package. 4 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "rules": { 4 | "declare_strict_types": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Contracts/Permission.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'user' => \App\Models\User::class, 8 | 9 | 'role' => \AmdadulHaq\Guard\Models\Role::class, 10 | 11 | 'permission' => \AmdadulHaq\Guard\Models\Permission::class, 12 | ], 13 | 'tables' => [ 14 | 'roles' => 'roles', 15 | 16 | 'permissions' => 'permissions', 17 | ], 18 | 19 | 'cache' => [ 20 | 'permissions_duration' => env('GUARD_PERMISSIONS_CACHE_DURATION', 3600), // Default to 3600 seconds (1 hour) 21 | 'roles_duration' => env('GUARD_ROLES_CACHE_DURATION', 3600), // Default to 3600 seconds (1 hour) 22 | ], 23 | ]; 24 | -------------------------------------------------------------------------------- /src/Contracts/User.php: -------------------------------------------------------------------------------- 1 | permissions()->save($permission); 15 | } 16 | 17 | public function syncPermissions(array $permissions): array 18 | { 19 | return $this->permissions()->sync($permissions); 20 | } 21 | 22 | public function revokePermissionTo(PermissionContract $permission): int 23 | { 24 | return $this->permissions()->detach($permission); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Models/Permission.php: -------------------------------------------------------------------------------- 1 | table = config('guard.tables.permissions') ?: parent::getTable(); 20 | } 21 | 22 | public function roles(): BelongsToMany 23 | { 24 | return $this->belongsToMany(config('guard.models.role')); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Models/Role.php: -------------------------------------------------------------------------------- 1 | table = config('guard.tables.roles') ?: parent::getTable(); 23 | } 24 | 25 | public function permissions(): BelongsToMany 26 | { 27 | return $this->belongsToMany(config('guard.models.permission')); 28 | } 29 | 30 | public function users(): BelongsToMany 31 | { 32 | return $this->belongsToMany(config('guard.models.user')); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Guard.php: -------------------------------------------------------------------------------- 1 | getTable(); 19 | } 20 | 21 | public function getSortPivotTable(array $array): array 22 | { 23 | $collection = collect($array); 24 | 25 | $sorted = $collection->sort(); 26 | 27 | return $sorted->values()->all(); 28 | } 29 | 30 | public function getPivotTableName(array $array): string 31 | { 32 | $collection = collect($array)->map(function ($value) { 33 | return self::getSingularName(self::getTableName($value)); 34 | }); 35 | 36 | $sorted = $collection->sort(); 37 | 38 | return $sorted->values()->implode('_'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `guard-laravel` will be documented in this file. 4 | 5 | ## v0.5.0 - 2025-03-07 6 | 7 | **Full Changelog**: https://github.com/amdad121/guard-laravel/compare/v0.4.0...v0.5.0 8 | 9 | ## v0.4.0 - 2025-03-07 10 | 11 | **Full Changelog**: https://github.com/amdad121/guard-laravel/compare/v0.3.0...v0.4.0 12 | 13 | ## v0.3.0 - 2024-10-08 14 | 15 | **Full Changelog**: https://github.com/amdad121/guard-laravel/compare/v0.2.2...v0.3.0 16 | 17 | ## v0.2.2 - 2024-10-08 18 | 19 | **Full Changelog**: https://github.com/amdad121/guard-laravel/compare/v0.2.1...v0.2.2 20 | 21 | ## v0.2.1 - 2024-10-08 22 | 23 | **Full Changelog**: https://github.com/amdad121/guard-laravel/compare/v0.2.0...v0.2.1 24 | 25 | ## v0.2.0 - 2024-10-08 26 | 27 | **Full Changelog**: https://github.com/amdad121/guard-laravel/compare/v0.1.1...v0.2.0 28 | 29 | ## v0.1.1 - 2024-10-08 30 | 31 | **Full Changelog**: https://github.com/amdad121/guard-laravel/compare/v0.1.0...v0.1.1 32 | 33 | ## v0.1.0 - 2024-10-08 34 | 35 | **Full Changelog**: https://github.com/amdad121/guard-laravel/commits/v0.1.0 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Amdadul Haq 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/HasRoles.php: -------------------------------------------------------------------------------- 1 | belongsToMany(config('guard.models.role')); 18 | } 19 | 20 | public function assignRole(Model $role): Model 21 | { 22 | return $this->roles()->save($role); 23 | } 24 | 25 | public function syncRoles(array $roles): array 26 | { 27 | return $this->roles()->sync($roles); 28 | } 29 | 30 | public function revokeRole(RoleContract $role): int 31 | { 32 | return $this->roles()->detach($role); 33 | } 34 | 35 | public function hasRole(string|Collection $role): bool 36 | { 37 | if (is_string($role)) { 38 | return $this->roles->contains('name', $role); 39 | } 40 | 41 | return (bool) $role->intersect($this->roles)->count(); 42 | } 43 | 44 | public function hasPermission(Model $permission): bool 45 | { 46 | return $this->hasRole(role: $permission->roles); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /database/migrations/create_permissions_table.php.stub: -------------------------------------------------------------------------------- 1 | id(); 24 | $table->string('name')->unique(); 25 | $table->string('label')->nullable(); 26 | $table->timestamps(); 27 | }); 28 | 29 | Schema::create($pivotTableName, function (Blueprint $table) use ($tables) { 30 | $table->foreignId(Guard::getSingularName($tables['permissions']).'_id')->constrained($tables['permissions'])->cascadeOnDelete(); 31 | $table->foreignId(Guard::getSingularName($tables['roles']).'_id')->constrained($tables['roles'])->cascadeOnDelete(); 32 | $table->primary([Guard::getSingularName($tables['permissions']).'_id', Guard::getSingularName($tables['roles']).'_id']); 33 | }); 34 | } 35 | 36 | /** 37 | * Reverse the migrations. 38 | */ 39 | public function down(): void 40 | { 41 | $tables = config('guard.tables'); 42 | $models = config('guard.models'); 43 | $pivotTableName = Guard::getPivotTableName(Arr::only($models, ['role', 'permission'])); 44 | 45 | Schema::dropIfExists($pivotTableName); 46 | Schema::dropIfExists($tables['permissions']); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /database/migrations/create_roles_table.php.stub: -------------------------------------------------------------------------------- 1 | id(); 24 | $table->string('name')->unique(); 25 | $table->string('label')->nullable(); 26 | $table->timestamps(); 27 | }); 28 | 29 | Schema::create($pivotTableName, function (Blueprint $table) use ($tables, $models) { 30 | $table->foreignId(Guard::getSingularName($tables['roles']).'_id')->constrained($tables['roles'])->cascadeOnDelete(); 31 | $table->foreignId(Guard::getSingularName(Guard::getTableName($models['user'])).'_id')->constrained(Guard::getTableName($models['user']))->cascadeOnDelete(); 32 | $table->primary([Guard::getSingularName($tables['roles']).'_id', Guard::getSingularName(Guard::getTableName($models['user'])).'_id']); 33 | }); 34 | } 35 | 36 | /** 37 | * Reverse the migrations. 38 | */ 39 | public function down(): void 40 | { 41 | $tables = config('guard.tables'); 42 | $models = config('guard.models'); 43 | $pivotTableName = Guard::getPivotTableName(Arr::only($models, ['role', 'user'])); 44 | 45 | Schema::dropIfExists($pivotTableName); 46 | Schema::dropIfExists($tables['roles']); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amdadulhaq/guard-laravel", 3 | "description": "Guard is Role and Permission management system for Laravel", 4 | "keywords": [ 5 | "Amdadul Haq", 6 | "laravel", 7 | "guard-laravel" 8 | ], 9 | "homepage": "https://github.com/amdad121/guard-laravel", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Amdadul Haq", 14 | "email": "amdadulhaq781@gmail.com", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.2", 20 | "illuminate/contracts": "^10.0|^11.0|^12.0", 21 | "spatie/laravel-package-tools": "^1.16" 22 | }, 23 | "require-dev": { 24 | "laravel/pint": "^1.14", 25 | "nunomaduro/collision": "^8.1.1|^7.10.0", 26 | "larastan/larastan": "^2.9|^3.0", 27 | "orchestra/testbench": "^10.0.0|^9.0.0|^8.22.0", 28 | "pestphp/pest": "^2.34|^3.0", 29 | "pestphp/pest-plugin-arch": "^2.7|^3.0", 30 | "pestphp/pest-plugin-laravel": "^2.3|^3.0", 31 | "phpstan/extension-installer": "^1.3|^2.0", 32 | "phpstan/phpstan-deprecation-rules": "^1.1|^2.0", 33 | "phpstan/phpstan-phpunit": "^1.3|^2.0" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "AmdadulHaq\\Guard\\": "src/" 38 | } 39 | }, 40 | "autoload-dev": { 41 | "psr-4": { 42 | "AmdadulHaq\\Guard\\Tests\\": "tests/" 43 | } 44 | }, 45 | "scripts": { 46 | "analyse": "vendor/bin/phpstan analyse", 47 | "test": "vendor/bin/pest", 48 | "test-coverage": "vendor/bin/pest --coverage", 49 | "format": "vendor/bin/pint" 50 | }, 51 | "config": { 52 | "sort-packages": true, 53 | "allow-plugins": { 54 | "pestphp/pest-plugin": true, 55 | "phpstan/extension-installer": true 56 | } 57 | }, 58 | "extra": { 59 | "laravel": { 60 | "providers": [ 61 | "AmdadulHaq\\Guard\\GuardServiceProvider" 62 | ], 63 | "aliases": { 64 | "Guard": "AmdadulHaq\\Guard\\Facades\\Guard" 65 | } 66 | } 67 | }, 68 | "minimum-stability": "dev", 69 | "prefer-stable": true 70 | } 71 | -------------------------------------------------------------------------------- /src/Commands/CreateRole.php: -------------------------------------------------------------------------------- 1 | argument('name'); 36 | 37 | if (! $name) { 38 | $name = text( 39 | label: 'The name of the role', 40 | required: true 41 | ); 42 | } 43 | 44 | $label = $this->argument('label'); 45 | 46 | if (! $label) { 47 | $label = text( 48 | label: 'The name of the label', 49 | required: false 50 | ); 51 | } 52 | 53 | $userId = $this->argument('user'); 54 | 55 | if (! $userId) { 56 | $userId = text( 57 | label: 'ID of the user', 58 | required: false 59 | ); 60 | } 61 | 62 | $role = Role::firstOrCreate(['name' => $name], ['name' => $name, 'label' => $label]); 63 | 64 | $message = ''; 65 | 66 | if ($userId) { 67 | /** @phpstan-ignore-next-line */ 68 | $user = User::find($userId); 69 | 70 | if ($user) { 71 | $user->assignRole($role); 72 | $message = 'Assign to the user ID of #'.$user->id.'.'; 73 | } else { 74 | $this->error('User is not exists. Try to using with correct user ID.'); 75 | $this->newLine(); 76 | 77 | return self::INVALID; 78 | } 79 | } 80 | 81 | if ($role->wasRecentlyCreated) { 82 | $this->info('Role created successfully. ID of the role is #'.$role->id.'. '.$message); 83 | $this->newLine(); 84 | 85 | return self::SUCCESS; 86 | } 87 | 88 | $this->info('Role already exist. ID of the role is #'.$role->id.'. '.$message); 89 | $this->newLine(); 90 | 91 | return self::SUCCESS; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Commands/CreatePermission.php: -------------------------------------------------------------------------------- 1 | argument('name'); 36 | 37 | if (! $name) { 38 | $name = text( 39 | label: 'The name of the role', 40 | required: true 41 | ); 42 | } 43 | 44 | $label = $this->argument('label'); 45 | 46 | if (! $label) { 47 | $label = text( 48 | label: 'The name of the label', 49 | required: false 50 | ); 51 | } 52 | 53 | $roleId = $this->argument('role'); 54 | 55 | if (! $roleId) { 56 | $roleId = text( 57 | label: 'ID of the role', 58 | required: false 59 | ); 60 | } 61 | 62 | $permission = Permission::firstOrCreate(['name' => $name], ['name' => $name, 'label' => $label]); 63 | 64 | $message = ''; 65 | 66 | if ($roleId) { 67 | $role = Role::find($roleId); 68 | 69 | if ($role) { 70 | $role->givePermissionTo($permission); 71 | $message = 'Permission give to the role ID of #'.$role->id.'.'; 72 | } else { 73 | $this->error('Role is not exists. Try to using with correct role ID.'); 74 | $this->newLine(); 75 | 76 | return self::INVALID; 77 | } 78 | } 79 | 80 | if ($permission->wasRecentlyCreated) { 81 | $this->info('Permission created successfully. ID of the permission is #'.$permission->id.'. '.$message); 82 | $this->newLine(); 83 | 84 | return self::SUCCESS; 85 | } 86 | 87 | $this->info('Permission already exist. ID of the permission is #'.$permission->id.'. '.$message); 88 | $this->newLine(); 89 | 90 | return self::SUCCESS; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/GuardServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('guard-laravel') 30 | ->hasConfigFile('guard') 31 | ->hasMigrations(['create_roles_table', 'create_permissions_table']) 32 | ->hasCommands([CreateRole::class, CreatePermission::class]); 33 | } 34 | 35 | public function bootingPackage(): void 36 | { 37 | parent::bootingPackage(); 38 | 39 | if ($this->permissionsTableExists()) { 40 | $this->defineGatePermissions(); 41 | $this->defineGateRoles(); 42 | } else { 43 | info('guard-laravel: Database not found or not yet migrated. Ignoring user permissions while booting app.'); 44 | } 45 | } 46 | 47 | public function registeringPackage(): void 48 | { 49 | parent::registeringPackage(); 50 | 51 | $this->app->bind(PermissionContract::class, fn ($app) => $app->make($app->config['guard.models.permission'])); 52 | $this->app->bind(RoleContract::class, fn ($app) => $app->make($app->config['guard.models.role'])); 53 | } 54 | 55 | protected function permissionsTableExists(): bool 56 | { 57 | try { 58 | return Schema::hasTable('permissions'); 59 | } catch (\Throwable $th) { 60 | return false; 61 | } 62 | } 63 | 64 | protected function defineGatePermissions(): void 65 | { 66 | foreach ($this->getPermissions() as $permission) { 67 | /** @phpstan-ignore-next-line */ 68 | Gate::define($permission->name, fn (UserContract $user) => $user->hasPermission($permission)); 69 | } 70 | } 71 | 72 | protected function defineGateRoles(): void 73 | { 74 | foreach ($this->getRoles() as $role) { 75 | /** @phpstan-ignore-next-line */ 76 | Gate::define($role->name, fn (UserContract $user) => $user->hasRole($role->name)); 77 | } 78 | } 79 | 80 | protected function getPermissions(): Collection 81 | { 82 | $cacheDuration = config('guard.cache.permissions_duration', 3600); // Default to 3600 seconds if not set 83 | 84 | return Cache::remember('permissions', $cacheDuration, function () { 85 | return Permission::with('roles')->get(); 86 | }); 87 | } 88 | 89 | protected function getRoles(): Collection 90 | { 91 | $cacheDuration = config('guard.cache.roles_duration', 3600); // Default to 3600 seconds if not set 92 | 93 | return Cache::remember('roles', $cacheDuration, function () { 94 | return Role::all(); 95 | }); 96 | } 97 | 98 | public static function clearCache(): void 99 | { 100 | Cache::forget('permissions'); 101 | Cache::forget('roles'); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guard Role And Permission Package For Laravel 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/amdadulhaq/guard-laravel.svg?style=flat-square)](https://packagist.org/packages/amdadulhaq/guard-laravel) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/amdad121/guard-laravel/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/amdad121/guard-laravel/actions?query=workflow%3Arun-tests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/amdad121/guard-laravel/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/amdad121/guard-laravel/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/amdadulhaq/guard-laravel.svg?style=flat-square)](https://packagist.org/packages/amdadulhaq/guard-laravel) 7 | 8 | Guard is Role and Permission management system for Laravel 9 | 10 | ## Installation 11 | 12 | You can install the package via composer: 13 | 14 | ```bash 15 | composer require amdadulhaq/guard-laravel 16 | ``` 17 | 18 | You can publish and run the migrations with: 19 | 20 | ```bash 21 | php artisan vendor:publish --tag="guard-laravel-migrations" 22 | php artisan migrate 23 | ``` 24 | 25 | Add `HasRoles` Trait and `UserContract` Interface on User Model 26 | 27 | ```php 28 | namespace App\Models; 29 | 30 | use AmdadulHaq\Guard\Contracts\User as UserContract; 31 | use AmdadulHaq\Guard\HasRoles; 32 | # ... 33 | 34 | class User extends Authenticatable implements UserContract 35 | { 36 | use HasRoles; 37 | } 38 | ``` 39 | 40 | ## Usage 41 | 42 | ### Role Create 43 | 44 | ```php 45 | use AmdadulHaq\Guard\Models\Role; 46 | 47 | Role::create(['name' => 'administrator']); 48 | ``` 49 | 50 | ### Permission Create 51 | 52 | ```php 53 | use AmdadulHaq\Guard\Models\Permission; 54 | use AmdadulHaq\Guard\Models\Role; 55 | 56 | $items = [ 57 | 'role' => ['viewAny', 'view', 'create', 'update', 'delete', 'restore', 'forceDelete'], 58 | 'permission' => ['viewAny', 'view', 'create', 'update', 'delete'], 59 | ]; 60 | 61 | $role = Role::first(); 62 | 63 | foreach ($items as $group => $names) { 64 | foreach ($names as $name) { 65 | $permission = Permission::create(['name' => $group.'.'.$name]); 66 | 67 | $role->givePermissionTo($permission); 68 | } 69 | } 70 | ``` 71 | 72 | ### Assign Role and Permission 73 | 74 | ```php 75 | use AmdadulHaq\Guard\Models\Permission; 76 | use AmdadulHaq\Guard\Models\Role; 77 | use App\Models\User; 78 | 79 | $user = User::first(); 80 | 81 | $role = Role::first(); 82 | 83 | // Assign role 84 | $user->assignRole($role); 85 | 86 | $permission = Permission::first(); 87 | 88 | // Assign permission 89 | $role->givePermissionTo($permission); 90 | ``` 91 | 92 | ### Revoke Role and Permission 93 | 94 | ```php 95 | use AmdadulHaq\Guard\Models\Permission; 96 | use AmdadulHaq\Guard\Models\Role; 97 | use App\Models\User; 98 | 99 | $user = User::first(); 100 | 101 | $role = Role::first(); 102 | 103 | // Revoke role 104 | $user->revokeRole($role); 105 | 106 | $permission = Permission::first(); 107 | 108 | // Revoke permission 109 | $role->revokePermissionTo($permission); 110 | ``` 111 | 112 | ### Check Role and Permission 113 | 114 | ```php 115 | use AmdadulHaq\Guard\Models\Permission; 116 | use AmdadulHaq\Guard\Models\Role; 117 | use App\Models\User; 118 | 119 | $user = User::first(); 120 | 121 | $role = Role::first(); 122 | 123 | // Role check 124 | $user->hasRole($role->name) // true or false 125 | 126 | $permission = Permission::first(); 127 | 128 | // Permission check 129 | $user->hasPermission($permission); // true or false 130 | ``` 131 | 132 | ### You can use multiple ways, some of are given bellow: 133 | 134 | ```php 135 | use Illuminate\Support\Facades\Gate; 136 | 137 | // for permission 138 | Gate::authorize('role.view'); 139 | 140 | // for role 141 | Gate::authorize('administrator'); 142 | ``` 143 | 144 | ```php 145 | // for permission 146 | $this->authorize('role.view'); 147 | 148 | // for role 149 | $this->authorize('administrator'); 150 | ``` 151 | 152 | ```php 153 | use Illuminate\Support\Facades\Route; 154 | 155 | // for permission 156 | Route::get('/', function () { 157 | // ... 158 | })->middleware('can:role.view'); 159 | 160 | // for role 161 | Route::get('/', function () { 162 | // ... 163 | })->middleware('can:administrator'); 164 | ``` 165 | 166 | ```blade 167 | // for permission 168 | @can('role.view') 169 | It's works 170 | @endcan 171 | 172 | // for role 173 | @can('administrator') 174 | It's works 175 | @endcan 176 | ``` 177 | 178 | ## Testing 179 | 180 | ```bash 181 | composer test 182 | ``` 183 | 184 | ## Changelog 185 | 186 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 187 | 188 | ## Contributing 189 | 190 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 191 | 192 | ## Security Vulnerabilities 193 | 194 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 195 | 196 | ## Credits 197 | 198 | - [Amdadul Haq](https://github.com/amdad121) 199 | - [All Contributors](../../contributors) 200 | 201 | ## License 202 | 203 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 204 | --------------------------------------------------------------------------------